Peter Ferland / Cayenne-MQTT-mbed-M1

Dependents:   5_Dragonfly_Cayenne_Sprint_IKS01A1

Fork of Cayenne-MQTT-mbed-MTSAS by Peter Ferland

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