diff --git a/.github/workflows/mqtt_cxx__build.yml b/.github/workflows/mqtt_cxx__build.yml index e99111c8dd..238df69662 100644 --- a/.github/workflows/mqtt_cxx__build.yml +++ b/.github/workflows/mqtt_cxx__build.yml @@ -13,9 +13,9 @@ jobs: name: Build strategy: matrix: - idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"] + idf_ver: ["release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5"] idf_target: ["esp32"] - test: [ { app: mqtt-basic, path: "components/esp_mqtt_cxx/examples" }] + test: [ { app: mqtt-basic, path: "components/esp_mqtt_cxx/examples" }, { app: test, path: "components/esp_mqtt_cxx/test/unit" }] runs-on: ubuntu-22.04 container: espressif/idf:${{ matrix.idf_ver }} steps: @@ -25,8 +25,7 @@ jobs: submodules: recursive - name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }} shell: bash - working-directory: ${{matrix.test.path}} run: | . ${IDF_PATH}/export.sh pip install idf-component-manager idf-build-apps --upgrade - python ../../../ci/build_apps.py ./${{ matrix.test.app }} --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app + python ./ci/build_apps.py ./${{ matrix.test.path }} --target ${{ matrix.idf_target }} -vv --preserve-all -c diff --git a/components/esp_mqtt_cxx/.cz.yaml b/components/esp_mqtt_cxx/.cz.yaml index f8ce49be5c..32e1cab2c2 100644 --- a/components/esp_mqtt_cxx/.cz.yaml +++ b/components/esp_mqtt_cxx/.cz.yaml @@ -3,6 +3,6 @@ commitizen: bump_message: 'bump(mqtt_cxx): $current_version -> $new_version' pre_bump_hooks: python ../../ci/changelog.py esp_mqtt_cxx tag_format: mqtt_cxx-v$version - version: 0.4.0 + version: 0.5.0 version_files: - idf_component.yml diff --git a/components/esp_mqtt_cxx/CHANGELOG.md b/components/esp_mqtt_cxx/CHANGELOG.md index a39fd7050c..8b14097e23 100644 --- a/components/esp_mqtt_cxx/CHANGELOG.md +++ b/components/esp_mqtt_cxx/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.5.0](https://github.com/espressif/esp-protocols/commits/mqtt_cxx-v0.5.0) + +### Bug Fixes + +- Implement simple unit tests ([f41c4a0a](https://github.com/espressif/esp-protocols/commit/f41c4a0a)) +- Fix to construct in two steps ([d979e1b3](https://github.com/espressif/esp-protocols/commit/d979e1b3), [#631](https://github.com/espressif/esp-protocols/issues/631)) +- Add explicit dependency on esp-mqtt if needed ([3d5e11b8](https://github.com/espressif/esp-protocols/commit/3d5e11b8)) + ## [0.4.0](https://github.com/espressif/esp-protocols/commits/mqtt_cxx-v0.4.0) ### Bug Fixes diff --git a/components/esp_mqtt_cxx/esp_mqtt_cxx.cpp b/components/esp_mqtt_cxx/esp_mqtt_cxx.cpp index 75c8d6db09..9e077719f2 100644 --- a/components/esp_mqtt_cxx/esp_mqtt_cxx.cpp +++ b/components/esp_mqtt_cxx/esp_mqtt_cxx.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -163,7 +163,20 @@ Client::Client(esp_mqtt_client_config_t const &config) : handler(esp_mqtt_clien throw MQTTException(ESP_FAIL); }; CHECK_THROW_SPECIFIC(esp_mqtt_client_register_event(handler.get(), MQTT_EVENT_ANY, mqtt_event_handler, this), mqtt::MQTTException); +} + +void Client::start() +{ + if (started) { + return; + } CHECK_THROW_SPECIFIC(esp_mqtt_client_start(handler.get()), mqtt::MQTTException); + started = true; +} + +bool Client::is_started() const noexcept +{ + return started; } void Client::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) noexcept diff --git a/components/esp_mqtt_cxx/examples/ssl/main/mqtt_ssl_example.cpp b/components/esp_mqtt_cxx/examples/ssl/main/mqtt_ssl_example.cpp index 3bdf35640d..1651a3f65a 100644 --- a/components/esp_mqtt_cxx/examples/ssl/main/mqtt_ssl_example.cpp +++ b/components/esp_mqtt_cxx/examples/ssl/main/mqtt_ssl_example.cpp @@ -83,8 +83,9 @@ extern "C" void app_main(void) idf::mqtt::Configuration config{}; MyClient client{broker, credentials, config}; + client.start(); while (true) { constexpr TickType_t xDelay = 500 / portTICK_PERIOD_MS; - vTaskDelay( xDelay ); + vTaskDelay(xDelay); } } diff --git a/components/esp_mqtt_cxx/examples/tcp/main/mqtt_tcp_example.cpp b/components/esp_mqtt_cxx/examples/tcp/main/mqtt_tcp_example.cpp index 67517d246d..fcd0e1bcaf 100644 --- a/components/esp_mqtt_cxx/examples/tcp/main/mqtt_tcp_example.cpp +++ b/components/esp_mqtt_cxx/examples/tcp/main/mqtt_tcp_example.cpp @@ -78,6 +78,7 @@ extern "C" void app_main(void) mqtt::Configuration config{}; MyClient client{broker, credentials, config}; + client.start(); while (true) { constexpr TickType_t xDelay = 500 / portTICK_PERIOD_MS; diff --git a/components/esp_mqtt_cxx/idf_component.yml b/components/esp_mqtt_cxx/idf_component.yml index 904dd3fc7d..bc041f2cb0 100644 --- a/components/esp_mqtt_cxx/idf_component.yml +++ b/components/esp_mqtt_cxx/idf_component.yml @@ -1,4 +1,4 @@ -version: "0.4.0" +version: "0.5.0" description: C++ APIs for ESP-MQTT library url: https://github.com/espressif/esp-protocols/tree/master/components/esp_mqtt_cxx issues: https://github.com/espressif/esp-protocols/issues @@ -8,7 +8,7 @@ dependencies: espressif/esp-idf-cxx: "^1.0.0-beta" # Required IDF version idf: - version: ">=5.0" + version: ">=5.0,<6.0" espressif/mqtt: rules: - if: idf_version >=6.0 diff --git a/components/esp_mqtt_cxx/include/esp_mqtt.hpp b/components/esp_mqtt_cxx/include/esp_mqtt.hpp index 4a5b9d286c..9f81ad7786 100644 --- a/components/esp_mqtt_cxx/include/esp_mqtt.hpp +++ b/components/esp_mqtt_cxx/include/esp_mqtt.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -165,6 +165,19 @@ class Client { */ Client(const esp_mqtt_client_config_t &config); + /** + * @brief Start the underlying esp-mqtt client + * + * Must be called after the derived class has finished constructing to avoid + * events being dispatched to partially constructed objects. + */ + void start(); + + /** + * @brief Check whether start() has been called + */ + [[nodiscard]] bool is_started() const noexcept; + /** * @brief Subscribe to topic * @@ -245,13 +258,13 @@ class Client { */ virtual void on_error(const esp_mqtt_event_handle_t event); /** - * @brief Called if there is an disconnection event + * @brief Called if there is a disconnection event * * @param event mqtt event data */ virtual void on_disconnected(const esp_mqtt_event_handle_t event); /** - * @brief Called if there is an subscribed event + * @brief Called if there is a subscribed event * * @param event mqtt event data */ @@ -263,26 +276,26 @@ class Client { */ virtual void on_unsubscribed(const esp_mqtt_event_handle_t event); /** - * @brief Called if there is an published event + * @brief Called if there is a published event * * @param event mqtt event data */ virtual void on_published(const esp_mqtt_event_handle_t event); /** - * @brief Called if there is an before connect event + * @brief Called if there is a before connect event * * @param event mqtt event data */ virtual void on_before_connect(const esp_mqtt_event_handle_t event); /** - * @brief Called if there is an connected event + * @brief Called if there is a connected event * * @param event mqtt event data * */ virtual void on_connected(const esp_mqtt_event_handle_t event) = 0; /** - * @brief Called if there is an data event + * @brief Called if there is a data event * * @param event mqtt event data * @@ -292,5 +305,6 @@ class Client { static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) noexcept; void init(const esp_mqtt_client_config_t &config); + bool started{false}; }; } // namespace idf::mqtt diff --git a/components/esp_mqtt_cxx/test/unit/CMakeLists.txt b/components/esp_mqtt_cxx/test/unit/CMakeLists.txt new file mode 100644 index 0000000000..f0596f7ec3 --- /dev/null +++ b/components/esp_mqtt_cxx/test/unit/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.5) + + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +if(${IDF_TARGET} STREQUAL "linux") + set(EXTRA_COMPONENT_DIRS "../../../../common_components/linux_compat") + set(COMPONENTS main) +endif() + +project(esp_mqtt_cxx_host_test) diff --git a/components/esp_mqtt_cxx/test/unit/README.md b/components/esp_mqtt_cxx/test/unit/README.md new file mode 100644 index 0000000000..166928d4d9 --- /dev/null +++ b/components/esp_mqtt_cxx/test/unit/README.md @@ -0,0 +1,24 @@ +# Test basic mqtt_cxx wrapper operations + +## Warning: Linux target not supported, this test works only on target + +## Example output +``` +I (588) main_task: Started on CPU0 +I (598) main_task: Calling app_main() +Randomness seeded to: 374196253 +I (608) mqtt_client_cpp: MQTT_EVENT_BEFORE_CONNECT +E (618) esp-tls: [sock=54] delayed connect error: Connection reset by peer +E (618) transport_base: Failed to open a new connection: 32772 +E (618) mqtt_client: Error transport connect +I (618) mqtt_client_cpp: MQTT_EVENT_ERROR +E (628) mqtt_client_cpp: Last error reported from esp-tls: 0x8004 +E (628) mqtt_client_cpp: Last error captured as transport's socket errno: 0x68 +I (638) mqtt_client_cpp: Last errno string (Connection reset by peer) +I (648) mqtt_client_cpp: MQTT_EVENT_DISCONNECTED +=============================================================================== +All tests passed (6 assertions in 1 test case) + +Test passed! +I (5658) main_task: Returned from app_main() +``` diff --git a/components/esp_mqtt_cxx/test/unit/main/CMakeLists.txt b/components/esp_mqtt_cxx/test/unit/main/CMakeLists.txt new file mode 100644 index 0000000000..0c90f37ae6 --- /dev/null +++ b/components/esp_mqtt_cxx/test/unit/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "test_esp_mqtt_cxx.cpp" + WHOLE_ARCHIVE) diff --git a/components/esp_mqtt_cxx/test/unit/main/idf_component.yml b/components/esp_mqtt_cxx/test/unit/main/idf_component.yml new file mode 100644 index 0000000000..6d27d03350 --- /dev/null +++ b/components/esp_mqtt_cxx/test/unit/main/idf_component.yml @@ -0,0 +1,5 @@ +dependencies: + espressif/catch2: "^3.4.0" + esp_mqtt_cxx: + version: "*" + override_path: '../../../' diff --git a/components/esp_mqtt_cxx/test/unit/main/test_esp_mqtt_cxx.cpp b/components/esp_mqtt_cxx/test/unit/main/test_esp_mqtt_cxx.cpp new file mode 100644 index 0000000000..d141724300 --- /dev/null +++ b/components/esp_mqtt_cxx/test/unit/main/test_esp_mqtt_cxx.cpp @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "catch2/catch_session.hpp" +#include "catch2/catch_test_macros.hpp" +#include "esp_mqtt.hpp" +#include "esp_mqtt_client_config.hpp" +#include "esp_netif.h" + +namespace mqtt = idf::mqtt; + +namespace { +class TestClient final : public mqtt::Client { +public: + using mqtt::Client::Client; + + bool constructed{false}; + bool before_connect{false}; + bool disconnected{false}; + + TestClient(const mqtt::BrokerConfiguration &broker, const mqtt::ClientCredentials &credentials, const mqtt::Configuration &config) : + mqtt::Client(broker, credentials, config) + { + constructed = true; + } + +private: + void on_connected(esp_mqtt_event_handle_t const event) override + { + CHECK(constructed); + } + + void on_data(esp_mqtt_event_handle_t const event) override + { + CHECK(constructed); + } + + void on_before_connect(esp_mqtt_event_handle_t const event) override + { + CHECK(constructed); + before_connect = true; + } + + void on_disconnected(const esp_mqtt_event_handle_t event) override + { + CHECK(constructed); + disconnected = true; + + } + +}; +} // namespace + +TEST_CASE("Client does not auto-start and can dispatch events after construction", "[esp_mqtt_cxx]") +{ + mqtt::BrokerConfiguration broker{ + .address = mqtt::URI{std::string{"mqtt://127.0.0.1:1883"}}, + .security = mqtt::Insecure{} + }; + mqtt::ClientCredentials credentials{}; + mqtt::Configuration config{}; + + TestClient client{broker, credentials, config}; + + REQUIRE(client.is_started() == false); + + // start the client and expect disconnection (reset by peer) + // since no server's running on this ESP32 + client.start(); + CHECK(client.is_started() == true); + + CHECK(client.before_connect); + usleep(10000); + CHECK(client.disconnected); +} + +extern "C" void app_main(void) +{ + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + Catch::Session session; + + int failures = session.run(); + if (failures > 0) { + printf("TEST FAILED! number of failures=%d\n", failures); + return; + } + printf("Test passed!\n"); +} diff --git a/components/esp_mqtt_cxx/test/unit/sdkconfig.defaults b/components/esp_mqtt_cxx/test/unit/sdkconfig.defaults new file mode 100644 index 0000000000..8bdcab28ad --- /dev/null +++ b/components/esp_mqtt_cxx/test/unit/sdkconfig.defaults @@ -0,0 +1,4 @@ +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/test_app/CMakeLists.txt b/test_app/CMakeLists.txt index bda7a057a9..294b604fd6 100644 --- a/test_app/CMakeLists.txt +++ b/test_app/CMakeLists.txt @@ -7,7 +7,6 @@ include($ENV{IDF_PATH}/tools/cmake/version.cmake) set(EXTRA_COMPONENT_DIRS ../components/eppp_link ../components/esp_modem - ../components/esp_mqtt_cxx ../components/esp_websocket_client ../components/console_cmd_ifconfig ../components/console_cmd_ping