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.
This commit is contained in:
Euripedes Rocha Filho 2020-12-15 13:10:10 +00:00 committed by Euripedes Rocha
parent be0c42cf9f
commit d53f0e074c
21 changed files with 1084 additions and 0 deletions

View File

@ -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()

View File

@ -0,0 +1,5 @@
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := ./
CXXFLAGS += -std=gnu++17

View File

@ -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 <string>
#include <algorithm>
#include "mqtt_client.h"
#include "esp_log.h"
#include "esp_mqtt.hpp"
#include "esp_mqtt_client_config.hpp"
namespace {
// Helper for static assert.
template<class T>
constexpr bool always_false = false;
template<class... Ts> struct overloaded : Ts... {
using Ts::operator()...;
};
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
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<decltype(unknown)>, "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<decltype(unknown)>, "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<decltype(unknown)>, "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<esp_mqtt_event_t *>(event_data);
auto &client = *static_cast<Client *>(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<MessageID> Client::subscribe(std::string const &topic, QoS qos)
{
auto res = esp_mqtt_client_subscribe(handler.get(), topic.c_str(),
static_cast<int>(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<std::string::const_iterator>(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);
}
}

View File

@ -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 <string_view>
#ifndef __cpp_exceptions
#error MQTT class can only be used when __cpp_exceptions is enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig
#endif
#include <optional>
#include <variant>
#include <utility>
#include <memory>
#include <string>
#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 <typename T> 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<std::string>;
[[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<MessageID> 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 <class Container> std::optional<MessageID> publish(const std::string &topic, const Message<Container>& 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 <class InputIt>
std::optional<MessageID> 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<int>(qos),
static_cast<int>(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<esp_mqtt_client, MqttClientDeleter>;
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

View File

@ -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 <cstddef>
#include <optional>
#include <string>
#include <variant>
#include <vector>
#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<Host, URI> 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<PEM, DER>;
/**
* @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<Insecure, GlobalCAStore, CryptographicInformation, PSK>;
/**
* @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<Password> 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<Password, ClientCertificate, SecureElement>;
struct BrokerConfiguration {
BrokerAddress address;
BrokerAuthentication security;
};
struct ClientCredentials {
std::optional<std::string> username; // MQTT username
AuthenticationFactor authentication;
std::vector<std::string> 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<std::string > 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

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "mqtt_ssl_example.cpp"
INCLUDE_DIRS ".")
target_compile_options(${COMPONENT_LIB} PRIVATE "-std=gnu++17")

View File

@ -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

View File

@ -0,0 +1 @@
COMPONENT_EMBED_TXTFILES := mqtt_eclipseprojects_io.pem

View File

@ -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-----

View File

@ -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-----

View File

@ -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 <cstdint>
#include <string>
#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 );
}
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "mqtt_tcp_example.cpp"
INCLUDE_DIRS ".")
target_compile_options(${COMPONENT_LIB} PRIVATE "-std=gnu++17")

View File

@ -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

View File

@ -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);
}
}

View File

@ -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