The Cayenne MQTT mbed Library provides functions to easily connect to the Cayenne IoT project builder.
Dependents: Cayenne-ESP8266Interface Cayenne-WIZnet_Library Cayenne-WIZnetInterface Cayenne-X-NUCLEO-IDW01M1 ... more
CayenneMQTTClient.h
00001 /* 00002 The MIT License(MIT) 00003 00004 Cayenne MQTT Client Library 00005 Copyright (c) 2016 myDevices 00006 00007 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 00008 documentation files(the "Software"), to deal in the Software without restriction, including without limitation 00009 the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, 00010 and to permit persons to whom the Software is furnished to do so, subject to the following conditions : 00011 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 00012 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 00013 WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR 00014 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 00015 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 00016 */ 00017 00018 #ifndef _CAYENNEMQTTCLIENT_h 00019 #define _CAYENNEMQTTCLIENT_h 00020 00021 #include <string.h> 00022 #include "MQTTClient.h" 00023 #include "../CayenneUtils/CayenneDefines.h" 00024 #include "../CayenneUtils/CayenneUtils.h" 00025 00026 namespace CayenneMQTT 00027 { 00028 /** 00029 * Cayenne message data passed to message handler functions. 00030 */ 00031 typedef struct MessageData 00032 { 00033 const char* clientID; /**< The client ID of the message. */ 00034 CayenneTopic topic; /**< The topic the message was received on. */ 00035 unsigned int channel; /**< The channel the message was received on. */ 00036 const char* id; /**< The message ID, if it is a command message, otherwise NULL. */ 00037 const char* type; /**< The type of data in the message, if it exists, otherwise NULL. */ 00038 const char* unit; /**< The unit of data in the message, if it exists, otherwise NULL. */ 00039 const char* value; /**< The value of data in the message, if it exists, otherwise NULL. */ 00040 00041 /** 00042 * Get message value. 00043 * @return Message value, can be NULL. 00044 */ 00045 const char* getValue() const { return value; } 00046 00047 /** 00048 * Get message unit. 00049 * @return Message unit, can be NULL. 00050 */ 00051 const char* getUnit() const { return unit; } 00052 } MessageData; 00053 00054 /** 00055 * Client class for connecting to Cayenne via MQTT. 00056 * @class MQTTClient 00057 * @param Network A network class with the methods: read, write. See NetworkInterface.h for function definitions. 00058 * @param Timer A timer class with the methods: countdown_ms, countdown, left_ms, expired. See TimerInterface.h for function definitions. 00059 * @param MAX_MQTT_PACKET_SIZE Maximum size of an MQTT message, in bytes. 00060 * @param MAX_MESSAGE_HANDLERS Maximum number of message handlers. 00061 */ 00062 template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE = CAYENNE_MAX_MESSAGE_SIZE, int MAX_MESSAGE_HANDLERS = 5> 00063 class MQTTClient : private MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, 1> 00064 { 00065 public: 00066 typedef MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, 1> Base ; 00067 typedef void(*CayenneMessageHandler)(MessageData&); 00068 00069 /** 00070 * Create an Cayenne MQTT client object. 00071 * @param[in] network Pointer to an instance of the Network class. Must be connected to the endpoint before calling MQTTClient connect. 00072 * @param[in] username Cayenne username 00073 * @param[in] password Cayenne password 00074 * @param[in] clientID Cayennne client ID 00075 * @param[in] command_timeout_ms Timeout for commands in milliseconds. 00076 */ 00077 MQTTClient(Network& network, const char* username = NULL, const char* password = NULL, const char* clientID = NULL, unsigned int command_timeout_ms = 30000) : 00078 Base (network, command_timeout_ms), _username(username), _password(password), _clientID(clientID) 00079 { 00080 Base::setDefaultMessageHandler(this, &MQTTClient::mqttMessageArrived); 00081 }; 00082 00083 /** 00084 * Initialize connection credentials. 00085 * @param[in] username Cayenne username 00086 * @param[in] password Cayenne password 00087 * @param[in] clientID Cayennne client ID 00088 */ 00089 void init(const char* username, const char* password, const char* clientID) 00090 { 00091 _username = username; 00092 _password = password; 00093 _clientID = clientID; 00094 }; 00095 00096 /** 00097 * Set default handler function called when a message is received. 00098 * @param[in] handler Function called when message is received, if no other handlers exist for the topic. 00099 */ 00100 void setDefaultMessageHandler(CayenneMessageHandler handler) 00101 { 00102 _defaultMessageHandler.attach(handler); 00103 }; 00104 00105 /** 00106 * Set default handler function called when a message is received. 00107 * @param item Address of initialized object 00108 * @param handler Function called when message is received, if no other handlers exist for the topic. 00109 */ 00110 template<class T> 00111 void setDefaultMessageHandler(T *item, void (T::*handler)(MessageData&)) 00112 { 00113 _defaultMessageHandler.attach(item, handler); 00114 } 00115 00116 /** 00117 * Connect to the Cayenne server. Connection credentials must be initialized before calling this function. 00118 * @param[in] username Cayenne username 00119 * @param[in] password Cayenne password 00120 * @param[in] clientID Cayennne client ID 00121 * @return success code 00122 */ 00123 int connect() { 00124 MQTTPacket_connectData data = MQTTPacket_connectData_initializer; 00125 data.MQTTVersion = 3; 00126 data.username.cstring = const_cast<char*>(_username); 00127 data.password.cstring = const_cast<char*>(_password); 00128 data.clientID.cstring = const_cast<char*>(_clientID); 00129 return Base::connect(data); 00130 }; 00131 00132 /** 00133 * Yield to allow MQTT message processing. 00134 * @param[in] timeout_ms The time in milliseconds to yield for 00135 * @return success code 00136 */ 00137 int yield(unsigned long timeout_ms = 1000L) { 00138 return Base::yield(timeout_ms); 00139 }; 00140 00141 /** 00142 * Check if the client is connected to the Cayenne server. 00143 * @return true if connected, false if not connected 00144 */ 00145 bool connected() { 00146 return Base::isConnected(); 00147 }; 00148 00149 /** 00150 * Disconnect from the Cayenne server. 00151 * @return success code 00152 */ 00153 int disconnect() { 00154 return Base::disconnect(); 00155 }; 00156 00157 /** 00158 * Send data to Cayenne. 00159 * @param[in] topic Cayenne topic 00160 * @param[in] channel The channel to send data to, or CAYENNE_NO_CHANNEL if there is none 00161 * @param[in] type Type to use for a type=value pair, can be NULL if sending to a topic that doesn't require type 00162 * @param[in] unit Optional unit to use for a type,unit=value payload, can be NULL 00163 * @param[in] value Data value 00164 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00165 * @return success code 00166 */ 00167 int publishData(CayenneTopic topic, unsigned int channel, const char* type, const char* unit, int value, const char* clientID = NULL) { 00168 char str[2 + 8 * sizeof(value)]; 00169 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00170 itoa(value, str, 10); 00171 #else 00172 snprintf(str, sizeof(str), "%d", value); 00173 #endif 00174 return publishData(topic, channel, type, unit, str, clientID); 00175 }; 00176 00177 /** 00178 * Send data to Cayenne. 00179 * @param[in] topic Cayenne topic 00180 * @param[in] channel The channel to send data to, or CAYENNE_NO_CHANNEL if there is none 00181 * @param[in] type Type to use for a type=value pair, can be NULL if sending to a topic that doesn't require type 00182 * @param[in] unit Optional unit to use for a type,unit=value payload, can be NULL 00183 * @param[in] value Data value 00184 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00185 * @return success code 00186 */ 00187 int publishData(CayenneTopic topic, unsigned int channel, const char* type, const char* unit, unsigned int value, const char* clientID = NULL) { 00188 char str[1 + 8 * sizeof(value)]; 00189 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00190 utoa(value, str, 10); 00191 #else 00192 snprintf(str, sizeof(str), "%u", value); 00193 #endif 00194 return publishData(topic, channel, type, unit, str, clientID); 00195 }; 00196 00197 /** 00198 * Send data to Cayenne. 00199 * @param[in] topic Cayenne topic 00200 * @param[in] channel The channel to send data to, or CAYENNE_NO_CHANNEL if there is none 00201 * @param[in] type Type to use for a type=value pair, can be NULL if sending to a topic that doesn't require type 00202 * @param[in] unit Optional unit to use for a type,unit=value payload, can be NULL 00203 * @param[in] value Data value 00204 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00205 * @return success code 00206 */ 00207 int publishData(CayenneTopic topic, unsigned int channel, const char* type, const char* unit, long value, const char* clientID = NULL) { 00208 char str[2 + 8 * sizeof(value)]; 00209 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00210 ltoa(value, str, 10); 00211 #else 00212 snprintf(str, sizeof(str), "%ld", value); 00213 #endif 00214 return publishData(topic, channel, type, unit, str, clientID); 00215 }; 00216 00217 /** 00218 * Send data to Cayenne. 00219 * @param[in] topic Cayenne topic 00220 * @param[in] channel The channel to send data to, or CAYENNE_NO_CHANNEL if there is none 00221 * @param[in] type Type to use for a type=value pair, can be NULL if sending to a topic that doesn't require type 00222 * @param[in] unit Optional unit to use for a type,unit=value payload, can be NULL 00223 * @param[in] value Data value 00224 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00225 * @return success code 00226 */ 00227 int publishData(CayenneTopic topic, unsigned int channel, const char* type, const char* unit, unsigned long value, const char* clientID = NULL) { 00228 char str[1 + 8 * sizeof(value)]; 00229 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00230 ultoa(value, str, 10); 00231 #else 00232 snprintf(str, sizeof(str), "%lu", value); 00233 #endif 00234 return publishData(topic, channel, type, unit, str, clientID); 00235 }; 00236 00237 /** 00238 * Send data to Cayenne. 00239 * @param[in] topic Cayenne topic 00240 * @param[in] channel The channel to send data to, or CAYENNE_NO_CHANNEL if there is none 00241 * @param[in] type Type to use for a type=value pair, can be NULL if sending to a topic that doesn't require type 00242 * @param[in] unit Optional unit to use for a type,unit=value payload, can be NULL 00243 * @param[in] value Data value 00244 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00245 * @return success code 00246 */ 00247 int publishData(CayenneTopic topic, unsigned int channel, const char* type, const char* unit, double value, const char* clientID = NULL) { 00248 char str[33]; 00249 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00250 dtostrf(value, 5, 3, str); 00251 #else 00252 snprintf(str, 33, "%2.3f", value); 00253 #endif 00254 return publishData(topic, channel, type, unit, str, clientID); 00255 }; 00256 00257 /** 00258 * Send data to Cayenne. 00259 * @param[in] topic Cayenne topic 00260 * @param[in] channel The channel to send data to, or CAYENNE_NO_CHANNEL if there is none 00261 * @param[in] type Type to use for a type=value pair, can be NULL if sending to a topic that doesn't require type 00262 * @param[in] unit Optional unit to use for a type,unit=value payload, can be NULL 00263 * @param[in] value Data value 00264 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00265 * @return success code 00266 */ 00267 int publishData(CayenneTopic topic, unsigned int channel, const char* type, const char* unit, float value, const char* clientID = NULL) { 00268 char str[33]; 00269 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00270 dtostrf(value, 5, 3, str); 00271 #else 00272 snprintf(str, 33, "%2.3f", value); 00273 #endif 00274 return publishData(topic, channel, type, unit, str, clientID); 00275 }; 00276 00277 /** 00278 * Send data to Cayenne. 00279 * @param[in] topic Cayenne topic 00280 * @param[in] channel The channel to send data to, or CAYENNE_NO_CHANNEL if there is none 00281 * @param[in] type Type to use for a type=value pair, can be NULL if sending to a topic that doesn't require type 00282 * @param[in] unit Optional unit to use for a type,unit=value payload, can be NULL 00283 * @param[in] value Data value 00284 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00285 * @return success code 00286 */ 00287 int publishData(CayenneTopic topic, unsigned int channel, const char* type, const char* unit, const char* value, const char* clientID = NULL) { 00288 char buffer[MAX_MQTT_PACKET_SIZE + 1] = { 0 }; 00289 int result = CayenneBuildTopic(buffer, sizeof(buffer), _username, clientID ? clientID : _clientID, topic, channel); 00290 if (result == CAYENNE_SUCCESS) { 00291 size_t size = strlen(buffer); 00292 char* payload = &buffer[size + 1]; 00293 size = sizeof(buffer) - (size + 1); 00294 result = CayenneBuildDataPayload(payload, &size, type, unit, value); 00295 if (result == CAYENNE_SUCCESS) { 00296 result = Base::publish(buffer, payload, size, MQTT::QOS0, (topic != COMMAND_TOPIC) ? true : false); 00297 } 00298 } 00299 return result; 00300 }; 00301 00302 /** 00303 * Send a response to a channel. 00304 * @param[in] id ID of message the response is for 00305 * @param[in] error Optional error message, NULL for success 00306 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00307 * @return success code 00308 */ 00309 int publishResponse(const char* id, const char* error, const char* clientID = NULL) { 00310 char buffer[MAX_MQTT_PACKET_SIZE + 1] = { 0 }; 00311 int result = CayenneBuildTopic(buffer, sizeof(buffer), _username, clientID ? clientID : _clientID, RESPONSE_TOPIC, CAYENNE_NO_CHANNEL); 00312 if (result == CAYENNE_SUCCESS) { 00313 size_t size = strlen(buffer); 00314 char* payload = &buffer[size + 1]; 00315 size = sizeof(buffer) - (size + 1); 00316 result = CayenneBuildResponsePayload(payload, &size, id, error); 00317 if (result == CAYENNE_SUCCESS) { 00318 result = Base::publish(buffer, payload, size, MQTT::QOS1, true); 00319 } 00320 } 00321 return result; 00322 } 00323 00324 /** 00325 * Subscribe to a topic. 00326 * @param[in] topic Cayenne topic 00327 * @param[in] channel The topic channel, CAYENNE_NO_CHANNEL for none, CAYENNE_ALL_CHANNELS for all 00328 * @param[in] handler The message handler, NULL to use default handler 00329 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with. This string is not copied, so it must remain available for the life of the subscription. 00330 * @return success code 00331 */ 00332 int subscribe(CayenneTopic topic, unsigned int channel, CayenneMessageHandler handler = NULL, const char* clientID = NULL) { 00333 char topicName[MAX_MQTT_PACKET_SIZE] = { 0 }; 00334 int result = CayenneBuildTopic(topicName, sizeof(topicName), _username, clientID ? clientID : _clientID, topic, channel); 00335 if (result == CAYENNE_SUCCESS) { 00336 result = Base::subscribe(topicName, MQTT::QOS0, NULL); 00337 if (handler && result == MQTT::QOS0) { 00338 for (int i = 0; i < MAX_MESSAGE_HANDLERS; ++i) { 00339 if (!_messageHandlers[i].fp.attached()) { 00340 _messageHandlers[i].clientID = clientID ? clientID : _clientID; 00341 _messageHandlers[i].topic = topic; 00342 _messageHandlers[i].channel = channel; 00343 _messageHandlers[i].fp.attach(handler); 00344 break; 00345 } 00346 } 00347 } 00348 } 00349 return result; 00350 }; 00351 00352 /** 00353 * Unsubscribe from a topic. 00354 * @param[in] topic Cayenne topic 00355 * @param[in] channel The topic channel, CAYENNE_NO_CHANNEL for none, CAYENNE_ALL_CHANNELS for all 00356 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00357 * @return success code 00358 */ 00359 int unsubscribe(CayenneTopic topic, unsigned int channel, const char* clientID = NULL) { 00360 char topicName[MAX_MQTT_PACKET_SIZE] = { 0 }; 00361 int result = CayenneBuildTopic(topicName, sizeof(topicName), _username, clientID ? clientID : _clientID, topic, channel); 00362 if (result == CAYENNE_SUCCESS) { 00363 result = Base::unsubscribe(topicName); 00364 if (result == MQTT::SUCCESS) { 00365 for (int i = 0; i < MAX_MESSAGE_HANDLERS; ++i) { 00366 if ((_messageHandlers[i].topic == topic && _messageHandlers[i].channel == channel) && 00367 (strcmp(clientID ? clientID : _clientID, _messageHandlers[i].clientID) == 0)) { 00368 _messageHandlers[i].clientID = NULL; 00369 _messageHandlers[i].topic = UNDEFINED_TOPIC; 00370 _messageHandlers[i].channel = CAYENNE_NO_CHANNEL; 00371 _messageHandlers[i].fp.detach(); 00372 } 00373 } 00374 } 00375 } 00376 return result; 00377 } 00378 00379 /** 00380 * Handler for incoming MQTT::Client messages. 00381 * @param[in] md Message data 00382 */ 00383 void mqttMessageArrived(MQTT::MessageData& md) 00384 { 00385 int result = MQTT::FAILURE; 00386 MessageData message; 00387 00388 result = CayenneParseTopic(&message.topic, &message.channel, &message.clientID, _username, md.topicName.lenstring.data, md.topicName.lenstring.len); 00389 if (result != CAYENNE_SUCCESS) 00390 return; 00391 //Null terminate the string since that is required by CayenneParsePayload. The readbuf is set to CAYENNE_MAX_MESSAGE_SIZE+1 to allow for appending a null. 00392 (static_cast<char*>(md.message.payload))[md.message.payloadlen] = '\0'; 00393 result = CayenneParsePayload(&message.type, &message.unit, &message.value, &message.id, message.topic, static_cast<char*>(md.message.payload)); 00394 if (result != CAYENNE_SUCCESS) 00395 return; 00396 00397 result = MQTT::FAILURE; 00398 for (int i = 0; i < MAX_MESSAGE_HANDLERS; ++i) { 00399 if (_messageHandlers[i].fp.attached() && _messageHandlers[i].topic == message.topic && 00400 (_messageHandlers[i].channel == message.channel || _messageHandlers[i].channel == CAYENNE_ALL_CHANNELS) && 00401 (strcmp(_messageHandlers[i].clientID, message.clientID) == 0)) 00402 { 00403 _messageHandlers[i].fp(message); 00404 result = MQTT::SUCCESS; 00405 } 00406 } 00407 00408 if (result == MQTT::FAILURE && _defaultMessageHandler.attached()) { 00409 _defaultMessageHandler(message); 00410 } 00411 } 00412 00413 private: 00414 const char* _username; 00415 const char* _password; 00416 const char* _clientID; 00417 struct CayenneMessageHandlers 00418 { 00419 const char* clientID; 00420 CayenneTopic topic; 00421 unsigned int channel; 00422 FP<void, MessageData&> fp; 00423 } _messageHandlers[MAX_MESSAGE_HANDLERS]; /* Message handlers are indexed by subscription topic */ 00424 FP<void, MessageData&> _defaultMessageHandler; 00425 }; 00426 00427 } 00428 00429 #endif
Generated on Wed Jul 13 2022 15:49:28 by 1.7.2