From d53f0e074ce7e8e35cbb2fac07eb7a23022edc4c Mon Sep 17 00:00:00 2001 From: Euripedes Rocha Filho Date: Tue, 15 Dec 2020 13:10:10 +0000 Subject: [PATCH] experimental/mqtt_cxx: Adds a C++ Mqtt client wrapper - Base class with separated event handlers for each Mqtt client event. - Topic matcher added to support data events. - Filter class to allow only mqtt valid topic filters. - Initial code for unit test on the host. --- .../components/esp_mqtt_cxx/CMakeLists.txt | 25 ++ .../components/esp_mqtt_cxx/component.mk | 5 + .../components/esp_mqtt_cxx/esp_mqtt_cxx.cpp | 299 ++++++++++++++++++ .../esp_mqtt_cxx/include/esp_mqtt.hpp | 229 ++++++++++++++ .../include/esp_mqtt_client_config.hpp | 221 +++++++++++++ .../esp_mqtt_cxx/ssl/CMakeLists.txt | 14 + .../experimental/esp_mqtt_cxx/ssl/Makefile | 12 + .../esp_mqtt_cxx/ssl/main/CMakeLists.txt | 4 + .../esp_mqtt_cxx/ssl/main/Kconfig.projbuild | 9 + .../esp_mqtt_cxx/ssl/main/component.mk | 1 + .../ssl/main/mqtt_eclipse_org.pem | 27 ++ .../ssl/main/mqtt_eclipseprojects_io.pem | 30 ++ .../ssl/main/mqtt_ssl_example.cpp | 85 +++++ .../esp_mqtt_cxx/ssl/sdkconfig.defaults | 3 + .../esp_mqtt_cxx/tcp/CMakeLists.txt | 12 + .../experimental/esp_mqtt_cxx/tcp/Makefile | 12 + .../esp_mqtt_cxx/tcp/main/CMakeLists.txt | 3 + .../esp_mqtt_cxx/tcp/main/Kconfig.projbuild | 9 + .../esp_mqtt_cxx/tcp/main/component.mk | 0 .../tcp/main/mqtt_tcp_example.cpp | 81 +++++ .../esp_mqtt_cxx/tcp/sdkconfig.defaults | 3 + 21 files changed, 1084 insertions(+) create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/CMakeLists.txt create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/component.mk create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/esp_mqtt_cxx.cpp create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/include/esp_mqtt.hpp create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/include/esp_mqtt_client_config.hpp create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/ssl/CMakeLists.txt create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/ssl/Makefile create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/ssl/main/CMakeLists.txt create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/ssl/main/Kconfig.projbuild create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/ssl/main/component.mk create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_eclipse_org.pem create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_eclipseprojects_io.pem create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_ssl_example.cpp create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/ssl/sdkconfig.defaults create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/tcp/CMakeLists.txt create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/tcp/Makefile create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/tcp/main/CMakeLists.txt create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/tcp/main/Kconfig.projbuild create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/tcp/main/component.mk create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/tcp/main/mqtt_tcp_example.cpp create mode 100644 examples/cxx/experimental/esp_mqtt_cxx/tcp/sdkconfig.defaults diff --git a/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/CMakeLists.txt b/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/CMakeLists.txt new file mode 100644 index 0000000000..5dd0329d08 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/CMakeLists.txt @@ -0,0 +1,25 @@ +idf_build_get_property(target IDF_TARGET) + +idf_component_register(SRCS "esp_mqtt_cxx.cpp" + INCLUDE_DIRS "include" + ) + +target_compile_options(${COMPONENT_LIB} PRIVATE "-std=gnu++17") + +if(TEST_BUILD) + message(STATUS "Test build") + idf_component_get_property(mqtt_dir mqtt COMPONENT_DIR) + idf_component_get_property(experimental_cpp_component_dir experimental_cpp_component COMPONENT_DIR) + idf_component_get_property(esp_common_dir esp_common COMPONENT_DIR) + idf_component_get_property(esp_event_dir esp_event COMPONENT_DIR) + target_include_directories(${COMPONENT_LIB} PUBLIC ${mqtt_dir}/esp-mqtt/include + ${esp_event_dir}/include + ${experimental_cpp_component_dir}/include + ${esp_common_dir}/include) + +else() + idf_component_get_property(mqtt_lib mqtt COMPONENT_LIB) + idf_component_get_property(log_lib log COMPONENT_LIB) + idf_component_get_property(experimental_cpp_component_lib experimental_cpp_component COMPONENT_LIB) + target_link_libraries(${COMPONENT_LIB} PUBLIC ${log_lib} ${mqtt_lib} ${experimental_cpp_component_lib}) +endif() diff --git a/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/component.mk b/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/component.mk new file mode 100644 index 0000000000..9e4987ab4e --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/component.mk @@ -0,0 +1,5 @@ +COMPONENT_ADD_INCLUDEDIRS := include + +COMPONENT_SRCDIRS := ./ + +CXXFLAGS += -std=gnu++17 diff --git a/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/esp_mqtt_cxx.cpp b/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/esp_mqtt_cxx.cpp new file mode 100644 index 0000000000..b5f705b180 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/esp_mqtt_cxx.cpp @@ -0,0 +1,299 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "mqtt_client.h" +#include "esp_log.h" + +#include "esp_mqtt.hpp" +#include "esp_mqtt_client_config.hpp" + +namespace { + +// Helper for static assert. +template +constexpr bool always_false = false; + +template struct overloaded : Ts... { + using Ts::operator()...; +}; +template overloaded(Ts...) -> overloaded; + +using namespace idf::mqtt; + +/* + * This function is responsible for fill in the configurations for the broker related data + * of mqtt_client_config_t + */ +void config_broker(esp_mqtt_client_config_t &mqtt_client_cfg, BrokerConfiguration const &broker) +{ + std::visit(overloaded{ + [&mqtt_client_cfg](Host const & host) + { + mqtt_client_cfg.host = host.address.c_str(); + mqtt_client_cfg.path = host.path.c_str(); + mqtt_client_cfg.transport = host.transport; + }, + [&mqtt_client_cfg](URI const & uri) + { + mqtt_client_cfg.uri = uri.address.c_str(); + }, + []([[maybe_unused ]]auto & unknown) + { + static_assert(always_false, "Missing type handler for variant handler"); + } + }, + broker.address.address); + + std::visit(overloaded{ + []([[maybe_unused]]Insecure const & insecure) {}, + [&mqtt_client_cfg](GlobalCAStore const & use_global_store) + { + mqtt_client_cfg.use_global_ca_store = true; + }, + [&mqtt_client_cfg](CryptographicInformation const & certificates) + { + std::visit(overloaded{ + [&mqtt_client_cfg](PEM const & pem) + { + mqtt_client_cfg.cert_pem = pem.data; + }, [&mqtt_client_cfg](DER const & der) + { + mqtt_client_cfg.cert_pem = der.data; + mqtt_client_cfg.cert_len = der.len; + }}, certificates); + }, + []([[maybe_unused]] PSK const & psk) {}, + []([[maybe_unused]] auto & unknown) + { + static_assert(always_false, "Missing type handler for variant handler"); + } + }, + broker.security); + mqtt_client_cfg.port = broker.address.port; +} + +/* + * This function is responsible for fill in the configurations for the client credentials related data + * of mqtt_client_config_t + */ +void config_client_credentials(esp_mqtt_client_config_t &mqtt_client_cfg, ClientCredentials const &credentials) +{ + mqtt_client_cfg.client_id = credentials.client_id.has_value() ? credentials.client_id.value().c_str() : nullptr ; + mqtt_client_cfg.username = credentials.username.has_value() ? credentials.username.value().c_str() : nullptr ; + std::visit(overloaded{ + [&mqtt_client_cfg](Password const & password) + { + mqtt_client_cfg.password = password.data.c_str(); + }, + [](ClientCertificate const & certificate) {}, + [](SecureElement const & enable_secure_element) {}, + []([[maybe_unused ]]auto & unknown) + { + static_assert(always_false, "Missing type handler for variant handler"); + } + }, credentials.authentication); +} + +esp_mqtt_client_config_t make_config(BrokerConfiguration const &broker, ClientCredentials const &credentials, Configuration const &config) +{ + esp_mqtt_client_config_t mqtt_client_cfg{}; + config_broker(mqtt_client_cfg, broker); + config_client_credentials(mqtt_client_cfg, credentials); + return mqtt_client_cfg; +} +} + +namespace idf::mqtt { + +Client::Client(BrokerConfiguration const &broker, ClientCredentials const &credentials, Configuration const &config): Client(make_config(broker, credentials, config)) {} + +Client::Client(esp_mqtt_client_config_t const &config) : handler(esp_mqtt_client_init(&config)) +{ + if (handler == nullptr) { + throw MQTTException(ESP_FAIL); + }; + CHECK_THROW_SPECIFIC(esp_mqtt_client_register_event(handler.get(), MQTT_EVENT_ANY, mqtt_event_handler, this), mqtt::MQTTException); + CHECK_THROW_SPECIFIC(esp_mqtt_client_start(handler.get()), mqtt::MQTTException); +} + + +void Client::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) noexcept +{ + ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id); + auto *event = static_cast(event_data); + auto &client = *static_cast(handler_args); + switch (event->event_id) { + case MQTT_EVENT_CONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); + client.on_connected(event); + break; + case MQTT_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); + client.on_disconnected(event); + break; + + case MQTT_EVENT_SUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); + client.on_subscribed(event); + break; + case MQTT_EVENT_UNSUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); + client.on_unsubscribed(event); + break; + case MQTT_EVENT_PUBLISHED: + ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); + client.on_published(event); + break; + case MQTT_EVENT_DATA: + ESP_LOGI(TAG, "MQTT_EVENT_DATA"); + client.on_data(event); + break; + case MQTT_EVENT_ERROR: + ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); + client.on_error(event); + break; + case MQTT_EVENT_BEFORE_CONNECT: + ESP_LOGI(TAG, "MQTT_EVENT_BEFORE_CONNECT"); + client.on_before_connect(event); + break; + default: + ESP_LOGI(TAG, "Other event id:%d", event->event_id); + break; + } +} + +void Client::on_error(esp_mqtt_event_handle_t const event) +{ + auto log_error_if_nonzero = [](const char *message, int error_code) { + if (error_code != 0) { + ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code); + } + }; + if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { + log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err); + log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err); + log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno); + ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno)); + } +} +void Client::on_disconnected(esp_mqtt_event_handle_t const event) +{ +} +void Client::on_subscribed(esp_mqtt_event_handle_t const event) +{ + printf("Subscribed to %.*s\r\n", event->topic_len, event->topic); +} +void Client::on_unsubscribed(esp_mqtt_event_handle_t const event) +{ +} +void Client::on_published(esp_mqtt_event_handle_t const event) +{ +} +void Client::on_before_connect(esp_mqtt_event_handle_t const event) +{ +} +void Client::on_connected(esp_mqtt_event_handle_t const event) +{ +} +void Client::on_data(esp_mqtt_event_handle_t const event) +{ +} + +std::optional Client::subscribe(std::string const &topic, QoS qos) +{ + auto res = esp_mqtt_client_subscribe(handler.get(), topic.c_str(), + static_cast(qos)); + if (res < 0) { + return std::nullopt; + } + return MessageID{res}; +} + +bool is_valid(std::string::const_iterator first, std::string::const_iterator last) +{ + if (first == last) { + return false; + } + auto number = std::find(first, last, '#'); + if (number != last) { + if (std::next(number) != last) { + return false; + } + if (*std::prev(number) != '/' && number != first) { + return false; + } + } + + auto plus = std::find(first, last, '+'); + if (plus != last) { + if (*(std::prev(plus)) != '/' && plus != first) { + return false; + } + if (std::next(plus) != last && *(std::next(plus)) != '/') { + return false; + } + } + return true; +} + +Filter::Filter(std::string user_filter) : filter(std::move(user_filter)) +{ + if (!is_valid(filter.begin(), filter.end())) { + throw std::domain_error("Forbidden Filter string"); + } +} + +[[nodiscard]] bool Filter::match(std::string::const_iterator topic_begin, std::string::const_iterator topic_end) const noexcept +{ + auto filter_begin = filter.begin(); + auto filter_end = filter.end(); + for (auto mismatch = std::mismatch(filter_begin, filter_end, topic_begin); + mismatch.first != filter.end() and mismatch.second != topic_end; + mismatch = std::mismatch(filter_begin, filter_end, topic_begin)) { + if (*mismatch.first != '#' and * mismatch.first != '+') { + return false; + } + if (*mismatch.first == '#') { + return true; + } + if (*mismatch.first == '+') { + filter_begin = advance(mismatch.first, filter_end); + topic_begin = advance(mismatch.second, topic_end); + if (filter_begin == filter_end and topic_begin != topic_end) { + return false; + } + } + } + return true; +} +const std::string &Filter::get() +{ + return filter; +} + +[[nodiscard]] bool Filter::match(char const *const first, int size) const noexcept +{ + auto it = static_cast(first); + return match(it, it + size); +} +std::string::const_iterator Filter::advance(std::string::const_iterator first, std::string::const_iterator last) const +{ + constexpr auto separator = '/'; + return std::find(first, last, separator); +} + +} diff --git a/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/include/esp_mqtt.hpp b/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/include/esp_mqtt.hpp new file mode 100644 index 0000000000..9d071f247a --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/include/esp_mqtt.hpp @@ -0,0 +1,229 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#ifndef __cpp_exceptions +#error MQTT class can only be used when __cpp_exceptions is enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig +#endif + +#include +#include +#include +#include +#include +#include "esp_exception.hpp" +#include "esp_mqtt_client_config.hpp" +#include "mqtt_client.h" + +namespace idf::mqtt { + +constexpr auto *TAG = "mqtt_client_cpp"; + +struct MQTTException : ESPException { + using ESPException::ESPException; +}; + +/** + * @brief QoS for publish and subscribe + * + * Sets the QoS as: + * AtMostOnce : Best effort delivery of messages. Message loss can occur. + * AtLeastOnce : Guaranteed delivery of messages. Duplicates can occur. + * ExactlyOnce : Guaranteed delivery of messages exactly once. + * + */ +enum class QoS { AtMostOnce = 0, AtLeastOnce = 1, ExactlyOnce = 2 }; + +/** + * @brief Sets if a message must be retained. + * + * Retained messages are delivered to future subscribers that match the topic name. + * + */ +enum class Retain : bool { NotRetained = false, Retained = true }; + + +/** + * @brief Message class template to publish. + * + */ +template struct Message { + T data; /*!< Data for publish. Should be a contiguous type*/ + QoS qos = QoS::AtLeastOnce; /*!< QoS for the message*/ + Retain retain = Retain::NotRetained; /*!< Retention mark for the message.*/ +}; + +/** + * @brief Message type that holds std::string for data + * + */ +using StringMessage = Message; + +[[nodiscard]] bool filter_is_valid(std::string::const_iterator first, std::string::const_iterator last); + +/** + * @brief Filter for mqtt topic subscription. + * @throws std::domain_error if the filter is invalid. + * + * Topic filter. + * + */ +class Filter { +public: + + explicit Filter(std::string user_filter); + + + /** + * @brief Get the filter string used. + * + */ + const std::string &get(); + + /** + * @brief Checks the filter string against a topic name. + * + * @param first Iterator to the beginning of the sequence. + * @param last Iterator to the end of the sequence. + * + * @return true if the topic name match the filter + */ + [[nodiscard]] bool match(std::string::const_iterator first, + std::string::const_iterator last) const noexcept; + + /** + * @brief Checks the filter string against a topic name. + * + * @param topic topic name + * + * @return true if the topic name match the filter + */ + [[nodiscard]] bool match(const std::string &topic) const noexcept; + + /** + * @brief Checks the filter string against a topic name. + * + * @param first Char array with topic name. + * @param last Size of given topic name. + * + * @return true if the topic name match the filter + */ + [[nodiscard]] bool match(const char *const begin, int size) const noexcept; + + +private: + + /** + * @brief Advance the topic to the next level. + * + * An mqtt topic ends with a /. This function is used to iterate in topic levels. + * + * @return Iterator to the start of the topic. + */ + [[nodiscard]] std::string::const_iterator advance(std::string::const_iterator begin, std::string::const_iterator end) const; + std::string filter; +}; + +/** + * @brief Message identifier to track delivery. + * + */ +enum class MessageID : int {}; + +/** + * @brief Base class for MQTT client + * + * Should be inherited to provide event handlers. + */ +class Client { +public: + + Client(const BrokerConfiguration &broker,const ClientCredentials &credentials,const Configuration &config); + + Client(const esp_mqtt_client_config_t &config); + + /** + * @brief Subscribe to topic + * + * @param filter + * @param qos QoS subscription, defaulted as QoS::AtLeastOnce + * + * @return Optional MessageID. In case of failure std::nullopt is returned. + */ + std::optional subscribe(const std::string &filter, QoS qos = QoS::AtLeastOnce); + + /** + * @brief publish message to topic + * + * @tparam Container Type for data container. Must be a contiguous memory. + * @param topic Topic name + * @param message Message struct containing data, qos and retain configuration. + * + * @return Optional MessageID. In case of failure std::nullopt is returned. + */ + template std::optional publish(const std::string &topic, const Message& message) + { + return publish(topic, std::begin(message.data), std::end(message.data), message.qos, message.retain); + } + + /** + * @brief publish message to topic + * + * @tparam InputIt Input data iterator type. + * @param topic Topic name + * @param first, last Iterator pair of data to publish + * @param message Message struct containing data, qos and retain configuration. + * + * @return Optional MessageID. In case of failure std::nullopt is returned. + */ + template + std::optional publish(const std::string &topic, InputIt first, InputIt last, QoS qos = QoS::AtLeastOnce, Retain retain = Retain::NotRetained) + { + auto size = std::distance(first, last); + auto res = esp_mqtt_client_publish(handler.get(), topic.c_str(), &(*first), size, static_cast(qos), + static_cast(retain)); + if (res < 0) { + return std::nullopt; + } + return MessageID{res}; + } + + virtual ~Client() = default; + +protected: + struct MqttClientDeleter { + void operator()(esp_mqtt_client *client_handler) + { + esp_mqtt_client_destroy(client_handler); + } + }; + + using ClientHandler = std::unique_ptr; + ClientHandler handler; + +private: + 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); + virtual void on_error(const esp_mqtt_event_handle_t event); + virtual void on_disconnected(const esp_mqtt_event_handle_t event); + virtual void on_subscribed(const esp_mqtt_event_handle_t event); + virtual void on_unsubscribed(const esp_mqtt_event_handle_t event); + virtual void on_published(const esp_mqtt_event_handle_t event); + virtual void on_before_connect(const esp_mqtt_event_handle_t event); + virtual void on_connected(const esp_mqtt_event_handle_t event) = 0; + virtual void on_data(const esp_mqtt_event_handle_t event) = 0; +}; +} // namespace idf::mqtt diff --git a/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/include/esp_mqtt_client_config.hpp b/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/include/esp_mqtt_client_config.hpp new file mode 100644 index 0000000000..0fbba636fc --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/components/esp_mqtt_cxx/include/esp_mqtt_client_config.hpp @@ -0,0 +1,221 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#include +#include +#include +#include + +#include "mqtt_client.h" + +namespace idf::mqtt { + + + +/** + * @brief Broker addresss + * + * Use this to set the broker without parsing the URI string. + * + */ +struct Host { + std::string address; /*!< Host name*/ + std::string path; /*!< Route path of the broker in host*/ + esp_mqtt_transport_t transport; /*!< Transport scheme to use. */ +}; + +/** + * @brief Broker addresss URI + * + * Use this to set the broker address using the URI. + * + */ +struct URI { + std::string address; /*!< Broker adddress URI*/ +}; + + +/** + * @brief Broker addresss. + * + */ +struct BrokerAddress { + std::variant address; /*!< Address, defined by URI or Host struct */ + uint32_t port = 0; /*!< Port used, defaults to 0 to select common port for the scheme used */ +}; + +/** + * @brief PEM formated data + * + * Store certificates, keys and cryptographic data. + * + */ +struct PEM { + const char *data; +}; + +/** + * @brief DER formated data + * + * Store certificates, keys and cryptographic data. + * + */ +struct DER { + const char *data; + size_t len; +}; + +/** + * @brief Holds cryptography related information + * + * Hold PEM or DER formated cryptographic data. + * + */ +using CryptographicInformation = std::variant; + + +/** + * @brief Do not verify broker certificate. + * + * To be used when doing MQTT over TLS connection but not verify broker's certificates. + * + */ +struct Insecure {}; + +/** + * @brief Use global CA store + * + * To be used when client should use the Global CA Store to get trusted certificates for the broker. + * + */ +struct GlobalCAStore {}; + +/** + * @brief Use a pre shared key for broker authentication. + * + * To be used when client should use a PSK to authenticate the broker. + * + */ +struct PSK { + const struct psk_key_hint *hint_key;/* Pointer to PSK struct defined in esp_tls.h to enable PSK authentication */ +}; + + +/** + * @brief Authentication method for Broker + * + * Selects the method for authentication based on the type it holds. + * + */ +using BrokerAuthentication = std::variant; + +/** + * @brief Password related data. + * + */ +struct Password { + std::string data; +}; + +/** + * @brief Data to authenticate client with certificates. + * + */ +struct ClientCertificate { + CryptographicInformation certificate; /*!< Certificate in PEM or DER format.*/ + CryptographicInformation key; /*!< Key data in PEM or DER format.*/ + std::optional key_password = std::nullopt; /*!< Optional password for key */ +}; + +/** + * @brief Used to select usage of Secure Element + * + * Enables the usage of the secure element present in ESP32-WROOM-32SE. + * + */ +struct SecureElement {}; + + +/** + * @brief Used to select usage of Digital Signature Peripheral. + * + * Enables the usage of the Digital Signature hardware accelerator. + * + */ +struct DigitalSignatureData { + void *ds_data; /* carrier of handle for digital signature parameters */ +}; + +using AuthenticationFactor = std::variant; + +struct BrokerConfiguration { + BrokerAddress address; + BrokerAuthentication security; +}; + +struct ClientCredentials { + std::optional username; // MQTT username + AuthenticationFactor authentication; + std::vector alpn_protos; /*!< List of supported application protocols to be used for ALPN */ + /* default is ``ESP32_%CHIPID%`` where %CHIPID% are last 3 bytes of MAC address in hex format */ + std::optional client_id = std::nullopt; +}; + +struct Event { + mqtt_event_callback_t event_handle; /*!< handle for MQTT events as a callback in legacy mode */ + esp_event_loop_handle_t event_loop_handle; /*!< handle for MQTT event loop library */ +}; + +struct LastWill { + const char *lwt_topic; /*!< LWT (Last Will and Testament) message topic (NULL by default) */ + const char *lwt_msg; /*!< LWT message (NULL by default) */ + int lwt_qos; /*!< LWT message qos */ + int lwt_retain; /*!< LWT retained message flag */ + int lwt_msg_len; /*!< LWT message length */ +}; + +struct Session { + LastWill last_will; + int disable_clean_session; /*!< mqtt clean session, default clean_session is true */ + int keepalive; /*!< mqtt keepalive, default is 120 seconds */ + bool disable_keepalive; /*!< Set disable_keepalive=true to turn off keep-alive mechanism, false by default (keepalive is active by default). Note: setting the config value `keepalive` to `0` doesn't disable keepalive feature, but uses a default keepalive period */ + esp_mqtt_protocol_ver_t protocol_ver; /*!< MQTT protocol version used for connection, defaults to value from menuconfig*/ +}; + +struct Task { + int task_prio; /*!< MQTT task priority, default is 5, can be changed in ``make menuconfig`` */ + int task_stack; /*!< MQTT task stack size, default is 6144 bytes, can be changed in ``make menuconfig`` */ +}; + +struct Connection { + esp_mqtt_transport_t transport; /*!< overrides URI transport */ + int reconnect_timeout_ms; /*!< Reconnect to the broker after this value in miliseconds if auto reconnect is not disabled (defaults to 10s) */ + int network_timeout_ms; /*!< Abort network operation if it is not completed after this value, in milliseconds (defaults to 10s) */ + int refresh_connection_after_ms; /*!< Refresh connection after this value (in milliseconds) */ + bool disable_auto_reconnect; /*!< this mqtt client will reconnect to server (when errors/disconnect). Set disable_auto_reconnect=true to disable */ +}; + +struct Configuration { + Event event; + Task task; + Session session; + Connection connection; + void *user_context; /*!< pass user context to this option, then can receive that context in ``event->user_context`` */ + int buffer_size; /*!< size of MQTT send/receive buffer, default is 1024 (only receive buffer size if ``out_buffer_size`` defined) */ + int out_buffer_size; /*!< size of MQTT output buffer. If not defined, both output and input buffers have the same size defined as ``buffer_size`` */ +}; + +} // idf::mqtt diff --git a/examples/cxx/experimental/esp_mqtt_cxx/ssl/CMakeLists.txt b/examples/cxx/experimental/esp_mqtt_cxx/ssl/CMakeLists.txt new file mode 100644 index 0000000000..a5f94f0875 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/ssl/CMakeLists.txt @@ -0,0 +1,14 @@ +# The following four lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +# (Not part of the boilerplate) +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common + $ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component + $ENV{IDF_PATH}/examples/cxx/experimental/esp_mqtt_cxx/components) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(mqtt_ssl_cxx) + +target_add_binary_data(mqtt_ssl_cxx.elf "main/mqtt_eclipseprojects_io.pem" TEXT) diff --git a/examples/cxx/experimental/esp_mqtt_cxx/ssl/Makefile b/examples/cxx/experimental/esp_mqtt_cxx/ssl/Makefile new file mode 100644 index 0000000000..392ad4a333 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/ssl/Makefile @@ -0,0 +1,12 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := mqtt_tcp_cxx + +EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/common_components/protocol_examples_common +EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/cxx/experimental/esp_mqtt_cxx/components +EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/cxx/experimental/experimental_cpp_component + +CXXFLAGS += -std=gnu++17 +include $(IDF_PATH)/make/project.mk diff --git a/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/CMakeLists.txt b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/CMakeLists.txt new file mode 100644 index 0000000000..9871f4c78f --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "mqtt_ssl_example.cpp" + INCLUDE_DIRS ".") + +target_compile_options(${COMPONENT_LIB} PRIVATE "-std=gnu++17") diff --git a/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/Kconfig.projbuild b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/Kconfig.projbuild new file mode 100644 index 0000000000..6d8dc0a35b --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/Kconfig.projbuild @@ -0,0 +1,9 @@ +menu "Example Configuration" + + config BROKER_URI + string "Broker URL" + default "mqtts://mqtt.eclipse.org:8883" + help + URL of the broker to connect to + +endmenu diff --git a/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/component.mk b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/component.mk new file mode 100644 index 0000000000..c0c0f87e68 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/component.mk @@ -0,0 +1 @@ +COMPONENT_EMBED_TXTFILES := mqtt_eclipseprojects_io.pem diff --git a/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_eclipse_org.pem b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_eclipse_org.pem new file mode 100644 index 0000000000..0002462ce8 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_eclipse_org.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_eclipseprojects_io.pem b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_eclipseprojects_io.pem new file mode 100644 index 0000000000..43b222a60a --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_eclipseprojects_io.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw +WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP +R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx +sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm +NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg +Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG +/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA +FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw +AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw +Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB +gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W +PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl +ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz +CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm +lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 +avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 +yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O +yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids +hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ +HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv +MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX +nLRbwHOoq7hHwg== +-----END CERTIFICATE----- diff --git a/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_ssl_example.cpp b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_ssl_example.cpp new file mode 100644 index 0000000000..8493d52d03 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/ssl/main/mqtt_ssl_example.cpp @@ -0,0 +1,85 @@ +/* C++ MQTT (over TCP) Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include "esp_mqtt_client_config.hpp" +#include "nvs_flash.h" +#include "protocol_examples_common.h" + + +#include "esp_log.h" +#include "esp_mqtt.hpp" + +namespace { +constexpr auto *TAG = "MQTT_EXAMPLE"; + +extern const char mqtt_eclipse_org_pem_start[] asm("_binary_mqtt_eclipseprojects_io_pem_start"); +extern const char mqtt_eclipse_org_pem_end[] asm("_binary_mqtt_eclipseprojects_io_pem_end"); + +class MyClient final : public idf::mqtt::Client { +public: + using idf::mqtt::Client::Client; +private: + void on_connected(esp_mqtt_event_handle_t const event) override + { + using idf::mqtt::QoS; + subscribe(messages.get()); + subscribe(sent_load.get(), QoS::AtMostOnce); + } + void on_data(esp_mqtt_event_handle_t const event) override + { + if (messages.match(event->topic, event->topic_len)) { + ESP_LOGI(TAG, "Received in the messages topic"); + } + } + idf::mqtt::Filter messages{std::string{"$SYS/broker/messages/received"}}; + idf::mqtt::Filter sent_load{std::string{"$SYS/broker/load/+/sent"}}; +}; +} + +namespace mqtt = idf::mqtt; + +extern "C" void app_main(void) +{ + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + + esp_log_level_set("*", ESP_LOG_INFO); + esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE); + esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE); + esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE); + esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE); + esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE); + esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + + mqtt::BrokerConfiguration broker{ + .address = {mqtt::URI{std::string{CONFIG_BROKER_URI}}}, + .security = mqtt::CryptographicInformation{mqtt::PEM{mqtt_eclipse_org_pem_start}} + }; + idf::mqtt::ClientCredentials credentials{}; + idf::mqtt::Configuration config{}; + + MyClient client{broker, credentials, config}; + while (true) { + constexpr TickType_t xDelay = 500 / portTICK_PERIOD_MS; + vTaskDelay( xDelay ); + } +} diff --git a/examples/cxx/experimental/esp_mqtt_cxx/ssl/sdkconfig.defaults b/examples/cxx/experimental/esp_mqtt_cxx/ssl/sdkconfig.defaults new file mode 100644 index 0000000000..a365ac6589 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/ssl/sdkconfig.defaults @@ -0,0 +1,3 @@ +# Enable C++ exceptions and set emergency pool size for exception objects +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 diff --git a/examples/cxx/experimental/esp_mqtt_cxx/tcp/CMakeLists.txt b/examples/cxx/experimental/esp_mqtt_cxx/tcp/CMakeLists.txt new file mode 100644 index 0000000000..29bdec2893 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/tcp/CMakeLists.txt @@ -0,0 +1,12 @@ +# The following four lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +# (Not part of the boilerplate) +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common + $ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component + $ENV{IDF_PATH}/examples/cxx/experimental/esp_mqtt_cxx/components) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(mqtt_tcp_cxx) diff --git a/examples/cxx/experimental/esp_mqtt_cxx/tcp/Makefile b/examples/cxx/experimental/esp_mqtt_cxx/tcp/Makefile new file mode 100644 index 0000000000..392ad4a333 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/tcp/Makefile @@ -0,0 +1,12 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := mqtt_tcp_cxx + +EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/common_components/protocol_examples_common +EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/cxx/experimental/esp_mqtt_cxx/components +EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/cxx/experimental/experimental_cpp_component + +CXXFLAGS += -std=gnu++17 +include $(IDF_PATH)/make/project.mk diff --git a/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/CMakeLists.txt b/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/CMakeLists.txt new file mode 100644 index 0000000000..07fa3e75c9 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "mqtt_tcp_example.cpp" + INCLUDE_DIRS ".") +target_compile_options(${COMPONENT_LIB} PRIVATE "-std=gnu++17") diff --git a/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/Kconfig.projbuild b/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/Kconfig.projbuild new file mode 100644 index 0000000000..34c04a02cb --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/Kconfig.projbuild @@ -0,0 +1,9 @@ +menu "Example Configuration" + + config BROKER_URL + string "Broker URL" + default "mqtt://mqtt.eclipse.org" + help + URL of the broker to connect to + +endmenu diff --git a/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/component.mk b/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/component.mk new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/mqtt_tcp_example.cpp b/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/mqtt_tcp_example.cpp new file mode 100644 index 0000000000..edfb535508 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/tcp/main/mqtt_tcp_example.cpp @@ -0,0 +1,81 @@ +/* C++ MQTT (over TCP) Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include "nvs_flash.h" +#include "protocol_examples_common.h" + +#include "esp_log.h" +#include "esp_mqtt.hpp" +#include "esp_mqtt_client_config.hpp" + +namespace mqtt = idf::mqtt; + +namespace { +constexpr auto *TAG = "MQTT_EXAMPLE"; + +class MyClient final : public mqtt::Client { +public: + using mqtt::Client::Client; + +private: + void on_connected(esp_mqtt_event_handle_t const event) override + { + using mqtt::QoS; + subscribe(messages.get()); + subscribe(sent_load.get(), QoS::AtMostOnce); + } + void on_data(esp_mqtt_event_handle_t const event) override + { + if (messages.match(event->topic, event->topic_len)) { + ESP_LOGI(TAG, "Received in the messages topic"); + } + } + mqtt::Filter messages{"$SYS/broker/messages/received"}; + mqtt::Filter sent_load{"$SYS/broker/load/+/sent"}; +}; +} + +extern "C" void app_main(void) +{ + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + + esp_log_level_set("*", ESP_LOG_INFO); + esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE); + esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE); + esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE); + esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE); + esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE); + esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + + mqtt::BrokerConfiguration broker{ + .address = {mqtt::URI{std::string{CONFIG_BROKER_URL}}}, + .security = mqtt::Insecure{} + }; + mqtt::ClientCredentials credentials{}; + mqtt::Configuration config{}; + + MyClient client{broker, credentials, config}; + + while (true) { + constexpr TickType_t xDelay = 500 / portTICK_PERIOD_MS; + vTaskDelay(xDelay); + } +} diff --git a/examples/cxx/experimental/esp_mqtt_cxx/tcp/sdkconfig.defaults b/examples/cxx/experimental/esp_mqtt_cxx/tcp/sdkconfig.defaults new file mode 100644 index 0000000000..a365ac6589 --- /dev/null +++ b/examples/cxx/experimental/esp_mqtt_cxx/tcp/sdkconfig.defaults @@ -0,0 +1,3 @@ +# Enable C++ exceptions and set emergency pool size for exception objects +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024