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 CayenneUtils.c Source File

CayenneUtils.c

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 
00019 #include <stdlib.h> 
00020 #include <string.h> 
00021 #include "CayenneUtils.h"
00022 
00023 #ifdef DIGITAL_AND_ANALOG_SUPPORT
00024     #ifdef PARSE_INFO_PAYLOADS
00025         #define PARSE_TOPICS_COUNT 13
00026     #else
00027         #define PARSE_TOPICS_COUNT 6
00028     #endif
00029 #else
00030     #ifdef PARSE_INFO_PAYLOADS
00031         #define PARSE_TOPICS_COUNT 7
00032     #else
00033         #define PARSE_TOPICS_COUNT 2
00034     #endif
00035 #endif
00036 
00037 #define THINGS_STRING CAYENNE_PSTR("/things/")
00038 
00039 typedef struct TopicChannel
00040 {
00041     CayenneTopic topic;
00042     unsigned int channel;
00043 } TopicChannel;
00044 
00045 /**
00046 * Build a specified topic string.
00047 * @param[out] topic Returned topic string
00048 * @param[in] length CayenneTopic buffer length
00049 * @param[in] username Cayenne username
00050 * @param[in] clientID Cayennne client ID
00051 * @param[in] suffix The topic suffix
00052 * @return CAYENNE_SUCCESS if topic string was created, error code otherwise
00053 */
00054 int buildTopic(char* topic, size_t length, const char* username, const char* clientID, const char* suffix) {
00055     size_t topicLength = 0;
00056     if (!topic || !username || !clientID || !suffix)
00057         return CAYENNE_FAILURE;
00058     topicLength = strlen(username) + strlen(clientID) + strlen(suffix) + 11;
00059     if (topicLength > length)
00060         return CAYENNE_BUFFER_OVERFLOW;
00061 
00062     topic[0] = '\0';
00063     strcat(topic, CAYENNE_VERSION);
00064     strcat(topic, "/");
00065     strcat(topic, username);
00066     CAYENNE_STRCAT(topic, THINGS_STRING);
00067     strcat(topic, clientID);
00068     strcat(topic, "/");
00069     strcat(topic, suffix);
00070     return CAYENNE_SUCCESS;
00071 }
00072 
00073 /**
00074 * Build a specified topic suffix string.
00075 * @param[out] suffix Returned suffix string
00076 * @param[in] length Suffix buffer length
00077 * @param[in] topic Cayenne topic
00078 * @param[in] channel The topic channel, CAYENNE_NO_CHANNEL for none, CAYENNE_ALL_CHANNELS for all
00079 * @return CAYENNE_SUCCESS if suffix string was created, error code otherwise
00080 */
00081 int buildSuffix(char* suffix, size_t length, const CayenneTopic topic, unsigned int channel) {
00082     char* topicString = NULL;
00083     if (!suffix)
00084         return CAYENNE_FAILURE;
00085     switch (topic)
00086     {
00087     case COMMAND_TOPIC:
00088         topicString = COMMAND_STRING;
00089         break;
00090     case CONFIG_TOPIC:
00091         topicString = CONFIG_STRING;
00092         break;
00093     case DATA_TOPIC:
00094         topicString = DATA_STRING;
00095         break;
00096     case RESPONSE_TOPIC:
00097         topicString = RESPONSE_STRING;
00098         break;
00099     case SYS_MODEL_TOPIC:
00100         topicString = SYS_MODEL_STRING;
00101         break;
00102     case SYS_VERSION_TOPIC:
00103         topicString = SYS_VERSION_STRING;
00104         break;
00105     case SYS_CPU_MODEL_TOPIC:
00106         topicString = SYS_CPU_MODEL_STRING;
00107         break;
00108     case SYS_CPU_SPEED_TOPIC:
00109         topicString = SYS_CPU_SPEED_STRING;
00110         break;
00111 #ifdef DIGITAL_AND_ANALOG_SUPPORT
00112     case DIGITAL_TOPIC:
00113         topicString = DIGITAL_STRING;
00114         break;
00115     case DIGITAL_COMMAND_TOPIC:
00116         topicString = DIGITAL_COMMAND_STRING;
00117         break;
00118     case DIGITAL_CONFIG_TOPIC:
00119         topicString = DIGITAL_CONFIG_STRING;
00120         break;
00121     case ANALOG_TOPIC:
00122         topicString = ANALOG_STRING;
00123         break;
00124     case ANALOG_COMMAND_TOPIC:
00125         topicString = ANALOG_COMMAND_STRING;
00126         break;
00127     case ANALOG_CONFIG_TOPIC:
00128         topicString = ANALOG_CONFIG_STRING;
00129         break;
00130 #endif
00131     default:
00132         return CAYENNE_FAILURE;
00133     }
00134 
00135     if (!topicString)
00136         return CAYENNE_FAILURE;
00137     if (CAYENNE_STRLEN(topicString) >= length)
00138         return CAYENNE_BUFFER_OVERFLOW;
00139 
00140     suffix[0] = '\0';
00141     CAYENNE_STRCAT(suffix, topicString);
00142     if (channel != CAYENNE_NO_CHANNEL) {
00143         strcat(suffix, "/");
00144         if (channel == CAYENNE_ALL_CHANNELS) {
00145             strcat(suffix, "+");
00146         }
00147         else {
00148 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32)
00149             itoa(channel, &suffix[strlen(suffix)], 10);
00150 #else
00151             snprintf(&suffix[strlen(suffix)], length - strlen(suffix), "%u", channel);
00152 #endif
00153         }
00154     }
00155     return CAYENNE_SUCCESS;
00156 }
00157 
00158 /**
00159 * Check if topic matches.
00160 * @param[in] filter Filter to check topic against
00161 * @param[in] topicName CayenneTopic name
00162 * @param[in] topicNameLen CayenneTopic name length
00163 * return true if topic matches, false otherwise
00164 */
00165 int topicMatches(char* filter, char* topicName, size_t topicNameLen)
00166 {
00167     char* curf = filter;
00168     char* curn = topicName;
00169     char* curn_end = topicName + topicNameLen;
00170 
00171     while (*curf && curn < curn_end)
00172     {
00173         if (*curn == '/' && *curf != '/')
00174             break;
00175         if (*curf != '+' && *curf != '#' && *curf != *curn)
00176             break;
00177         if (*curf == '+')
00178         {   // skip until we meet the next separator, or end of string
00179             char* nextpos = curn + 1;
00180             while (nextpos < curn_end && *nextpos != '/')
00181                 nextpos = ++curn + 1;
00182         }
00183         else if (*curf == '#')
00184             curn = curn_end - 1;    // skip until end of string
00185         curf++;
00186         curn++;
00187     };
00188 
00189     return ((curn == curn_end) && (*curf == '\0'));
00190 }
00191 
00192 /**
00193 * Parse a null terminated payload string in place. This may modify the payload string.
00194 * @param[out] type Returned type, NULL if there is none
00195 * @param[out] unit Returned unit, NULL if there is none
00196 * @param[out] value Returned value, NULL if there is none
00197 * @param[in] payload Payload string, must be null terminated
00198 * @param[in] token Character token for splitting "unit=value" payloads, 0 to just parse first comma delimited value
00199 * @return CAYENNE_SUCCESS if value and id were parsed, error code otherwise
00200 */
00201 int parsePayload(const char** type, const char** unit, const char** value, char* payload, char token) {
00202     char* index = payload;
00203     *type = NULL;
00204     *unit = NULL;
00205     *value = NULL;
00206     while (*index && index != '\0') {
00207         if ((*index == ',') || (*index == token)) {
00208             if (*index == ',') {
00209                 *type = payload;
00210                 *unit = index + 1;
00211                 *index = '\0';
00212                 if (token == 0)
00213                     break;
00214             }
00215             else if (*index == token) {
00216                 *type = payload;
00217                 *value = index + 1;
00218                 *index = '\0';
00219                 break;
00220             }
00221         }
00222         index++;
00223     };
00224     return CAYENNE_SUCCESS;
00225 }
00226 
00227 /**
00228 * Build a specified topic string.
00229 * @param[out] topicName Returned topic string
00230 * @param[in] length CayenneTopic buffer length
00231 * @param[in] username Cayenne username
00232 * @param[in] clientID Cayennne client ID
00233 * @param[in] topic Cayenne topic
00234 * @param[in] channel The topic channel, CAYENNE_NO_CHANNEL for none, CAYENNE_ALL_CHANNELS for all
00235 * @return CAYENNE_SUCCESS if topic string was created, error code otherwise
00236 */
00237 int CayenneBuildTopic(char* topicName, size_t length, const char* username, const char* clientID, CayenneTopic topic, unsigned int channel) {
00238     char channelSuffix[20] = {0};
00239     int result = buildSuffix(channelSuffix, sizeof(channelSuffix), topic, channel);
00240     if (result != CAYENNE_SUCCESS)
00241         return result;
00242     return buildTopic(topicName, length, username, clientID, channelSuffix);
00243 }
00244 
00245 /**
00246 * Build a specified data payload.
00247 * @param[out] payload Returned payload
00248 * @param[in,out] length Payload buffer length
00249 * @param[in] type Optional type to use for type,unit=value payload, can be NULL
00250 * @param[in] unit Payload unit
00251 * @param[in] value Payload value
00252 * @return CAYENNE_SUCCESS if topic string was created, error code otherwise
00253 */
00254 int CayenneBuildDataPayload(char* payload, size_t* length, const char* type, const char* unit, const char* value) {
00255     size_t payloadLength = 0;
00256     if (unit) {
00257         payloadLength += strlen(unit) + 1;
00258     }
00259     else if (type) {
00260         // If type exists but unit does not, use UNIT_UNDEFINED for the unit.
00261         payloadLength += strlen(UNIT_UNDEFINED) + 1;
00262     }
00263     payloadLength += value ? strlen(value) + 1 : 0;
00264     payloadLength += type ? strlen(type) + 1 : 0;
00265     //If payload can't fit the payload plus a terminating null byte return.
00266     if (payloadLength > *length) {
00267         return CAYENNE_BUFFER_OVERFLOW;
00268     }
00269 
00270     payload[0] = '\0';
00271     if (type) {
00272         strcat(payload, type);
00273     }
00274     if (payload[0] != '\0')
00275         strcat(payload, ",");
00276     if (unit)
00277         strcat(payload, unit);
00278     else if (type)
00279         strcat(payload, UNIT_UNDEFINED);
00280     if (payload[0] != '\0' && value)
00281         strcat(payload, "=");
00282     if (value)
00283         strcat(payload, value);
00284     *length = --payloadLength; //Subtract terminating null 
00285     return CAYENNE_SUCCESS;
00286 }
00287 
00288 /**
00289 * Build a specified response payload.
00290 * @param[out] payload Returned payload
00291 * @param[in,out] length Payload buffer length
00292 * @param[in] id ID of message the response is for
00293 * @param[in] error Optional error message, NULL for success
00294 * @return CAYENNE_SUCCESS if payload string was created, error code otherwise
00295 */
00296 int CayenneBuildResponsePayload(char* payload, size_t* length, const char* id, const char* error) {
00297     if (error) {
00298         return CayenneBuildDataPayload(payload, length, "error", id, error);
00299     }
00300     return CayenneBuildDataPayload(payload, length, "ok", id, error);
00301 }
00302 
00303 /**
00304 * Parse a topic string in place. This may modify the topic string.
00305 * @param[out] topic Returned Cayenne topic
00306 * @param[out] channel Returned channel, CAYENNE_NO_CHANNEL if there is none
00307 * @param[out] clientID Returned client ID
00308 * @param[in] username Cayenne username
00309 * @param[in] topicName Topic name string
00310 * @param[in] length Topic name string length
00311 * @return CAYENNE_SUCCESS if topic was parsed, error code otherwise
00312 */
00313 int CayenneParseTopic(CayenneTopic* topic, unsigned int* channel, const char** clientID, const char* username, char* topicName, size_t length) {
00314     char* index = NULL;
00315     int i = 0;
00316     TopicChannel parseTopics[PARSE_TOPICS_COUNT] = { { COMMAND_TOPIC, CAYENNE_ALL_CHANNELS },{ CONFIG_TOPIC, CAYENNE_ALL_CHANNELS },
00317 #ifdef DIGITAL_AND_ANALOG_SUPPORT
00318         { ANALOG_COMMAND_TOPIC, CAYENNE_ALL_CHANNELS },{ ANALOG_CONFIG_TOPIC, CAYENNE_ALL_CHANNELS },{ DIGITAL_COMMAND_TOPIC, CAYENNE_ALL_CHANNELS },{ DIGITAL_CONFIG_TOPIC, CAYENNE_ALL_CHANNELS },
00319 #ifdef PARSE_INFO_PAYLOADS
00320         { DIGITAL_TOPIC, CAYENNE_ALL_CHANNELS },{ ANALOG_TOPIC, CAYENNE_ALL_CHANNELS },
00321 #endif
00322 #endif
00323 #ifdef PARSE_INFO_PAYLOADS
00324         { DATA_TOPIC, CAYENNE_ALL_CHANNELS },{ SYS_MODEL_TOPIC, CAYENNE_NO_CHANNEL },{ SYS_VERSION_TOPIC, CAYENNE_NO_CHANNEL },{ SYS_CPU_MODEL_TOPIC, CAYENNE_NO_CHANNEL },{ SYS_CPU_SPEED_TOPIC, CAYENNE_NO_CHANNEL }
00325 #endif
00326     };
00327 
00328     if (!topic || !channel || !topicName)
00329     {
00330         return CAYENNE_FAILURE;
00331     }
00332     if (length > CAYENNE_MAX_MESSAGE_SIZE)
00333     {
00334         return CAYENNE_BUFFER_OVERFLOW;
00335     }
00336     if (strncmp(CAYENNE_VERSION, topicName, strlen(CAYENNE_VERSION)) != 0)
00337         return CAYENNE_FAILURE;
00338     index = topicName + strlen(CAYENNE_VERSION) + 1;
00339     if (strncmp(username, index, strlen(username)) != 0)
00340         return CAYENNE_FAILURE;
00341     index += strlen(username);
00342     if (CAYENNE_STRNCMP(index, THINGS_STRING, CAYENNE_STRLEN(THINGS_STRING)) != 0)
00343         return CAYENNE_FAILURE;
00344     index += CAYENNE_STRLEN(THINGS_STRING);
00345     char* deviceIDEnd = strchr(index, '/');
00346     if (!deviceIDEnd)
00347         return CAYENNE_FAILURE;
00348     *clientID = index;
00349     *deviceIDEnd = '\0';
00350 
00351     index = deviceIDEnd + 1;
00352     *topic = UNDEFINED_TOPIC;
00353     *channel = CAYENNE_NO_CHANNEL;
00354     length -= (index - topicName);
00355     for (i = 0; i < PARSE_TOPICS_COUNT; ++i)
00356     {
00357         char channelSuffix[32] = { 0 };
00358         if (buildSuffix(channelSuffix, sizeof(channelSuffix), parseTopics[i].topic, parseTopics[i].channel) == CAYENNE_SUCCESS && topicMatches(channelSuffix, index, length)) {
00359             *topic = parseTopics[i].topic;
00360             break;
00361         }
00362     }
00363 
00364     if (*topic == UNDEFINED_TOPIC)
00365         return CAYENNE_FAILURE;
00366 
00367     if (parseTopics[i].channel != CAYENNE_NO_CHANNEL) {
00368         if (length == 0 || length > 31)
00369             return CAYENNE_FAILURE;
00370         char* channelIndex = NULL;
00371         char buffer[32] = { 0 };
00372         memcpy(buffer, index, length);
00373         buffer[length] = '\0';
00374         channelIndex = strrchr(buffer, '/');
00375         if (channelIndex && ++channelIndex) {
00376             char* indexEnd = NULL;
00377             unsigned int channelNumber = strtoul(channelIndex, &indexEnd, 10);
00378             if (indexEnd && *indexEnd == '\0') {
00379                 if (((channelNumber != 0) && (*channelIndex != '0')) || ((channelNumber == 0) && (*channelIndex == '0') && (channelIndex + 1 == indexEnd))) {
00380                     *channel = channelNumber;
00381                 }
00382             }
00383         }
00384     }
00385 
00386     return CAYENNE_SUCCESS;
00387 }
00388 
00389 /**
00390 * Parse a null terminated payload in place. This may modify the payload string.
00391 * @param[out] type Returned type, NULL if there is none
00392 * @param[out] unit Returned unit, NULL if there is none
00393 * @param[out] value Returned value, NULL if there is none
00394 * @param[out] id Returned message id, empty string if there is none
00395 * @param[in] topic Cayenne topic
00396 * @param[in] payload Payload string, must be null terminated.
00397 * @return CAYENNE_SUCCESS if topic string was created, error code otherwise
00398 */
00399 int CayenneParsePayload(const char** type, const char** unit, const char** value, const char** id, CayenneTopic topic, char* payload) {
00400     if (!payload)
00401         return CAYENNE_FAILURE;
00402 
00403     *type = NULL;
00404     *unit = NULL;
00405     *value = NULL;
00406     *id = NULL;
00407     switch (topic)
00408     {
00409 #ifdef PARSE_INFO_PAYLOADS
00410     case DATA_TOPIC:
00411         parsePayload(type, unit, value, payload, '=');
00412         if (!*value)
00413             return CAYENNE_FAILURE;
00414         break;
00415 #endif
00416 #ifdef DIGITAL_AND_ANALOG_SUPPORT
00417 #ifdef PARSE_INFO_PAYLOADS
00418     case ANALOG_TOPIC:
00419         //Use unit to store resolution
00420         parsePayload(value, unit, unit, payload, 0);
00421         if (!*value)
00422             return CAYENNE_FAILURE;
00423         break;
00424 #endif
00425     case DIGITAL_COMMAND_TOPIC:
00426     case ANALOG_COMMAND_TOPIC:
00427 #endif
00428     case COMMAND_TOPIC:
00429         parsePayload(id, value, unit, payload, 0);
00430         if (!*value)
00431             return CAYENNE_FAILURE;
00432         break;
00433     default:
00434         break;
00435     }
00436 
00437     if (!*value) {
00438         *value = payload;
00439         *unit = NULL;
00440         *type = NULL;
00441         *id = NULL;
00442     }
00443 
00444     return CAYENNE_SUCCESS;
00445 }
00446 
00447 
00448