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

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers CayenneMQTTClient.h Source File

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