From 2f7f42b4607658f29cb61496725020949fd4e9ce Mon Sep 17 00:00:00 2001 From: TD-er Date: Mon, 11 May 2026 21:32:09 +0200 Subject: [PATCH 1/5] [PubSubClient3] Update to latest MQTT Client PubSubClient3 --- .../.github/ISSUE_TEMPLATE/bug_report.yml | 105 +++ .../ISSUE_TEMPLATE/feature_request.yml | 32 + lib/pubsubclient3/.github/build-arduino.sh | 2 +- lib/pubsubclient3/.github/clang-lint.sh | 2 +- lib/pubsubclient3/.github/dependabot.yaml | 7 + .../.github/doxygen-update-version.sh | 0 .../.github/workflows/compile-examples.yml | 103 +++ .../.github/workflows/gh-pages.yml | 13 +- lib/pubsubclient3/.github/workflows/lint.yml | 28 + .../.github/workflows/report-size-deltas.yml | 24 + .../workflows/{build.yml => unit-tests.yml} | 13 +- lib/pubsubclient3/.gitignore | 8 +- lib/pubsubclient3/CHANGELOG.md | 192 +++++ lib/pubsubclient3/CHANGES.txt | 136 ---- lib/pubsubclient3/LICENSE.txt | 0 lib/pubsubclient3/README.md | 20 +- .../examples/mqtt_auth/mqtt_auth.ino | 4 +- .../examples/mqtt_basic/mqtt_basic.ino | 0 .../mqtt_esp.ino} | 10 +- .../mqtt_esp_large_message.ino} | 2 - .../examples/mqtt_progmem/mqtt_progmem.ino | 3 + .../examples/mqtt_progmem/platformio.ini | 40 ++ .../mqtt_progmem/src/mqtt_progmem.cpp | 54 ++ .../mqtt_publish_in_callback.ino | 4 +- .../mqtt_reconnect_nonblocking.ino | 4 +- .../examples/mqtt_stream/mqtt_stream.ino | 4 +- lib/pubsubclient3/keywords.txt | 3 + lib/pubsubclient3/library.json | 4 +- lib/pubsubclient3/library.properties | 2 +- lib/pubsubclient3/src/PubSubClient.cpp | 671 ++++++++++-------- lib/pubsubclient3/src/PubSubClient.h | 317 +++++++-- lib/pubsubclient3/tests/src/lib/Arduino.h | 5 + lib/pubsubclient3/tests/src/publish_spec.cpp | 175 ++++- lib/pubsubclient3/tests/src/receive_spec.cpp | 53 ++ .../tests/src/subscribe_spec.cpp | 110 +++ 35 files changed, 1613 insertions(+), 537 deletions(-) create mode 100644 lib/pubsubclient3/.github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 lib/pubsubclient3/.github/ISSUE_TEMPLATE/feature_request.yml mode change 100644 => 100755 lib/pubsubclient3/.github/build-arduino.sh mode change 100644 => 100755 lib/pubsubclient3/.github/clang-lint.sh create mode 100644 lib/pubsubclient3/.github/dependabot.yaml mode change 100644 => 100755 lib/pubsubclient3/.github/doxygen-update-version.sh create mode 100644 lib/pubsubclient3/.github/workflows/compile-examples.yml create mode 100644 lib/pubsubclient3/.github/workflows/lint.yml create mode 100644 lib/pubsubclient3/.github/workflows/report-size-deltas.yml rename lib/pubsubclient3/.github/workflows/{build.yml => unit-tests.yml} (74%) create mode 100644 lib/pubsubclient3/CHANGELOG.md delete mode 100644 lib/pubsubclient3/CHANGES.txt mode change 100644 => 100755 lib/pubsubclient3/LICENSE.txt mode change 100644 => 100755 lib/pubsubclient3/examples/mqtt_auth/mqtt_auth.ino mode change 100644 => 100755 lib/pubsubclient3/examples/mqtt_basic/mqtt_basic.ino rename lib/pubsubclient3/examples/{mqtt_esp8266/mqtt_esp8266.ino => mqtt_esp/mqtt_esp.ino} (93%) rename lib/pubsubclient3/examples/{mqtt_large_message/mqtt_large_message.ino => mqtt_esp_large_message/mqtt_esp_large_message.ino} (98%) create mode 100644 lib/pubsubclient3/examples/mqtt_progmem/mqtt_progmem.ino create mode 100644 lib/pubsubclient3/examples/mqtt_progmem/platformio.ini create mode 100644 lib/pubsubclient3/examples/mqtt_progmem/src/mqtt_progmem.cpp mode change 100644 => 100755 lib/pubsubclient3/keywords.txt mode change 100644 => 100755 lib/pubsubclient3/src/PubSubClient.cpp mode change 100644 => 100755 lib/pubsubclient3/src/PubSubClient.h diff --git a/lib/pubsubclient3/.github/ISSUE_TEMPLATE/bug_report.yml b/lib/pubsubclient3/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..29734e694 --- /dev/null +++ b/lib/pubsubclient3/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,105 @@ +name: Bug Report +description: Create a report to help improve PubSubClient3 +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + - type: textarea + id: description + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + placeholder: Tell us what happened... + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Steps to reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Connect to '...' + 2. Publish message to '...' + 3. See error + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + validations: + required: true + + - type: input + id: version + attributes: + label: Library version + description: What version of PubSubClient3 are you running? + placeholder: v3.3.0 + validations: + required: true + + - type: dropdown + id: platform + attributes: + label: Platform + description: What platform are you using? + options: + - Arduino AVR + - ESP32 + - ESP8266 + - Other (specify in environment) + validations: + required: true + + - type: textarea + id: environment + attributes: + label: Environment + description: | + Provide details about your hardware and software environment, including: + - Board: (e.g., Arduino Uno, ESP12E, ESP32, NodeMCU, etc.) + - Framework Version: (e.g., Arduino AVR 5.2.0, ESP32 Arduino 3.3.2, ESP8266 Core 3.1.2 / 3.2.0-git) + - Development Environment: (e.g., Arduino IDE 2.3.6, PlatformIO Core 6.1.8) + - Platform: (if Other was selected above) + placeholder: | + - Board: + - Framework Version: + - Development Environment: + render: markdown + validations: + required: true + + - type: textarea + id: example_code + attributes: + label: Minimal reproducible example (MVP) + description: Provide the smallest code/config and exact steps that reproduce the bug. Paste complete minimal sketch or commands. + placeholder: | + ```cpp + #include + #include + + // minimal setup showing the bug + void setup() { + // ... + } + + void loop() { + // ... + } + ``` + render: markdown + + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output + render: shell diff --git a/lib/pubsubclient3/.github/ISSUE_TEMPLATE/feature_request.yml b/lib/pubsubclient3/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..2147de989 --- /dev/null +++ b/lib/pubsubclient3/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,32 @@ +name: Feature Request +description: Suggest a new feature for PubSubClient3 +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to suggest a new feature! + + - type: textarea + id: reason + attributes: + label: Describe the reason of your feature request + description: A clear and concise description of what the problem is. + placeholder: I want to do [...] + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional context + description: Add any other context or code examples about the feature request here. + render: markdown diff --git a/lib/pubsubclient3/.github/build-arduino.sh b/lib/pubsubclient3/.github/build-arduino.sh old mode 100644 new mode 100755 index 551fe8bd7..07c59915a --- a/lib/pubsubclient3/.github/build-arduino.sh +++ b/lib/pubsubclient3/.github/build-arduino.sh @@ -29,7 +29,7 @@ ln -s $GITHUB_WORKSPACE $HOME/Arduino/libraries/CI_Test_Library # Compile all *.ino files for the Arduino for f in **/*.ino ; do - if [[ "$f" != *mqtt_esp8266.ino && "$f" != *mqtt_large_message.ino ]]; then + if [[ "$f" != *"mqtt_esp"* ]]; then echo "################################################################" echo "Arduino Uno compiling file ${f}" arduino-cli compile -b arduino:avr:uno $f diff --git a/lib/pubsubclient3/.github/clang-lint.sh b/lib/pubsubclient3/.github/clang-lint.sh old mode 100644 new mode 100755 index 2018fd2be..8551898b5 --- a/lib/pubsubclient3/.github/clang-lint.sh +++ b/lib/pubsubclient3/.github/clang-lint.sh @@ -9,7 +9,7 @@ cd $GITHUB_WORKSPACE #command -v clang-format >/dev/null 2>&1 || sudo apt-get -y install clang-format # need Ubuntu clang-format version 19.1.1 (1ubuntu1~24.04.2) # default Ubuntu clang-format version 18.1.3 (1ubuntu1) is not working -sudo apt-get -y install clang-format-19 +#sudo apt-get -y install clang-format-19 # Check clang-format output for f in **/*.{h,c,hpp,cpp,ino} ; do #if [ -f "$f" ] && [[ "$f" != "tests/"* ]]; then diff --git a/lib/pubsubclient3/.github/dependabot.yaml b/lib/pubsubclient3/.github/dependabot.yaml new file mode 100644 index 000000000..83b26ea5b --- /dev/null +++ b/lib/pubsubclient3/.github/dependabot.yaml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + time: "06:00" diff --git a/lib/pubsubclient3/.github/doxygen-update-version.sh b/lib/pubsubclient3/.github/doxygen-update-version.sh old mode 100644 new mode 100755 diff --git a/lib/pubsubclient3/.github/workflows/compile-examples.yml b/lib/pubsubclient3/.github/workflows/compile-examples.yml new file mode 100644 index 000000000..f4a87820a --- /dev/null +++ b/lib/pubsubclient3/.github/workflows/compile-examples.yml @@ -0,0 +1,103 @@ +name: Compile examples + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/compile-examples.yml" + - 'examples/**' + - 'src/**' + pull_request: + paths: + - ".github/workflows/compile-examples.yml" + - 'examples/**' + - 'src/**' + +jobs: + build: + name: ${{ matrix.board.fqbn }} + runs-on: ubuntu-latest + + env: + SKETCHES_REPORTS_PATH: sketches-reports + + strategy: + fail-fast: false + + matrix: + board: + - fqbn: arduino:avr:uno + platforms: | + - name: arduino:avr + libraries: "" + artifact-name-suffix: arduino-avr-uno + - fqbn: arduino:megaavr:uno2018 + platforms: | + - name: arduino:megaavr + libraries: | + - name: WiFiNINA + artifact-name-suffix: arduino-megaavr-uno2018 + - fqbn: arduino:samd:mkr1000 + platforms: | + - name: arduino:samd + libraries: | + - name: WiFi101 + artifact-name-suffix: arduino-samd-mkr1000 + - fqbn: esp8266:esp8266:generic + platforms: | + - name: esp8266:esp8266 + source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json + libraries: "" + artifact-name-suffix: esp8266-esp8266-generic + - fqbn: "esp32:esp32:esp32" + platforms: | + - name: esp32:esp32 + libraries: "" + artifact-name-suffix: esp32-esp32-esp32 + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Download SRAM library + run: | + mkdir -p ./arduino-libraries + git -C ./arduino-libraries clone https://github.com/ennui2342/arduino-sram.git + + - name: Filter examples + if: success() + run: | + if [[ "${{ matrix.board.fqbn }}" == esp32:esp32:* ]]; then + echo "Removing non-ESP32 specific examples" + rm -rf examples/mqtt_stream + fi + if [[ ! "${{ matrix.board.fqbn }}" == *:esp*:* ]]; then + echo "Removing ESP specific examples" + rm -rf examples/mqtt_esp* + fi + + - name: Compile examples + uses: arduino/compile-sketches@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fqbn: ${{ matrix.board.fqbn }} + platforms: ${{ matrix.board.platforms }} + libraries: | + # Install the library from the local path + - source-path: ./ + - source-path: ./arduino-libraries/arduino-sram + # Install libraries from GitHub + #- source-url: https://github.com/ennui2342/arduino-sram + - name: Ethernet + ${{ matrix.board.libraries }} + sketch-paths: | + - examples + enable-deltas-report: true + sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} + + - name: Save sketches report as workflow artifact + uses: actions/upload-artifact@v7 + with: + if-no-files-found: error + path: ${{ env.SKETCHES_REPORTS_PATH }} + name: sketches-report-${{ matrix.board.artifact-name-suffix }} diff --git a/lib/pubsubclient3/.github/workflows/gh-pages.yml b/lib/pubsubclient3/.github/workflows/gh-pages.yml index 6eed1ea16..f7d3faedd 100644 --- a/lib/pubsubclient3/.github/workflows/gh-pages.yml +++ b/lib/pubsubclient3/.github/workflows/gh-pages.yml @@ -7,7 +7,7 @@ name: Generate Doxygen API and deploy content to GitHub Pages on: # Runs on pushes targeting the default branch push: - branches: ["master"] + branches: ["release"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -34,12 +34,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Update Doxyfile PROJECT_NUMBER run: bash .github/doxygen-update-version.sh - name: Doxygenize - #uses: langroodi/doxygenize@v1.7.1 # waiting for update ... - uses: langroodi/doxygenize@6e920681c5d838e9a1b5cd273b814d1e4023b63d + uses: langroodi/doxygenize@v1.7.1 #uses: hmueller01/doxygenize@update-alpine-linux+patches with: # Doxygen configuration file path @@ -54,12 +53,12 @@ jobs: # run: | # find ./site - name: Setup Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: #path: '.' # Upload entire repository path: './site' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/lib/pubsubclient3/.github/workflows/lint.yml b/lib/pubsubclient3/.github/workflows/lint.yml new file mode 100644 index 000000000..9d45a9ee0 --- /dev/null +++ b/lib/pubsubclient3/.github/workflows/lint.yml @@ -0,0 +1,28 @@ +name: Lint + +on: + push: + branches: [ "master" ] + paths: + - 'examples/**' + - 'src/**' + - 'tests/**' + pull_request: + branches: [ "master" ] + +jobs: + run-checks: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install deps + # need Ubuntu clang-format version 19.1.1 (1ubuntu1~24.04.2) + # default Ubuntu clang-format version 18.1.3 (1ubuntu1) is not working + run: | + sudo apt-get update + sudo apt-get install -y clang-format-19 + + - name: Check clang-format conformity + run: bash .github/clang-lint.sh diff --git a/lib/pubsubclient3/.github/workflows/report-size-deltas.yml b/lib/pubsubclient3/.github/workflows/report-size-deltas.yml new file mode 100644 index 000000000..39e2a0ad2 --- /dev/null +++ b/lib/pubsubclient3/.github/workflows/report-size-deltas.yml @@ -0,0 +1,24 @@ +name: Report Size Deltas + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/report-size-deltas.yml" + schedule: + # Run at the minimum interval allowed by GitHub Actions. + # Note: GitHub Actions periodically has outages which result in workflow failures. + # In this event, the workflows will start passing again once the service recovers. + - cron: "*/5 * * * *" + workflow_dispatch: + repository_dispatch: + +jobs: + report: + runs-on: ubuntu-latest + steps: + - name: Comment size deltas reports to PRs + uses: arduino/report-size-deltas@v1 + with: + # Regex matching the names of the workflow artifacts created by the "Compile Examples" workflow + sketches-reports-source: ^sketches-report-.+ diff --git a/lib/pubsubclient3/.github/workflows/build.yml b/lib/pubsubclient3/.github/workflows/unit-tests.yml similarity index 74% rename from lib/pubsubclient3/.github/workflows/build.yml rename to lib/pubsubclient3/.github/workflows/unit-tests.yml index 110a8f0ec..8e8542f4b 100644 --- a/lib/pubsubclient3/.github/workflows/build.yml +++ b/lib/pubsubclient3/.github/workflows/unit-tests.yml @@ -1,4 +1,4 @@ -name: build +name: Unit tests on: push: @@ -11,14 +11,11 @@ on: branches: [ "master" ] jobs: - build: + run-tests: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 - - - name: Check clang-format conformity - run: bash .github/clang-lint.sh + uses: actions/checkout@v6 - name: Build tests # Build / Execute tests defined in tests folder @@ -31,5 +28,5 @@ jobs: #run: make test run: bin/connect_spec && bin/publish_spec && bin/receive_spec && bin/subscribe_spec - - name: Build on Arduino CLI - run: bash .github/build-arduino.sh +# - name: Build on Arduino CLI +# run: bash .github/build-arduino.sh diff --git a/lib/pubsubclient3/.gitignore b/lib/pubsubclient3/.gitignore index 9ffb670e2..4b55b9500 100644 --- a/lib/pubsubclient3/.gitignore +++ b/lib/pubsubclient3/.gitignore @@ -1,6 +1,8 @@ -tests/bin -.pioenvs -.piolibdeps .clang_complete .gcc-flags.json +.pio/ +.pioenvs +.piolibdeps +.vscode/ site/api +tests/bin diff --git a/lib/pubsubclient3/CHANGELOG.md b/lib/pubsubclient3/CHANGELOG.md new file mode 100644 index 000000000..2153b36f6 --- /dev/null +++ b/lib/pubsubclient3/CHANGELOG.md @@ -0,0 +1,192 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to +[Semantic Versioning](https://semver.org/). + +## Unreleased + +### Changed + +* Improved test coverage for all QoS levels (0, 1, 2) +* Increased robustness and correctness in client-side handling of QoS 1 and QoS 2 messages (proper handling of PUBACK, PUBREC, PUBREL, PUBCOMP sequences) +* Merged and cleaned up tests to ensure MQTT protocol compliance for all QoS scenarios + +## [3.3.0] - 2025-12-14 + +### Added + +* Speedup publish large messages by using a buffer by @TD-er in [#59](https://github.com/hmueller01/pubsubclient3/pull/59) +* Support `PROGMEM` and `__FlashStringHelper` topics by @hmueller01 in [#72](https://github.com/hmueller01/pubsubclient3/pull/72) +* Added issue template by @hmueller01 in [#76](https://github.com/hmueller01/pubsubclient3/pull/76) +* Report delta sizes in pull requests by @hmueller01 in [#77](https://github.com/hmueller01/pubsubclient3/pull/77) + +### Changed + +* Refactored private members by @hmueller01 in [#68](https://github.com/hmueller01/pubsubclient3/pull/68) +* Refactored private functions by @hmueller01 in [#70](https://github.com/hmueller01/pubsubclient3/pull/70) +* Do not crash if `_client` not set by @hmueller01 in [#69](https://github.com/hmueller01/pubsubclient3/pull/69) +* Minor updates that come across by @hmueller01 in [#73](https://github.com/hmueller01/pubsubclient3/pull/73) +* Inline optimisation by @hmueller01 in [#75](https://github.com/hmueller01/pubsubclient3/pull/75) +* Bump actions/upload-pages-artifact from 3 to 4 by @dependabot[bot] in [#78](https://github.com/hmueller01/pubsubclient3/pull/78) +* Bump actions/checkout from 2 to 5 by @dependabot[bot] in [#79](https://github.com/hmueller01/pubsubclient3/pull/79) +* Tag unused parameter to avoid warnings by @hmueller01 in [#82](https://github.com/hmueller01/pubsubclient3/pull/82) +* Fix compile warning at strnlen bound by @hmueller01 in [#83](https://github.com/hmueller01/pubsubclient3/pull/83) +* Fix deprecated `BUILTIN_LED` by @hmueller01 in [#84](https://github.com/hmueller01/pubsubclient3/pull/84) +* Bump actions/checkout from 5 to 6 by @dependabot[bot] in [#85](https://github.com/hmueller01/pubsubclient3/pull/85) +* Update workflows by @hmueller01 in [#86](https://github.com/hmueller01/pubsubclient3/pull/86) + +## [3.2.1] - 2025-09-11 + +### Fixed + +- Fixed bug introduced with QoS > 0 support (#65) + +## [3.2.0] - 2025-07-20 + +### Added + +- Support publish at QoS 1 and 2 (by @hmueller01) (#49) +- Doxygen API documentation on gh-pages (by @hmueller01) (#51) + +### Changed + +- Add check for nullptr in `CHECK_STRING_LENGTH` (by @TD-er) (#48) +- Move `CHECK_STRING_LENGTH` to `PubSubClient.cpp` (by @hmueller01) (#50) +- Get rid of lazy calling parameter (by @hmueller01) (#53) +- Implemented `writeNextMsgId()` (by @hmueller01) (#54) +- Cleanup `writeString()` (by @hmueller01) (#55) + +## [3.1.0] - 2025-03-20 + +### Added + +- Added `ERROR_PSC_PRINTF` and `ERROR_PSC_PRINTF_P` outputs (#25) + +### Changed + +- Fix `setServer(domain, ...)` by copying domain string to an internal buffer (prevent potential dangling pointer) (#10) +- Connect to broker only if port != 0 (protect against incorrect initialization) (#10) +- Refactored buffer length types to `size_t` (#11) +- Added warnings and `-Werror` to tests Makefile (#11) +- Reformatted tests code using Google style (see `.clang-format`) (#11) +- Fix keepalive handling (thanks to @uschnindler) (#14) +- Use initializer lists for constructors (#15) +- Fix `DEBUG_PSC_PRINTF` outputs (#18) +- Introduce `MQTTRETAINED` flag (#21) +- Added documentation on `readByte()` and `readPacket()` (#23) +- Refactored `buildHeader()`, `readPacket()`, `readByte()`, `beginPublish()`/`endPublish()`, `publish()`/`publish_P()`, `connect()`, `connected()`, `subscribe()`/`unsubscribe()` and internal timing handling (#22, #23, #28, #30, #31, #33, #34, #37, #38, #39, #40, #44) +- Fixed potential memory corruption in `MQTTPUBLISH` callback preparation (#25) +- Fixed a missing failure test on `_client->read()` in `readByte()` (#32) +- Switch from constructor init-lists to class member initialization (C++ core guideline C.45) (#42) + +## [3.0.2] - 2025-02-27 + +### Added + +- GitHub workflow to execute tests +- GitHub workflow to compile examples using Arduino CLI +- Examples: Use a better source of randomness (#9) +- Optionally deactivate `std::function` usage (#7) + +## [3.0.1] - 2025-02-20 + +### Added + +- Added `.editorconfig` +- Added GitHub workflow to check code style on push/PR + +### Changed + +- Reformatted code using Google style +- Fixes for tests: missing def for `strlen_P` & Python libs + +## [3.0] - 2025-02-17 + +Maintenance taken over by Holger Mueller + +### Added + +- In-source documentation added to `PubSubClient` header +- Add flag for enabling debugging of library + +### Changed + +- Always use `PubSubClient()` constructor +- Use `bool` instead of `boolean` +- Add `yield()` calls in `connect()` and `write()` to avoid watchdog resets +- Fix bug in `publish_P` handling of PROGMEM payload length +- Fix increase `bytesToWrite` to `uint16_t` to prevent overflow +- Remove compiler warning about unsigned `expectedLength` +- Extend usage of `std::function` where available +- Fix for keep alive zero + +## [2.8] - 2020-05-20 + +### Added + +- `setBufferSize()` to override `MQTT_MAX_PACKET_SIZE` +- `setKeepAlive()` to override `MQTT_KEEPALIVE` +- `setSocketTimeout()` to override `MQTT_SOCKET_TIMEOUT` +- Check to prevent subscribe/unsubscribe to empty topics +- Declare WiFi mode prior to connect in ESP example +- Use `strnlen` to avoid overruns +- Support pre-connected `Client` objects + +## [2.7] - 2018-11-02 + +### Added + +- Added large-payload API: `beginPublish`/`write`/`publish`/`endPublish` +- Added `yield` calls to improve reliability on ESP +- Added Clean Session flag to connect options +- Added ESP32 support for functional callback signature + +### Fixed + +- Fixed remaining-length handling to prevent buffer overrun + +## [2.4] - 2015-11-21 + +### Added + +- `MQTT_SOCKET_TIMEOUT` to avoid blocking indefinitely while waiting for inbound data + +### Fixed + +- Fixed return code when publishing >256 bytes + +## [2.3] - 2015-09-11 + +### Added + +- `publish(topic,payload,retained)` convenience overload + +## [2.2] - 2015-09-07 + +### Changed + +- Changed code layout to match Arduino Library requirements + +## [2.1] - 2015-09-07 + +### Added + +- `MAX_TRANSFER_SIZE` to chunk messages if needed +- Reject topic/payloads that exceed `MQTT_MAX_PACKET_SIZE` + +## [2.0] - 2015-08-28 + +### Added + +- MQTT 3.1.1 support +- Fix PROGMEM handling for Intel Galileo/ESP8266 +- Overloaded constructors for convenience +- Chainable setters for server/callback/client/stream +- `state()` function to return CONNACK code + +## [1.0] - 2009-02-02 + +- Initial release notes: QoS 0 only, 128 byte max message size, keepalive 30s, no Will messages + diff --git a/lib/pubsubclient3/CHANGES.txt b/lib/pubsubclient3/CHANGES.txt deleted file mode 100644 index c06984322..000000000 --- a/lib/pubsubclient3/CHANGES.txt +++ /dev/null @@ -1,136 +0,0 @@ -3.1.0 - * Fix setServer(domain, ...) by copy domain string to own buffer (prevent potential dangling string pointer) #10 - * Connect to broker only if port != 0 (e.g. in case of incorrect PubSubClient initialization) #10 - * Refactored different buffer length types to size_t #11 - * Added a bunch of warnings and -Werror to tests Makefile #11 - * Reformatted tests code using Google sytle (see .clang-format) #11 - * Fix keepalive handling, thanks to @uschnindler providing this to thingsboard #14 - * Use initializer lists instead of assigning global members in constructor #15 - * Fix DEBUG_PSC_PRINTF outputs #18 - * Added ERROR_PSC_PRINTF and ERROR_PSC_PRINTF_P outputs #25 - * Introduce MQTTRETAINED flag instead of hard coding #21 - * Added documentation on readByte() and readPacket() functions #23 - * Refactored buildHeader() (change return type, use MQTT_MAX_HEADER_SIZE, locals) #22, #34, #37 - * Refactored readPacket() (changed return type to size_t, used MQTT_MAX_HEADER_SIZE, locals) #23 - * Refactored readByte() #23 - * Refactored beginPublish() and endPublish() #28 - * Refactored publish() #31 - * Refactored publish_P() #30 - * Refactored connect() #40 - * Refactored connected() (mostly rewritten and simplified, added setting pingOutstanding, don't need flush()) #44 - * Refactored subscribe() and unsubscribe() #44 - * Refactored internal socketTimeout and keepAlive to millis to improve performance #33, #38, #39 - * Fix potential memory corruption in MQTTPUBLISH callback preparation #25 - * Fix potential error in readByte() (missing failure test on _client->read()) #32 - * Switch from constructor init-lists to class member initialization, see C++ core guideline C.45 #42 - -3.0.2 - * Added github workflow to execute tests - * Added github workflow to compile examples using arduino-cli - * Examples: Use a better source of random #9 - * Deactivate use of std::function on demand #7 - -3.0.1 - * Reformatted code using Google sytle (see .clang-format) - * Added .editorconfig - * Added github workflow to check code sytle on push and pull request - * Fix-tests: missing def for strlen_P & Python libs - -3.0 - * Maintenance taken over by Holger Mueller - * Adds in-source documentation to PubSubClient header. - * Add flag for enabling debugging of library - * Always use PubSubClient() constructor - * Use bool instead of boolean - * Add yield() calls in connect() and write() to avoid wdt resets if either blocks for too long - * Fix bug in publish_P which will always treat the payload length as 0 when used with PROGMEM - * Fix increase `bytesToWrite` to uint16_t to prevent overflow - * Remove compiler warning `expectedLength` should be unsigned - * Extend usage of std::function on all the platforms where it's available - * Fix for keep alive zero - -2.8 - * Add setBufferSize() to override MQTT_MAX_PACKET_SIZE - * Add setKeepAlive() to override MQTT_KEEPALIVE - * Add setSocketTimeout() to overide MQTT_SOCKET_TIMEOUT - * Added check to prevent subscribe/unsubscribe to empty topics - * Declare wifi mode prior to connect in ESP example - * Use `strnlen` to avoid overruns - * Support pre-connected Client objects - -2.7 - * Fix remaining-length handling to prevent buffer overrun - * Add large-payload API - beginPublish/write/publish/endPublish - * Add yield call to improve reliability on ESP - * Add Clean Session flag to connect options - * Add ESP32 support for functional callback signature - * Various other fixes - -2.4 - * Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely - whilst waiting for inbound data - * Fixed return code when publishing >256 bytes - -2.3 - * Add publish(topic,payload,retained) function - -2.2 - * Change code layout to match Arduino Library reqs - -2.1 - * Add MAX_TRANSFER_SIZE def to chunk messages if needed - * Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE - -2.0 - * Add (and default to) MQTT 3.1.1 support - * Fix PROGMEM handling for Intel Galileo/ESP8266 - * Add overloaded constructors for convenience - * Add chainable setters for server/callback/client/stream - * Add state function to return connack return code - -1.9 - * Do not split MQTT packets over multiple calls to _client->write() - * API change: All constructors now require an instance of Client - to be passed in. - * Fixed example to match 1.8 api changes - dpslwk - * Added username/password support - WilHall - * Added publish_P - publishes messages from PROGMEM - jobytaffey - -1.8 - * KeepAlive interval is configurable in PubSubClient.h - * Maximum packet size is configurable in PubSubClient.h - * API change: Return boolean rather than int from various functions - * API change: Length parameter in message callback changed - from int to unsigned int - * Various internal tidy-ups around types - -1.7 - * Improved keepalive handling - * Updated to the Arduino-1.0 API - -1.6 - * Added the ability to publish a retained message - -1.5 - * Added default constructor - * Fixed compile error when used with arduino-0021 or later - -1.4 - * Fixed connection lost handling - -1.3 - * Fixed packet reading bug in PubSubClient.readPacket - -1.2 - * Fixed compile error when used with arduino-0016 or later - -1.1 - * Reduced size of library - * Added support for Will messages - * Clarified licensing - see LICENSE.txt - -1.0 - * Only Quality of Service (QOS) 0 messaging is supported - * The maximum message size, including header, is 128 bytes - * The keepalive interval is set to 30 seconds - * No support for Will messages diff --git a/lib/pubsubclient3/LICENSE.txt b/lib/pubsubclient3/LICENSE.txt old mode 100644 new mode 100755 diff --git a/lib/pubsubclient3/README.md b/lib/pubsubclient3/README.md index f285bee6e..70e75e6a0 100644 --- a/lib/pubsubclient3/README.md +++ b/lib/pubsubclient3/README.md @@ -29,7 +29,7 @@ Full API documentation is available here: https://hmueller01.github.io/pubsubcli ## Limitations - The client is based on the [MQTT Version 3.1.1 specification](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html) with some limitations. - - It can publish at QoS 0, 1 or 2. + - It can publish at QoS 0 and since [v3.2.0](https://github.com/hmueller01/pubsubclient3/releases/tag/v3.2.0) also at QoS 1 or 2. **WARNING:** No retransmission is supported to keep the library as much memory friendly as possible. @@ -45,7 +45,17 @@ Full API documentation is available here: https://hmueller01.github.io/pubsubcli `PubSubClient::setKeepAlive(keepAlive)`. - The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by changing value of `MQTT_VERSION` in `PubSubClient.h`. - + - Since [v3.3.0](https://github.com/hmueller01/pubsubclient3/releases/tag/v3.3.0) it can publish and subscribe to `PROGMEM` or `__FlashStringHelper` topics. + Details see [mqtt_progmem](https://github.com/hmueller01/pubsubclient3/blob/aae84e4d1aa65e752e19e30239b5796b4fe2705b/examples/mqtt_progmem/src/mqtt_progmem.cpp#L39-L48) example. + But if you like to publish `PROGMEM` topics you have to use + ```c + const char TOPIC[] PROGMEM = "test"; + const char HELLO_WORLD[] PROGMEM = "hello world"; + client.beginPublish_P(TOPIC, strlen_P(HELLO_WORLD), MQTT_QOS0, false); + client.write_P(HELLO_WORLD); + client.endPublish(); + ``` + as `client.publish_P(...)` is already used for `PROGMEM` payloads. ## Compatible Hardware @@ -69,6 +79,10 @@ The library cannot currently be used with hardware based on the ENC28J60 chip such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an [alternative library](https://github.com/njh/NanodeMQTT) available. +## Changelog + +See [CHANGELOG.md](CHANGELOG.md) + ## License -This code is released under the MIT License. +This code is released under the [MIT License](LICENSE.txt). diff --git a/lib/pubsubclient3/examples/mqtt_auth/mqtt_auth.ino b/lib/pubsubclient3/examples/mqtt_auth/mqtt_auth.ino old mode 100644 new mode 100755 index 5d5d95ce4..7ae83689a --- a/lib/pubsubclient3/examples/mqtt_auth/mqtt_auth.ino +++ b/lib/pubsubclient3/examples/mqtt_auth/mqtt_auth.ino @@ -11,12 +11,14 @@ #include #include +#define _UNUSED_ __attribute__((unused)) + // Update these with values suitable for your network. byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; IPAddress ip(172, 16, 0, 100); IPAddress server(172, 16, 0, 2); -void callback(char* topic, uint8_t* payload, size_t plength) { +void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t plength) { // handle message arrived } diff --git a/lib/pubsubclient3/examples/mqtt_basic/mqtt_basic.ino b/lib/pubsubclient3/examples/mqtt_basic/mqtt_basic.ino old mode 100644 new mode 100755 diff --git a/lib/pubsubclient3/examples/mqtt_esp8266/mqtt_esp8266.ino b/lib/pubsubclient3/examples/mqtt_esp/mqtt_esp.ino similarity index 93% rename from lib/pubsubclient3/examples/mqtt_esp8266/mqtt_esp8266.ino rename to lib/pubsubclient3/examples/mqtt_esp/mqtt_esp.ino index 9ffe0e725..61dade922 100644 --- a/lib/pubsubclient3/examples/mqtt_esp8266/mqtt_esp8266.ino +++ b/lib/pubsubclient3/examples/mqtt_esp/mqtt_esp.ino @@ -23,7 +23,7 @@ #elif defined(ESP32) #include #include -#define BUILTIN_LED A0 +#define LED_BUILTIN A0 #define RANDOM_REG32 esp_random() #else #error Platform not supported. @@ -77,11 +77,11 @@ void callback(char* topic, uint8_t* payload, size_t plength) { // Switch on the LED if an 1 was received as first character if ((char)payload[0] == '1') { - digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level + digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level // but actually the LED is on; this is because // it is active low on the ESP-01) } else { - digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH + digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH } } @@ -110,7 +110,7 @@ void reconnect() { } void setup() { - pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output + pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output Serial.begin(115200); setup_wifi(); client.setServer(mqtt_server, 1883); @@ -127,7 +127,7 @@ void loop() { if (now - lastMsg > 2000) { lastMsg = now; ++value; - snprintf(msg, MSG_BUFFER_SIZE, "hello world #%ld", value); + snprintf(msg, MSG_BUFFER_SIZE, "hello world #%d", value); Serial.print("Publish message: "); Serial.println(msg); client.publish("outTopic", msg); diff --git a/lib/pubsubclient3/examples/mqtt_large_message/mqtt_large_message.ino b/lib/pubsubclient3/examples/mqtt_esp_large_message/mqtt_esp_large_message.ino similarity index 98% rename from lib/pubsubclient3/examples/mqtt_large_message/mqtt_large_message.ino rename to lib/pubsubclient3/examples/mqtt_esp_large_message/mqtt_esp_large_message.ino index 3fc23c8c5..21a2e59b8 100644 --- a/lib/pubsubclient3/examples/mqtt_large_message/mqtt_large_message.ino +++ b/lib/pubsubclient3/examples/mqtt_esp_large_message/mqtt_esp_large_message.ino @@ -28,7 +28,6 @@ #elif defined(ESP32) #include #include -#define BUILTIN_LED A0 #define RANDOM_REG32 esp_random() #else #error Platform not supported. @@ -170,7 +169,6 @@ void reconnect() { } void setup() { - pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output Serial.begin(115200); setup_wifi(); client.setServer(mqtt_server, 1883); diff --git a/lib/pubsubclient3/examples/mqtt_progmem/mqtt_progmem.ino b/lib/pubsubclient3/examples/mqtt_progmem/mqtt_progmem.ino new file mode 100644 index 000000000..d75fcb9b5 --- /dev/null +++ b/lib/pubsubclient3/examples/mqtt_progmem/mqtt_progmem.ino @@ -0,0 +1,3 @@ +// Example sketch showing how to use PubSubClient with strings stored in PROGMEM. +// This needs to be an empty file to satisfy the arduino-cli build system. +// See src/mqtt_progmem.cpp for the actual example code. diff --git a/lib/pubsubclient3/examples/mqtt_progmem/platformio.ini b/lib/pubsubclient3/examples/mqtt_progmem/platformio.ini new file mode 100644 index 000000000..85ef7ba10 --- /dev/null +++ b/lib/pubsubclient3/examples/mqtt_progmem/platformio.ini @@ -0,0 +1,40 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +; https://docs.platformio.org/en/latest/boards/atmelavr/uno.html + +[platformio] +description = Basic MQTT example with Authentication and Progmem strings + +[env] +framework = arduino +lib_deps = + arduino-libraries/Ethernet @ ^2.0.2 + hmueller01/PubSubClient3 @ ^3.3.0 +build_flags = +; -D DEBUG_ESP_PORT=Serial +; -D DEBUG_PUBSUBCLIENT +; -D MQTT_MAX_PACKET_SIZE=512 +; -D MQTT_KEEPALIVE=120 + +[env:uno] +platform = atmelavr +board = uno + +[env:esp8266] +platform = espressif8266 +platform_packages = platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git +board = esp12e +build_flags = + ${env.build_flags} + -D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305 + +[env:esp32] +platform = espressif32 +board = esp32dev diff --git a/lib/pubsubclient3/examples/mqtt_progmem/src/mqtt_progmem.cpp b/lib/pubsubclient3/examples/mqtt_progmem/src/mqtt_progmem.cpp new file mode 100644 index 000000000..9088c7ff5 --- /dev/null +++ b/lib/pubsubclient3/examples/mqtt_progmem/src/mqtt_progmem.cpp @@ -0,0 +1,54 @@ +/* + Basic MQTT example with Authentication + + - connects to an MQTT server, providing username + and password + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic" +*/ + +#include +#include +#include + +#define _UNUSED_ __attribute__((unused)) + +// Update these with values suitable for your network. +byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); +const char HELLO_WORLD_3[] PROGMEM = "hello world 3"; +const char HELLO_WORLD_4[] PROGMEM = "hello world 4"; +const char SUBSCRIBE_TOPIC[] PROGMEM = "inTopic1"; + +void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t plength) { + // handle message arrived +} + +EthernetClient ethClient; +PubSubClient client(server, 1883, callback, ethClient); + +void setup() { + Ethernet.begin(mac, ip); + // Note - the default maximum packet size is 128 bytes. If the + // combined length of clientId, username and password exceed this use the + // following to increase the buffer size: + // client.setBufferSize(255); + + if (client.connect("arduinoClient", "testuser", "testpass")) { + client.publish(F("outTopic"), "hello world 1", MQTT_QOS0, false); + client.publish(F("outTopic"), F("hello world 2"), MQTT_QOS1, false); + client.publish_P(F("outTopic"), HELLO_WORLD_3, MQTT_QOS2, false); + + client.beginPublish(F("outTopic"), strlen_P(HELLO_WORLD_4), MQTT_QOS1, false); + client.write_P(HELLO_WORLD_4); + client.endPublish(); + + client.subscribe(F("inTopic")); + client.subscribe_P(SUBSCRIBE_TOPIC, MQTT_QOS1); + } +} + +void loop() { + client.loop(); +} diff --git a/lib/pubsubclient3/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino b/lib/pubsubclient3/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino index f30159706..e8baef356 100644 --- a/lib/pubsubclient3/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino +++ b/lib/pubsubclient3/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino @@ -17,6 +17,8 @@ #include #include +#define _UNUSED_ __attribute__((unused)) + // Update these with values suitable for your network. byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; IPAddress ip(172, 16, 0, 100); @@ -29,7 +31,7 @@ EthernetClient ethClient; PubSubClient client(server, 1883, callback, ethClient); // Callback function -void callback(char* topic, uint8_t* payload, size_t plength) { +void callback(_UNUSED_ char* topic, uint8_t* payload, size_t plength) { // In order to republish this payload, a copy must be made // as the orignal payload buffer will be overwritten whilst // constructing the PUBLISH packet. diff --git a/lib/pubsubclient3/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino b/lib/pubsubclient3/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino index d0797419b..883193833 100644 --- a/lib/pubsubclient3/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino +++ b/lib/pubsubclient3/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino @@ -11,12 +11,14 @@ #include #include +#define _UNUSED_ __attribute__((unused)) + // Update these with values suitable for your hardware/network. byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; IPAddress ip(172, 16, 0, 100); IPAddress server(172, 16, 0, 2); -void callback(char* topic, uint8_t* payload, size_t plength) { +void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t plength) { // handle message arrived } diff --git a/lib/pubsubclient3/examples/mqtt_stream/mqtt_stream.ino b/lib/pubsubclient3/examples/mqtt_stream/mqtt_stream.ino index 54ab20fa0..922568476 100644 --- a/lib/pubsubclient3/examples/mqtt_stream/mqtt_stream.ino +++ b/lib/pubsubclient3/examples/mqtt_stream/mqtt_stream.ino @@ -14,6 +14,8 @@ #include #include +#define _UNUSED_ __attribute__((unused)) + // Update these with values suitable for your network. byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; IPAddress ip(172, 16, 0, 100); @@ -21,7 +23,7 @@ IPAddress server(172, 16, 0, 2); SRAM sram(4, SRAM_1024); -void callback(char* topic, uint8_t* payload, size_t plength) { +void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, size_t plength) { sram.seek(1); // do something with the message diff --git a/lib/pubsubclient3/keywords.txt b/lib/pubsubclient3/keywords.txt old mode 100644 new mode 100755 index 960a033f9..cf6f969d8 --- a/lib/pubsubclient3/keywords.txt +++ b/lib/pubsubclient3/keywords.txt @@ -17,9 +17,12 @@ disconnect KEYWORD2 publish KEYWORD2 publish_P KEYWORD2 beginPublish KEYWORD2 +beginPublish_P KEYWORD2 endPublish KEYWORD2 write KEYWORD2 +write_P KEYWORD2 subscribe KEYWORD2 +subscribe_P KEYWORD2 unsubscribe KEYWORD2 loop KEYWORD2 connected KEYWORD2 diff --git a/lib/pubsubclient3/library.json b/lib/pubsubclient3/library.json index 626eadc4b..e9a997bc6 100644 --- a/lib/pubsubclient3/library.json +++ b/lib/pubsubclient3/library.json @@ -6,13 +6,15 @@ "type": "git", "url": "https://github.com/hmueller01/pubsubclient3.git" }, - "version": "3.1.0", + "version": "3.3.0", "license": "MIT", "exclude": "tests", "examples": "examples/*/*.ino", "frameworks": "arduino", "platforms": [ "atmelavr", + "atmelsam", + "atmelmegaavr", "espressif8266", "espressif32" ] diff --git a/lib/pubsubclient3/library.properties b/lib/pubsubclient3/library.properties index 3de5fe1d9..3ca3575d6 100644 --- a/lib/pubsubclient3/library.properties +++ b/lib/pubsubclient3/library.properties @@ -1,5 +1,5 @@ name=PubSubClient3 -version=3.1.0 +version=3.3.0 author=Nick O'Leary maintainer=Holger Mueller license=MIT diff --git a/lib/pubsubclient3/src/PubSubClient.cpp b/lib/pubsubclient3/src/PubSubClient.cpp old mode 100644 new mode 100755 index bdc3673e0..418e963b8 --- a/lib/pubsubclient3/src/PubSubClient.cpp +++ b/lib/pubsubclient3/src/PubSubClient.cpp @@ -11,14 +11,14 @@ #include "PubSubClient.h" /** - * @brief Macro to check if a string 's' can be safely added to the MQTT buffer. + * @brief Macro to check if a string 's' can be safely added to the MQTT _buffer. * * If either check fails, the client connection is stopped and the function returns false. - * @param l current length in the buffer + * @param l current length in the _buffer * @param s string to check */ #define CHECK_STRING_LENGTH(l, s) \ - if ((!s) || (l + 2 + strnlen(s, this->bufferSize) > this->bufferSize)) { \ + if ((!s) || (l + 2 + strnlen(s, _bufferSize) > _bufferSize)) { \ _client->stop(); \ return false; \ } @@ -106,52 +106,36 @@ PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGN } PubSubClient::~PubSubClient() { - free(this->domain); - free(this->buffer); -} - -bool PubSubClient::connect(const char* id) { - return connect(id, nullptr, nullptr, nullptr, MQTT_QOS0, false, nullptr, true); -} - -bool PubSubClient::connect(const char* id, const char* user, const char* pass) { - return connect(id, user, pass, nullptr, MQTT_QOS0, false, nullptr, true); -} - -bool PubSubClient::connect(const char* id, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage) { - return connect(id, nullptr, nullptr, willTopic, willQos, willRetain, willMessage, true); -} - -bool PubSubClient::connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, - const char* willMessage) { - return connect(id, user, pass, willTopic, willQos, willRetain, willMessage, true); + free(_domain); + free(_buffer); } bool PubSubClient::connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage, bool cleanSession) { + if (!_client) return false; // do not crash if client not set if (!connected()) { int result = 0; if (_client->connected()) { result = 1; - } else if (this->port != 0) { - if (this->domain) { - result = _client->connect(this->domain, this->port); + } else if (_port != 0) { + if (_domain) { + result = _client->connect(_domain, _port); } else { - result = _client->connect(this->ip, this->port); + result = _client->connect(_ip, _port); } } if (result == 1) { - nextMsgId = 1; // init msgId (packet identifier) + _nextMsgId = 1; // init msgId (packet identifier) #if MQTT_VERSION == MQTT_VERSION_3_1 const uint8_t protocol[9] = {0x00, 0x06, 'M', 'Q', 'I', 's', 'd', 'p', MQTT_VERSION}; #elif MQTT_VERSION == MQTT_VERSION_3_1_1 const uint8_t protocol[7] = {0x00, 0x04, 'M', 'Q', 'T', 'T', MQTT_VERSION}; #endif - // Leave room in the buffer for header and variable length field - memcpy(this->buffer + MQTT_MAX_HEADER_SIZE, protocol, sizeof(protocol)); + // Leave room in the _buffer for header and variable length field + memcpy(_buffer + MQTT_MAX_HEADER_SIZE, protocol, sizeof(protocol)); size_t length = MQTT_MAX_HEADER_SIZE + sizeof(protocol); uint8_t flags = 0x00; @@ -167,38 +151,41 @@ bool PubSubClient::connect(const char* id, const char* user, const char* pass, c flags = flags | (0x01 << 6); // set password flag bit 6 } } - const uint16_t keepAlive = this->keepAliveMillis / 1000; - this->buffer[length++] = flags; - this->buffer[length++] = keepAlive >> 8; - this->buffer[length++] = keepAlive & 0xFF; + const uint16_t keepAlive = _keepAliveMillis / 1000; + _buffer[length++] = flags; + _buffer[length++] = keepAlive >> 8; + _buffer[length++] = keepAlive & 0xFF; CHECK_STRING_LENGTH(length, id) - length = writeString(id, this->buffer, length, this->bufferSize); + length = writeString(id, length); if (willTopic) { CHECK_STRING_LENGTH(length, willTopic) - length = writeString(willTopic, this->buffer, length, this->bufferSize); + length = writeString(willTopic, length); CHECK_STRING_LENGTH(length, willMessage) - length = writeString(willMessage, this->buffer, length, this->bufferSize); + length = writeString(willMessage, length); } if (user) { CHECK_STRING_LENGTH(length, user) - length = writeString(user, this->buffer, length, this->bufferSize); + length = writeString(user, length); if (pass) { CHECK_STRING_LENGTH(length, pass) - length = writeString(pass, this->buffer, length, this->bufferSize); + length = writeString(pass, length); } } - write(MQTTCONNECT, this->buffer, length - MQTT_MAX_HEADER_SIZE); - - lastInActivity = lastOutActivity = millis(); - pingOutstanding = false; + if (!writeControlPacket(MQTTCONNECT, length - MQTT_MAX_HEADER_SIZE)) { + _state = MQTT_CONNECT_FAILED; + _client->stop(); + return false; + } + _lastInActivity = _lastOutActivity = millis(); + _pingOutstanding = false; while (!_client->available()) { yield(); unsigned long t = millis(); - if (t - lastInActivity >= this->socketTimeoutMillis) { + if (t - _lastInActivity >= _socketTimeoutMillis) { DEBUG_PSC_PRINTF("connect aborting due to timeout\n"); _state = MQTT_CONNECTION_TIMEOUT; _client->stop(); @@ -209,12 +196,12 @@ bool PubSubClient::connect(const char* id, const char* user, const char* pass, c size_t len = readPacket(&hdrLen); if (len == 4) { - if (buffer[3] == 0) { - lastInActivity = millis(); + if (_buffer[3] == 0) { + _lastInActivity = millis(); _state = MQTT_CONNECTED; return true; } else { - _state = buffer[3]; + _state = _buffer[3]; } } DEBUG_PSC_PRINTF("connect aborting due to protocol error\n"); @@ -236,35 +223,39 @@ bool PubSubClient::connected() { DEBUG_PSC_PRINTF("lost connection (client may have more details)\n"); _state = MQTT_CONNECTION_LOST; _client->stop(); - pingOutstanding = false; + _pingOutstanding = false; } return false; } void PubSubClient::disconnect() { DEBUG_PSC_PRINTF("disconnect called\n"); - this->buffer[0] = MQTTDISCONNECT; - this->buffer[1] = 0; - _client->write(this->buffer, 2); _state = MQTT_DISCONNECTED; - _client->flush(); - _client->stop(); - lastInActivity = lastOutActivity = millis(); - pingOutstanding = false; + if (_client) { + _buffer[0] = MQTTDISCONNECT; + _buffer[1] = 0; + _client->write(_buffer, 2); + _client->flush(); + _client->stop(); + _lastInActivity = _lastOutActivity = millis(); + } + _pingOutstanding = false; } /** * @brief Reads a byte into result. * * @param result Pointer to result buffer. - * @return true if byte was read, false if socketTimeout occurred. + * @return true if byte was read, false if socketTimeout occurred or _client was not set. */ bool PubSubClient::readByte(uint8_t* result) { + if (!_client) return false; // do not crash if client not set + unsigned long previousMillis = millis(); while (!_client->available()) { yield(); unsigned long currentMillis = millis(); - if (currentMillis - previousMillis >= this->socketTimeoutMillis) { + if (currentMillis - previousMillis >= _socketTimeoutMillis) { return false; } } @@ -292,15 +283,15 @@ bool PubSubClient::readByte(uint8_t* result, size_t* pos) { } /** - * @brief Reads a complete packet (header, topic, payload) into this->buffer. + * @brief Reads a complete packet (header, topic, payload) into _buffer. * * @param *hdrLen Returns the variable header length send by MQTT broker (1 .. MQTT_MAX_HEADER_SIZE - 1) - * @return Number of read bytes, 0 in case of an error (socketTimeout, buffer overflow) + * @return Number of read bytes, 0 in case of an error (socketTimeout, buffer overflow, _client not set). */ size_t PubSubClient::readPacket(uint8_t* hdrLen) { size_t len = 0; - if (!readByte(this->buffer, &len)) return 0; - bool isPublish = (this->buffer[0] & 0xF0) == MQTTPUBLISH; + if (!readByte(_buffer, &len)) return 0; + bool isPublish = (_buffer[0] & 0xF0) == MQTTPUBLISH; uint32_t multiplier = 1; size_t length = 0; uint8_t digit = 0; @@ -316,7 +307,7 @@ size_t PubSubClient::readPacket(uint8_t* hdrLen) { return 0; } if (!readByte(&digit)) return 0; - this->buffer[len++] = digit; + _buffer[len++] = digit; length += (digit & 0x7F) * multiplier; // length is coded in the lower 7 bits multiplier <<= 7; // multiplier *= 128 } while ((digit & 0x80) != 0); // do while 8th continuation bit is set @@ -326,11 +317,11 @@ size_t PubSubClient::readPacket(uint8_t* hdrLen) { if (isPublish) { // Read in topic length to calculate bytes to skip over for Stream writing - if (!readByte(this->buffer, &len)) return 0; - if (!readByte(this->buffer, &len)) return 0; - skip = (this->buffer[*hdrLen + 1] << 8) + this->buffer[*hdrLen + 2]; + if (!readByte(_buffer, &len)) return 0; + if (!readByte(_buffer, &len)) return 0; + skip = (_buffer[*hdrLen + 1] << 8) + _buffer[*hdrLen + 2]; start = 2; - if (MQTT_HDR_GET_QOS(this->buffer[0]) > MQTT_QOS0) { + if (MQTT_HDR_GET_QOS(_buffer[0]) > MQTT_QOS0) { // skip msgId (packet identifier) for QoS 1 and 2 messages skip += 2; } @@ -339,20 +330,20 @@ size_t PubSubClient::readPacket(uint8_t* hdrLen) { for (size_t i = start; i < length; i++) { if (!readByte(&digit)) return 0; - if (this->stream) { - if (isPublish && idx - *hdrLen - 2 > skip) { - this->stream->write(digit); + if (_stream) { + if (isPublish && (idx - *hdrLen - 2 > skip)) { + _stream->write(digit); } } - if (len < this->bufferSize) { - this->buffer[len++] = digit; + if (len < _bufferSize) { + _buffer[len++] = digit; } idx++; } - if (!this->stream && idx > this->bufferSize) { - DEBUG_PSC_PRINTF("readPacket ignoring packet of size %zu exceeding buffer of size %zu\n", length, this->bufferSize); + if (!_stream && (idx > _bufferSize)) { + DEBUG_PSC_PRINTF("readPacket ignoring packet of size %zu exceeding buffer of size %zu\n", length, _bufferSize); len = 0; // This will cause the packet to be ignored. } return len; @@ -362,11 +353,11 @@ size_t PubSubClient::readPacket(uint8_t* hdrLen) { * @brief After a packet is read handle the content here (call the callback, handle pings). * * @param hdrLen Variable header length send by MQTT broker (1 .. MQTT_MAX_HEADER_SIZE - 1). - * @param length Number of read bytes in this->buffer. + * @param length Number of read bytes in _buffer. * @return true if packet was successfully processed, false if a buffer over or underflow occurred. */ bool PubSubClient::handlePacket(uint8_t hdrLen, size_t length) { - uint8_t type = this->buffer[0] & 0xF0; + uint8_t type = _buffer[0] & 0xF0; DEBUG_PSC_PRINTF("received message of type %u\n", type); switch (type) { case MQTTPUBLISH: @@ -374,17 +365,17 @@ bool PubSubClient::handlePacket(uint8_t hdrLen, size_t length) { // MQTT Publish packet: See section 3.3 MQTT v3.1.1 protocol specification: // - Header: 1 byte // - Remaining header length: hdrLen bytes, multibyte field (1 .. MQTT_MAX_HEADER_SIZE - 1) - // - Topic length: 2 bytes (starts at buffer[hdrLen + 1]) - // - Topic: topicLen bytes (starts at buffer[hdrLen + 3]) - // - Packet Identifier (msgId): 0 bytes for QoS 0, 2 bytes for QoS 1 and 2 (starts at buffer[hdrLen + 3 + topicLen]) - // - Payload (for QoS = 0): length - (hdrLen + 3 + topicLen) bytes (starts at buffer[hdrLen + 3 + topicLen]) - // - Payload (for QoS > 0): length - (hdrLen + 5 + topicLen) bytes (starts at buffer[hdrLen + 5 + topicLen]) + // - Topic length: 2 bytes (starts at _buffer[hdrLen + 1]) + // - Topic: topicLen bytes (starts at _buffer[hdrLen + 3]) + // - Packet Identifier (msgId): 0 bytes for QoS 0, 2 bytes for QoS 1 and 2 (starts at _buffer[hdrLen + 3 + topicLen]) + // - Payload (for QoS = 0): length - (hdrLen + 3 + topicLen) bytes (starts at _buffer[hdrLen + 3 + topicLen]) + // - Payload (for QoS > 0): length - (hdrLen + 5 + topicLen) bytes (starts at _buffer[hdrLen + 5 + topicLen]) // To get a null reminated 'C' topic string we move the topic 1 byte to the front (overwriting the LSB of the topic lenght) - uint16_t topicLen = (this->buffer[hdrLen + 1] << 8) + this->buffer[hdrLen + 2]; // topic length in bytes - char* topic = (char*)(this->buffer + hdrLen + 3 - 1); // set the topic in the LSB of the topic lenght, as we move it there - uint16_t payloadOffset = hdrLen + 3 + topicLen; // payload starts after header and topic (if there is no packet identifier) - size_t payloadLen = length - payloadOffset; // this might change by 2 if we have a QoS 1 or 2 message - uint8_t* payload = this->buffer + payloadOffset; + uint16_t topicLen = (_buffer[hdrLen + 1] << 8) + _buffer[hdrLen + 2]; // topic length in bytes + char* topic = (char*)(_buffer + hdrLen + 3 - 1); // set the topic in the LSB of the topic lenght, as we move it there + uint16_t payloadOffset = hdrLen + 3 + topicLen; // payload starts after header and topic (if there is no packet identifier) + size_t payloadLen = length - payloadOffset; // this might change by 2 if we have a QoS 1 or 2 message + uint8_t* payload = _buffer + payloadOffset; if (length < payloadOffset) { // do not move outside the max bufferSize ERROR_PSC_PRINTF_P("handlePacket(): Suspicious topicLen (%u) points outside of received buffer length (%zu)\n", topicLen, length); @@ -393,7 +384,7 @@ bool PubSubClient::handlePacket(uint8_t hdrLen, size_t length) { memmove(topic, topic + 1, topicLen); // move topic inside buffer 1 byte to front topic[topicLen] = '\0'; // end the topic as a 'C' string with \x00 - if (MQTT_HDR_GET_QOS(this->buffer[0]) == MQTT_QOS0) { + if (MQTT_HDR_GET_QOS(_buffer[0]) == MQTT_QOS0) { // No msgId for QOS == 0 callback(topic, payload, payloadLen); } else { @@ -402,15 +393,18 @@ bool PubSubClient::handlePacket(uint8_t hdrLen, size_t length) { ERROR_PSC_PRINTF_P("handlePacket(): Missing msgId in QoS 1/2 message\n"); return false; } - uint16_t msgId = (this->buffer[payloadOffset] << 8) + this->buffer[payloadOffset + 1]; + uint8_t publishQos = MQTT_HDR_GET_QOS(_buffer[0]); // save QoS before _buffer[0] is overwritten + uint16_t msgId = (_buffer[payloadOffset] << 8) + _buffer[payloadOffset + 1]; callback(topic, payload + 2, payloadLen - 2); // remove the msgId from the callback payload - this->buffer[0] = MQTTPUBACK; - this->buffer[1] = 2; - this->buffer[2] = (msgId >> 8); - this->buffer[3] = (msgId & 0xFF); - if (_client->write(this->buffer, 4) == 4) { - lastOutActivity = millis(); + // QoS 1: respond with PUBACK + // QoS 2: respond with PUBREC (first step of the QoS 2 subscriber handshake) + _buffer[0] = (publishQos == MQTT_QOS1) ? MQTTPUBACK : MQTTPUBREC; + _buffer[1] = 2; + _buffer[2] = (msgId >> 8); + _buffer[3] = (msgId & 0xFF); + if (_client->write(_buffer, 4) == 4) { + _lastOutActivity = millis(); } } } @@ -424,20 +418,36 @@ bool PubSubClient::handlePacket(uint8_t hdrLen, size_t length) { // No futher action here, as resending is not supported. break; case MQTTPUBREC: - // MQTT Publish Received (QoS 2 publish received, part 1): See section 3.5 MQTT v3.1.1 protocol specification + // MQTT Publish Received (QoS 2 publisher handshake, part 1): broker acknowledges our QoS 2 PUBLISH. + // See section 3.5 MQTT v3.1.1 protocol specification. if (length < 4) { ERROR_PSC_PRINTF_P("handlePacket(): Received PUBREC packet with length %zu, expected at least 4 bytes\n", length); return false; } - // MQTT Publish Release (QoS 2 publish received, part 2): See section 3.6 MQTT v3.1.1 protocol specification - buffer[0] = MQTTPUBREL | 2; // PUBREL with bit 1 set - // bytes 1-3 of PUBREL are the same as of PUBREC - if (_client->write(buffer, 4) == 4) { - lastOutActivity = millis(); + // MQTT Publish Release (QoS 2 publisher handshake, part 2): See section 3.6 MQTT v3.1.1 protocol specification + _buffer[0] = MQTTPUBREL | 2; // PUBREL fixed header: bit 1 must be set per spec + // bytes 1-3 of PUBREL are the same as of PUBREC (remaining length + msgId) + if (_client->write(_buffer, 4) == 4) { + _lastOutActivity = millis(); + } + break; + case MQTTPUBREL: + // MQTT Publish Release (QoS 2 subscriber handshake, part 2): broker releases the message to us. + // See section 3.6 MQTT v3.1.1 protocol specification. + if (length < 4) { + ERROR_PSC_PRINTF_P("handlePacket(): Received PUBREL packet with length %zu, expected at least 4 bytes\n", length); + return false; + } + // MQTT Publish Complete (QoS 2 subscriber handshake, part 3): See section 3.7 MQTT v3.1.1 protocol specification + _buffer[0] = MQTTPUBCOMP; + // bytes 1-3 of PUBCOMP are the same as of PUBREL (remaining length + msgId) + if (_client->write(_buffer, 4) == 4) { + _lastOutActivity = millis(); } break; case MQTTPUBCOMP: - // MQTT Publish Complete (QoS 2 publish received, part 3): See section 3.7 MQTT v3.1.1 protocol specification + // MQTT Publish Complete (QoS 2 publisher handshake, part 3): broker confirms delivery of our QoS 2 PUBLISH. + // See section 3.7 MQTT v3.1.1 protocol specification. if (length < 4) { ERROR_PSC_PRINTF_P("handlePacket(): Received PUBCOMP packet with length %zu, expected at least 4 bytes\n", length); return false; @@ -446,14 +456,14 @@ bool PubSubClient::handlePacket(uint8_t hdrLen, size_t length) { break; case MQTTPINGREQ: // MQTT Ping Request: See section 3.12 MQTT v3.1.1 protocol specification - this->buffer[0] = MQTTPINGRESP; - this->buffer[1] = 0; - if (_client->write(this->buffer, 2) == 2) { - lastOutActivity = millis(); + _buffer[0] = MQTTPINGRESP; + _buffer[1] = 0; + if (_client->write(_buffer, 2) == 2) { + _lastOutActivity = millis(); } break; case MQTTPINGRESP: - pingOutstanding = false; + _pingOutstanding = false; break; default: break; @@ -467,19 +477,27 @@ bool PubSubClient::loop() { } bool ret = true; const unsigned long t = millis(); - if (keepAliveMillis && ((t - lastInActivity > this->keepAliveMillis) || (t - lastOutActivity > this->keepAliveMillis))) { - if (pingOutstanding) { + if (_keepAliveMillis && ((t - _lastInActivity > _keepAliveMillis) || (t - _lastOutActivity > _keepAliveMillis))) { + if (_pingOutstanding) { DEBUG_PSC_PRINTF("loop aborting due to timeout\n"); _state = MQTT_CONNECTION_TIMEOUT; _client->stop(); - pingOutstanding = false; + _pingOutstanding = false; return false; + } else if (_bufferWritePos > 0) { + // There is still data in the _buffer to be sent, so send it now instead of a ping + if (flushBuffer() == 0) { + _state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + _pingOutstanding = false; + return false; + } } else { - this->buffer[0] = MQTTPINGREQ; - this->buffer[1] = 0; - if (_client->write(this->buffer, 2) == 2) { - lastInActivity = lastOutActivity = t; - pingOutstanding = true; + _buffer[0] = MQTTPINGREQ; + _buffer[1] = 0; + if (_client->write(_buffer, 2) == 2) { + _lastInActivity = _lastOutActivity = t; + _pingOutstanding = true; } } } @@ -487,7 +505,7 @@ bool PubSubClient::loop() { uint8_t hdrLen; size_t len = readPacket(&hdrLen); if (len > 0) { - lastInActivity = t; + _lastInActivity = t; ret = handlePacket(hdrLen, len); if (!ret) { _state = MQTT_DISCONNECTED; @@ -501,101 +519,88 @@ bool PubSubClient::loop() { return ret; } -bool PubSubClient::publish(const char* topic, const char* payload) { - return publish(topic, payload, MQTT_QOS0, false); -} - -bool PubSubClient::publish(const char* topic, const char* payload, bool retained) { - return publish(topic, payload, MQTT_QOS0, retained); -} - -bool PubSubClient::publish(const char* topic, const char* payload, uint8_t qos, bool retained) { - return publish(topic, (const uint8_t*)payload, payload ? strnlen(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, qos, retained); -} - -bool PubSubClient::publish(const char* topic, const uint8_t* payload, size_t plength) { - return publish(topic, payload, plength, MQTT_QOS0, false); -} - -bool PubSubClient::publish(const char* topic, const uint8_t* payload, size_t plength, bool retained) { - return publish(topic, payload, plength, MQTT_QOS0, retained); -} - bool PubSubClient::publish(const char* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained) { if (beginPublish(topic, plength, qos, retained)) { size_t rc = write(payload, plength); - lastOutActivity = millis(); return endPublish() && (rc == plength); } return false; } -bool PubSubClient::publish_P(const char* topic, const char* payload, bool retained) { - return publish_P(topic, payload, MQTT_QOS0, retained); -} - -bool PubSubClient::publish_P(const char* topic, const char* payload, uint8_t qos, bool retained) { - return publish_P(topic, (const uint8_t*)payload, payload ? strnlen_P(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, qos, retained); -} - -bool PubSubClient::publish_P(const char* topic, const uint8_t* payload, size_t plength, bool retained) { - return publish_P(topic, payload, plength, MQTT_QOS0, retained); +bool PubSubClient::publish(const __FlashStringHelper* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained) { + if (beginPublish(topic, plength, qos, retained)) { + size_t rc = write(payload, plength); + return endPublish() && (rc == plength); + } + return false; } bool PubSubClient::publish_P(const char* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained) { if (beginPublish(topic, plength, qos, retained)) { - size_t rc = 0; - for (size_t i = 0; i < plength; i++) { - rc += _client->write((uint8_t)pgm_read_byte_near(payload + i)); - } - lastOutActivity = millis(); + size_t rc = write_P(payload, plength); return endPublish() && (rc == plength); } return false; } -bool PubSubClient::beginPublish(const char* topic, size_t plength, bool retained) { - return beginPublish(topic, plength, MQTT_QOS0, retained); +bool PubSubClient::publish_P(const __FlashStringHelper* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained) { + if (beginPublish(topic, plength, qos, retained)) { + size_t rc = write_P(payload, plength); + return endPublish() && (rc == plength); + } + return false; } -bool PubSubClient::beginPublish(const char* topic, size_t plength, uint8_t qos, bool retained) { +/** + * @brief Internal beginPublish implementation using topic stored in RAM or PROGMEM. + * + * @param progmem true if the topic is stored in PROGMEM/Flash, false if in RAM. + * @param topic The topic to publish to. + * @param plength The length of the payload. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ +bool PubSubClient::beginPublishImpl(bool progmem, const char* topic, size_t plength, uint8_t qos, bool retained) { if (!topic) return false; - if (strlen(topic) == 0) return false; // empty topic is not allowed - if (qos > MQTT_QOS2) { // only valid QoS supported - this->_qos = MQTT_QOS0; // reset QoS to 0, that endPublish() will not send a nextMsgId + + // get topic length depending on storage (RAM vs PROGMEM) + size_t topicLen = progmem ? strlen_P(topic) : strlen(topic); + if (topicLen == 0) return false; // empty topic is not allowed + + if (qos > MQTT_QOS2) { // only valid QoS supported ERROR_PSC_PRINTF_P("beginPublish() called with invalid QoS %u\n", qos); return false; } - this->_qos = qos; // save the QoS for later endPublish() operation - // check if the header and the topic (including 2 length bytes) fit into the buffer - if (connected() && MQTT_MAX_HEADER_SIZE + strlen(topic) + 2 <= this->bufferSize) { - // first write the topic at the end of the maximal variable header (MQTT_MAX_HEADER_SIZE) to the buffer - size_t topicLen = writeString(topic, this->buffer, MQTT_MAX_HEADER_SIZE, this->bufferSize) - MQTT_MAX_HEADER_SIZE; - // we now know the length of the topic string (lenght + 2 bytes signalling the length) and can build the variable header information + + const size_t nextMsgLen = (qos > MQTT_QOS0) ? 2 : 0; // add 2 bytes for nextMsgId if QoS > 0 + // check if the header, the topic (including 2 length bytes) and nextMsgId fit into the _buffer + if (connected() && (MQTT_MAX_HEADER_SIZE + topicLen + 2 + nextMsgLen <= _bufferSize)) { + // first write the topic at the end of the maximal variable header (MQTT_MAX_HEADER_SIZE) to the _buffer + topicLen = writeStringImpl(progmem, topic, MQTT_MAX_HEADER_SIZE) - MQTT_MAX_HEADER_SIZE; + if (qos > MQTT_QOS0) { + // if QoS 1 or 2, we need to send the nextMsgId (packet identifier) after topic + writeNextMsgId(MQTT_MAX_HEADER_SIZE + topicLen); + } + // we now know the length of the topic string (length + 2 bytes signalling the length) and can build the variable header information const uint8_t header = MQTTPUBLISH | MQTT_QOS_GET_HDR(qos) | (retained ? MQTTRETAINED : 0); - const size_t nextMsgLen = (qos) ? 2 : 0; // add 2 bytes for the nextMsgId if QoS > 0 - uint8_t hdrLen = buildHeader(header, this->buffer, topicLen + plength + nextMsgLen); + uint8_t hdrLen = buildHeader(header, topicLen + nextMsgLen + plength); if (hdrLen == 0) return false; // exit here in case of header generation failure // as the header length is variable, it starts at MQTT_MAX_HEADER_SIZE - hdrLen (see buildHeader() documentation) - size_t rc = _client->write(this->buffer + (MQTT_MAX_HEADER_SIZE - hdrLen), hdrLen + topicLen); - lastOutActivity = millis(); - return (rc == (hdrLen + topicLen)); + size_t rc = _client->write(_buffer + (MQTT_MAX_HEADER_SIZE - hdrLen), hdrLen + topicLen + nextMsgLen); + _lastOutActivity = millis(); + return (rc == (hdrLen + topicLen + nextMsgLen)); } return false; } bool PubSubClient::endPublish() { - flushBuffer(); if (connected()) { - if (this->_qos > MQTT_QOS0) { - // QoS == 1 or 2, send the msgId - uint8_t buf[2]; - writeNextMsgId(buf, 0, 2); - size_t rc = _client->write(buf, 2); - lastOutActivity = millis(); - return (rc == 2); + if (_bufferWritePos > 0) { + // still data in the _buffer to be sent + if (flushBuffer() == 0) return false; } - // QoS == 0, no msgId to send return true; } return false; @@ -604,14 +609,13 @@ bool PubSubClient::endPublish() { /** * @brief Build up the header ready to send. * Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start - * (MQTT_MAX_HEADER_SIZE - ) bytes into the buffer. + * (MQTT_MAX_HEADER_SIZE - ) bytes into the _buffer. * * @param header Header byte, e.g. MQTTCONNECT, MQTTPUBLISH, MQTTSUBSCRIBE, MQTTUNSUBSCRIBE. - * @param buf Buffer to write header to. * @param length Length to encode in the header. * @return Returns the size of the header (1 .. MQTT_MAX_HEADER_SIZE), or 0 in case of a failure (e.g. length to big). */ -uint8_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, size_t length) { +uint8_t PubSubClient::buildHeader(uint8_t header, size_t length) { uint8_t hdrBuf[MQTT_MAX_HEADER_SIZE - 1]; uint8_t hdrLen = 0; uint8_t digit; @@ -623,188 +627,231 @@ uint8_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, size_t length) { digit |= 0x80; } hdrBuf[hdrLen++] = digit; - } while (len > 0 && hdrLen < MQTT_MAX_HEADER_SIZE - 1); + } while ((len > 0) && (hdrLen < MQTT_MAX_HEADER_SIZE - 1)); if (len > 0) { - ERROR_PSC_PRINTF_P("buildHeader() length too big %zu, left %zu\n", length, len); + ERROR_PSC_PRINTF_P("buildHeader: header=0x%02X, length too big %zu, left %zu\n", header, length, len); return 0; } - buf[MQTT_MAX_HEADER_SIZE - 1 - hdrLen] = header; - memcpy(buf + MQTT_MAX_HEADER_SIZE - hdrLen, hdrBuf, hdrLen); + _buffer[MQTT_MAX_HEADER_SIZE - 1 - hdrLen] = header; + memcpy(_buffer + MQTT_MAX_HEADER_SIZE - hdrLen, hdrBuf, hdrLen); return hdrLen + 1; // Full header size is variable length bit plus the 1-byte fixed header } size_t PubSubClient::write(uint8_t data) { - const size_t rc = appendBuffer(data); - if (rc != 0) { - lastOutActivity = millis(); - } - return rc; + return appendBuffer(data); } -size_t PubSubClient::write(const uint8_t* buffer, size_t size) { - const size_t rc = appendBuffer(buffer, size); - if (rc != 0) { - lastOutActivity = millis(); +size_t PubSubClient::write(const uint8_t* buf, size_t size) { + for (size_t i = 0; i < size; i++) { + if (appendBuffer(buf[i]) == 0) return i; } - return rc; + return size; +} + +size_t PubSubClient::write_P(const uint8_t* buf, size_t size) { + for (size_t i = 0; i < size; i++) { + if (appendBuffer((uint8_t)pgm_read_byte_near(buf + i)) == 0) return i; + } + return size; } /** - * @brief Send the header and the prepared data to the client / MQTT broker. + * @brief Write a MQTT Control Packet (header and the prepared data) to the client / MQTT broker. The prepared data of size length must already be in the + * internal _buffer starting at MQTT_MAX_HEADER_SIZE (space is needed for header generation). * * @param header Header byte, e.g. MQTTCONNECT, MQTTPUBLISH, MQTTSUBSCRIBE, MQTTUNSUBSCRIBE. - * @param buf Buffer of data to write. - * @param length Length of buf to write. - * @return True if successfully sent, otherwise false if buildHeader() failed or buf could not be written. + * @param length Length of _buffer to write. + * @return True if successfully sent, otherwise false if build header failed or buffer could not be written. */ -bool PubSubClient::write(uint8_t header, uint8_t* buf, size_t length) { - bool result = true; - size_t rc; - uint8_t hdrLen = buildHeader(header, buf, length); +bool PubSubClient::writeControlPacket(uint8_t header, size_t length) { + uint8_t hdrLen = buildHeader(header, length); if (hdrLen == 0) return false; // exit here in case of header generation failure -#ifdef MQTT_MAX_TRANSFER_SIZE - uint8_t* writeBuf = buf + (MQTT_MAX_HEADER_SIZE - hdrLen); - size_t bytesRemaining = length + hdrLen; // Match the length type - size_t bytesToWrite; - while ((bytesRemaining > 0) && result) { - yield(); - bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE) ? MQTT_MAX_TRANSFER_SIZE : bytesRemaining; - rc = _client->write(writeBuf, bytesToWrite); - result = (rc == bytesToWrite); - bytesRemaining -= rc; - writeBuf += rc; - if (result) { - lastOutActivity = millis(); - } - } -#else - rc = _client->write(buf + (MQTT_MAX_HEADER_SIZE - hdrLen), length + hdrLen); - result = (rc == length + hdrLen); - if (result) { - lastOutActivity = millis(); - } -#endif - return result; + return writeBuffer(MQTT_MAX_HEADER_SIZE - hdrLen, hdrLen + length); } /** - * @brief Write an UTF-8 encoded string to the give buffer and position. The string can have a length of 0 to 65535 bytes. The buffer is prefixed with - * two bytes representing the length of the string. See section 1.5.3 of MQTT v3.1.1 protocol specification. + * @brief Write the internal _buffer to the client / MQTT broker. + * + * @param pos Position in the _buffer to start writing from. + * @param size Number of bytes to write from the _buffer. + * @return Number of bytes written to the client / MQTT broker (0 .. bufferSize). If 0 is returned a write error occurred or buffer index error. + */ +size_t PubSubClient::writeBuffer(size_t pos, size_t size) { + size_t rc = 0; + if (_client && (size > 0) && (pos + size <= _bufferSize)) { +#ifdef MQTT_MAX_TRANSFER_SIZE + uint8_t* writeBuf = _buffer + pos; + size_t bytesRemaining = size; + bool result = true; + while ((bytesRemaining > 0) && result) { + size_t bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE) ? MQTT_MAX_TRANSFER_SIZE : bytesRemaining; + size_t bytesWritten = _client->write(writeBuf, bytesToWrite); + result = (bytesWritten == bytesToWrite); + bytesRemaining -= bytesWritten; + writeBuf += bytesWritten; + if (result) { + _lastOutActivity = millis(); + } + yield(); + } + rc = result ? size : 0; // if result is false indicate a write error +#else + rc = _client->write(_buffer + pos, size); + if (rc == size) { + _lastOutActivity = millis(); + } else { + rc = 0; // indicate a write error + } +#endif + } + return rc; +} + +/** + * @brief Internal implementation of writeString using RAM or PROGMEM string. + * Write an UTF-8 encoded string to the internal buffer at a given position. The string can have a length of 0 to 65535 bytes (depending on size of + * internal buffer). The buffer is prefixed with two bytes representing the length of the string. See section 1.5.3 of MQTT v3.1.1 protocol specification. * @note If the string does not fit in the buffer or is longer than 65535 bytes nothing is written to the buffer and the returned position is * unchanged. * + * @param progmem true if the string is stored in PROGMEM, false if in RAM. * @param string 'C' string of the data that shall be written in the buffer. - * @param buf Buffer to write the string into. - * @param pos Position in the buffer to write the string. - * @param size Maximal size of the buffer. - * @return New position in the buffer (pos + 2 + string length), or pos if a buffer overrun would occur or the string is a nullptr. + * @param pos Position in the internal buffer to write the string. + * @return New position in the internal buffer (pos + 2 + string length), or pos if a buffer overrun would occur or the string is a nullptr. */ -size_t PubSubClient::writeString(const char* string, uint8_t* buf, size_t pos, size_t size) { +size_t PubSubClient::writeStringImpl(bool progmem, const char* string, size_t pos) { if (!string) return pos; - size_t sLen = strlen(string); - if (pos + 2 + sLen <= size && sLen <= 0xFFFF) { - buf[pos++] = (uint8_t)(sLen >> 8); - buf[pos++] = (uint8_t)(sLen & 0xFF); - memcpy(buf + pos, string, sLen); + size_t sLen = progmem ? strlen_P(string) : strlen(string); + if ((pos + 2 + sLen <= _bufferSize) && (sLen <= 0xFFFF)) { + _buffer[pos++] = (uint8_t)(sLen >> 8); + _buffer[pos++] = (uint8_t)(sLen & 0xFF); + if (progmem) { + memcpy_P(_buffer + pos, string, sLen); + } else { + memcpy(_buffer + pos, string, sLen); + } pos += sLen; } else { - ERROR_PSC_PRINTF_P("writeString(): string (%zu) does not fit into buf (%zu)\n", pos + 2 + sLen, size); + ERROR_PSC_PRINTF_P("writeStringImpl(): string (%zu) does not fit into buf (%zu)\n", pos + 2 + sLen, _bufferSize); } return pos; } +/** + * @brief Write an UTF-8 encoded string to the internal buffer at a given position. The string can have a length of 0 to 65535 bytes (depending on size of + * internal buffer). The buffer is prefixed with two bytes representing the length of the string. See section 1.5.3 of MQTT v3.1.1 protocol specification. + * @note If the string does not fit in the buffer or is longer than 65535 bytes nothing is written to the buffer and the returned position is + * unchanged. + * + * @param string 'C' string of the data that shall be written in the buffer. + * @param pos Position in the internal buffer to write the string. + * @return New position in the internal buffer (pos + 2 + string length), or pos if a buffer overrun would occur or the string is a nullptr. + */ +inline size_t PubSubClient::writeString(const char* string, size_t pos) { + return writeStringImpl(false, string, pos); +} +/** + * @brief Write nextMsgId to the internal buffer at the given position. + * @note If the nextMsgId (2 bytes) does not fit in the buffer nothing is written to the buffer and the returned position is unchanged. + * + * @param pos Position in the internal buffer to write the nextMsgId. + * @return New position in the internal buffer (pos + 2), or pos if a buffer overrun would occur. + */ +size_t PubSubClient::writeNextMsgId(size_t pos) { + if ((pos + 2) <= _bufferSize) { + _nextMsgId = (++_nextMsgId == 0) ? 1 : _nextMsgId; // increment msgId (must not be 0, so start at 1) + _buffer[pos++] = (uint8_t)(_nextMsgId >> 8); + _buffer[pos++] = (uint8_t)(_nextMsgId & 0xFF); + } else { + ERROR_PSC_PRINTF_P("writeNextMsgId(): buffer overrun (%zu) \n", pos + 2); + } + return pos; +} + +/** + * @brief Append a byte to the internal _buffer. If the _buffer is full it is flushed to the client / MQTT broker. + * + * @param data Byte to append to the _buffer. + * @return Number of bytes appended to the _buffer (0 or 1). If 0 is returned a write error occurred. + */ size_t PubSubClient::appendBuffer(uint8_t data) { - buffer[_bufferWritePos] = data; - ++_bufferWritePos; - if (_bufferWritePos >= bufferSize) { + _buffer[_bufferWritePos++] = data; + if (_bufferWritePos >= _bufferSize) { 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; -} - +/** + * @brief Flush the internal _buffer (bytes 0 .. _bufferWritePos) to the client / MQTT broker. + * This is used by endPublish() to flush data written by appendBuffer(). + * + * @return Number of bytes written to the client / MQTT broker (0 .. bufferSize). If 0 is returned a write error occurred or the _buffer was empty. + */ 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; + if (connected()) { + rc = writeBuffer(0, _bufferWritePos); } + _bufferWritePos = 0; return rc; } /** - * @brief Write nextMsgId to the give buffer and position. - * @note If the nextMsgId (2 bytes) does not fit in the buffer nothing is written to the buffer and the returned position is unchanged. - * - * @param buf Buffer to write the nextMsgId into. - * @param pos Position in the buffer to write the nextMsgId. - * @param size Maximal size of the buffer. - * @return New position in the buffer (pos + 2), or pos if a buffer overrun would occur. + * @brief Internal subscribes to messages published to the specified topic. The topic can be stored in RAM or PROGMEM. + * @param progmem true if the topic is stored in PROGMEM/Flash, false if in RAM. + * @param topic The topic to subscribe to. + * @param qos The qos to subscribe at. [0, 1]. + * @return true If sending the subscribe succeeded. + * false If sending the subscribe failed, either connection lost or message too large. */ -size_t PubSubClient::writeNextMsgId(uint8_t* buf, size_t pos, size_t size) { - if (pos + 2 <= size) { - nextMsgId = (++nextMsgId == 0) ? 1 : nextMsgId; // increment msgId (must not be 0, so start at 1) - buf[pos++] = (uint8_t)(nextMsgId >> 8); - buf[pos++] = (uint8_t)(nextMsgId & 0xFF); - } else { - ERROR_PSC_PRINTF_P("writeNextMsgId(): buffer (%zu) does not fit into buf (%zu)\n", pos + 2, size); - } - return pos; -} - -bool PubSubClient::subscribe(const char* topic) { - return subscribe(topic, MQTT_QOS0); -} - -bool PubSubClient::subscribe(const char* topic, uint8_t qos) { +bool PubSubClient::subscribeImpl(bool progmem, const char* topic, uint8_t qos) { if (!topic) return false; if (qos > MQTT_QOS1) return false; // only QoS 0 and 1 supported - size_t topicLen = strnlen(topic, this->bufferSize); - if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2 + 2 + topicLen + 1) { + // get topic length depending on storage (RAM vs PROGMEM) + size_t topicLen = progmem ? strnlen_P(topic, _bufferSize) : strnlen(topic, _bufferSize); + if (_bufferSize < MQTT_MAX_HEADER_SIZE + 2 + 2 + topicLen + 1) { // Too long: header + nextMsgId (2) + topic length bytes (2) + topicLen + QoS (1) return false; } if (connected()) { - // Leave room in the buffer for header and variable length field + // Leave room in the _buffer for header and variable length field uint16_t length = MQTT_MAX_HEADER_SIZE; - length = writeNextMsgId(buffer, length, this->bufferSize); // buffer size is checked before - length = writeString(topic, this->buffer, length, this->bufferSize); - this->buffer[length++] = qos; - return write(MQTTSUBSCRIBE | MQTT_QOS_GET_HDR(MQTT_QOS1), this->buffer, length - MQTT_MAX_HEADER_SIZE); + length = writeNextMsgId(length); // _buffer size is checked before + length = writeStringImpl(progmem, topic, length); + _buffer[length++] = qos; + return writeControlPacket(MQTTSUBSCRIBE | MQTT_QOS_GET_HDR(MQTT_QOS1), length - MQTT_MAX_HEADER_SIZE); } return false; } -bool PubSubClient::unsubscribe(const char* topic) { +/** + * @brief Internal unsubscribes from messages published to the specified topic. The topic can be stored in RAM or PROGMEM. + * @param progmem true if the topic is stored in PROGMEM/Flash, false if in RAM. + * @param topic The topic to unsubscribe from. + * @return true If sending the unsubscribe succeeded. + * false If sending the unsubscribe failed, either connection lost or message too large. + */ +bool PubSubClient::unsubscribeImpl(bool progmem, const char* topic) { if (!topic) return false; - size_t topicLen = strnlen(topic, this->bufferSize); - if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2 + 2 + topicLen) { + // get topic length depending on storage (RAM vs PROGMEM) + size_t topicLen = progmem ? strnlen_P(topic, _bufferSize) : strnlen(topic, _bufferSize); + if (_bufferSize < MQTT_MAX_HEADER_SIZE + 2 + 2 + topicLen) { // Too long: header + nextMsgId (2) + topic length bytes (2) + topicLen return false; } if (connected()) { uint16_t length = MQTT_MAX_HEADER_SIZE; - length = writeNextMsgId(buffer, length, this->bufferSize); // buffer size is checked before - length = writeString(topic, this->buffer, length, this->bufferSize); - return write(MQTTUNSUBSCRIBE | MQTT_QOS_GET_HDR(MQTT_QOS1), this->buffer, length - MQTT_MAX_HEADER_SIZE); + length = writeNextMsgId(length); // _buffer size is checked before + length = writeStringImpl(progmem, topic, length); + return writeControlPacket(MQTTUNSUBSCRIBE | MQTT_QOS_GET_HDR(MQTT_QOS1), length - MQTT_MAX_HEADER_SIZE); } return false; } @@ -815,26 +862,26 @@ PubSubClient& PubSubClient::setServer(uint8_t* ip, uint16_t port) { } PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { - this->ip = ip; - this->port = port; - free(this->domain); - this->domain = nullptr; + _ip = ip; + _port = port; + free(_domain); + _domain = nullptr; return *this; } PubSubClient& PubSubClient::setServer(const char* domain, uint16_t port) { char* newDomain = nullptr; if (domain) { - newDomain = (char*)realloc(this->domain, strlen(domain) + 1); + newDomain = (char*)realloc(_domain, strlen(domain) + 1); } if (newDomain) { strcpy(newDomain, domain); - this->domain = newDomain; - this->port = port; + _domain = newDomain; + _port = port; } else { - free(this->domain); - this->domain = nullptr; - this->port = 0; + free(_domain); + _domain = nullptr; + _port = 0; } return *this; } @@ -845,12 +892,12 @@ PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { } PubSubClient& PubSubClient::setClient(Client& client) { - this->_client = &client; + _client = &client; return *this; } PubSubClient& PubSubClient::setStream(Stream& stream) { - this->stream = &stream; + _stream = &stream; return *this; } @@ -859,34 +906,34 @@ bool PubSubClient::setBufferSize(size_t size) { // Cannot set it back to 0 return false; } - if (this->bufferSize == 0) { - this->buffer = (uint8_t*)malloc(size); + if (_bufferSize == 0) { + _buffer = (uint8_t*)malloc(size); } else { - uint8_t* newBuffer = (uint8_t*)realloc(this->buffer, size); + uint8_t* newBuffer = (uint8_t*)realloc(_buffer, size); if (newBuffer) { - this->buffer = newBuffer; + _buffer = newBuffer; } else { return false; } } - this->bufferSize = size; - return (this->buffer != nullptr); + _bufferSize = size; + return (_buffer != nullptr); } size_t PubSubClient::getBufferSize() { - return this->bufferSize; + return _bufferSize; } PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive) { - this->keepAliveMillis = keepAlive * 1000UL; + _keepAliveMillis = keepAlive * 1000UL; return *this; } PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout) { - this->socketTimeoutMillis = timeout * 1000UL; + _socketTimeoutMillis = timeout * 1000UL; return *this; } int PubSubClient::state() { - return this->_state; + return _state; } diff --git a/lib/pubsubclient3/src/PubSubClient.h b/lib/pubsubclient3/src/PubSubClient.h old mode 100644 new mode 100755 index a632c0382..9677d9f4e --- a/lib/pubsubclient3/src/PubSubClient.h +++ b/lib/pubsubclient3/src/PubSubClient.h @@ -34,7 +34,7 @@ * @brief Maximum packet size defined by MQTT protocol. */ #ifndef MQTT_MAX_POSSIBLE_PACKET_SIZE -#define MQTT_MAX_POSSIBLE_PACKET_SIZE 268435455 +#define MQTT_MAX_POSSIBLE_PACKET_SIZE ((size_t)268435455) // might be limited to 65535 if size_t is 16-bit (unsigned int) #endif /** @@ -176,36 +176,39 @@ class PubSubClient : public Print { private: Client* _client{}; - uint8_t* buffer{}; - size_t bufferSize{}; - unsigned long keepAliveMillis{}; - unsigned long socketTimeoutMillis{}; - uint16_t nextMsgId{}; - unsigned long lastOutActivity{}; - unsigned long lastInActivity{}; - bool pingOutstanding{}; - MQTT_CALLBACK_SIGNATURE{}; - IPAddress ip{}; - char* domain{}; - uint16_t port{}; - Stream* stream{}; - int _state{MQTT_DISCONNECTED}; + uint8_t* _buffer{}; + size_t _bufferSize{}; size_t _bufferWritePos{}; - uint8_t _qos{MQTT_QOS0}; + unsigned long _keepAliveMillis{}; + unsigned long _socketTimeoutMillis{}; + uint16_t _nextMsgId{}; + unsigned long _lastOutActivity{}; + unsigned long _lastInActivity{}; + bool _pingOutstanding{}; + MQTT_CALLBACK_SIGNATURE{}; + IPAddress _ip{}; + char* _domain{}; + uint16_t _port{}; + Stream* _stream{}; + int _state{MQTT_DISCONNECTED}; size_t readPacket(uint8_t* hdrLen); bool handlePacket(uint8_t hdrLen, size_t len); bool readByte(uint8_t* result); bool readByte(uint8_t* result, size_t* pos); - uint8_t buildHeader(uint8_t header, uint8_t* buf, size_t length); - bool write(uint8_t header, uint8_t* buf, size_t length); - size_t writeString(const char* string, uint8_t* buf, size_t pos, size_t size); - size_t writeNextMsgId(uint8_t* buf, size_t pos, size_t size); + uint8_t buildHeader(uint8_t header, size_t length); + bool writeControlPacket(uint8_t header, size_t length); + size_t writeBuffer(size_t pos, size_t size); + size_t writeStringImpl(bool progmem, const char* string, size_t pos); + size_t writeString(const char* string, size_t pos); + size_t writeNextMsgId(size_t pos); + bool beginPublishImpl(bool progmem, const char* topic, size_t plength, uint8_t qos, bool retained); + bool subscribeImpl(bool progmem, const char* topic, uint8_t qos); + bool unsubscribeImpl(bool progmem, const char* topic); - // Add to buffer and flush if full (only to be used with beginPublish/endPublish)Add commentMore actions + // Add to buffer and flush if full (only to be used with beginPublish/endPublish) size_t appendBuffer(uint8_t data); - size_t appendBuffer(const uint8_t *data, size_t size); size_t flushBuffer(); public: @@ -428,7 +431,9 @@ class PubSubClient : public Print { * @return true If client succeeded in establishing a connection to the broker. * false If client failed to establish a connection to the broker. */ - bool connect(const char* id); + inline bool connect(const char* id) { + return connect(id, nullptr, nullptr, nullptr, MQTT_QOS0, false, nullptr, true); + } /** * @brief Connects the client using a clean session with username and password. @@ -440,7 +445,9 @@ class PubSubClient : public Print { * @return true If client succeeded in establishing a connection to the broker. * false If client failed to establish a connection to the broker. */ - bool connect(const char* id, const char* user, const char* pass); + inline bool connect(const char* id, const char* user, const char* pass) { + return connect(id, user, pass, nullptr, MQTT_QOS0, false, nullptr, true); + } /** * @brief Connects the client using a clean session and will. @@ -453,7 +460,9 @@ class PubSubClient : public Print { * @return true If client succeeded in establishing a connection to the broker. * false If client failed to establish a connection to the broker. */ - bool connect(const char* id, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage); + inline bool connect(const char* id, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage) { + return connect(id, nullptr, nullptr, willTopic, willQos, willRetain, willMessage, true); + } /** * @brief Connects the client using a clean session with username, password and will. @@ -470,7 +479,10 @@ class PubSubClient : public Print { * @return true If client succeeded in establishing a connection to the broker. * false If client failed to establish a connection to the broker. */ - bool connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage); + inline bool connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, + const char* willMessage) { + return connect(id, user, pass, willTopic, willQos, willRetain, willMessage, true); + } /** * @brief Connects the client with all possible parameters (user, password, will and session). @@ -503,7 +515,9 @@ class PubSubClient : public Print { * @return true If the publish succeeded. * false If the publish failed, either connection lost or message too large. */ - bool publish(const char* topic, const char* payload); + inline bool publish(const char* topic, const char* payload) { + return publish(topic, payload, MQTT_QOS0, false); + } /** * @brief Publishes a message to the specified topic using QoS 0. @@ -513,7 +527,9 @@ class PubSubClient : public Print { * @return true If the publish succeeded. * false If the publish failed, either connection lost or message too large. */ - bool publish(const char* topic, const char* payload, bool retained); + inline bool publish(const char* topic, const char* payload, bool retained) { + return publish(topic, payload, MQTT_QOS0, retained); + } /** * @brief Publishes a message to the specified topic. @@ -524,7 +540,35 @@ class PubSubClient : public Print { * @return true If the publish succeeded. * false If the publish failed, either connection lost or message too large. */ - bool publish(const char* topic, const char* payload, uint8_t qos, bool retained); + inline bool publish(const char* topic, const char* payload, uint8_t qos, bool retained) { + return publish(topic, reinterpret_cast(payload), payload ? strlen(payload) : 0, qos, retained); + } + + /** + * @brief Publishes a message to the specified topic. + * @param topic The topic from __FlashStringHelper to publish to. + * @param payload The message to publish. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + inline bool publish(const __FlashStringHelper* topic, const char* payload, uint8_t qos, bool retained) { + return publish(topic, reinterpret_cast(payload), payload ? strlen(payload) : 0, qos, retained); + } + + /** + * @brief Publishes a message from __FlashStringHelper to the specified topic from __FlashStringHelper. + * @param topic The topic to publish to. + * @param payload The message to publish. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + inline bool publish(const __FlashStringHelper* topic, const __FlashStringHelper* payload, uint8_t qos, bool retained) { + return publish_P(topic, reinterpret_cast(payload), payload ? strlen_P(reinterpret_cast(payload)) : 0, qos, retained); + } /** * @brief Publishes a non retained message to the specified topic using QoS 0. @@ -534,7 +578,9 @@ class PubSubClient : public Print { * @return true If the publish succeeded. * false If the publish failed, either connection lost or message too large. */ - bool publish(const char* topic, const uint8_t* payload, size_t plength); + inline bool publish(const char* topic, const uint8_t* payload, size_t plength) { + return publish(topic, payload, plength, MQTT_QOS0, false); + } /** * @brief Publishes a message to the specified topic using QoS 0. @@ -545,7 +591,9 @@ class PubSubClient : public Print { * @return true If the publish succeeded. * false If the publish failed, either connection lost or message too large. */ - bool publish(const char* topic, const uint8_t* payload, size_t plength, bool retained); + inline bool publish(const char* topic, const uint8_t* payload, size_t plength, bool retained) { + return publish(topic, payload, plength, MQTT_QOS0, retained); + } /** * @brief Publishes a message to the specified topic. @@ -559,6 +607,18 @@ class PubSubClient : public Print { */ bool publish(const char* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained); + /** + * @brief Publishes a message to the specified topic. + * @param topic The topic from __FlashStringHelper to publish to. + * @param payload The message to publish. + * @param plength The length of the payload. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + bool publish(const __FlashStringHelper* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained); + /** * @brief Publishes a message stored in PROGMEM to the specified topic using QoS 0. * @param topic The topic to publish to. @@ -567,7 +627,9 @@ class PubSubClient : public Print { * @return true If the publish succeeded. * false If the publish failed, either connection lost or message too large. */ - bool publish_P(const char* topic, const char* payload, bool retained); + inline bool publish_P(const char* topic, PGM_P payload, bool retained) { + return publish_P(topic, payload, MQTT_QOS0, retained); + } /** * @brief Publishes a message stored in PROGMEM to the specified topic. @@ -578,7 +640,22 @@ class PubSubClient : public Print { * @return true If the publish succeeded. * false If the publish failed, either connection lost or message too large. */ - bool publish_P(const char* topic, const char* payload, uint8_t qos, bool retained); + inline bool publish_P(const char* topic, PGM_P payload, uint8_t qos, bool retained) { + return publish_P(topic, reinterpret_cast(payload), payload ? strlen_P(payload) : 0, qos, retained); + } + + /** + * @brief Publishes a message stored in PROGMEM to the specified topic. + * @param topic The topic from __FlashStringHelper to publish to. + * @param payload The message to publish. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + bool publish_P(const __FlashStringHelper* topic, PGM_P payload, uint8_t qos, bool retained) { + return publish_P(topic, reinterpret_cast(payload), payload ? strlen_P(payload) : 0, qos, retained); + } /** * @brief Publishes a message stored in PROGMEM to the specified topic using QoS 0. @@ -589,7 +666,9 @@ class PubSubClient : public Print { * @return true If the publish succeeded. * false If the publish failed, either connection lost or message too large. */ - bool publish_P(const char* topic, const uint8_t* payload, size_t plength, bool retained); + inline bool publish_P(const char* topic, const uint8_t* payload, size_t plength, bool retained) { + return publish_P(topic, payload, plength, MQTT_QOS0, retained); + } /** * @brief Publishes a message stored in PROGMEM to the specified topic. @@ -603,6 +682,18 @@ class PubSubClient : public Print { */ bool publish_P(const char* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained); + /** + * @brief Publishes a message stored in PROGMEM to the specified topic. + * @param topic The topic from __FlashStringHelper to publish to. + * @param payload The message from PROGMEM to publish. + * @param plength The length of the payload. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + bool publish_P(const __FlashStringHelper* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained); + /** * @brief Start to publish a message using QoS 0. * This API: @@ -617,7 +708,9 @@ class PubSubClient : public Print { * @return true If the publish succeeded. * false If the publish failed, either connection lost or message too large. */ - bool beginPublish(const char* topic, size_t plength, bool retained); + inline bool beginPublish(const char* topic, size_t plength, bool retained) { + return beginPublishImpl(false, topic, plength, MQTT_QOS0, retained); + } /** * @brief Start to publish a message. @@ -634,7 +727,48 @@ class PubSubClient : public Print { * @return true If the publish succeeded. * false If the publish failed, either connection lost or message too large. */ - bool beginPublish(const char* topic, size_t plength, uint8_t qos, bool retained); + inline bool beginPublish(const char* topic, size_t plength, uint8_t qos, bool retained) { + return beginPublishImpl(false, topic, plength, qos, retained); + } + + /** + * @brief Start to publish a message using a topic from __FlashStringHelper F(). + * This API: + * beginPublish(...) + * one or more calls to write(...) + * endPublish() + * Allows for arbitrarily large payloads to be sent without them having to be copied into + * a new buffer and held in memory at one time. + * @param topic The topic from __FlashStringHelper to publish to. + * @param plength The length of the payload. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + inline bool beginPublish(const __FlashStringHelper* topic, size_t plength, uint8_t qos, bool retained) { + // convert FlashStringHelper in PROGMEM-pointer + return beginPublishImpl(true, reinterpret_cast(topic), plength, qos, retained); + } + + /** + * @brief Start to publish a message using a topic in PROGMEM. + * This API: + * beginPublish_P(...) + * one or more calls to write(...) + * endPublish() + * Allows for arbitrarily large payloads to be sent without them having to be copied into + * a new buffer and held in memory at one time. + * @param topic The topic in PROGMEM to publish to. + * @param plength The length of the payload. + * @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2]. + * @param retained Publish the message with the retain flag. + * @return true If the publish succeeded. + * false If the publish failed, either connection lost or message too large. + */ + inline bool beginPublish_P(PGM_P topic, size_t plength, uint8_t qos, bool retained) { + return beginPublishImpl(true, reinterpret_cast(topic), plength, qos, retained); + } /** * @brief Finish sending a message that was started with a call to beginPublish. @@ -645,21 +779,43 @@ class PubSubClient : public Print { /** * @brief Writes a single byte as a component of a publish started with a call to beginPublish. - * For performance reasons, this will be appended to the internal buffer, + * For performance reasons, this will be appended to the internal buffer, * which will be flushed when full or on a call to endPublish(). * @param data A byte to write to the publish payload. - * @return The number of bytes written. + * @return The number of bytes written (0 or 1). If 0 is returned a write error occurred. */ virtual size_t write(uint8_t data); /** * @brief Writes an array of bytes as a component of a publish started with a call to beginPublish. - * For performance reasons, this will be appended to the internal buffer, - * which will be flushed when full or on a call to endPublish(). * @param buffer The bytes to write. + * For performance reasons, this will be appended to the internal buffer, + * which will be flushed when full or on a call to endPublish(). + * @param buf The bytes to write. * @param size The length of the payload to be sent. - * @return The number of bytes written. + * @return The number of bytes written. If return value is != size a write error occurred. */ - virtual size_t write(const uint8_t* buffer, size_t size); + virtual size_t write(const uint8_t* buf, size_t size); + + /** + * @brief Writes a string in PROGMEM as a component of a publish started with a call to beginPublish. + * For performance reasons, this will be appended to the internal buffer, + * which will be flushed when full or on a call to endPublish(). + * @param string The message to write. + * @return The number of bytes written. If return value is != string length a write error occurred. + */ + inline size_t write_P(PGM_P string) { + return write_P(reinterpret_cast(string), strlen_P(string)); + } + + /** + * @brief Writes an array of progmem bytes as a component of a publish started with a call to beginPublish. + * For performance reasons, this will be appended to the internal buffer, + * which will be flushed when full or on a call to endPublish(). + * @param buf The bytes to write. + * @param size The length of the payload to be sent. + * @return The number of bytes written. If return value is != size a write error occurred. + */ + size_t write_P(const uint8_t* buf, size_t size); /** * @brief Subscribes to messages published to the specified topic using QoS 0. @@ -667,7 +823,30 @@ class PubSubClient : public Print { * @return true If sending the subscribe succeeded. * false If sending the subscribe failed, either connection lost or message too large. */ - bool subscribe(const char* topic); + inline bool subscribe(const char* topic) { + return subscribeImpl(false, topic, MQTT_QOS0); + } + + /** + * @brief Subscribes to messages published to the specified topic from __FlashStringHelper using QoS 0. + * @param topic The topic from __FlashStringHelper to subscribe to. + * @return true If sending the subscribe succeeded. + * false If sending the subscribe failed, either connection lost or message too large. + */ + inline bool subscribe(const __FlashStringHelper* topic) { + // convert FlashStringHelper in PROGMEM-pointer + return subscribeImpl(true, reinterpret_cast(topic), MQTT_QOS0); + } + + /** + * @brief Subscribes to messages published to the specified topic in PROGMEM using QoS 0. + * @param topic The topic in PROGMEM to subscribe to. + * @return true If sending the subscribe succeeded. + * false If sending the subscribe failed, either connection lost or message too large. + */ + inline bool subscribe_P(PGM_P topic) { + return subscribeImpl(true, reinterpret_cast(topic), MQTT_QOS0); + } /** * @brief Subscribes to messages published to the specified topic. @@ -676,7 +855,32 @@ class PubSubClient : public Print { * @return true If sending the subscribe succeeded. * false If sending the subscribe failed, either connection lost or message too large. */ - bool subscribe(const char* topic, uint8_t qos); + inline bool subscribe(const char* topic, uint8_t qos) { + return subscribeImpl(false, topic, qos); + } + + /** + * @brief Subscribes to messages published to the specified topic from __FlashStringHelper. + * @param topic The topic from __FlashStringHelper to subscribe to. + * @param qos The qos to subscribe at. [0, 1]. + * @return true If sending the subscribe succeeded. + * false If sending the subscribe failed, either connection lost or message too large. + */ + inline bool subscribe(const __FlashStringHelper* topic, uint8_t qos) { + // convert FlashStringHelper in PROGMEM-pointer + return subscribeImpl(true, reinterpret_cast(topic), qos); + } + + /** + * @brief Subscribes to messages published to the specified topic in PROGMEM. + * @param topic The topic in PROGMEM to subscribe to. + * @param qos The qos to subscribe at. [0, 1]. + * @return true If sending the subscribe succeeded. + * false If sending the subscribe failed, either connection lost or message too large. + */ + inline bool subscribe_P(PGM_P topic, uint8_t qos) { + return subscribeImpl(true, reinterpret_cast(topic), qos); + } /** * @brief Unsubscribes from the specified topic. @@ -684,7 +888,30 @@ class PubSubClient : public Print { * @return true If sending the unsubscribe succeeded. * false If sending the unsubscribe failed, either connection lost or message too large. */ - bool unsubscribe(const char* topic); + inline bool unsubscribe(const char* topic) { + return unsubscribeImpl(false, topic); + } + + /** + * @brief Unsubscribes from the specified topic from __FlashStringHelper. + * @param topic The topic from __FlashStringHelper to unsubscribe from. + * @return true If sending the unsubscribe succeeded. + * false If sending the unsubscribe failed, either connection lost or message too large. + */ + inline bool unsubscribe(const __FlashStringHelper* topic) { + // convert FlashStringHelper in PROGMEM-pointer + return unsubscribeImpl(true, reinterpret_cast(topic)); + } + + /** + * @brief Unsubscribes from the specified topic in PROGMEM. + * @param topic The topic in PROGMEM to unsubscribe from. + * @return true If sending the unsubscribe succeeded. + * false If sending the unsubscribe failed, either connection lost or message too large. + */ + inline bool unsubscribe_P(PGM_P topic) { + return unsubscribeImpl(true, reinterpret_cast(topic)); + } /** * @brief This should be called regularly to allow the client to process incoming messages and maintain its connection to the server. diff --git a/lib/pubsubclient3/tests/src/lib/Arduino.h b/lib/pubsubclient3/tests/src/lib/Arduino.h index b5903ea5c..ab830f059 100644 --- a/lib/pubsubclient3/tests/src/lib/Arduino.h +++ b/lib/pubsubclient3/tests/src/lib/Arduino.h @@ -20,9 +20,14 @@ extern void loop(void); unsigned long millis(void); } +class __FlashStringHelper; #define PROGMEM +#define PGM_P const char* +#define memcpy_P memcpy +#define strlen_P strlen #define strnlen_P strnlen #define pgm_read_byte_near(x) *(x) +#define F(x) (reinterpret_cast(x)) #define yield(x) {} diff --git a/lib/pubsubclient3/tests/src/publish_spec.cpp b/lib/pubsubclient3/tests/src/publish_spec.cpp index 241ee57e2..5e83a623b 100644 --- a/lib/pubsubclient3/tests/src/publish_spec.cpp +++ b/lib/pubsubclient3/tests/src/publish_spec.cpp @@ -13,6 +13,7 @@ int test_publish_bytes(); int test_publish_retained(); int test_publish_retained_2(); int test_publish_not_connected(); +int test_publish_long(); int test_publish_too_long(); int test_publish_P(); int test_publish_P_too_long(); @@ -22,6 +23,10 @@ int test_publish_qos1(); int test_publish_qos2(); int test_publish_P_qos1(); int test_publish_P_qos2(); +int test_publish_FlashStringHelper(); +int test_publish_FlashStringHelper2(); +int test_publish_P_FlashStringHelper(); +int test_publish_P_P(); void callback(_UNUSED_ char* topic, _UNUSED_ uint8_t* payload, _UNUSED_ size_t plength) { // handle message arrived @@ -139,6 +144,49 @@ int test_publish_not_connected() { END_IT } +int test_publish_long() { + IT("publishes with long payload message (> buffer size)"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + // buffer size 64 bytes - 5 bytes header - 2 bytes topic length = max. 57 bytes topic + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + char topic[] = "123456789012345678901234567890123456789012345678901234567"; + + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + char payload[] = "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; + size_t plength = strlen(payload); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, 4); + + PubSubClient client(server, 1883, callback, shimClient); + client.setBufferSize(64); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte publish[256]; + publish[0] = 0x30; // PUBLISH, QoS 0, no retain + publish[1] = 0xb3; // Remaining length byte 1: 2 + 57 + 120 bytes (topic length bytes + topic length + payload length = 179) + publish[2] = 0x01; // Remaining length byte 2 + publish[3] = 0x00; // Topic length MSB + publish[4] = 0x39; // Topic length LSB (57 bytes) + memcpy(&publish[5], topic, sizeof(topic) - 1); + memcpy(&publish[5 + sizeof(topic) - 1], payload, sizeof(payload) - 1); + shimClient.expect(publish, 5 + sizeof(topic) - 1 + sizeof(payload) - 1); + + rc = client.beginPublish(topic, plength, 0, false); + IS_TRUE(rc); + plength = client.write((uint8_t*)payload, plength); + IS_EQUAL(plength, strlen(payload)); + rc = client.endPublish(); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + int test_publish_too_long() { IT("publish fails when topic/payload are too long"); ShimClient shimClient; @@ -167,7 +215,7 @@ int test_publish_too_long() { } int test_publish_P() { - IT("publishes using PROGMEM"); + IT("publishes using PROGMEM payload"); ShimClient shimClient; shimClient.setAllowConnect(true); @@ -193,7 +241,7 @@ int test_publish_P() { } int test_publish_P_too_long() { - IT("publish using PROGMEM fails when topic is too long"); + IT("publish using PROGMEM payload fails when topic is too long"); ShimClient shimClient; shimClient.setAllowConnect(true); @@ -219,6 +267,112 @@ int test_publish_P_too_long() { END_IT } +int test_publish_FlashStringHelper() { + IT("publishes using FlashStringHelper topic"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + char payload[] = "12345"; + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, sizeof(connack)); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x31, 0x0c, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', '1', '2', '3', '4', '5'}; + shimClient.expect(publish, sizeof(publish)); + + rc = client.publish(F("topic"), payload, MQTT_QOS0, true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_FlashStringHelper2() { + IT("publishes using FlashStringHelper topic and payload"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + char payload[] = "12345"; + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, sizeof(connack)); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x31, 0x0c, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', '1', '2', '3', '4', '5'}; + shimClient.expect(publish, sizeof(publish)); + + rc = client.publish(F("topic"), F(payload), MQTT_QOS0, true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_P_FlashStringHelper() { + IT("publishes using FlashStringHelper topic and PROGMEM payload"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + char payload[] PROGMEM = "12345"; + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, sizeof(connack)); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x31, 0x0c, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', '1', '2', '3', '4', '5'}; + shimClient.expect(publish, sizeof(publish)); + + rc = client.publish_P(F("topic"), payload, MQTT_QOS0, true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_P_P() { + IT("publishes using PROGMEM topic and PROGMEM payload"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + char topic[] PROGMEM = "topic"; + char payload[] PROGMEM = "12345"; + size_t length = strlen_P(payload); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, sizeof(connack)); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x31, 0x0c, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', '1', '2', '3', '4', '5'}; + shimClient.expect(publish, sizeof(publish)); + + rc = client.beginPublish_P(topic, length, MQTT_QOS0, true); + IS_TRUE(rc); + length = client.write_P(payload); + IS_EQUAL(length, strlen_P(payload)); + rc = client.endPublish(); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + int test_publish_empty_topic() { IT("publish fails when topic is empty"); ShimClient shimClient; @@ -275,7 +429,7 @@ int test_publish_qos1() { IS_TRUE(rc); // Example publish packet for QoS 1 (0x32) - byte publish[] = {0x32, 0x10, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 'p', 'a', 'y', 'l', 'o', 'a', 'd', 0x00, 0x02}; + byte publish[] = {0x32, 0x10, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x00, 0x02, 'p', 'a', 'y', 'l', 'o', 'a', 'd'}; shimClient.expect(publish, sizeof(publish)); rc = client.publish("topic", "payload", MQTT_QOS1, false); @@ -299,7 +453,7 @@ int test_publish_qos2() { IS_TRUE(rc); // Example publish packet for QoS 2 (0x34) - byte publish[] = {0x34, 0x10, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 'p', 'a', 'y', 'l', 'o', 'a', 'd', 0x00, 0x02}; + byte publish[] = {0x34, 0x10, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x00, 0x02, 'p', 'a', 'y', 'l', 'o', 'a', 'd'}; shimClient.expect(publish, sizeof(publish)); rc = client.publish("topic", "payload", MQTT_QOS2, false); @@ -311,7 +465,7 @@ int test_publish_qos2() { } int test_publish_P_qos1() { - IT("publishes using PROGMEM with QoS 1 retained"); + IT("publishes using PROGMEM payload with QoS 1 retained"); ShimClient shimClient; shimClient.setAllowConnect(true); @@ -325,7 +479,7 @@ int test_publish_P_qos1() { bool rc = client.connect("client_test1"); IS_TRUE(rc); - byte publish[] = {0x33, 0x0e, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x02}; + byte publish[] = {0x33, 0x0e, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x00, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05}; shimClient.expect(publish, sizeof(publish)); rc = client.publish_P("topic", payload, length, MQTT_QOS1, true); @@ -337,7 +491,7 @@ int test_publish_P_qos1() { } int test_publish_P_qos2() { - IT("publishes using PROGMEM with QoS 2 retained"); + IT("publishes using PROGMEM payload with QoS 2 retained"); ShimClient shimClient; shimClient.setAllowConnect(true); @@ -351,7 +505,7 @@ int test_publish_P_qos2() { bool rc = client.connect("client_test1"); IS_TRUE(rc); - byte publish[] = {0x35, 0x0e, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x02}; + byte publish[] = {0x35, 0x0e, 0x00, 0x05, 't', 'o', 'p', 'i', 'c', 0x00, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05}; shimClient.expect(publish, sizeof(publish)); rc = client.publish_P("topic", payload, length, MQTT_QOS2, true); @@ -371,6 +525,7 @@ int main() { test_publish_qos1(); test_publish_qos2(); test_publish_null_payload(); + test_publish_long(); test_publish_not_connected(); test_publish_empty_topic(); test_publish_too_long(); @@ -378,6 +533,10 @@ int main() { test_publish_P_qos1(); test_publish_P_qos2(); test_publish_P_too_long(); + test_publish_FlashStringHelper(); + test_publish_FlashStringHelper2(); + test_publish_P_FlashStringHelper(); + test_publish_P_P(); FINISH } diff --git a/lib/pubsubclient3/tests/src/receive_spec.cpp b/lib/pubsubclient3/tests/src/receive_spec.cpp index 8cbe0efae..b3fcc7937 100644 --- a/lib/pubsubclient3/tests/src/receive_spec.cpp +++ b/lib/pubsubclient3/tests/src/receive_spec.cpp @@ -28,6 +28,7 @@ int test_receive_oversized_message(); int test_resize_buffer(); int test_receive_oversized_stream_message(); int test_receive_qos1(); +int test_receive_qos2(); void reset_callback() { callback_called = false; @@ -330,6 +331,57 @@ int test_receive_qos1() { END_IT } +int test_receive_qos2() { + IT("receives a qos2 message - responds PUBREC then PUBCOMP"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, 4); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + // QoS 2 PUBLISH from broker (0x34 = MQTTPUBLISH | QoS2 bits) + // Fixed header 0x34, remaining length 0x10 (16), topic len 0x0005, topic "topic", + // msgId 0x1234, payload "payload" + byte publish[] = {0x34, 0x10, 0x0, 0x5, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x34, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64}; + shimClient.respond(publish, 18); + + // Client must respond with PUBREC (0x50), remaining length 2, msgId 0x1234 + byte pubrec[] = {0x50, 0x02, 0x12, 0x34}; + shimClient.expect(pubrec, 4); + + rc = client.loop(); + IS_TRUE(rc); + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic, "topic") == 0); + IS_TRUE(memcmp(lastPayload, "payload", 7) == 0); + IS_TRUE(lastLength == 7); + IS_FALSE(shimClient.error()); + + reset_callback(); + + // Broker sends PUBREL (0x62 = MQTTPUBREL | bit1), remaining length 2, msgId 0x1234 + byte pubrel[] = {0x62, 0x02, 0x12, 0x34}; + shimClient.respond(pubrel, 4); + + // Client must respond with PUBCOMP (0x70), remaining length 2, msgId 0x1234 + byte pubcomp[] = {0x70, 0x02, 0x12, 0x34}; + shimClient.expect(pubcomp, 4); + + rc = client.loop(); + IS_TRUE(rc); + IS_FALSE(callback_called); // callback must NOT fire again on PUBREL + + IS_FALSE(shimClient.error()); + + END_IT +} + int main() { SUITE("Receive"); test_receive_callback(); @@ -340,6 +392,7 @@ int main() { test_resize_buffer(); test_receive_oversized_stream_message(); test_receive_qos1(); + test_receive_qos2(); FINISH } diff --git a/lib/pubsubclient3/tests/src/subscribe_spec.cpp b/lib/pubsubclient3/tests/src/subscribe_spec.cpp index c00eddf75..c297e58f1 100644 --- a/lib/pubsubclient3/tests/src/subscribe_spec.cpp +++ b/lib/pubsubclient3/tests/src/subscribe_spec.cpp @@ -10,6 +10,10 @@ byte server[] = {172, 16, 0, 2}; void callback(char* topic, uint8_t* payload, size_t plength); int test_subscribe_no_qos(); int test_subscribe_qos_1(); +int test_subscribe_P(); +int test_subscribe_P_qos_1(); +int test_subscribe_FlashStringHelper(); +int test_subscribe_FlashStringHelper_qos_1(); int test_subscribe_not_connected(); int test_subscribe_invalid_qos(); int test_subscribe_too_long(); @@ -70,6 +74,108 @@ int test_subscribe_qos_1() { END_IT } +int test_subscribe_P() { + IT("subscribe using PROGMEM"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, 4); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte subscribe[] = {0x82, 0xa, 0x0, 0x2, 0x0, 0x5, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x0}; + shimClient.expect(subscribe, 12); + byte suback[] = {0x90, 0x3, 0x0, 0x2, 0x0}; + shimClient.respond(suback, 5); + + char topic[] PROGMEM = "topic"; + rc = client.subscribe_P(topic); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_P_qos_1() { + IT("subscribe using PROGMEM with QoS 1"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, 4); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte subscribe[] = {0x82, 0xa, 0x0, 0x2, 0x0, 0x5, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x1}; + shimClient.expect(subscribe, 12); + byte suback[] = {0x90, 0x3, 0x0, 0x2, 0x1}; + shimClient.respond(suback, 5); + + char topic[] PROGMEM = "topic"; + rc = client.subscribe_P(topic, MQTT_QOS1); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_FlashStringHelper() { + IT("subscribe using FlashStringHelper"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, 4); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte subscribe[] = {0x82, 0xa, 0x0, 0x2, 0x0, 0x5, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x0}; + shimClient.expect(subscribe, 12); + byte suback[] = {0x90, 0x3, 0x0, 0x2, 0x0}; + shimClient.respond(suback, 5); + + rc = client.subscribe(F("topic")); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_FlashStringHelper_qos_1() { + IT("subscribe using FlashStringHelper with QoS 1"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = {0x20, 0x02, 0x00, 0x00}; + shimClient.respond(connack, 4); + + PubSubClient client(server, 1883, callback, shimClient); + bool rc = client.connect("client_test1"); + IS_TRUE(rc); + + byte subscribe[] = {0x82, 0xa, 0x0, 0x2, 0x0, 0x5, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x1}; + shimClient.expect(subscribe, 12); + byte suback[] = {0x90, 0x3, 0x0, 0x2, 0x1}; + shimClient.respond(suback, 5); + + rc = client.subscribe(F("topic"), MQTT_QOS1); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + int test_subscribe_not_connected() { IT("subscribe fails when not connected"); ShimClient shimClient; @@ -176,6 +282,10 @@ int main() { SUITE("Subscribe"); test_subscribe_no_qos(); test_subscribe_qos_1(); + test_subscribe_P(); + test_subscribe_P_qos_1(); + test_subscribe_FlashStringHelper(); + test_subscribe_FlashStringHelper_qos_1(); test_subscribe_not_connected(); test_subscribe_invalid_qos(); test_subscribe_too_long(); From e694a845c1a39ff7b10eb3661f3b2b8100453f8d Mon Sep 17 00:00:00 2001 From: TD-er Date: Mon, 11 May 2026 22:08:47 +0200 Subject: [PATCH 2/5] [Network] Improve response time on network disconnect for MQTT client --- src/src/Helpers/PeriodicalActions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index 880a12d79..8fd354c5e 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -408,7 +408,7 @@ void processMQTTdelayQueue() { } void updateMQTTclient_connected() { - const bool actual_MQTTclient_connected = ESPEasy::net::NetworkConnected() && MQTTclient.connected(); + const bool actual_MQTTclient_connected = ESPEasy::net::NetworkConnected(true) && MQTTclient.connected(); if (MQTTclient_connected != actual_MQTTclient_connected) { MQTTclient_connected = actual_MQTTclient_connected; if (!actual_MQTTclient_connected) mqtt.stop(); // Make sure PubSubClient isn't trying to do a graceful disconnect @@ -456,7 +456,7 @@ void updateMQTTclient_connected() { void runPeriodicalMQTT() { START_TIMER // MQTT_KEEPALIVE = 15 seconds. - if (!NetworkConnected(10)) { + if (!ESPEasy::net::NetworkConnected()) { updateMQTTclient_connected(); return; } From 31ba16e7cc12bc46471b1cec597418b2e8bac7fc Mon Sep 17 00:00:00 2001 From: TD-er Date: Tue, 12 May 2026 13:14:12 +0200 Subject: [PATCH 3/5] [Network] Add setting whether to collect network stats (ESP32-only) --- .../net/DataStructs/NWPluginData_base.cpp | 105 +++++++++--------- .../NW001_data_struct_WiFi_STA.cpp | 4 +- .../NW002_data_struct_WiFi_AP.cpp | 39 ++++--- .../NW005_data_struct_PPP_modem.cpp | 13 ++- src/ESPEasy/net/_NWPlugin_Helper.cpp | 4 +- src/src/CustomBuild/ESPEasyDefaults.h | 8 +- src/src/DataStructs/SettingsStruct.h | 9 +- src/src/DataStructs_templ/SettingsStruct.cpp | 24 +++- src/src/Globals/MQTT.h | 1 + src/src/Helpers/ESPEasy_FactoryDefault.cpp | 5 +- src/src/Helpers/ESPEasy_Storage.cpp | 2 +- src/src/Helpers/I2C_access.cpp | 2 + src/src/WebServer/AdvancedConfigPage.cpp | 16 --- src/src/WebServer/NetworkPage.cpp | 12 +- 14 files changed, 146 insertions(+), 98 deletions(-) diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_base.cpp b/src/ESPEasy/net/DataStructs/NWPluginData_base.cpp index 594372660..8af6c91c4 100644 --- a/src/ESPEasy/net/DataStructs/NWPluginData_base.cpp +++ b/src/ESPEasy/net/DataStructs/NWPluginData_base.cpp @@ -1,13 +1,14 @@ #include "../DataStructs/NWPluginData_base.h" #include "../../../src/DataStructs/ESPEasy_EventStruct.h" +#include "../../../src/Globals/Settings.h" #include "../../../src/Helpers/StringConverter.h" #include "../../../src/Helpers/Misc.h" #include "../../../src/Helpers/Networking.h" #if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS # include "../../../src/Helpers/_ESPEasy_key_value_store.h" -#include "../_NWPlugin_Helper.h" -#endif +# include "../_NWPlugin_Helper.h" +#endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS #ifdef ESP32 # include # include @@ -46,8 +47,11 @@ NWPluginData_base::NWPluginData_base( NWPluginData_base::~NWPluginData_base() { #if FEATURE_NETWORK_STATS - delete _plugin_stats_array; - _plugin_stats_array = nullptr; + + if (_plugin_stats_array) { + delete _plugin_stats_array; + _plugin_stats_array = nullptr; + } #endif // if FEATURE_NETWORK_STATS #if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS @@ -58,6 +62,7 @@ NWPluginData_base::~NWPluginData_base() } #ifdef ESP32 + bool NWPluginData_base::isDefaultRoute() const { if (_netif) { return _netif->isDefault(); @@ -65,14 +70,14 @@ bool NWPluginData_base::isDefaultRoute() const { return false; } -#endif +#endif // ifdef ESP32 -bool NWPluginData_base::getStaticIPAddresses(IPAddress & ip, IPAddress & gateway, IPAddress & subnetmask, IPAddress & dns ) const +bool NWPluginData_base::getStaticIPAddresses(IPAddress& ip, IPAddress& gateway, IPAddress& subnetmask, IPAddress& dns) const { - getStaticIPAddress(IPAddressType::IP, ip); - getStaticIPAddress(IPAddressType::Gateway, gateway); + getStaticIPAddress(IPAddressType::IP, ip); + getStaticIPAddress(IPAddressType::Gateway, gateway); getStaticIPAddress(IPAddressType::Subnetmask, subnetmask); - getStaticIPAddress(IPAddressType::DNS, dns); + getStaticIPAddress(IPAddressType::DNS, dns); return IPAddressSet(ip) && IPAddressSet(gateway) && IPAddressSet(subnetmask); } @@ -116,31 +121,35 @@ void NWPluginData_base::initPluginStats( float errorValue, const PluginStats_Config_t& displayConfig) { - if (networkStatsVarIndex < INVALID_NETWORK_STATS_VAR_INDEX) { - if (_plugin_stats_array == nullptr) { - constexpr unsigned size = sizeof(PluginStats_array); - void *ptr = special_calloc(1, size); + if (!Settings.getNetworkCollectStats(_networkIndex) || + (networkStatsVarIndex >= INVALID_NETWORK_STATS_VAR_INDEX)) { + return; + } - if (ptr != nullptr) { - _plugin_stats_array = new (ptr) PluginStats_array(); - } - } + if (_plugin_stats_array == nullptr) { + constexpr unsigned size = sizeof(PluginStats_array); + void *ptr = special_calloc(1, size); - if (_plugin_stats_array != nullptr) { - _plugin_stats_array->initPluginStats( - networkStatsVarIndex, - label, - nrDecimals, - errorValue, - displayConfig); + if (ptr != nullptr) { + _plugin_stats_array = new (ptr) PluginStats_array(); } } + + if (_plugin_stats_array != nullptr) { + _plugin_stats_array->initPluginStats( + networkStatsVarIndex, + label, + nrDecimals, + errorValue, + displayConfig); + } } # if FEATURE_NETWORK_TRAFFIC_COUNT void NWPluginData_base::initPluginStats_trafficCount(networkStatsVarIndex_t networkStatsVarIndex, bool isTX) { + if (!Settings.getNetworkCollectStats(_networkIndex)) { return; } PluginStats_Config_t displayConfig; displayConfig.setAxisPosition(PluginStats_Config_t::AxisPosition::Right); @@ -160,13 +169,14 @@ bool NWPluginData_base::initPluginStats() { # if FEATURE_NETWORK_TRAFFIC_COUNT - // Virtual function has no override in derived class, so only init traffic count - initPluginStats_trafficCount(0, true); // TX - initPluginStats_trafficCount(1, false); // RX - return true; -# else // if FEATURE_NETWORK_TRAFFIC_COUNT - return false; + if (Settings.getNetworkCollectStats(_networkIndex)) { + // Virtual function has no override in derived class, so only init traffic count + initPluginStats_trafficCount(0, true); // TX + initPluginStats_trafficCount(1, false); // RX + return true; + } # endif // if FEATURE_NETWORK_TRAFFIC_COUNT + return false; } void NWPluginData_base::clearPluginStats(networkStatsVarIndex_t networkStatsVarIndex) @@ -334,9 +344,9 @@ bool NWPluginData_base::handle_priority_route_changed() if ((_netif != nullptr) && _netif->isDefault()) { auto cache = getNWPluginData_static_runtime(); - if (!cache) { - if (NWPlugin::forceDHCP_request(_netif)) { - return true; + if (!cache) { + if (NWPlugin::forceDHCP_request(_netif)) { + return true; } } @@ -404,12 +414,13 @@ PluginStats * NWPluginData_base::getPluginStats(networkStatsVarIndex_t networkSt #endif // if FEATURE_NETWORK_STATS #ifdef ESP32 -/* -bool NWPluginData_base::_restore_DNS_cache() -{ - bool res{}; - if ((_netif != nullptr) && _netif->isDefault()) { +/* + bool NWPluginData_base::_restore_DNS_cache() + { + bool res{}; + + if ((_netif != nullptr) && _netif->isDefault()) { if (NWPlugin::forceDHCP_request(_netif)) { return true; } auto cache = getNWPluginData_static_runtime(); @@ -433,24 +444,18 @@ bool NWPluginData_base::_restore_DNS_cache() res = true; } } - } - return res; -} - */ + } + return res; + } + */ #endif // ifdef ESP32 #if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS -bool NWPluginData_base::_load() -{ - return load_nwpluginTaskData_KVS(_kvs, _networkIndex, _nw_data_pluginID); -} +bool NWPluginData_base::_load() { return load_nwpluginTaskData_KVS(_kvs, _networkIndex, _nw_data_pluginID); } -bool NWPluginData_base::_store() -{ - return store_nwpluginTaskData_KVS(_kvs, _networkIndex, _nw_data_pluginID); -} +bool NWPluginData_base::_store() { return store_nwpluginTaskData_KVS(_kvs, _networkIndex, _nw_data_pluginID); } #endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS diff --git a/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp b/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp index 3f36ef62c..d4e32efc2 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW001_data_struct_WiFi_STA.cpp @@ -99,13 +99,14 @@ bool NW001_data_struct_WiFi_STA::init(EventStruct *event) { auto runtime_data = getNWPluginData_static_runtime(); + if (runtime_data) { IPAddress ip, gateway, sn, dns; getStaticIPAddresses(ip, gateway, sn, dns); runtime_data->setStaticIP(ip, gateway, sn, dns); } } - + ESPEasy::net::wifi::initWiFi(); return true; @@ -171,6 +172,7 @@ bool NW001_data_struct_WiFi_STA::handle_priority_route_changed() bool NW001_data_struct_WiFi_STA::initPluginStats() { + if (!Settings.getNetworkCollectStats(_networkIndex)) { return false; } networkStatsVarIndex_t networkStatsVarIndex{}; PluginStats_Config_t displayConfig; diff --git a/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp b/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp index 6ae83fccd..da534666d 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW002_data_struct_WiFi_AP.cpp @@ -93,6 +93,7 @@ bool NW002_data_struct_WiFi_AP::init(EventStruct *event) { { auto runtime_data = getNWPluginData_static_runtime(); + if (runtime_data) { IPAddress ip, gateway, sn, dns; getStaticIPAddresses(ip, gateway, sn, dns); @@ -146,26 +147,27 @@ NWPluginData_static_runtime * NW002_data_struct_WiFi_AP::getNWPluginData_static_ bool NW002_data_struct_WiFi_AP::getStaticIPAddress(IPAddressType addressType, IPAddress& ip) const { // TODO TD-er: Implement for AP -/* - IPAddress res; - switch (addressType) - { - case IPAddressType::IP: res = IPAddress(Settings.IP); - break; - case IPAddressType::Gateway: res = IPAddress(Settings.Gateway); - break; - case IPAddressType::Subnetmask: res = IPAddress(Settings.Subnet); - break; - case IPAddressType::DNS: res = IPAddress(Settings.DNS); - break; - } + /* + IPAddress res; - if (IPAddressSet(res)) { - ip = res; - return true; - } -*/ + switch (addressType) + { + case IPAddressType::IP: res = IPAddress(Settings.IP); + break; + case IPAddressType::Gateway: res = IPAddress(Settings.Gateway); + break; + case IPAddressType::Subnetmask: res = IPAddress(Settings.Subnet); + break; + case IPAddressType::DNS: res = IPAddress(Settings.DNS); + break; + } + + if (IPAddressSet(res)) { + ip = res; + return true; + } + */ return false; } @@ -180,6 +182,7 @@ bool NW002_data_struct_WiFi_AP::handle_priority_route_changed() { return NW002_u bool NW002_data_struct_WiFi_AP::initPluginStats() { + if (!Settings.getNetworkCollectStats(_networkIndex)) { return false; } networkStatsVarIndex_t networkStatsVarIndex{}; PluginStats_Config_t displayConfig; diff --git a/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp b/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp index 61cb711b2..bcb963a51 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp @@ -16,7 +16,7 @@ # include "../../../src/WebServer/ESPEasy_key_value_store_webform.h" # if FEATURE_TASKVALUE_UNIT_OF_MEASURE -# include "../../../src/Helpers/ESPEasy_UnitOfMeasure.h" +# include "../../../src/Helpers/ESPEasy_UnitOfMeasure.h" # endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE @@ -875,7 +875,8 @@ void NW005_begin_modem_task(void *parameter) NW_PLUGIN_INTERFACE.cmd(F("AT&D1"), 9000); digitalWrite(modem_task_data->dtrPin, HIGH); } - if (!NW_PLUGIN_INTERFACE.attached()) modem_task_data->modem_init_failed = true; + + if (!NW_PLUGIN_INTERFACE.attached()) { modem_task_data->modem_init_failed = true; } } else { modem_task_data->modem_init_failed = true; } @@ -1047,6 +1048,7 @@ bool NW005_data_struct_PPP_modem::check_connect_failed() { _modem_task_data.modem_init_failed = false; auto stats = getNWPluginData_static_runtime(); + if (stats) { stats->mark_connect_failed(); } @@ -1059,6 +1061,7 @@ bool NW005_data_struct_PPP_modem::check_connect_failed() bool NW005_data_struct_PPP_modem::initPluginStats() { + if (!Settings.getNetworkCollectStats(_networkIndex)) { return false; } networkStatsVarIndex_t networkStatsVarIndex{}; PluginStats_Config_t displayConfig; @@ -1108,13 +1111,13 @@ bool NW005_data_struct_PPP_modem::record_stats() NWPluginData_static_runtime * NW005_data_struct_PPP_modem::getNWPluginData_static_runtime() { return &stats_and_cache; } -bool NW005_data_struct_PPP_modem::getStaticIPAddress(IPAddressType addressType, IPAddress & ip) const +bool NW005_data_struct_PPP_modem::getStaticIPAddress(IPAddressType addressType, IPAddress& ip) const { // No static IP for PPP modem - return false; + return false; } -void NW005_data_struct_PPP_modem::onEvent(arduino_event_id_t event, arduino_event_info_t info) { +void NW005_data_struct_PPP_modem::onEvent(arduino_event_id_t event, arduino_event_info_t info) { // TODO TD-er: Must store flags from events in static (or global) object to act on it later. switch (event) { diff --git a/src/ESPEasy/net/_NWPlugin_Helper.cpp b/src/ESPEasy/net/_NWPlugin_Helper.cpp index b8cedd639..aaaaccb07 100644 --- a/src/ESPEasy/net/_NWPlugin_Helper.cpp +++ b/src/ESPEasy/net/_NWPlugin_Helper.cpp @@ -58,7 +58,9 @@ bool initNWPluginData(ESPEasy::net::networkIndex_t networkIndex, NWPluginData_ba #endif // if FEATURE_STORE_NETWORK_INTERFACE_SETTINGS #if FEATURE_NETWORK_STATS - NWPlugin_task_data[networkIndex]->initPluginStats(); + if (Settings.getNetworkCollectStats(networkIndex)) { + NWPlugin_task_data[networkIndex]->initPluginStats(); + } #endif // if FEATURE_NETWORK_STATS } else { diff --git a/src/src/CustomBuild/ESPEasyDefaults.h b/src/src/CustomBuild/ESPEasyDefaults.h index 65246cd61..f701d805d 100644 --- a/src/src/CustomBuild/ESPEasyDefaults.h +++ b/src/src/CustomBuild/ESPEasyDefaults.h @@ -394,7 +394,13 @@ #define DEFAULT_ENABLE_TIMING_STATS false #endif - +#ifndef DEFAULT_NETWORK_COLLECT_STATS_BITS +#ifdef PLUGIN_BUILD_MAX_ESP32 +#define DEFAULT_NETWORK_COLLECT_STATS_BITS 0xFF +#else +#define DEFAULT_NETWORK_COLLECT_STATS_BITS 0 +#endif +#endif // --- Advanced Settings --------------------------------------------------------------------------------- #if defined(ESP32) diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index 6be8e31fc..2535d4fe2 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -506,6 +506,13 @@ public: void setNetworkInterfaceStartupDelay(ESPEasy::net::networkIndex_t index, uint32_t delay_ms); +# if FEATURE_NETWORK_STATS + bool getNetworkCollectStats(ESPEasy::net::networkIndex_t index) const; + + void setNetworkCollectStats(ESPEasy::net::networkIndex_t index, bool enabled); +#endif + + uint32_t PID = 0; int Version = 0; int16_t Build = 0; @@ -581,7 +588,7 @@ public: uint32_t ConnectionFailuresThreshold = 0; int16_t TimeZone = 0; - boolean MQTTRetainFlag_unused = false; + uint8_t NetworkCollectStats_bits = DEFAULT_NETWORK_COLLECT_STATS_BITS; 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}; diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp index a071f76d4..983249f09 100644 --- a/src/src/DataStructs_templ/SettingsStruct.cpp +++ b/src/src/DataStructs_templ/SettingsStruct.cpp @@ -747,7 +747,9 @@ void SettingsStruct_tmpl::clearMisc() { SyslogPort = 514; VariousBits_3._all_bits = 0; ConnectionFailuresThreshold = 0; - MQTTRetainFlag_unused = false; + #if FEATURE_NETWORK_STATS + NetworkCollectStats_bits = DEFAULT_NETWORK_COLLECT_STATS_BITS; + #endif InitSPI = DEFAULT_SPI; deepSleepOnFail = false; UseValueLogger = false; @@ -1621,4 +1623,24 @@ void SettingsStruct_tmpl::setNetworkInterfaceStartupDelay(ESPEasy::net: } +# if FEATURE_NETWORK_STATS + +template +bool SettingsStruct_tmpl::getNetworkCollectStats(ESPEasy::net::networkIndex_t index) const +{ + if (validNetworkIndex(index)) { return bitRead(NetworkCollectStats_bits, index); } + return false; +} + +template +void SettingsStruct_tmpl::setNetworkCollectStats(ESPEasy::net::networkIndex_t index, bool enabled) +{ + if (validNetworkIndex(index)) { + bitWrite(NetworkCollectStats_bits, index, enabled); + } +} + +# endif // if FEATURE_NETWORK_STATS + + #endif // ifndef DATASTRUCTS_SETTINGSSTRUCT_CPP diff --git a/src/src/Globals/MQTT.h b/src/src/Globals/MQTT.h index a786958c9..cea3720d8 100644 --- a/src/src/Globals/MQTT.h +++ b/src/src/Globals/MQTT.h @@ -65,6 +65,7 @@ extern bool MQTTclient_must_send_LWT_connected; extern bool MQTTclient_connected; extern int mqtt_reconnect_count; extern LongTermTimer MQTTclient_next_connect_attempt; + #endif // if FEATURE_MQTT #ifdef USES_P037 diff --git a/src/src/Helpers/ESPEasy_FactoryDefault.cpp b/src/src/Helpers/ESPEasy_FactoryDefault.cpp index aac872006..563cbba12 100644 --- a/src/src/Helpers/ESPEasy_FactoryDefault.cpp +++ b/src/src/Helpers/ESPEasy_FactoryDefault.cpp @@ -295,10 +295,13 @@ void ResetFactory(bool formatFS) // 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; + #if FEATURE_NETWORK_STATS + Settings.NetworkCollectStats_bits = DEFAULT_NETWORK_COLLECT_STATS_BITS; + #endif + // allow to set default latitude and longitude #ifdef DEFAULT_LATITUDE Settings.Latitude = DEFAULT_LATITUDE; diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index f91856c63..1de1f8ff2 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -392,7 +392,7 @@ bool BuildFixes() safe_strncpy(ControllerSettings->ClientID, clientid, sizeof(ControllerSettings->ClientID)); ControllerSettings->mqtt_uniqueMQTTclientIdReconnect(Settings.uniqueMQTTclientIdReconnect_unused()); - ControllerSettings->mqtt_retainFlag(Settings.MQTTRetainFlag_unused); + ControllerSettings->mqtt_retainFlag(DEFAULT_MQTT_RETAIN); SaveControllerSettings(controller_idx, *ControllerSettings); } } diff --git a/src/src/Helpers/I2C_access.cpp b/src/src/Helpers/I2C_access.cpp index d0e0f0d4f..226e9b4d2 100644 --- a/src/src/Helpers/I2C_access.cpp +++ b/src/src/Helpers/I2C_access.cpp @@ -9,6 +9,8 @@ #include "../Helpers/Hardware_I2C.h" #include "../Helpers/StringConverter.h" +#include "../Helpers/I2C_access.h" + #if FEATURE_I2C_MULTIPLE # include "../WebServer/Markup_Forms.h" #endif // if FEATURE_I2C_MULTIPLE diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index 5b3db5209..85a791b4c 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -107,10 +107,6 @@ void handle_advanced() { 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 @@ -199,18 +195,6 @@ void handle_advanced() { 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()); diff --git a/src/src/WebServer/NetworkPage.cpp b/src/src/WebServer/NetworkPage.cpp index 843b9f489..74fb1b0a5 100644 --- a/src/src/WebServer/NetworkPage.cpp +++ b/src/src/WebServer/NetworkPage.cpp @@ -172,6 +172,9 @@ void handle_networks_CopySubmittedSettings_NWPluginCall(ESPEasy::net::networkInd # if FEATURE_USE_IPV6 Settings.setNetworkEnabled_IPv6(networkindex, isFormItemChecked(F("en_ipv6"))); +# endif +# if FEATURE_NETWORK_STATS + Settings.setNetworkCollectStats(networkindex, isFormItemChecked(F("collect_netw_stats"))); # endif Settings.setNetworkInterfaceStartupDelay(networkindex, getFormItemInt(F("delay_start"))); String dummy; @@ -197,9 +200,9 @@ void handle_networks_ShowAllNetworksTable() html_table_header(F("Hostname/SSID")); html_table_header(F("HW Address")); html_table_header(F("IP")); -# ifdef ESP32 +# ifdef ESP32 html_table_header(F("Port")); -# endif +# endif for (ESPEasy::net::networkIndex_t x = 0; x < MAX_NR_NETWORKS_IN_TABLE; x++) { @@ -415,6 +418,11 @@ void handle_networks_NetworkSettingsPage(ESPEasy::net::networkIndex_t networkind } # endif // if FEATURE_USE_IPV6 +# if FEATURE_NETWORK_STATS + addFormCheckBox(F("Collect Network Stats"), F("collect_netw_stats"), Settings.getNetworkCollectStats(networkindex)); + addFormNote(F("Needs reboot after change")); +# endif // if FEATURE_NETWORK_STATS + String str; ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD, &TempEvent, str); From 976bd2fc1b82773d7e6783dcdf2c643727dc7c8b Mon Sep 17 00:00:00 2001 From: TD-er Date: Tue, 12 May 2026 17:02:29 +0200 Subject: [PATCH 4/5] [ESP32S2] Improve free RAM on ESP32S2 --- platformio_core_defs.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index e528d1b28..ce8dc25c6 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -190,8 +190,8 @@ extra_scripts = ${esp82xx_common.extra_scripts} ;platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF55_gcc152 ;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2904-2115-5.5/framework-arduinoespressif32-release_v5.5-f2a3fa2b.tar.xz -platform = https://github.com/tasmota/platform-espressif32/releases/download/2026.04.50/platform-espressif32.zip -platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/0405-1702-5.5/framework-arduinoespressif32-release_v5.5-f2a3fa2b.tar.xz +platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF55_gcc152 +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1205-1404-5.5/framework-arduinoespressif32-release_v5.5-65012b38.tar.xz custom_remove_include = true From 419170689e1b31351725b36892ab04598983805e Mon Sep 17 00:00:00 2001 From: TD-er Date: Tue, 12 May 2026 21:30:10 +0200 Subject: [PATCH 5/5] [Network] Fix crash saving network interface settings with p2p active --- src/ESPEasy/net/Helpers/_NWPlugin_init.cpp | 4 ++++ src/src/Helpers/ESPEasy_time.cpp | 2 +- src/src/Helpers/Networking.cpp | 15 +++++++++++++-- src/src/Helpers/Networking.h | 1 + src/src/WebServer/NetworkPage.cpp | 1 - 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/ESPEasy/net/Helpers/_NWPlugin_init.cpp b/src/ESPEasy/net/Helpers/_NWPlugin_init.cpp index fe803fc98..f44db25ad 100644 --- a/src/ESPEasy/net/Helpers/_NWPlugin_init.cpp +++ b/src/ESPEasy/net/Helpers/_NWPlugin_init.cpp @@ -6,6 +6,7 @@ #include "../../../src/Globals/Settings.h" #include "../../../src/Globals/ESPEasy_Scheduler.h" //#include "../../../src/Helpers/Misc.h" +#include "../../../src/Helpers/Networking.h" #include "../../../src/Helpers/StringConverter.h" #include "../Globals/NWPlugins.h" @@ -2137,6 +2138,9 @@ bool do_NWPluginCall(networkDriverIndex_t networkDriverIndex, NWPlugin:: // addLog(LOG_LEVEL_ERROR, strformat(F("Network %d was not (yet) initialized"), event->NetworkIndex + 1)); return false; } +#if FEATURE_ESPEASY_P2P + stopUDPport(); +#endif } diff --git a/src/src/Helpers/ESPEasy_time.cpp b/src/src/Helpers/ESPEasy_time.cpp index 9b06b7f54..387ea8b7a 100644 --- a/src/src/Helpers/ESPEasy_time.cpp +++ b/src/src/Helpers/ESPEasy_time.cpp @@ -509,7 +509,7 @@ bool ESPEasy_time::systemTimePresent() const { bool ESPEasy_time::getNtpTime(double& unixTime_d) { - if (!Settings.UseNTP() || !ESPEasy::net::NetworkConnected()) { + if (!Settings.UseNTP() || !ESPEasy::net::NetworkConnected(true)) { return false; } diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index e44fc50d3..b6caa4074 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -154,9 +154,19 @@ void sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) /*********************************************************************************************\ Update UDP port (ESPEasy propiertary protocol) \*********************************************************************************************/ +uint16_t lastUsedUDPPort = 0; + +void stopUDPport() +{ + if (lastUsedUDPPort == 0) return; + if (ESPEasy::net::NetworkConnected(true)) { + portUDP.stop(); + } + lastUsedUDPPort = 0; +} + void updateUDPport(bool force) { - static uint16_t lastUsedUDPPort = 0; if (!force && (Settings.UDPPort == lastUsedUDPPort)) { return; @@ -169,7 +179,8 @@ void updateUDPport(bool force) //const bool connected = ESPEasy::net::NetworkConnected(); if (!connected || (Settings.UDPPort != lastUsedUDPPort)) { if (lastUsedUDPPort != 0) { - portUDP.stop(); + if (connected) + portUDP.stop(); lastUsedUDPPort = 0; #ifndef BUILD_NO_DEBUG addLogMove(LOG_LEVEL_INFO, concat(F("UDP : Stop listening on port "), Settings.UDPPort)); diff --git a/src/src/Helpers/Networking.h b/src/src/Helpers/Networking.h index 2b44f3bae..73ee57f9e 100644 --- a/src/src/Helpers/Networking.h +++ b/src/src/Helpers/Networking.h @@ -38,6 +38,7 @@ /*********************************************************************************************\ Update UDP port (ESPEasy propiertary protocol) \*********************************************************************************************/ +void stopUDPport(); void updateUDPport(bool force); diff --git a/src/src/WebServer/NetworkPage.cpp b/src/src/WebServer/NetworkPage.cpp index 74fb1b0a5..5c46f4fb0 100644 --- a/src/src/WebServer/NetworkPage.cpp +++ b/src/src/WebServer/NetworkPage.cpp @@ -420,7 +420,6 @@ void handle_networks_NetworkSettingsPage(ESPEasy::net::networkIndex_t networkind # if FEATURE_NETWORK_STATS addFormCheckBox(F("Collect Network Stats"), F("collect_netw_stats"), Settings.getNetworkCollectStats(networkindex)); - addFormNote(F("Needs reboot after change")); # endif // if FEATURE_NETWORK_STATS String str;