Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: 5_Dragonfly_Cayenne_Sprint_IKS01A1
Fork of Cayenne-MQTT-mbed-MTSAS by
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 #include "../CayenneUtils/CayenneDataArray.h" 00026 00027 namespace CayenneMQTT 00028 { 00029 /** 00030 * Cayenne message data passed to message handler functions. 00031 */ 00032 typedef struct MessageData 00033 { 00034 const char* clientID; /**< The client ID of the message. */ 00035 CayenneTopic topic; /**< The topic the message was received on. */ 00036 unsigned int channel; /**< The channel the message was received on. */ 00037 const char* id; /**< The message ID, if it is a command message, otherwise NULL. */ 00038 const char* type; /**< The type of data in the message, if it exists, otherwise NULL. */ 00039 CayenneValuePair values[CAYENNE_MAX_MESSAGE_VALUES]; /**< The unit/value data pairs in the message. The units and values can be NULL. */ 00040 size_t valueCount; /**< The count of items in the values array. */ 00041 00042 /** 00043 * Get value at specified index. 00044 * @param[in] index Index of value to retrieve, if none is specified it gets the first value. 00045 * @return Value at the specified index, can be NULL. 00046 */ 00047 const char* getValue(size_t index = 0) const { return values[index].value; } 00048 00049 /** 00050 * Get unit at specified index. 00051 * @param[in] index Index of unit to retrieve, if none is specified it gets the first unit. 00052 * @return Unit at the specified index, can be NULL. 00053 */ 00054 const char* getUnit(size_t index = 0) const { return values[index].unit; } 00055 } MessageData; 00056 00057 /** 00058 * Client class for connecting to Cayenne via MQTT. 00059 * @class MQTTClient 00060 * @param Network A network class with the methods: read, write. See NetworkInterface.h for function definitions. 00061 * @param Timer A timer class with the methods: countdown_ms, countdown, left_ms, expired. See TimerInterface.h for function definitions. 00062 * @param MAX_MQTT_PACKET_SIZE Maximum size of an MQTT message, in bytes. 00063 * @param MAX_MESSAGE_HANDLERS Maximum number of message handlers. 00064 */ 00065 template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE = CAYENNE_MAX_MESSAGE_SIZE, int MAX_MESSAGE_HANDLERS = 5> 00066 class MQTTClient : private MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, 0> 00067 { 00068 public: 00069 typedef MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, 0> Base ; 00070 typedef void(*CayenneMessageHandler)(MessageData&); 00071 00072 /** 00073 * Create an Cayenne MQTT client object. 00074 * @param[in] network Pointer to an instance of the Network class. Must be connected to the endpoint before calling MQTTClient connect. 00075 * @param[in] username Cayenne username 00076 * @param[in] password Cayenne password 00077 * @param[in] clientID Cayennne client ID 00078 * @param[in] command_timeout_ms Timeout for commands in milliseconds. 00079 */ 00080 MQTTClient(Network& network, const char* username = NULL, const char* password = NULL, const char* clientID = NULL, unsigned int command_timeout_ms = 30000) : 00081 Base (network, command_timeout_ms), _username(username), _password(password), _clientID(clientID) 00082 { 00083 Base::setDefaultMessageHandler(this, &MQTTClient::mqttMessageArrived); 00084 }; 00085 00086 /** 00087 * Initialize connection credentials. 00088 * @param[in] username Cayenne username 00089 * @param[in] password Cayenne password 00090 * @param[in] clientID Cayennne client ID 00091 */ 00092 void init(const char* username, const char* password, const char* clientID) 00093 { 00094 _username = username; 00095 _password = password; 00096 _clientID = clientID; 00097 }; 00098 00099 /** 00100 * Set default handler function called when a message is received. 00101 * @param[in] handler Function called when message is received, if no other handlers exist for the topic. 00102 */ 00103 void setDefaultMessageHandler(CayenneMessageHandler handler) 00104 { 00105 _defaultMessageHandler.attach(handler); 00106 }; 00107 00108 /** 00109 * Set default handler function called when a message is received. 00110 * @param item Address of initialized object 00111 * @param handler Function called when message is received, if no other handlers exist for the topic. 00112 */ 00113 template<class T> 00114 void setDefaultMessageHandler(T *item, void (T::*handler)(MessageData&)) 00115 { 00116 _defaultMessageHandler.attach(item, handler); 00117 } 00118 00119 /** 00120 * Connect to the Cayenne server. Connection credentials must be initialized before calling this function. 00121 * @param[in] username Cayenne username 00122 * @param[in] password Cayenne password 00123 * @param[in] clientID Cayennne client ID 00124 * @return success code 00125 */ 00126 int connect() { 00127 MQTTPacket_connectData data = MQTTPacket_connectData_initializer; 00128 data.MQTTVersion = 3; 00129 data.username.cstring = const_cast<char*>(_username); 00130 data.password.cstring = const_cast<char*>(_password); 00131 data.clientID.cstring = const_cast<char*>(_clientID); 00132 return Base::connect(data); 00133 }; 00134 00135 /** 00136 * Yield to allow MQTT message processing. 00137 * @param[in] timeout_ms The time in milliseconds to yield for 00138 * @return success code 00139 */ 00140 int yield(unsigned long timeout_ms = 1000L) { 00141 return Base::yield(timeout_ms); 00142 }; 00143 00144 /** 00145 * Check if the client is connected to the Cayenne server. 00146 * @return true if connected, false if not connected 00147 */ 00148 bool connected() { 00149 return Base::isConnected(); 00150 }; 00151 00152 /** 00153 * Disconnect from the Cayenne server. 00154 * @return success code 00155 */ 00156 int disconnect() { 00157 return Base::disconnect(); 00158 }; 00159 00160 /** 00161 * Send data to Cayenne. 00162 * @param[in] topic Cayenne topic 00163 * @param[in] channel The channel to send data to, or CAYENNE_NO_CHANNEL if there is none 00164 * @param[in] type Type to use for a type=value pair, can be NULL if sending to a topic that doesn't require type 00165 * @param[in] unit Optional unit to use for a type,unit=value payload, can be NULL 00166 * @param[in] value Data value 00167 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00168 * @return success code 00169 */ 00170 int publishData(CayenneTopic topic, unsigned int channel, const char* type, const char* unit, const char* value, const char* clientID = NULL) { 00171 CayenneValuePair valuePair[1]; 00172 valuePair[0].value = value; 00173 valuePair[0].unit = unit; 00174 return publishData(topic, channel, type, valuePair, 1, 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, int value, const char* clientID = NULL) { 00188 char str[2 + 8 * sizeof(value)]; 00189 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00190 itoa(value, str, 10); 00191 #else 00192 snprintf(str, sizeof(str), "%d", 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, unsigned int value, const char* clientID = NULL) { 00208 char str[1 + 8 * sizeof(value)]; 00209 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00210 utoa(value, str, 10); 00211 #else 00212 snprintf(str, sizeof(str), "%u", 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, long value, const char* clientID = NULL) { 00228 char str[2 + 8 * sizeof(value)]; 00229 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00230 ltoa(value, str, 10); 00231 #else 00232 snprintf(str, sizeof(str), "%ld", 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, unsigned long value, const char* clientID = NULL) { 00248 char str[1 + 8 * sizeof(value)]; 00249 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00250 ultoa(value, str, 10); 00251 #else 00252 snprintf(str, sizeof(str), "%lu", 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, double 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, float value, const char* clientID = NULL) { 00288 char str[33]; 00289 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00290 dtostrf(value, 5, 3, str); 00291 #else 00292 snprintf(str, 33, "%2.3f", value); 00293 #endif 00294 return publishData(topic, channel, type, unit, str, clientID); 00295 }; 00296 00297 /** 00298 * Send data to Cayenne. 00299 * @param[in] topic Cayenne topic 00300 * @param[in] channel The channel to send data to, or CAYENNE_NO_CHANNEL if there is none 00301 * @param[in] type Type to use for a type=value pair, can be NULL if sending to a topic that doesn't require type 00302 * @param[in] unit Optional unit to use for a type,unit=value payload, can be NULL 00303 * @param[in] value Data value 00304 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00305 * @return success code 00306 */ 00307 int publishData(CayenneTopic topic, unsigned int channel, const char* type, const CayenneValuePair* values, size_t valueCount, const char* clientID = NULL) { 00308 char buffer[MAX_MQTT_PACKET_SIZE + 1] = { 0 }; 00309 int result = CayenneBuildTopic(buffer, sizeof(buffer), _username, clientID ? clientID : _clientID, topic, channel); 00310 if (result == CAYENNE_SUCCESS) { 00311 size_t size = strlen(buffer); 00312 char* payload = &buffer[size + 1]; 00313 size = sizeof(buffer) - (size + 1); 00314 result = CayenneBuildDataPayload(payload, &size, type, values, valueCount); 00315 if (result == CAYENNE_SUCCESS) { 00316 result = Base::publish(buffer, payload, size, MQTT::QOS0, true); 00317 } 00318 } 00319 return result; 00320 }; 00321 00322 /** 00323 * Send a response to a channel. 00324 * @param[in] id ID of message the response is for 00325 * @param[in] error Optional error message, NULL for success 00326 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00327 * @return success code 00328 */ 00329 int publishResponse(const char* id, const char* error, const char* clientID = NULL) { 00330 char buffer[MAX_MQTT_PACKET_SIZE + 1] = { 0 }; 00331 int result = CayenneBuildTopic(buffer, sizeof(buffer), _username, clientID ? clientID : _clientID, RESPONSE_TOPIC, CAYENNE_NO_CHANNEL); 00332 if (result == CAYENNE_SUCCESS) { 00333 size_t size = strlen(buffer); 00334 char* payload = &buffer[size + 1]; 00335 size = sizeof(buffer) - (size + 1); 00336 result = CayenneBuildResponsePayload(payload, &size, id, error); 00337 if (result == CAYENNE_SUCCESS) { 00338 result = Base::publish(buffer, payload, size, MQTT::QOS1, true); 00339 } 00340 } 00341 return result; 00342 } 00343 00344 /** 00345 * Subscribe to a topic. 00346 * @param[in] topic Cayenne topic 00347 * @param[in] channel The topic channel, CAYENNE_NO_CHANNEL for none, CAYENNE_ALL_CHANNELS for all 00348 * @param[in] handler The message handler, NULL to use default handler 00349 * @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. 00350 * @return success code 00351 */ 00352 int subscribe(CayenneTopic topic, unsigned int channel, CayenneMessageHandler handler = NULL, const char* clientID = NULL) { 00353 char topicName[MAX_MQTT_PACKET_SIZE] = { 0 }; 00354 int result = CayenneBuildTopic(topicName, sizeof(topicName), _username, clientID ? clientID : _clientID, topic, channel); 00355 if (result == CAYENNE_SUCCESS) { 00356 result = Base::subscribe(topicName, MQTT::QOS0, NULL); 00357 if (handler && result == MQTT::QOS0) { 00358 for (int i = 0; i < MAX_MESSAGE_HANDLERS; ++i) { 00359 if (!_messageHandlers[i].fp.attached()) { 00360 _messageHandlers[i].clientID = clientID ? clientID : _clientID; 00361 _messageHandlers[i].topic = topic; 00362 _messageHandlers[i].channel = channel; 00363 _messageHandlers[i].fp.attach(handler); 00364 break; 00365 } 00366 } 00367 } 00368 } 00369 return result; 00370 }; 00371 00372 /** 00373 * Unsubscribe from a topic. 00374 * @param[in] topic Cayenne topic 00375 * @param[in] channel The topic channel, CAYENNE_NO_CHANNEL for none, CAYENNE_ALL_CHANNELS for all 00376 * @param[in] clientID The client ID to use in the topic, NULL to use the clientID the client was initialized with 00377 * @return success code 00378 */ 00379 int unsubscribe(CayenneTopic topic, unsigned int channel, const char* clientID = NULL) { 00380 char topicName[MAX_MQTT_PACKET_SIZE] = { 0 }; 00381 int result = CayenneBuildTopic(topicName, sizeof(topicName), _username, clientID ? clientID : _clientID, topic, channel); 00382 if (result == CAYENNE_SUCCESS) { 00383 result = Base::unsubscribe(topicName); 00384 if (result == MQTT::SUCCESS) { 00385 for (int i = 0; i < MAX_MESSAGE_HANDLERS; ++i) { 00386 if ((_messageHandlers[i].topic == topic && _messageHandlers[i].channel == channel) && 00387 (strcmp(clientID ? clientID : _clientID, _messageHandlers[i].clientID) == 0)) { 00388 _messageHandlers[i].clientID = NULL; 00389 _messageHandlers[i].topic = UNDEFINED_TOPIC; 00390 _messageHandlers[i].channel = CAYENNE_NO_CHANNEL; 00391 _messageHandlers[i].fp.detach(); 00392 } 00393 } 00394 } 00395 } 00396 return result; 00397 } 00398 00399 /** 00400 * Handler for incoming MQTT::Client messages. 00401 * @param[in] md Message data 00402 */ 00403 void mqttMessageArrived(MQTT::MessageData& md) 00404 { 00405 int result = MQTT::FAILURE; 00406 MessageData message; 00407 00408 result = CayenneParseTopic(&message.topic, &message.channel, &message.clientID, _username, md.topicName.lenstring.data, md.topicName.lenstring.len); 00409 if (result != CAYENNE_SUCCESS) 00410 return; 00411 //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. 00412 (static_cast<char*>(md.message.payload))[md.message.payloadlen] = '\0'; 00413 message.valueCount = CAYENNE_MAX_MESSAGE_VALUES; 00414 result = CayenneParsePayload(message.values, &message.valueCount, &message.type, &message.id, message.topic, static_cast<char*>(md.message.payload)); 00415 if (result != CAYENNE_SUCCESS) 00416 return; 00417 00418 result = MQTT::FAILURE; 00419 for (int i = 0; i < MAX_MESSAGE_HANDLERS; ++i) { 00420 if (_messageHandlers[i].fp.attached() && _messageHandlers[i].topic == message.topic && 00421 (_messageHandlers[i].channel == message.channel || _messageHandlers[i].channel == CAYENNE_ALL_CHANNELS) && 00422 (strcmp(_messageHandlers[i].clientID, message.clientID) == 0)) 00423 { 00424 _messageHandlers[i].fp(message); 00425 result = MQTT::SUCCESS; 00426 } 00427 } 00428 00429 if (result == MQTT::FAILURE && _defaultMessageHandler.attached()) { 00430 _defaultMessageHandler(message); 00431 } 00432 } 00433 00434 private: 00435 const char* _username; 00436 const char* _password; 00437 const char* _clientID; 00438 struct CayenneMessageHandlers 00439 { 00440 const char* clientID; 00441 CayenneTopic topic; 00442 unsigned int channel; 00443 FP<void, MessageData&> fp; 00444 } _messageHandlers[MAX_MESSAGE_HANDLERS]; /* Message handlers are indexed by subscription topic */ 00445 FP<void, MessageData&> _defaultMessageHandler; 00446 }; 00447 00448 } 00449 00450 #endif
Generated on Wed Jul 13 2022 08:52:00 by
1.7.2
