| 2 |
raymond |
1 |
#ifndef __MQTT_REMOTE_H__
|
|
|
2 |
#define __MQTT_REMOTE_H__
|
|
|
3 |
|
|
|
4 |
#include "IMQTTRemote.h"
|
|
|
5 |
|
|
|
6 |
#include <freertos/FreeRTOS.h>
|
|
|
7 |
#include <freertos/event_groups.h>
|
|
|
8 |
#include <functional>
|
|
|
9 |
#include <map>
|
|
|
10 |
#include <mqtt_client.h>
|
|
|
11 |
#include <optional>
|
|
|
12 |
#include <string>
|
|
|
13 |
|
|
|
14 |
namespace MQTTRemoteLog {
|
|
|
15 |
const char TAG[] = "MQTTRemote";
|
|
|
16 |
} // namespace MQTTRemoteLog
|
|
|
17 |
|
|
|
18 |
namespace MQTTRemoteDefaults {
|
|
|
19 |
const uint32_t CONNECTION_STATUS_STACK_SIZE = 4096;
|
|
|
20 |
const uint32_t CONNECTION_STATUS_TASK_PRIORITY = 7;
|
|
|
21 |
} // namespace MQTTRemoteDefaults
|
|
|
22 |
|
|
|
23 |
/**
|
|
|
24 |
* @brief MQTT wrapper for setting up MQTT connection (and will) and provide API for sending and subscribing to
|
|
|
25 |
* messages.
|
|
|
26 |
*/
|
|
|
27 |
class MQTTRemote : public IMQTTRemote {
|
|
|
28 |
public:
|
|
|
29 |
enum ConnectionState : uint8_t {
|
|
|
30 |
Connected = BIT0,
|
|
|
31 |
Disconnected = BIT1,
|
|
|
32 |
};
|
|
|
33 |
|
|
|
34 |
/**
|
|
|
35 |
* Additional configuration where most user can go with defaults.
|
|
|
36 |
*/
|
|
|
37 |
struct Configuration {
|
|
|
38 |
// ESP-IDF 4.4 backward compatibility
|
|
|
39 |
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
|
40 |
typedef esp_mqtt_client_config_t::broker_t::verification_t verification_t;
|
|
|
41 |
#else
|
|
|
42 |
// See
|
|
|
43 |
// https://github.com/espressif/esp-mqtt/blob/ae53d799da294f03ef65c33e88fa33648e638134/include/mqtt_client.h#L244
|
|
|
44 |
struct verification_t {
|
|
|
45 |
bool use_global_ca_store = false;
|
|
|
46 |
esp_err_t (*crt_bundle_attach)(void *conf) = nullptr;
|
|
|
47 |
const char *certificate = nullptr;
|
|
|
48 |
size_t certificate_len = 0;
|
|
|
49 |
bool skip_cert_common_name_check = false;
|
|
|
50 |
const struct psk_key_hint *psk_hint_key = nullptr;
|
|
|
51 |
const char **alpn_protos = nullptr;
|
|
|
52 |
};
|
|
|
53 |
#endif
|
|
|
54 |
|
|
|
55 |
/**
|
|
|
56 |
* Maximum message size, in bytes, for incoming messages. Messages larger than this will be truncated.
|
|
|
57 |
* This will be allocated on the heap upon MQTTRemote object creation.
|
|
|
58 |
*/
|
|
|
59 |
uint32_t rx_buffer_size = 1024;
|
|
|
60 |
|
|
|
61 |
/**
|
|
|
62 |
* Maximum message size, in bytes, for outgoing messages. Messages larger than this will be truncated.
|
|
|
63 |
* This will be allocated on the heap upon MQTTRemote object creation.
|
|
|
64 |
*/
|
|
|
65 |
uint32_t tx_buffer_size = 1024;
|
|
|
66 |
|
|
|
67 |
/**
|
|
|
68 |
* Task size to use for the ESP-IDF MQTT task. If you are handling large and/or many messages (queues), you might
|
|
|
69 |
* need to increase this.
|
|
|
70 |
* Normally defaults to 6144 bytes, setting this to non std::opt will override setting from menuconfig.
|
|
|
71 |
*/
|
|
|
72 |
std::optional<uint32_t> task_size = 4096;
|
|
|
73 |
|
|
|
74 |
/**
|
|
|
75 |
* MQTT keep alive interval, in seconds. If the client fails to communicate with the broker within the specified
|
|
|
76 |
* Keep Alive period, the LWT/Last Will message is sent (by the broker).
|
|
|
77 |
*/
|
|
|
78 |
uint32_t keep_alive_s = 10;
|
|
|
79 |
|
|
|
80 |
/**
|
|
|
81 |
* Values, see esp_mqtt_transport_t:
|
|
|
82 |
* - MQTT_TRANSPORT_OVER_TCP = mqtt
|
|
|
83 |
* - MQTT_TRANSPORT_OVER_SSL = mqtts (TLS)
|
|
|
84 |
* - MQTT_TRANSPORT_OVER_WS = ws (websockets)
|
|
|
85 |
* - MQTT_TRANSPORT_OVER_WSS = wss (websockets with TLS)
|
|
|
86 |
*
|
|
|
87 |
* If not set, will try to decude the transport to use given the host, based on schemes `mqtt`, `mqtts`, `ws`,
|
|
|
88 |
* `wss`. If no scheme is found, will default to `mqtt`.
|
|
|
89 |
*/
|
|
|
90 |
std::optional<esp_mqtt_transport_t> transport = std::nullopt;
|
|
|
91 |
|
|
|
92 |
/**
|
|
|
93 |
* If using TLS, this configures the certificate verification. Must be set if using TLS.
|
|
|
94 |
*
|
|
|
95 |
* With this, you can configure to use your own private certificate, or using the global bundle.
|
|
|
96 |
*
|
|
|
97 |
* Example to use the global bundle:
|
|
|
98 |
* ```cpp
|
|
|
99 |
* #include <esp_rls.h>
|
|
|
100 |
*
|
|
|
101 |
* .vertification = {
|
|
|
102 |
* .use_global_ca_store = true,
|
|
|
103 |
* },
|
|
|
104 |
* ```
|
|
|
105 |
*
|
|
|
106 |
* Then before calling start():
|
|
|
107 |
*
|
|
|
108 |
* ```cpp
|
|
|
109 |
* mbedtls_ssl_config conf;
|
|
|
110 |
* mbedtls_ssl_config_init(&conf);
|
|
|
111 |
* esp_tls_init_global_ca_store();
|
|
|
112 |
* ```
|
|
|
113 |
* Here you have the option to use other TLS implementation like mbedtls or wolfssl.
|
|
|
114 |
*
|
|
|
115 |
*
|
|
|
116 |
* Example how to use letsencrypt (or similary for your own certificates):
|
|
|
117 |
* 1. Download the ISRG Root X1 PEM file from https://letsencrypt.org/certs/isrgrootx1.pem
|
|
|
118 |
* 2. Add in main directory
|
|
|
119 |
* 3. Add `target_add_binary_data(${COMPONENT_TARGET} "isrgrootx1.pem" TEXT)` to your CMakeLists.txt
|
|
|
120 |
*
|
|
|
121 |
* Setup verification as follows:
|
|
|
122 |
* ```cpp
|
|
|
123 |
* extern const uint8_t isrgrootx1_pem_start[] asm("_binary_isrgrootx1_pem_start");
|
|
|
124 |
*
|
|
|
125 |
* .vertification = {
|
|
|
126 |
* .certificate = (const char *)isrgrootx1_pem_start,
|
|
|
127 |
* },
|
|
|
128 |
* ```
|
|
|
129 |
*
|
|
|
130 |
* If using your own certificate, you might need to set `skip_cert_common_name_check` to true in the verification.
|
|
|
131 |
*/
|
|
|
132 |
verification_t verification = {};
|
|
|
133 |
};
|
|
|
134 |
|
|
|
135 |
/**
|
|
|
136 |
* @brief Construct a new MQTTRemote object
|
|
|
137 |
*
|
|
|
138 |
* To set log level for this object, use: esp_log_level_set(MQTTRemoteLog::TAG, ESP_LOG_*);
|
|
|
139 |
*
|
|
|
140 |
* A call to start() most follow.
|
|
|
141 |
*
|
|
|
142 |
* @param client_id Base ID for this device. This is used for the last will / status
|
|
|
143 |
* topic. Example, if this is "esp_now_router", then the status/last will topic will be "esp_now_router/status". This
|
|
|
144 |
* is also used as client ID for the MQTT connection. This has to be [a-zA-Z0-9_] only and unique among all MQTT
|
|
|
145 |
* clients on the server. It should also be stable across connections.
|
|
|
146 |
* @param host MQTT hostname or IP for MQTT server. Supports `mqtt`, `mqtts`, `ws`, `wss` schemes. For example,
|
|
|
147 |
* mqtts://hostname or just hostname/IP. If not using schemes, transport must be set in Configuration. Remember to
|
|
|
148 |
* also specify the correct port to use for your schema.
|
|
|
149 |
* @param port MQTT port number.
|
|
|
150 |
* @param username MQTT username.
|
|
|
151 |
* @param password MQTT password.
|
|
|
152 |
*/
|
|
|
153 |
MQTTRemote(std::string client_id, std::string host, int port, std::string username, std::string password)
|
|
|
154 |
: MQTTRemote(std::move(client_id), std::move(host), port, std::move(username), std::move(password),
|
|
|
155 |
Configuration{}) {}
|
|
|
156 |
|
|
|
157 |
/**
|
|
|
158 |
* @brief Construct a new MQTTRemote object
|
|
|
159 |
*
|
|
|
160 |
* To set log level for this object, use: esp_log_level_set(MQTTRemoteLog::TAG, ESP_LOG_*);
|
|
|
161 |
*
|
|
|
162 |
* A call to start() most follow.
|
|
|
163 |
*
|
|
|
164 |
* @param client_id Base ID for this device. This is used for the last will / status
|
|
|
165 |
* topic. Example, if this is "esp_now_router", then the status/last will topic will be "esp_now_router/status". This
|
|
|
166 |
* is also used as client ID for the MQTT connection. This has to be [a-zA-Z0-9_] only and unique among all MQTT
|
|
|
167 |
* clients on the server. It should also be stable across connections.
|
|
|
168 |
* @param host MQTT hostname or IP for MQTT server.
|
|
|
169 |
* @param port MQTT port number.
|
|
|
170 |
* @param username MQTT username.
|
|
|
171 |
* @param password MQTT password.
|
|
|
172 |
* @param configuration Additional configuration where most user can go with defaults.
|
|
|
173 |
*/
|
|
|
174 |
MQTTRemote(std::string client_id, std::string host, int port, std::string username, std::string password,
|
|
|
175 |
Configuration configuration);
|
|
|
176 |
|
|
|
177 |
/**
|
|
|
178 |
* @brief Call once there is a WIFI connection on which the host can be reached.
|
|
|
179 |
* Will connect to the server and setup any subscriptions as well as start the MQTT loop.
|
|
|
180 |
* @param on_connection_change optional callback on connection state change. Will be called when the client is
|
|
|
181 |
* connected to server (every time, so expect calls on reconnection), and on disconnect. The parameter will be true on
|
|
|
182 |
* new connection and false on disconnection. This callback will run from a dedicated task. Task size and priority can
|
|
|
183 |
* be set.
|
|
|
184 |
* @param task_size the stack size for the task that will call the on_connection_change callback, if set.
|
|
|
185 |
* @param task_priority the priority for the task that will call the on_connection_change callback, if set.
|
|
|
186 |
*
|
|
|
187 |
* NOTE: Can only be called once WIFI has been setup! ESP-IDF will assert otherwise.
|
|
|
188 |
*/
|
|
|
189 |
void start(std::function<void(bool)> on_connection_change = {},
|
|
|
190 |
unsigned long task_size = MQTTRemoteDefaults::CONNECTION_STATUS_STACK_SIZE,
|
|
|
191 |
uint8_t task_priority = MQTTRemoteDefaults::CONNECTION_STATUS_TASK_PRIORITY);
|
|
|
192 |
|
|
|
193 |
/**
|
|
|
194 |
* @brief Call once there is a WIFI connection on which the host can be reached.
|
|
|
195 |
* Will connect to the server and setup any subscriptions as well as start the MQTT loop.
|
|
|
196 |
* @param connection_state_changed_event_group optional event group for connection state change. Will be be set with
|
|
|
197 |
* ConnectionState::Connected when the client is connected to server (every time, so expect re-setting on
|
|
|
198 |
* reconnection), and ConnectionState::Disconnected on disconnect.
|
|
|
199 |
*
|
|
|
200 |
* NOTE: Can only be called once WIFI has been setup! ESP-IDF will assert otherwise.
|
|
|
201 |
*/
|
|
|
202 |
void start(EventGroupHandle_t connection_state_changed_event_group);
|
|
|
203 |
|
|
|
204 |
/**
|
|
|
205 |
* @brief Publish a message.
|
|
|
206 |
*
|
|
|
207 |
* @param topic the topic to publish to.
|
|
|
208 |
* @param message The message to send. This cannot be larger than the value set for max_message_size in the
|
|
|
209 |
* constructor.
|
|
|
210 |
* @param retain True to set this message as retained.
|
|
|
211 |
* @param qos quality of service for published message (0 (default), 1 or 2)
|
|
|
212 |
* @returns true on success, or false on failure.
|
|
|
213 |
*/
|
|
|
214 |
bool publishMessage(std::string topic, std::string message, bool retain = false, uint8_t qos = 0) override;
|
|
|
215 |
|
|
|
216 |
/**
|
|
|
217 |
* Same as publishMessage(), but will print the message and topic and the result on serial.
|
|
|
218 |
*/
|
|
|
219 |
bool publishMessageVerbose(std::string topic, std::string message, bool retain = false, uint8_t qos = 0) override;
|
|
|
220 |
|
|
|
221 |
/**
|
|
|
222 |
* @brief returns if there is a connection to the MQTT server.
|
|
|
223 |
*/
|
|
|
224 |
bool connected() override { return _connected; }
|
|
|
225 |
|
|
|
226 |
/**
|
|
|
227 |
* @brief Subscribe to a topic. The callback will be invoked on every new message.
|
|
|
228 |
* There can only be one callback per topic. If trying to subscribe to an already subscribe topic, it will be ignored.
|
|
|
229 |
* Don't do heavy operations in the callback or delays as this will block the MQTT callback.
|
|
|
230 |
*
|
|
|
231 |
* Can be called before being connected. All subscriptions will be (re-)subscribed to once a connection is
|
|
|
232 |
* (re-)established.
|
|
|
233 |
*
|
|
|
234 |
* @param message_callback a message callback with the topic and the message. The topic is repeated for convenience,
|
|
|
235 |
* but it will always be for the subscribed topic.
|
|
|
236 |
* @return true if a subcription was successful. Will return false if there is no active MQTT connection. In this
|
|
|
237 |
* case, the subscription will be performed once connected. Will return false if this subscription is already
|
|
|
238 |
* subscribed to.
|
|
|
239 |
*/
|
|
|
240 |
bool subscribe(std::string topic, IMQTTRemote::SubscriptionCallback message_callback) override;
|
|
|
241 |
|
|
|
242 |
/**
|
|
|
243 |
* @brief Unsubscribe a topic.
|
|
|
244 |
*/
|
|
|
245 |
bool unsubscribe(std::string topic) override;
|
|
|
246 |
|
|
|
247 |
/**
|
|
|
248 |
* @brief The client ID for this device. This is used for the last will / status
|
|
|
249 |
* topic.Example, if this is "esp_now_router", then the status/last will topic will be "esp_now_router/status". This
|
|
|
250 |
* has to be [a-zA-Z0-9_] only.
|
|
|
251 |
*/
|
|
|
252 |
std::string &clientId() override { return _client_id; }
|
|
|
253 |
|
|
|
254 |
private:
|
|
|
255 |
void startInternal();
|
|
|
256 |
|
|
|
257 |
/*
|
|
|
258 |
* @brief Event handler registered to receive MQTT events
|
|
|
259 |
*
|
|
|
260 |
* This function is called by the MQTT client event loop.
|
|
|
261 |
*
|
|
|
262 |
* @param handler_args user data registered to the event.
|
|
|
263 |
* @param base Event base for the handler(always MQTT Base in this example).
|
|
|
264 |
* @param event_id The id for the received event.
|
|
|
265 |
* @param event_data The data for the event, esp_mqtt_event_handle_t.
|
|
|
266 |
*/
|
|
|
267 |
static void onMqttEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data);
|
|
|
268 |
|
|
|
269 |
static void runTask(void *pvParams);
|
|
|
270 |
|
|
|
271 |
private:
|
|
|
272 |
bool _started = false;
|
|
|
273 |
std::string _client_id;
|
|
|
274 |
bool _connected = false;
|
|
|
275 |
std::string _last_will_topic;
|
|
|
276 |
esp_mqtt_client_handle_t _mqtt_client;
|
|
|
277 |
std::function<void(bool)> _on_connection_change;
|
|
|
278 |
EventGroupHandle_t _connection_state_changed_event_group;
|
|
|
279 |
std::map<std::string, SubscriptionCallback> _subscriptions;
|
|
|
280 |
};
|
|
|
281 |
|
|
|
282 |
#endif // __MQTT_REMOTE_H__
|