/* * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #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. * * @note * When subscribing to a topic the QoS means the maximum QoS that should be sent to * client on this topic */ 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. * * Topic filter. * */ class Filter { public: /** * @brief Constructs the topic filter from the user filter * @throws std::domain_error if the filter is invalid. * * @param user_filter Filter to be used. */ explicit Filter(std::string user_filter); /** * @brief Get the filter string used. * * @return Reference to the topic filter. */ const std::string &get(); /** * @brief Checks the filter string against a topic name. * * @param topic_begin Iterator to the beginning of the sequence. * @param topic_end Iterator to the end of the sequence. * * @return true if the topic name match the filter */ [[nodiscard]] bool match(std::string::const_iterator topic_begin, std::string::const_iterator topic_end) 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 begin Char array with topic name. * @param size Size of given topic name. * * @return true if the topic name match the filter */ [[nodiscard]] bool match(char *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: /** * @brief Constructor of the client * * @param broker Configuration for broker connection * @param credentials client credentials to be presented to the broker * @param config Mqtt client configuration */ Client(const BrokerConfiguration &broker, const ClientCredentials &credentials, const Configuration &config); /** * @brief Constructs Client using the same configuration used for * `esp_mqtt_client` * @param config config struct to `esp_mqtt_client` */ Client(const esp_mqtt_client_config_t &config); /** * @brief Subscribe to topic * * @param topic_filter MQTT topic 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 &topic_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 qos Set qos message * @param retain Set if message should be retained * * @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: /** * @brief Helper type to be used as custom deleter for std::unique_ptr. */ struct MqttClientDeleter { void operator()(esp_mqtt_client *client_handler) { esp_mqtt_client_destroy(client_handler); } }; /** * @brief Type of the handler for the underlying mqtt_client handler. * It uses std::unique_ptr for lifetime management */ using ClientHandler = std::unique_ptr; /** * @brief esp_mqtt_client handler * */ ClientHandler handler; /** * @brief Called if there is an error event * * @param event mqtt event data */ virtual void on_error(const esp_mqtt_event_handle_t event); /** * @brief Called if there is an 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 * * @param event mqtt event data */ virtual void on_subscribed(const esp_mqtt_event_handle_t event); /** * @brief Called if there is an unsubscribed event * * @param event mqtt event data */ virtual void on_unsubscribed(const esp_mqtt_event_handle_t event); /** * @brief Called if there is an 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 * * @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 * * @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 * * @param event mqtt event data * */ virtual void on_data(const esp_mqtt_event_handle_t event) = 0; 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); }; } // namespace idf::mqtt