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
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
Generated on Wed Jul 13 2022 15:49:28 by 1.7.2