From b49073877dbdaf321bf845713329c764906d9010 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Wed, 17 Aug 2022 13:49:09 +0200 Subject: [PATCH] added json lib and mqtt sources --- CMakeLists.txt | 9 +- extlibs/jRead.c | 784 +++++++++++++++++++++++++++++++++++++++++++++++ extlibs/jRead.h | 128 ++++++++ extlibs/jWrite.c | 561 +++++++++++++++++++++++++++++++++ extlibs/jWrite.h | 217 +++++++++++++ include/MQTT.h | 82 +++++ src/MQTT.c | 740 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 2520 insertions(+), 1 deletion(-) create mode 100644 extlibs/jRead.c create mode 100644 extlibs/jRead.h create mode 100644 extlibs/jWrite.c create mode 100644 extlibs/jWrite.h create mode 100644 include/MQTT.h create mode 100644 src/MQTT.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cb2974..a615773 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,9 +10,16 @@ idf_component_register( "src/GSMTransport.c" "src/ETHTransport.c" "src/SNTP.c" + "src/MQTT.c" "src/OTA.c" + "extlibs/jRead.c" + "extlibs/jWrite.c" + + INCLUDE_DIRS "." + "include" + "src" + "extlibs" - INCLUDE_DIRS "." "include" "src" REQUIRES nvs_flash libespfs esp_http_server diff --git a/extlibs/jRead.c b/extlibs/jRead.c new file mode 100644 index 0000000..9e73e22 --- /dev/null +++ b/extlibs/jRead.c @@ -0,0 +1,784 @@ +// jRead.cpp +// Version 1v6 +// +// jRead - an in-place JSON element reader +// ======================================= +// +// Instead of parsing JSON into some structure, this maintains the input JSON as unaltered text +// and allows queries to be made on it directly. +// +// e.g. with the simple JSON: +// { +// "astring":"This is a string", +// "anumber":42, +// "myarray":[ "one", 2, {"description":"element 3"}, null ], +// "yesno":true, +// "HowMany":"1234", +// "foo":null +// } +// +// calling: +// jRead( json, "{'myarray'[0", &jElem ); +// +// would return: +// jElem.dataType= JREAD_STRING; +// jElem.elements= 1 +// jElem.bytelen= 3 +// jElem.pValue -> "one" +// +// or you could call the helper functions: +// jRead_string( json, "{'astring'", destString, MAXLEN ); +// jRead_int( json, "{'anumber'", &myint ); +// jRead_string( json, "{'myarray'[3", destString, MAXLEN ); +// etc. +// +// Note that the helper functions do type coersion and always return a value +// (on error an empty string is returned or value of zero etc.) +// +// The query string simply defines the route to the required data item +// as an arbitary list of object or array specifiers: +// object element= "{'keyname'" +// array element= "[INDEX" +// +// The jRead() function fills a jReadElement structure to describe the located element +// this can be used to locate any element, not just terminal values +// e.g. +// jRead( json, "{'myarray'", &jElem ); +// +// in this case jElem would contain: +// jElem.dataType= JSON_ARRAY +// jElem.elements= 4 +// jElem.bytelen= 46 +// jElem.pValue -> [ "one", 2, {"descripton":"element 3"}, null ] ... +// +// allowing jRead to be called again on the array: +// e.g. +// jRead( jElem.pValue, "[3", &jElem ); // get 4th element - the null value +// +// .oO! see main.c runExamples() for a whole bunch of examples !Oo. +// ------------------------------------------------------- +// +// Note that jRead never modifies the source JSON and does not allocate any memory. +// i.e. elements are returned as pointer and length into the source text. +// +// Functions +// ========= +// Main JSON reader: +// int jRead( char * JsonSource, char *query, jReadElement &pResult ); +// +// Extended function using query parameters for indexing: +// int jRead( char * JsonSource, char *query, jReadElement &pResult, int *queryParams ); +// +// Function to step thru JSON arrays instead of indexing: +// char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult ); +// +// Optional Helper functions: +// long jRead_long( char *pJson, char *pQuery ); +// int jRead_int( char *pJson, char *pQuery ); +// double jRead_double( char *pJson, char *pQuery ); +// int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen ); +// +// Optional String output Functions +// char * jReadTypeToString( int dataType ); // string describes dataType +// char * jReadErrorToString( int error ); // string descibes error code +// +// *NEW* in 1v2 +// - "{NUMBER" returns the "key" value at that index within an object +// - jReadParam() adds queryParams which can be used as indexes into arrays (or into +// objects to return key values) by specifying '*' in the query string +// e.g. jReadParam( pJson, "[*", &result, &index ) +// *NEW in 1v4 +// - fixed a couple of error return values +// - added #define JREAD_DOUBLE_QUOTE_IN_QUERY +// *NEW* in 1v5 (11mar2015) +// - fixed null ptr if '[*' used when null param passed +// *NEW* in 1v6 (24sep2016) +// - fixed handling of empty arrays and objects +// +// TonyWilk, 24sep2016 +// mail at tonywilk . co .uk +// +// License: "Free as in You Owe Me a Beer" +// - actually, since some people really worry about licenses, you are free to apply +// whatever licence you want. +// +// Note: jRead_atol() and jRead_atof() are modified from original routines +// fast_atol() and fast_atof() 09-May-2009 Tom Van Baak (tvb) www.LeapSecond.com +// +// You may want to replace the use of jRead_atol() and jRead_atof() in helper functions +// of your own. Especially note that my atof does not handle exponents. +// +// +#include +#include + + +// By default we use single quote in query strings so it's a lot easier +// to type in code i.e. "{'key'" instead of "{\"key\"" +// +#ifdef JREAD_DOUBLE_QUOTE_IN_QUERY +#define QUERY_QUOTE '\"' +#else +#define QUERY_QUOTE '\'' +#endif + +//------------------------------------------------------ +// Internal Functions + +char * jReadSkipWhitespace( char *sp ); +char * jReadFindTok( char *sp, int *tokType ); +char * jReadGetString( char *pJson, struct jReadElement *pElem, char quote ); +int jReadTextLen( char *pJson ); +int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 ); +char * jReadCountObject( char *pJson, struct jReadElement *pResult, int keyIndex ); +char * jReadCountArray( char *pJson, struct jReadElement *pResult ); +char * jRead_atoi( char *p, unsigned int *result ); +char * jRead_atol( char *p, long *result ); +char * jRead_atof( char *p, double *result); + +//======================================================= + +char *jReadSkipWhitespace( char *sp ) +{ + while( (*sp != '\0') && (*sp <= ' ') ) + sp++; + return sp; +}; + + +// Find start of a token +// - returns pointer to start of next token or element +// returns type via tokType +// +char *jReadFindTok( char *sp, int *tokType ) +{ + char c; + sp= jReadSkipWhitespace(sp); + c= *sp; + if( c == '\0' ) *tokType= JREAD_EOL; + else if((c == '"') || (c == QUERY_QUOTE))*tokType= JREAD_STRING; + else if((c >= '0') && (c <= '9')) *tokType= JREAD_NUMBER; + else if( c == '-') *tokType= JREAD_NUMBER; + else if( c == '{') *tokType= JREAD_OBJECT; + else if( c == '[') *tokType= JREAD_ARRAY; + else if( c == '}') *tokType= JREAD_EOBJECT; + else if( c == ']') *tokType= JREAD_EARRAY; + else if((c == 't') || (c == 'f')) *tokType= JREAD_BOOL; + else if( c == 'n') *tokType= JREAD_NULL; + else if( c == ':') *tokType= JREAD_COLON; + else if( c == ',') *tokType= JREAD_COMMA; + else if( c == '*') *tokType= JREAD_QPARAM; + else *tokType= JREAD_ERROR; + return sp; +}; + +// jReadGetString +// - assumes next element is "string" which may include "\" sequences +// - returns pointer to -------------^ +// - pElem contains result ( JREAD_STRING, length, pointer to string) +// - pass quote = '"' for Json, quote = '\'' for query scanning +// +// returns: pointer into pJson after the string (char after the " terminator) +// pElem contains pointer and length of string (or dataType=JREAD_ERROR) +// +char * jReadGetString( char *pJson, struct jReadElement *pElem, char quote ) +{ + short skipch; + pElem->dataType= JREAD_ERROR; + pElem->elements= 1; + pElem->bytelen= 0; + pJson= jReadSkipWhitespace( pJson ); + if( *pJson == quote ) + { + pJson++; + pElem->pValue= pJson; // -> start of actual string + pElem->bytelen=0; + skipch= 0; + while( *pJson != '\0' ) + { + if( skipch ) + skipch= 0; + else if( *pJson == '\\' ) // "\" sequence + skipch= 1; + else if( *pJson == quote ) + { + pElem->dataType= JREAD_STRING; + pJson++; + break; + } + pElem->bytelen++; + pJson++; + }; + }; + return pJson; +}; + +// jReadTextLen +// - used to identify length of element text +// - returns no. of chars from pJson upto a terminator +// - terminators: ' ' , } ] +// +int jReadTextLen( char *pJson ) +{ + int len= 0; + while( (*pJson > ' ' ) && // any ctrl char incl '\0' + (*pJson != ',' ) && + (*pJson != '}' ) && + (*pJson != ']' ) ) + { + len++; + pJson++; + } + return len; +} + +// compare two json elements +// returns: 0 if they are identical strings, else 1 +// +int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 ) +{ + int i; + if( (j1->dataType != JREAD_STRING) || + (j2->dataType != JREAD_STRING) || + (j1->bytelen != j2->bytelen ) ) + return 1; + + for( i=0; i< j1->bytelen; i++ ) + if( ((char *)(j1->pValue))[i] != ((char *)(j2->pValue))[i] ) + return 1; + return 0; +} + +// read unsigned int from string +char * jRead_atoi( char *p, unsigned int *result ) +{ + unsigned int x = 0; + while (*p >= '0' && *p <= '9') { + x = (x*10) + (*p - '0'); + ++p; + } + *result= x; + return p; +} + +// read long int from string +// +char * jRead_atol( char *p, long *result ) +{ + long x = 0; + int neg = 0; + if (*p == '-') { + neg = 1; + ++p; + } + while (*p >= '0' && *p <= '9') { + x = (x*10) + (*p - '0'); + ++p; + } + if (neg) { + x = -x; + } + *result= x; + return p; +} + + +#define valid_digit(c) ((c) >= '0' && (c) <= '9') + +// read double from string +// *CAUTION* does not handle exponents +// +// +char * jRead_atof( char *p, double *result) +{ + double sign, value; + + // Get sign, if any. + sign = 1.0; + if (*p == '-') { + sign = -1.0; + p += 1; + + } else if (*p == '+') { + p += 1; + } + + // Get digits before decimal point or exponent, if any. + for (value = 0.0; valid_digit(*p); p += 1) { + value = value * 10.0 + (*p - '0'); + } + + // Get digits after decimal point, if any. + if (*p == '.') { + double pow10 = 10.0; + p += 1; + while (valid_digit(*p)) { + value += (*p - '0') / pow10; + pow10 *= 10.0; + p += 1; + } + } + *result= sign * value; + return p; +} + +// read element into destination buffer and add '\0' terminator +// - always copies element irrespective of dataType (unless it's an error) +// - destBuffer is always '\0'-terminated (even on zero lenght returns) +// - returns pointer to destBuffer +// +char *jRead_strcpy( char *destBuffer, int destLength, struct jReadElement *pElement ) +{ + int i; + int len= pElement->bytelen; + char *pdest= destBuffer; + char *psrc= (char *)pElement->pValue; + if( pElement->error == 0 ) + { + if( len >= destLength ) + len= destLength; + for( i=0; i "{... " +// - used to skip unwanted values which are objects +// - keyIndex normally passed as -1 unless we're looking for the nth "key" value +// in which case keyIndex is the index of the key we want +// +char * jReadCountObject( char *pJson, struct jReadElement *pResult, int keyIndex ) +{ + struct jReadElement jElement; + int jTok; + char *sp; + pResult->dataType= JREAD_OBJECT; + pResult->error= 0; + pResult->elements= 0; + pResult->pValue= pJson; + sp= jReadFindTok( pJson+1, &jTok ); // check for empty object + if( jTok == JREAD_EOBJECT ) + { + pJson= sp+1; + }else + { + while( 1 ) + { + pJson= jReadGetString( ++pJson, &jElement, '\"' ); + if( jElement.dataType != JREAD_STRING ) + { + pResult->error= 3; // Expected "key" + break; + } + if( pResult->elements == keyIndex ) // if passed keyIndex + { + *pResult= jElement; // we return "key" at this index + pResult->dataType= JREAD_KEY; + return pJson; + } + pJson= jReadFindTok( pJson, &jTok ); + if( jTok != JREAD_COLON ) + { + pResult->error= 4; // Expected ":" + break; + } + pJson= jRead( ++pJson, "", &jElement ); + if( pResult->error ) + break; + pJson= jReadFindTok( pJson, &jTok ); + pResult->elements++; + if( jTok == JREAD_EOBJECT ) + { + pJson++; + break; + } + if( jTok != JREAD_COMMA ) + { + pResult->error= 6; // Expected "," in object + break; + } + } + } + if( keyIndex >= 0 ) + { + // we wanted a "key" value - that we didn't find + pResult->dataType= JREAD_ERROR; + pResult->error= 11; // Object key not found (bad index) + }else{ + pResult->bytelen= pJson - (char *)pResult->pValue; + } + return pJson; +} + + + +// jReadCountArray +// - used when query ends at an array, we want to return the array length +// - on entry pJson -> "[... " +// - used to skip unwanted values which are arrays +// +char * jReadCountArray( char *pJson, struct jReadElement *pResult ) +{ + struct jReadElement jElement; + int jTok; + char *sp; + pResult->dataType= JREAD_ARRAY; + pResult->error= 0; + pResult->elements= 0; + pResult->pValue= pJson; + sp= jReadFindTok( pJson+1, &jTok ); // check for empty array + if( jTok == JREAD_EARRAY ) + { + pJson= sp+1; + }else + { + while( 1 ) + { + pJson= jRead( ++pJson, "", &jElement ); // array value + if( pResult->error ) + break; + pJson= jReadFindTok( pJson, &jTok ); // , or ] + pResult->elements++; + if( jTok == JREAD_EARRAY ) + { + pJson++; + break; + } + if( jTok != JREAD_COMMA ) + { + pResult->error= 9; // Expected "," in array + break; + } + } + } + pResult->bytelen= pJson - (char *)pResult->pValue; + return pJson; +} + +// jReadArrayStep() +// - reads one value from an array +// - assumes pJsonArray points at the start of an array or array element +// +char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult ) +{ + int jTok; + + pJsonArray= jReadFindTok( pJsonArray, &jTok ); + switch( jTok ) + { + case JREAD_ARRAY: // start of array + case JREAD_COMMA: // element separator + return jRead( ++pJsonArray, "", pResult ); + + case JREAD_EARRAY: // end of array + pResult->error= 13; // End of array found + break; + default: // some other error + pResult->error= 9; // expected comma in array + break; + } + pResult->dataType= JREAD_ERROR; + return pJsonArray; +} + + +// jRead +// - reads a complete JSON +// - matches pQuery against pJson, results in pResult +// returns: pointer into pJson +// +// Note: is recursive +// +char * jRead( char *pJson, char *pQuery, struct jReadElement *pResult ) +{ + return jReadParam( pJson, pQuery, pResult, NULL ); +} + +char * jReadParam( char *pJson, char *pQuery, struct jReadElement *pResult, int *queryParams ) +{ + int qTok, jTok, bytelen; + unsigned int index, count; + struct jReadElement qElement, jElement; + + pJson= jReadFindTok( pJson, &jTok ); + pQuery= jReadFindTok( pQuery, &qTok ); + + pResult->dataType= jTok; + pResult->bytelen= pResult->elements= pResult->error= 0; + pResult->pValue= pJson; + + if( (qTok != JREAD_EOL) && (qTok != jTok) ) + { + pResult->error= 1; // JSON does not match Query + return pJson; + } + + switch( jTok ) + { + case JREAD_ERROR: // general error, eof etc. + pResult->error= 2; // Error reading JSON value + break; + + case JREAD_OBJECT: // "{" + if( qTok == JREAD_EOL ) + return jReadCountObject( pJson, pResult, -1 ); // return length of object + + pQuery= jReadFindTok( ++pQuery, &qTok ); // "('key'...", "{NUMBER", "{*" or EOL + if( qTok != JREAD_STRING ) + { + index= 0; + switch( qTok ) + { + case JREAD_NUMBER: + pQuery= jRead_atoi( (char *)pQuery, &index ); // index value + break; + case JREAD_QPARAM: + pQuery++; + index= (queryParams != NULL) ? *queryParams++ : 0; // substitute parameter + break; + default: + pResult->error= 12; // Bad Object key + return pJson; + } + return jReadCountObject( pJson, pResult, index ); + } + + pQuery= jReadGetString( pQuery, &qElement, QUERY_QUOTE ); // qElement = query 'key' + // + // read : , ... } + // loop 'til key matched + // + while( 1 ) + { + pJson= jReadGetString( ++pJson, &jElement, '\"' ); + if( jElement.dataType != JREAD_STRING ) + { + pResult->error= 3; // Expected "key" + break; + } + pJson= jReadFindTok( pJson, &jTok ); + if( jTok != JREAD_COLON ) + { + pResult->error= 4; // Expected ":" + break; + } + // compare object keys + if( jReadStrcmp( &qElement, &jElement ) == 0 ) + { + // found object key + return jReadParam( ++pJson, pQuery, pResult, queryParams ); + } + // no key match... skip this value + pJson= jRead( ++pJson, "", pResult ); + pJson= jReadFindTok( pJson, &jTok ); + if( jTok == JREAD_EOBJECT ) + { + pResult->error= 5; // Object key not found + break; + } + if( jTok != JREAD_COMMA ) + { + pResult->error= 6; // Expected "," in object + break; + } + } + break; + case JREAD_ARRAY: // "[NUMBER" or "[*" + // + // read index, skip values 'til index + // + if( qTok == JREAD_EOL ) + return jReadCountArray( pJson, pResult ); // return length of object + + index= 0; + pQuery= jReadFindTok( ++pQuery, &qTok ); // "[NUMBER" or "[*" + if( qTok == JREAD_NUMBER ) + { + pQuery= jRead_atoi( pQuery, &index ); // get array index + }else if( qTok == JREAD_QPARAM ) + { + pQuery++; + index= (queryParams != NULL) ? *queryParams++ : 0; // substitute parameter + } + + count=0; + while( 1 ) + { + if( count == index ) + return jReadParam( ++pJson, pQuery, pResult, queryParams ); // return value at index + // not this index... skip this value + pJson= jRead( ++pJson, "", &jElement ); + if( pResult->error ) + break; + count++; + pJson= jReadFindTok( pJson, &jTok ); // , or ] + if( jTok == JREAD_EARRAY ) + { + pResult->error= 10; // Array element not found (bad index) + break; + } + if( jTok != JREAD_COMMA ) + { + pResult->error= 9; // Expected "," in array + break; + } + } + break; + case JREAD_STRING: // "string" + pJson= jReadGetString( pJson, pResult, '\"' ); + break; + case JREAD_NUMBER: // number (may be -ve) int or float + case JREAD_BOOL: // true or false + case JREAD_NULL: // null + bytelen= jReadTextLen( pJson ); + pResult->dataType= jTok; + pResult->bytelen= bytelen; + pResult->pValue= pJson; + pResult->elements= 1; + pJson += bytelen; + break; + default: + pResult->error= 8; // unexpected character (in pResult->dataType) + } + // We get here on a 'terminal value' + // - make sure the query string is empty also + pQuery= jReadFindTok( pQuery, &qTok ); + if( !pResult->error && (qTok != JREAD_EOL) ) + pResult->error= 7; // terminal value found before end of query + if( pResult->error ) + { + pResult->dataType= JREAD_ERROR; + pResult->elements= pResult->bytelen= 0; + pResult->pValue= pJson; // return pointer into JSON at error point + } + return pJson; +} + + +//-------------------------------------------------------------------- +// Optional helper functions +// - simple routines to extract values from JSON +// - does coercion of types where possible +// - always returns a value (e.g. 0 or "" on error) +// +// Note: by default, pass NULL for queryParams +// unless you are using '*' in the query for indexing +// + +// jRead_long +// - reads signed long value from JSON +// - returns number from NUMBER or STRING elements (if possible) +// returns 1 or 0 from BOOL elements +// otherwise returns 0 +// +long jRead_long( char *pJson, char *pQuery, int *queryParams ) +{ + struct jReadElement elem; + long result; + jReadParam( pJson, pQuery, &elem, queryParams ); + if( (elem.dataType == JREAD_ERROR) || (elem.dataType == JREAD_NULL)) + return 0; + if( elem.dataType == JREAD_BOOL ) + return *((char *)elem.pValue)=='t' ? 1 : 0; + + jRead_atol( (char *)elem.pValue, &result ); + return result; +} + +int jRead_int( char *pJson, char *pQuery, int *queryParams ) +{ + return (int)jRead_long( pJson, pQuery, queryParams ); +} + +// jRead_double +// - returns double from JSON +// - returns number from NUMBER or STRING elements +// otherwise returns 0.0 +// +double jRead_double( char *pJson, char *pQuery, int *queryParams ) +{ + struct jReadElement elem; + double result; + jReadParam( pJson, pQuery, &elem, queryParams ); + if( elem.dataType == JREAD_ERROR ) + return 0.0; + jRead_atof( (char *)elem.pValue, &result ); + return result; +} + +// jRead_string +// Copy string to pDest and '\0'-terminate it (upto destlen total bytes) +// returns: character length of string (excluding '\0' terminator) +// +// Note: any element can be returned as a string +// +int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen, int *queryParams ) +{ + struct jReadElement elem; + int i; + + *pDest= '\0'; + jReadParam( pJson, pQuery, &elem, queryParams ); + if( elem.dataType == JREAD_ERROR ) + return 0; + + for( i=0; (i=0 ) && (error <= 14)) + return jReadErrorStrings[ error ]; + return "Unknown error"; +}; + +// end of jRead.c diff --git a/extlibs/jRead.h b/extlibs/jRead.h new file mode 100644 index 0000000..7ccaa05 --- /dev/null +++ b/extlibs/jRead.h @@ -0,0 +1,128 @@ +// jRead.h +// +// see jRead.c for more information +// + +// uncomment this if you really want to use double quotes in query strings instead of ' +//#define JREAD_DOUBLE_QUOTE_IN_QUERY + +// +// return dataTypes: +#define JREAD_ERROR 0 // general error, eof etc. +#define JREAD_OBJECT 1 // "{" +#define JREAD_ARRAY 2 // "[" +#define JREAD_STRING 3 // "string" +#define JREAD_NUMBER 4 // number (may be -ve) int or float +#define JREAD_BOOL 5 // true or false +#define JREAD_NULL 6 // null +#define JREAD_KEY 7 // object "key" +// internal values: +#define JREAD_COLON 8 // ":" +#define JREAD_EOL 9 // end of input string (ptr at '\0') +#define JREAD_COMMA 10 // "," +#define JREAD_EOBJECT 11 // "}" +#define JREAD_EARRAY 12 // "]" +#define JREAD_QPARAM 13 // "*" query string parameter + +//------------------------------------------------------ +// jReadElement +// - structure to return JSON elements +// - error=0 for valid returns +// +// *NOTES* +// the returned pValue pointer points into the passed JSON +// string returns are not '\0' terminated. +// bytelen specifies the length of the returned data pointed to by pValue +// +struct jReadElement{ + int dataType; // one of JREAD_... + int elements; // number of elements (e.g. elements in array or object) + int bytelen; // byte length of element (e.g. length of string, array text "[ ... ]" etc.) + void * pValue; // pointer to value string in JSON text + int error; // error value if dataType == JREAD_ERROR +}; + +//------------------------------------------------------ +// The JSON reader function +// +// - reads a '\0'-terminated JSON text string from pJson +// - traverses the JSON according to the pQuery string +// - returns the result value in pResult +// +// returns: pointer into pJson after the queried value +// +// e.g. +// With JSON like: "{ ..., "key":"value", ... }" +// +// jRead( pJson, "{'key'", &result ); +// returns with: +// result.dataType= JREAD_STRING, result.pValue->'value', result.bytelen=5 +// +char * jRead( char *pJson, char *pQuery, struct jReadElement *pResult ); + +// version of jRead which allows one or more queryParam integers to be substituted +// for array or object indexes marked by a '*' in the query +// +// e.g. jReadParam( pJson, "[*", &resultElement, &arrayIndex ); +// +// *!* CAUTION *!* +// You can supply an array of integers which are indexed for each '*' in pQuery +// however, horrid things will happen if you don't supply enough parameters +// +char * jReadParam( char *pJson, char *pQuery, struct jReadElement *pResult, int *queryParams ); + +// Array Stepping function +// - assumes pJsonArray is JSON source of an array "[ ... ]" +// - returns next element of the array in pResult +// - returns pointer to end of element, to be passed to next call of jReadArrayStep() +// - if end of array is encountered, pResult->error = 13 "End of array found" +// +// e.g. +// With JSON like: "{ ... "arrayInObject":[ elem1,elem2,... ], ... }" +// +// pJson= jRead( pJson, "{'arrayInObject'", &theArray ); +// if( theArray.dataType == JREAD_ARRAY ) +// { +// char *pArray= (char *)theArray.pValue; +// jReadElement arrayElement; +// int index; +// for( index=0; index < theArray.elements; index++ ) +// { +// pArray= jReadArrayStep( pArray, &arrayElement ); +// ... +// +// Note: this significantly speeds up traversing arrays. +// +char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult ); + + +#define EXPORT_OPTIONAL_FUNCTIONS +#ifdef EXPORT_OPTIONAL_FUNCTIONS + +//------------------------------------------------------ +// Optional Helper Functions +// +long jRead_long( char *pJson, char *pQuery, int *queryParams ); +int jRead_int( char *pJson, char *pQuery, int *queryParams ); +double jRead_double( char *pJson, char *pQuery, int *queryParams ); +int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen, int *queryParams ); + +//------------------------------------------------------ +// Optional String output Functions +// +char *jReadTypeToString( int dataType ); // string describes dataType +char * jReadErrorToString( int error ); // string descibes error code + +//------------------------------------------------------ +// Other jRead utilities which may be useful... +// +char * jRead_atoi( char *p, unsigned int *result ); // string to unsigned int +char * jRead_atol( char *p, long *result ); // string to signed long +char * jRead_atof( char *p, double *result); // string to double (does not do exponents) +int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 ); // compare STRING elements + +// copy element to '\0'-terminated buffer +char * jRead_strcpy( char *destBuffer, int destLength, struct jReadElement *pElement ); + +#endif +// end of jRead.h diff --git a/extlibs/jWrite.c b/extlibs/jWrite.c new file mode 100644 index 0000000..e129c4b --- /dev/null +++ b/extlibs/jWrite.c @@ -0,0 +1,561 @@ +// +// jWrite.c version 1v2 +// +// A *really* simple JSON writer in C +// +// see: jWrite.h for info +// +// TonyWilk, Mar 2015 +// +#define _CRT_SECURE_NO_WARNINGS // stop complaining about deprecated functions + +#include +#include +#include // memset() + +#include + +#include // definintion of uint32_t, int32_t +//typedef unsigned int uint32_t; +//typedef int int32_t; + + +// the jWrite functions take the above jWriteControl structure pointer +// to maintain state while writing a JSON string. +// +// You can opt to use a single global instance of a jWriteControl structure +// which simplifies the function parameters or to supply your own structure +// +#ifdef JW_GLOBAL_CONTROL_STRUCT +struct jWriteControl g_jWriteControl; // global control struct +#define JWC_DECL // function parameter decl is empty +#define JWC_DECL0 +#define JWC(x) g_jWriteControl.x // functions access global +#define JWC_PARAM // pointer to struct is empty +#define JWC_PARAM0 +#else +#define JWC_DECL struct jWriteControl *jwc, // function parameter is ptr to control struct +#define JWC_DECL0 struct jWriteControl *jwc // function parameter, no params +#define JWC(x) jwc->x // functions use pointer +#define JWC_PARAM jwc, // pointer to stuct +#define JWC_PARAM0 jwc // pointer to stuct, no params +#endif + +//------------------------------------------ +// Internal functions +// +void jwPutch( JWC_DECL char c ); +void jwPutstr( JWC_DECL char *str ); +void jwPutraw( JWC_DECL char *str ); +void modp_itoa10(int32_t value, char* str); +void modp_dtoa2(double value, char* str, int prec); +void jwPretty( JWC_DECL0 ); +enum jwNodeType jwPop( JWC_DECL0 ); +void jwPush( JWC_DECL enum jwNodeType nodeType ); + + +//------------------------------------------ +// jwOpen +// - open writing of JSON starting with rootType = JW_OBJECT or JW_ARRAY +// - initialise with user string buffer of length buflen +// - isPretty=JW_PRETTY adds \n and spaces to prettify output (else JW_COMPACT) +// +void jwOpen( JWC_DECL char *buffer, unsigned int buflen, + enum jwNodeType rootType, int isPretty ) +{ + memset( buffer, 0, buflen ); // zap the whole destination buffer + JWC(buffer)= buffer; + JWC(buflen)= buflen; + JWC(bufp)= buffer; + JWC(nodeStack)[0].nodeType= rootType; + JWC(nodeStack)[0].elementNo= 0; + JWC(stackpos)=0; + JWC(error)= JWRITE_OK; + JWC(callNo)= 1; + JWC(isPretty)= isPretty; + jwPutch( JWC_PARAM (rootType==JW_OBJECT) ? '{' : '[' ); +} + +//------------------------------------------ +// jwClose +// - closes the root JSON object started by jwOpen() +// - returns error code +// +int jwClose( JWC_DECL0 ) +{ + if( JWC(error) == JWRITE_OK ) + { + if( JWC(stackpos) == 0 ) + { + enum jwNodeType node= JWC(nodeStack)[0].nodeType; + if( JWC(isPretty) ) + jwPutch( JWC_PARAM '\n' ); + jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']'); + }else{ + JWC(error)= JWRITE_NEST_ERROR; // nesting error, not all objects closed when jwClose() called + } + } + return JWC(error); +} + +//------------------------------------------ +// End the current array/object +// +int jwEnd( JWC_DECL0 ) +{ + if( JWC(error) == JWRITE_OK ) + { + enum jwNodeType node; + int lastElemNo= JWC(nodeStack)[JWC(stackpos)].elementNo; + node= jwPop( JWC_PARAM0 ); + if( lastElemNo > 0 ) + jwPretty( JWC_PARAM0 ); + jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']'); + } + return JWC(error); +} + + +//------------------------------------------ +// jwErrorPos +// - Returns position of error: the nth call to a jWrite function +// +int jwErrorPos( JWC_DECL0 ) +{ + return JWC(callNo); +} + + +//------------------------------------------ +// Object insert functions +// +int _jwObj( JWC_DECL char *key ); + +// put raw string to object (i.e. contents of rawtext without quotes) +// +void jwObj_raw( JWC_DECL char *key, char *rawtext ) +{ + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + jwPutraw( JWC_PARAM rawtext); +} + +// put "quoted" string to object +// +void jwObj_string( JWC_DECL char *key, char *value ) +{ + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + jwPutstr( JWC_PARAM value ); +} + +void jwObj_int( JWC_DECL char *key, int value ) +{ + modp_itoa10( value, JWC(tmpbuf) ); + jwObj_raw( JWC_PARAM key, JWC(tmpbuf) ); +} + +void jwObj_double( JWC_DECL char *key, double value ) +{ + modp_dtoa2( value, JWC(tmpbuf), 6 ); + jwObj_raw( JWC_PARAM key, JWC(tmpbuf) ); +} + +void jwObj_bool( JWC_DECL char *key, int oneOrZero ) +{ + jwObj_raw( JWC_PARAM key, (oneOrZero) ? "true" : "false" ); +} + +void jwObj_null( JWC_DECL char *key ) +{ + jwObj_raw( JWC_PARAM key, "null" ); +} + +// put Object in Object +// +void jwObj_object( JWC_DECL char *key ) +{ + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '{' ); + jwPush( JWC_PARAM JW_OBJECT ); + } +} + +// put Array in Object +// +void jwObj_array( JWC_DECL char *key ) +{ + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '[' ); + jwPush( JWC_PARAM JW_ARRAY ); + } +} + +//------------------------------------------ +// Array insert functions +// +int _jwArr( JWC_DECL0 ); + +// put raw string to array (i.e. contents of rawtext without quotes) +// +void jwArr_raw( JWC_DECL char *rawtext ) +{ + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + jwPutraw( JWC_PARAM rawtext); +} + +// put "quoted" string to array +// +void jwArr_string( JWC_DECL char *value ) +{ + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + jwPutstr( JWC_PARAM value ); +} + +void jwArr_int( JWC_DECL int value ) +{ + modp_itoa10( value, JWC(tmpbuf) ); + jwArr_raw( JWC_PARAM JWC(tmpbuf) ); +} + +void jwArr_double( JWC_DECL double value ) +{ + modp_dtoa2( value, JWC(tmpbuf), 6 ); + jwArr_raw( JWC_PARAM JWC(tmpbuf) ); +} + +void jwArr_bool( JWC_DECL int oneOrZero ) +{ + jwArr_raw( JWC_PARAM (oneOrZero) ? "true" : "false" ); +} + +void jwArr_null( JWC_DECL0 ) +{ + jwArr_raw( JWC_PARAM "null" ); +} + +void jwArr_object( JWC_DECL0 ) +{ + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '{' ); + jwPush( JWC_PARAM JW_OBJECT ); + } +} + +void jwArr_array( JWC_DECL0 ) +{ + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '[' ); + jwPush( JWC_PARAM JW_ARRAY ); + } +} + + +//------------------------------------------ +// jwErrorToString +// - returns string describing error code +// +char *jwErrorToString( int err ) +{ + switch( err ) + { + case JWRITE_OK: return "OK"; + case JWRITE_BUF_FULL: return "output buffer full"; + case JWRITE_NOT_ARRAY: return "tried to write Array value into Object"; + case JWRITE_NOT_OBJECT: return "tried to write Object key/value into Array"; + case JWRITE_STACK_FULL: return "array/object nesting > JWRITE_STACK_DEPTH"; + case JWRITE_STACK_EMPTY:return "stack underflow error (too many 'end's)"; + case JWRITE_NEST_ERROR: return "nesting error, not all objects closed when jwClose() called"; + } + return "Unknown error"; +} + +//============================================================================ +// Internal functions +// +void jwPretty( JWC_DECL0 ) +{ + int i; + if( JWC(isPretty) ) + { + jwPutch( JWC_PARAM '\n' ); + for( i=0; i= JWRITE_STACK_DEPTH ) + JWC(error)= JWRITE_STACK_FULL; // array/object nesting > JWRITE_STACK_DEPTH + else + { + JWC(nodeStack[++JWC(stackpos)]).nodeType= nodeType; + JWC(nodeStack[JWC(stackpos)]).elementNo= 0; + } +} + +enum jwNodeType jwPop( JWC_DECL0 ) +{ + enum jwNodeType retval= JWC(nodeStack[JWC(stackpos)]).nodeType; + if( JWC(stackpos) == 0 ) + JWC(error)= JWRITE_STACK_EMPTY; // stack underflow error (too many 'end's) + else + JWC(stackpos)--; + return retval; +} + +void jwPutch( JWC_DECL char c ) +{ + if( (unsigned int)(JWC(bufp) - JWC(buffer)) >= JWC(buflen) ) + { + JWC(error)= JWRITE_BUF_FULL; + }else{ + *JWC(bufp)++ = c; + } +} + +// put string enclosed in quotes +// +void jwPutstr( JWC_DECL char *str ) +{ + jwPutch( JWC_PARAM '\"' ); + while( *str != '\0' ) + jwPutch( JWC_PARAM *str++ ); + jwPutch( JWC_PARAM '\"' ); +} + +// put raw string +// +void jwPutraw( JWC_DECL char *str ) +{ + while( *str != '\0' ) + jwPutch( JWC_PARAM *str++ ); +} + + +// *common Object function* +// - checks error +// - checks current node is OBJECT +// - adds comma if reqd +// - adds "key" : +// +int _jwObj( JWC_DECL char *key ) +{ + if(JWC(error) == JWRITE_OK) + { + JWC(callNo)++; + if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_OBJECT ) + JWC(error)= JWRITE_NOT_OBJECT; // tried to write Object key/value into Array + else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 ) + jwPutch( JWC_PARAM ',' ); + jwPretty( JWC_PARAM0 ); + jwPutstr( JWC_PARAM key ); + jwPutch( JWC_PARAM ':' ); + if( JWC(isPretty) ) + jwPutch( JWC_PARAM ' ' ); + } + return JWC(error); +} + +// *common Array function* +// - checks error +// - checks current node is ARRAY +// - adds comma if reqd +// +int _jwArr( JWC_DECL0 ) +{ + if(JWC(error) == JWRITE_OK) + { + JWC(callNo)++; + if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_ARRAY ) + JWC(error)= JWRITE_NOT_ARRAY; // tried to write array value into Object + else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 ) + jwPutch( JWC_PARAM ',' ); + jwPretty( JWC_PARAM0 ); + } + return JWC(error); +} + +//================================================================= +// +// modp value-to-string functions +// - modified for C89 +// +// We use these functions as they are a lot faster than sprintf() +// +// Origin of these routines: +/* + *
+ * Copyright © 2007, Nick Galbreath -- nickg [at] modp [dot] com
+ * All rights reserved.
+ * http://code.google.com/p/stringencoders/
+ * Released under the bsd license.
+ * 
+ */ + +static void strreverse(char* begin, char* end) +{ + char aux; + while (end > begin) + aux = *end, *end-- = *begin, *begin++ = aux; +} + +/** \brief convert an signed integer to char buffer + * + * \param[in] value + * \param[out] buf the output buffer. Should be 16 chars or more. + */ +void modp_itoa10(int32_t value, char* str) +{ + char* wstr=str; + // Take care of sign + unsigned int uvalue = (value < 0) ? -value : value; + // Conversion. Number is reversed. + do *wstr++ = (char)(48 + (uvalue % 10)); while(uvalue /= 10); + if (value < 0) *wstr++ = '-'; + *wstr='\0'; + + // Reverse string + strreverse(str,wstr-1); +} + +/** + * Powers of 10 + * 10^0 to 10^9 + */ +static const double pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000, + 10000000, 100000000, 1000000000}; + +/** \brief convert a floating point number to char buffer with a + * variable-precision format, and no trailing zeros + * + * This is similar to "%.[0-9]f" in the printf style, except it will + * NOT include trailing zeros after the decimal point. This type + * of format oddly does not exists with printf. + * + * If the input value is greater than 1<<31, then the output format + * will be switched exponential format. + * + * \param[in] value + * \param[out] buf The allocated output buffer. Should be 32 chars or more. + * \param[in] precision Number of digits to the right of the decimal point. + * Can only be 0-9. + */ +void modp_dtoa2(double value, char* str, int prec) +{ + /* if input is larger than thres_max, revert to exponential */ + const double thres_max = (double)(0x7FFFFFFF); + int count; + double diff = 0.0; + char* wstr = str; + int neg= 0; + int whole; + double tmp; + uint32_t frac; + + /* Hacky test for NaN + * under -fast-math this won't work, but then you also won't + * have correct nan values anyways. The alternative is + * to link with libmath (bad) or hack IEEE double bits (bad) + */ + if (! (value == value)) { + str[0] = 'n'; str[1] = 'a'; str[2] = 'n'; str[3] = '\0'; + return; + } + + if (prec < 0) { + prec = 0; + } else if (prec > 9) { + /* precision of >= 10 can lead to overflow errors */ + prec = 9; + } + + /* we'll work in positive values and deal with the + negative sign issue later */ + if (value < 0) { + neg = 1; + value = -value; + } + + + whole = (int) value; + tmp = (value - whole) * pow10[prec]; + frac = (uint32_t)(tmp); + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + /* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */ + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && ((frac == 0) || (frac & 1))) { + /* if halfway, round up if odd, OR + if last digit is 0. That last part is strange */ + ++frac; + } + + /* for very large numbers switch back to native sprintf for exponentials. + anyone want to write code to replace this? */ + /* + normal printf behavior is to print EVERY whole number digit + which can be 100s of characters overflowing your buffers == bad + */ + if (value > thres_max) { + sprintf(str, "%e", neg ? -value : value); + return; + } + + if (prec == 0) { + diff = value - whole; + if (diff > 0.5) { + /* greater than 0.5, round up, e.g. 1.6 -> 2 */ + ++whole; + } else if (diff == 0.5 && (whole & 1)) { + /* exactly 0.5 and ODD, then round up */ + /* 1.5 -> 2, but 2.5 -> 2 */ + ++whole; + } + + //vvvvvvvvvvvvvvvvvvv Diff from modp_dto2 + } else if (frac) { + count = prec; + // now do fractional part, as an unsigned number + // we know it is not 0 but we can have leading zeros, these + // should be removed + while (!(frac % 10)) { + --count; + frac /= 10; + } + //^^^^^^^^^^^^^^^^^^^ Diff from modp_dto2 + + // now do fractional part, as an unsigned number + do { + --count; + *wstr++ = (char)(48 + (frac % 10)); + } while (frac /= 10); + // add extra 0s + while (count-- > 0) *wstr++ = '0'; + // add decimal + *wstr++ = '.'; + } + + // do whole part + // Take care of sign + // Conversion. Number is reversed. + do *wstr++ = (char)(48 + (whole % 10)); while (whole /= 10); + if (neg) { + *wstr++ = '-'; + } + *wstr='\0'; + strreverse(str, wstr-1); +} +//================================================================= + +/* end of jWrite.c */ diff --git a/extlibs/jWrite.h b/extlibs/jWrite.h new file mode 100644 index 0000000..308139c --- /dev/null +++ b/extlibs/jWrite.h @@ -0,0 +1,217 @@ +// +// jWrite.h +// +// A *really* simple JSON writer in C (C89) +// - a collection of functions to generate JSON semi-automatically +// +// The idea is to simplify writing native C values into a JSON string and +// to provide some error trapping to ensure that the result is valid JSON. +// +// Example: +// jwOpen( buffer, buflen, JW_OBJECT, JW_PRETTY ); // open root node as object +// jwObj_string( "key", "value" ); +// jwObj_int( "int", 1 ); +// jwObj_array( "anArray"); +// jwArr_int( 0 ); +// jwArr_int( 1 ); +// jwArr_int( 2 ); +// jwEnd(); +// err= jwClose(); // close root object +// +// results in: +// +// { +// "key": "value", +// "int": 1, +// "anArray": [ +// 0, +// 1, +// 2 +// ] +// } +// +// Note that jWrite handles string quoting and getting commas in the right place. +// If the sequence of calls is incorrect +// e.g. +// jwOpen( buffer, buflen, JW_OBJECT, 1 ); +// jwObj_string( "key", "value" ); +// jwArr_int( 0 ); +// ... +// +// then the error code returned from jwClose() would indicate that you attempted to +// put an array element into an object (instead of a key:value pair) +// To locate the error, the supplied buffer has the JSON created upto the error point +// and a call to jwErrorPos() would return the function call at which the error occurred +// - in this case 3, the 3rd function call "jwArr_int(0)" is not correct at this point. +// +// The root JSON type can be JW_OBJECT or JW_ARRAY. +// +// For more information on each function, see the prototypes below. +// +// +// GLOBAL vs. Application-Supplied Control Structure +// ------------------------------------------------- +// jWrite requires a jWriteControl structure to save the internal state. +// For many applications it is much simpler for this to be a global variable as +// used by the above examples. +// +// To use multiple instances of jWrite, an application has to supply unique instances +// of jWriteControl structures. +// +// This feature is enabled by commenting out the definition of JW_GLOBAL_CONTROL_STRUCT +// +// All the jWrite functions then take an additional parameter: a ptr to the structure +// e.g. +// struct jWriteControl jwc; +// +// jwOpen( &jwc, buffer, buflen, JW_OBJECT, 1 ); +// jwObj_string( &jwc, "key", "value" ); +// jwObj_int( &jwc, "int", 1 ); +// jwObj_array( &jwc, "anArray"); +// jwArr_int( &jwc, 0 ); +// jwArr_int( &jwc, 1 ); +// jwArr_int( &jwc, 2 ); +// jwEnd( &jwc ); +// err= jwClose( &jwc ); +// +// - which is more flexible, but a pain to type in ! +// +// TonyWilk, Mar 2015 +// +// + +#ifndef JWRITE_H_ +#define JWRITE_H_ + + +#define JW_GLOBAL_CONTROL_STRUCT // <--- comment this out to use applic-supplied jWriteControl +#define JWRITE_STACK_DEPTH 32 // max nesting depth of objects/arrays + +#define JW_COMPACT 0 // output string control for jwOpen() +#define JW_PRETTY 1 // pretty adds \n and indentation + +enum jwNodeType{ + JW_OBJECT= 1, + JW_ARRAY +}; + +struct jwNodeStack{ + enum jwNodeType nodeType; + int elementNo; +}; + +struct jWriteControl{ + char *buffer; // pointer to application's buffer + unsigned int buflen; // length of buffer + char *bufp; // current write position in buffer + char tmpbuf[32]; // local buffer for int/double convertions + int error; // error code + int callNo; // API call on which error occurred + struct jwNodeStack nodeStack[JWRITE_STACK_DEPTH]; // stack of array/object nodes + int stackpos; + int isPretty; // 1= pretty output (inserts \n and spaces) +}; + +// Error Codes +// ----------- +#define JWRITE_OK 0 +#define JWRITE_BUF_FULL 1 // output buffer full +#define JWRITE_NOT_ARRAY 2 // tried to write Array value into Object +#define JWRITE_NOT_OBJECT 3 // tried to write Object key/value into Array +#define JWRITE_STACK_FULL 4 // array/object nesting > JWRITE_STACK_DEPTH +#define JWRITE_STACK_EMPTY 5 // stack underflow error (too many 'end's) +#define JWRITE_NEST_ERROR 6 // nesting error, not all objects closed when jwClose() called + + +// API functions +// ------------- + +// Returns '\0'-termianted string describing the error (as returned by jwClose()) +// +char *jwErrorToString( int err ); + + +#ifdef JW_GLOBAL_CONTROL_STRUCT /* USING GLOBAL g_jWriteControl */ + +// jwOpen +// - initialises jWrite with the application supplied 'buffer' of length 'buflen' +// in operation, the buffer will always contain a valid '\0'-terminated string +// - jWrite will not overrun the buffer (it returns an "output buffer full" error) +// - rootType is the base JSON type: JW_OBJECT or JW_ARRAY +// - isPretty controls 'prettifying' the output: JW_PRETTY or JW_COMPACT +void jwOpen( char *buffer, unsigned int buflen, enum jwNodeType rootType, int isPretty ); + +// jwClose +// - closes the element opened by jwOpen() +// - returns error code (0 = JWRITE_OK) +// - after an error, all following jWrite calls are skipped internally +// so the error code is for the first error detected +int jwClose( ); + +// jwErrorPos +// - if jwClose returned an error, this function returns the number of the jWrite function call +// which caused that error. +int jwErrorPos( ); + +// Object insertion functions +// - used to insert "key":"value" pairs into an object +// +void jwObj_string( char *key, char *value ); +void jwObj_int( char *key, int value ); +void jwObj_double( char *key, double value ); +void jwObj_bool( char *key, int oneOrZero ); +void jwObj_null( char *key ); +void jwObj_object( char *key ); +void jwObj_array( char *key ); + +// Array insertion functions +// - used to insert "value" elements into an array +// +void jwArr_string( char *value ); +void jwArr_int( int value ); +void jwArr_double( double value ); +void jwArr_bool( int oneOrZero ); +void jwArr_null( ); +void jwArr_object( ); +void jwArr_array( ); + +// jwEnd +// - defines the end of an Object or Array definition +int jwEnd( ); + + +// these 'raw' routines write the JSON value as the contents of rawtext +// i.e. enclosing quotes are not added +// - use if your app. supplies its own value->string functions +// +void jwObj_raw( char *key, char *rawtext ); +void jwArr_raw( char *rawtext ); + +#else /* JW_GLOBAL_CONTROL_STRUCT not defined */ +// Same API functions with app-supplied control struct option +// +void jwOpen( struct jWriteControl *jwc, char *buffer, unsigned int buflen, enum jwNodeType rootType, int isPretty ); +int jwClose( struct jWriteControl *jwc ); +int jwErrorPos( struct jWriteControl *jwc ); +void jwObj_string( struct jWriteControl *jwc, char *key, char *value ); +void jwObj_int( struct jWriteControl *jwc, char *key, int value ); +void jwObj_double( struct jWriteControl *jwc, char *key, double value ); +void jwObj_bool( struct jWriteControl *jwc, char *key, int oneOrZero ); +void jwObj_null( struct jWriteControl *jwc, char *key ); +void jwObj_object( struct jWriteControl *jwc, char *key ); +void jwObj_array( struct jWriteControl *jwc, char *key ); +void jwArr_string( struct jWriteControl *jwc, char *value ); +void jwArr_int( struct jWriteControl *jwc, int value ); +void jwArr_double( struct jWriteControl *jwc, double value ); +void jwArr_bool( struct jWriteControl *jwc, int oneOrZero ); +void jwArr_null( struct jWriteControl *jwc ); +void jwArr_object( struct jWriteControl *jwc ); +void jwArr_array( struct jWriteControl *jwc ); +int jwEnd( struct jWriteControl *jwc ); +void jwObj_raw( struct jWriteControl *jwc, char *key, char *rawtext ); +void jwArr_raw( struct jWriteControl *jwc, char *rawtext ); + +#endif /* JW_GLOBAL_CONTROL_STRUCT */ + +#endif /*JWRITE_H_ */ +/* end of jWrite.h */ diff --git a/include/MQTT.h b/include/MQTT.h new file mode 100644 index 0000000..5ba6c46 --- /dev/null +++ b/include/MQTT.h @@ -0,0 +1,82 @@ + /* Copyright 2022 Bogdan Pilyugin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * File name: MQTT.h + * Project: ChargePointMainboard + * Created on: 2022-07-21 + * Author: Bogdan Pilyugin + * Description: + */ + +#ifndef MAIN_INCLUDE_MQTT_H_ +#define MAIN_INCLUDE_MQTT_H_ + +#include "mqtt_client.h" + +typedef int mqtt_app_err_t; + +#define API_OK 0 /*!< success (no error) */ +#define API_INTERNAL_ERR 1 +#define API_WRONG_JSON_ERR 2 +#define API_NO_ID_ERR 3 +#define API_ID_OVERSIZE_ERR 4 +#define API_NO_API_ERR 5 +#define API_VERSION_ERR 6 +#define API_NO_REQUEST_ERR 7 +#define API_UNSUPPORTED_METHOD_ERR 8 +#define API_NO_URL_ERR 9 +#define API_URL_OVERSIZE_ERR 10 + +#define API_URL_NOT_FOUND_ERR 11 + +#define API_NO_POSTDATA_ERR 12 +#define API_POSTDATA_OVERSIZE_ERR 13 +#define API_FILE_OVERSIZE_ERR 14 +#define API_FILE_EMPTY_ERR 15 +#define API_UNKNOWN_ERR 16 + + + + +typedef enum +{ + PUBLISH_CONTROL_DATA, + PUBLISH_SCREEN_DATA +} publish_data_type; + + +typedef struct +{ + publish_data_type dt; + char *raw_data_ptr; + uint32_t data_lenth; +} DATA_SEND_STRUCT; + +/** + * @brief wrapper around esp_mqtt_client_handle_t with additional info + * + */ +typedef struct +{ + int mqtt_index; /// numerical index of mqtt client + esp_mqtt_client_handle_t mqtt; /// mqtt client handle + QueueHandle_t mqtt_queue; /// queue for data sending over current mqtt client + int wait_delivery_bit; /// is message delivered before timeout flag + bool is_connected; /// is client connected flag +} mqtt_client_t; + +mqtt_client_t* GetMQTTHandlesPool(int idx); +QueueHandle_t GetMQTTSendQueue(int idx); + +#endif /* MAIN_INCLUDE_MQTT_H_ */ diff --git a/src/MQTT.c b/src/MQTT.c new file mode 100644 index 0000000..495a6a6 --- /dev/null +++ b/src/MQTT.c @@ -0,0 +1,740 @@ +/* Copyright 2022 Bogdan Pilyugin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * File name: MQTT.c + * Project: ChargePointMainboard + * Created on: 2022-07-21 + * Author: Bogdan Pilyugin + * Description: + */ +#include "esp_log.h" +#include "Helpers.h" +#include "SystemConfiguration.h" +#include "jWrite.h" +#include "jRead.h" +#include "NetTransport.h" +#include "HTTPServer.h" +#include "romfs.h" +#include "MQTT.h" + +#define CH_MESSAGE_BUFER_LENTH 32 //size of mqtt queue + +#define MESSAGE_LENGTH 32 //base message length, mainly depended by radio requirements +#define MAX_JSON_MESSAGE 256 //max size of mqtt message to publish +#define MAX_FILE_PUBLISH 4096 //bufer for mqtt data publish +#define MAX_DYNVAR_LENTH 64 +#define MAX_FILENAME_LENTH 15 +#define MAX_ERROR_MESSAGE 32 +#define MAX_ERROR_JSON 256 +#define MAX_MESSAGE_ID 15 + +#define MAX_POST_DATA_LENTH 512 +#define MQTT_RECONNECT_CHANGE_ADAPTER 3 + +static SemaphoreHandle_t xSemaphoreMQTTHandle = NULL; +static StaticSemaphore_t xSemaphoreMQTTBuf; + +static const int MQTT1_WAIT_DELIVERY_BIT = BIT0; +static const int MQTT2_WAIT_DELIVERY_BIT = BIT1; + +static StaticQueue_t xStaticMQTT1MessagesQueue; +static StaticQueue_t xStaticMQTT2MessagesQueue; +uint8_t MQTT1MessagesQueueStorageArea[CH_MESSAGE_BUFER_LENTH * sizeof(DATA_SEND_STRUCT)]; +uint8_t MQTT2MessagesQueueStorageArea[CH_MESSAGE_BUFER_LENTH * sizeof(DATA_SEND_STRUCT)]; + + +mqtt_client_t mqtt[MQTT_CLIENTS_NUM] = { 0 }; + +static const char topic_tx[] = "/UPLINK/"; //subtopic for transmit +static const char topic_rx[] = "/DOWNLINK/"; //subtopic to receive + +const char apiver[] = "2.0"; +const char tagGet[] = "GET"; +const char tagPost[] = "POST"; + +#define TAG "MQTTApp" + +const char* mqtt_app_err_descr[] = { +"Operation OK", +"Internal error", +"Wrong json format", +"Key 'idmess' not found", +"Key 'idmess' value too long", +"Key 'api' not found", +"API version not supported", +"Key 'request' not found", +"Unsupported HTTP method", +"Key 'url' not found", +"Key 'url' value too long", +"URL not found", +"Key 'postdata' not found", +"Key 'postdata' too long", +"File size too big", +"File is empty", +"Unknown error" +}; + +const char* mqtt_app_err_breef[] = { +"OK", +"INTERNAL_ERR", +"WRONG_JSON_ERR", +"NO_ID_ERR", +"ID_OVERSIZE_ERR", +"NO_API_ERR", +"VERSION_ERR", +"NO_REQUEST_ERR", +"UNSUPPORTED_METHOD_ERR", +"NO_URL_ERR", +"URL_OVERSIZE_ERR", +"URL_NOT_FOUND_ERR", +"NO_POSTDATA_ERR", +"POSTDATA_OVERSIZE_ERR", +"FILE_OVERSIZE_ERR", +"FILE_EMPTY_ERR", +"UNKNOWN_ERR" +}; + +static void ControlDataHandler(char *data, uint32_t len, int idx); +static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); + +mqtt_client_t* GetMQTTHandlesPool(int idx) +{ + return &mqtt[idx]; +} + +QueueHandle_t GetMQTTSendQueue(int idx) +{ + return mqtt[idx].mqtt_queue; +} + +bool GetMQTT1Connected(void) +{ + return mqtt[0].is_connected; +} + +bool GetMQTT2Connected(void) +{ + return mqtt[1].is_connected; +} + +static void log_error_if_nonzero(const char *message, int error_code) +{ + if (error_code != 0) + { + ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code); + } +} + +static void ComposeTopicControl(char *topic, char *roottopic, char *ident, uint8_t dir) +{ + char id[4]; + char id2[8]; + strcpy((char*) topic, roottopic); + if (dir == 0) + strcat((char*) topic, topic_rx); + else + strcat((char*) topic, topic_tx); + GetChipId((uint8_t*) id); + BytesToStr((unsigned char*) id, (unsigned char*) id2, 4); + strcat((char*) topic, (const char*) id2); + strcat((char*) topic, "/"); + strcat((char*) topic, ident); + strcat((char*) topic, (const char*) "/CONTROL"); +} + +static void ComposeTopicScreen(char *topic, char *roottopic, char *ident, uint8_t dir) +{ + char id[4]; + char id2[8]; + strcpy((char*) topic, roottopic); + if (dir == 0) + strcat((char*) topic, topic_rx); + else + strcat((char*) topic, topic_tx); + GetChipId((uint8_t*) id); + BytesToStr((unsigned char*) id, (unsigned char*) id2, 4); + strcat((char*) topic, (const char*) id2); + strcat((char*) topic, "/"); + strcat((char*) topic, ident); + strcat((char*) topic, (const char*) "/SCREEN"); +} + +typedef enum +{ + UNSUPPORTED = 0, + GET, + POST +} mqtt_api_request_t; + +static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + xSemaphoreTake(xSemaphoreMQTTHandle, pdMS_TO_TICKS(1000)); + ESP_LOGI(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id); + esp_mqtt_event_handle_t event = event_data; + esp_mqtt_client_handle_t client = event->client; + mqtt_client_t *ctx = (mqtt_client_t*) event->user_context; + + int msg_id; + static int MQTTReconnectCounter = 0; //Change network adapter every MQTT_RECONNECT_CHANGE_ADAPTER number attempts + switch ((esp_mqtt_event_id_t) event_id) + { + case MQTT_EVENT_CONNECTED: + ctx->is_connected = true; + MQTTReconnectCounter = 0; + ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED client %d", ctx->mqtt_index); + char sub[64]; + ComposeTopicControl(sub, GetSysConf()->mqttStation[ctx->mqtt_index].RootTopic, + GetSysConf()->mqttStation[ctx->mqtt_index].ClientID, + 0); + msg_id = esp_mqtt_client_subscribe(client, (const char*) sub, 0); + ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + ComposeTopicScreen(sub, GetSysConf()->mqttStation[ctx->mqtt_index].RootTopic, + GetSysConf()->mqttStation[ctx->mqtt_index].ClientID, + 0); + msg_id = esp_mqtt_client_subscribe(client, (const char*) sub, 0); + ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + + break; + case MQTT_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED client %d", ctx->mqtt_index); + if (++MQTTReconnectCounter > MQTT_RECONNECT_CHANGE_ADAPTER) + { + MQTTReconnectCounter = 0; + NextDefaultNetIF(); + } + ctx->is_connected = false; + break; + case MQTT_EVENT_SUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_UNSUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_PUBLISHED: + ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_DATA: + ESP_LOGI(TAG, "MQTT_EVENT_DATA, client %d", ctx->mqtt_index); + char topic[64]; + //Check if topic is CONTROL and pass data to handler + ComposeTopicControl(topic, GetSysConf()->mqttStation[ctx->mqtt_index].RootTopic, + GetSysConf()->mqttStation[ctx->mqtt_index].ClientID, + 0); + if (!memcmp(topic, event->topic, event->topic_len)) + { + ControlDataHandler(event->data, event->data_len, ctx->mqtt_index); + ESP_LOGI(TAG, "Control data handler on client %d", ctx->mqtt_index); + + } + //end of CONTROL + + //Check if topic is SCREEN and pass data to handler + ComposeTopicScreen(topic, GetSysConf()->mqttStation[ctx->mqtt_index].RootTopic, + GetSysConf()->mqttStation[ctx->mqtt_index].ClientID, + 0); + if (!memcmp(topic, event->topic, event->topic_len)) + { + // ScreenDataHandler(event->data, event->data_len, ctx->mqtt_index); + ESP_LOGI(TAG, "Screen data handler on client %d", ctx->mqtt_index); + } + + //end of SCREEN + + break; + case MQTT_EVENT_ERROR: + ESP_LOGI(TAG, "MQTT_EVENT_ERROR, client %d", ctx->mqtt_index); + if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) + { + log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err); + log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err); + log_error_if_nonzero("captured as transport's socket errno", + event->error_handle->esp_transport_sock_errno); + ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno)); + } + break; + default: + ESP_LOGI(TAG, "Other event id:%d", event->event_id); + break; + } + xSemaphoreGive(xSemaphoreMQTTHandle); +} + +static void reconnect_MQTT_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, + void *event_data) +{ + for (int i = 0; i < MQTT_CLIENTS_NUM; ++i) + { + if (mqtt[i].mqtt) + { + esp_mqtt_client_disconnect(mqtt[i].mqtt); + esp_mqtt_client_reconnect(mqtt[i].mqtt); + } + } +} + +static mqtt_app_err_t ResponceWithError(int idx, + espfs_file_t *file, + char *id, + char *url, + mqtt_app_err_t api_err) +{ + espfs_fclose(file); + char JSONErrorMess[MAX_ERROR_JSON]; + jwOpen(JSONErrorMess, MAX_ERROR_JSON, JW_OBJECT, JW_PRETTY); + if (id[0]) + jwObj_string("messid", (char*) id); + else + jwObj_string("messid", "ERROR"); + jwObj_string("api", (char*) apiver); + jwObj_string("responce", (char*) mqtt_app_err_breef[api_err]); + jwObj_string("descript", (char*) mqtt_app_err_descr[api_err]); + if (url[0]) + jwObj_string("url", (char*) url); + else + jwObj_string("url", "ERROR"); + jwEnd(); + jwClose(); + char *buf = (char*) malloc(strlen(JSONErrorMess) + 1); + if (buf) + { + memcpy(buf, JSONErrorMess, strlen(JSONErrorMess)); + DATA_SEND_STRUCT DSS; + DSS.dt = PUBLISH_CONTROL_DATA; + DSS.raw_data_ptr = buf; + DSS.data_lenth = strlen(JSONErrorMess); + if (xQueueSend(mqtt[idx].mqtt_queue, &DSS, pdMS_TO_TICKS(1000)) == pdPASS) + return API_OK; + else + { + free(buf); + return API_INTERNAL_ERR; + } + } + else + { // ERR internal error on publish error + return API_INTERNAL_ERR; + } +} + +static mqtt_app_err_t ResponceWithFile(int idx, espfs_file_t *file, + char *id, + char *url) +{ + struct espfs_stat_t stat; + char *filebuf = NULL; + char *outbuf = NULL; + uint32_t fileLenth, filePtr, readBytes; + espfs_fstat(file, &stat); + fileLenth = stat.size; + mqtt_app_err_t api_err = API_UNKNOWN_ERR; + if (fileLenth > MAX_FILE_PUBLISH) + { + ESP_LOGE(TAG, "File is too big"); + api_err = API_FILE_OVERSIZE_ERR; + goto file_send_err; + } + outbuf = (char*) malloc(MAX_FILE_PUBLISH + 256); + filebuf = (char*) malloc(fileLenth); + + if (!outbuf || !filebuf) + { + ESP_LOGE(TAG, "Failed to allocate memory"); + api_err = API_INTERNAL_ERR; + goto file_send_err; + } + + if (espfs_fread(file, filebuf, fileLenth) == 0) + { + ESP_LOGE(TAG, "Read no bytes from file"); + api_err = API_FILE_EMPTY_ERR; + goto file_send_err; + } + espfs_fclose(file); + + jwOpen(outbuf, MAX_FILE_PUBLISH, JW_OBJECT, JW_PRETTY); + jwObj_string("messid", (char*) id); + jwObj_string("api", (char*) apiver); + jwObj_string("responce", (char*) mqtt_app_err_breef[API_OK]); + jwObj_string("descript", (char*) mqtt_app_err_descr[API_OK]); + jwObj_string("url", (char*) url); + jwObj_string("body", "bodydata"); + jwEnd(); + jwClose(); + + char *fdata = memmem(outbuf, MAX_FILE_PUBLISH, "\"bodydata\"", strlen("\"bodydata\"")); + filePtr = 0; + readBytes = 0; + while (filePtr < fileLenth && readBytes < (MAX_FILE_PUBLISH - MAX_DYNVAR_LENTH)) + { + if (filebuf[filePtr] == '~') + { + int k = 0; + char DynVarName[16]; + while (filePtr < fileLenth && k < 16 && (filebuf[++filePtr] != '~')) + DynVarName[k++] = filebuf[filePtr]; + if (filebuf[filePtr] == '~') + { //got valid dynamic variable name + DynVarName[k] = 0x00; + readBytes += HTTPPrint(NULL, &fdata[readBytes], DynVarName); + filePtr++; + } + } + else + fdata[readBytes++] = filebuf[filePtr++]; + } + const char tail[] = "}"; + strcat((fdata + readBytes), tail); + free(filebuf); + DATA_SEND_STRUCT DSS; + DSS.dt = PUBLISH_CONTROL_DATA; + DSS.raw_data_ptr = outbuf; + DSS.data_lenth = (fdata - outbuf) + readBytes + strlen(tail); + if (xQueueSend(mqtt[idx].mqtt_queue, &DSS, pdMS_TO_TICKS(1000)) == pdPASS) + return API_OK; + else + { + ESP_LOGE(TAG, "Failed to write mqtt queue"); + api_err = API_INTERNAL_ERR; + goto file_send_err; + } +file_send_err: + free(outbuf); + free(filebuf); + return api_err; +} + +static void ControlDataHandler(char *data, uint32_t len, int idx) +{ + struct jReadElement result; + char URL[MAX_FILENAME_LENTH + 1]; + char ID[MAX_MESSAGE_ID + 1]; + char POST_DATA[MAX_POST_DATA_LENTH + 1]; + mqtt_api_request_t req = UNSUPPORTED; + mqtt_app_err_t api_err = API_UNKNOWN_ERR; + ID[0] = 0x00; + URL[0] = 0x00; + espfs_file_t *file = NULL; + + jRead(data, "", &result); + if (result.dataType == JREAD_OBJECT) + { + /*Checking and get ID message*/ + jRead(data, "{'messid'", &result); + if (result.elements == 1) + { + if (result.bytelen > MAX_MESSAGE_ID) + { + api_err = API_ID_OVERSIZE_ERR; + goto api_json_err; + } + memcpy(ID, result.pValue, result.bytelen); + ID[result.bytelen] = 0x00; + } + else + { + api_err = API_NO_ID_ERR; + goto api_json_err; + } + + /*Checking and get API version*/ + jRead(data, "{'api'", &result); + if (result.elements == 1) + { + if (memcmp(apiver, result.pValue, result.bytelen)) + { + api_err = API_VERSION_ERR; + goto api_json_err; + } + } + else + { + api_err = API_NO_API_ERR; + goto api_json_err; + } + + /*Checking and get request type POST, GET or UNSUPPORTED*/ + jRead(data, "{'request'", &result); + if (result.elements == 1) + { + if (!memcmp(tagGet, result.pValue, result.bytelen)) + req = GET; + else if (!memcmp(tagPost, result.pValue, result.bytelen)) + req = POST; + } + else + { + api_err = API_NO_REQUEST_ERR; + goto api_json_err; + } + + /*Checking and get url*/ + jRead(data, "{'url'", &result); + if (result.elements == 1) + { + if (result.bytelen > MAX_FILENAME_LENTH) + { + api_err = API_URL_OVERSIZE_ERR; + goto api_json_err; + } + memcpy(URL, result.pValue, result.bytelen); + URL[result.bytelen] = 0x00; + file = espfs_fopen(fs, URL); + if (!file) + { + api_err = API_URL_NOT_FOUND_ERR; + goto api_json_err; + } + + } + else + { + api_err = API_NO_URL_ERR; + goto api_json_err; + } + + if (req == POST) + { + /*Checking and get POST DATA*/ + jRead(data, "{'postdata'", &result); + if (result.elements == 1) + { + if (result.bytelen > MAX_POST_DATA_LENTH) + { + api_err = API_POSTDATA_OVERSIZE_ERR; + goto api_json_err; + } + memcpy(POST_DATA, result.pValue, result.bytelen); + POST_DATA[result.bytelen] = 0x00; + } + else + { + api_err = API_NO_POSTDATA_ERR; + goto api_json_err; + } + + HTTPPostApp(NULL, URL, POST_DATA); + + jRead(data, "{'reload'", &result); + if (result.elements == 1 && !memcmp("true", result.pValue, result.bytelen)) + { + if (ResponceWithFile(idx, file, ID, URL) == API_OK) + return; + else + goto api_json_err; + } + else + { + if (ResponceWithError(idx, file, ID, URL, API_OK) != API_OK) + ESP_LOGE(TAG, "Failed to allocate memory for file MQTT message"); + } + return; + } + else if (req == GET) + { + //Here GET handler, send file wrapped into json + if (ResponceWithFile(idx, file, ID, URL) == API_OK) + return; + else + goto api_json_err; + } + else + { + api_err = API_UNSUPPORTED_METHOD_ERR; + goto api_json_err; + } + } + else + { //ERR no json format + api_err = API_WRONG_JSON_ERR; + goto api_json_err; + } + +api_json_err: + ESP_LOGE(TAG, "ERROR:%s:%s", mqtt_app_err_breef[api_err], mqtt_app_err_descr[api_err]); + if (ResponceWithError(idx, file, ID, URL, api_err) != API_OK) + ESP_LOGE(TAG, "Failed to allocate memory for file MQTT message"); +} + +void MQTTStart(void) +{ + for (int i = 0; i < MQTT_CLIENTS_NUM; ++i) + { + if (mqtt[i].mqtt) + esp_mqtt_client_reconnect(mqtt[i].mqtt); + } +} + +void MQTTStop(void) +{ + for (int i = 0; i < MQTT_CLIENTS_NUM; ++i) + { + if (mqtt[i].mqtt) + esp_mqtt_client_disconnect(mqtt[i].mqtt); + } +} + +void MQTTReconnect(void) +{ + for (int i = 0; i < MQTT_CLIENTS_NUM; ++i) + { + if (mqtt[i].mqtt) + { + esp_mqtt_client_disconnect(mqtt[i].mqtt); + esp_mqtt_client_reconnect(mqtt[i].mqtt); + } + } +} + +static void MQTTPublish(mqtt_client_t *mqtt, DATA_SEND_STRUCT *DSS) +{ + char top[64]; + switch (DSS->dt) + { + + case PUBLISH_CONTROL_DATA: + ComposeTopicControl(top, GetSysConf()->mqttStation[mqtt->mqtt_index].RootTopic, + GetSysConf()->mqttStation[mqtt->mqtt_index].ClientID, + 1); + if (mqtt) + { + esp_mqtt_client_publish(mqtt->mqtt, (const char*) top, (const char*) DSS->raw_data_ptr, + DSS->data_lenth, + 0, + 0); + + ESP_LOGI(TAG, "TOPIC=%.*s", strlen(top), top); + } + else + ESP_LOGE(TAG, "MQTT client not initialized"); + + break; + + + case PUBLISH_SCREEN_DATA: + ComposeTopicScreen(top, GetSysConf()->mqttStation[mqtt->mqtt_index].RootTopic, + GetSysConf()->mqttStation[mqtt->mqtt_index].ClientID, + 1); + if (mqtt) + { + esp_mqtt_client_publish(mqtt->mqtt, (const char*) top, (const char*) DSS->raw_data_ptr, + DSS->data_lenth, + 0, + 0); + + ESP_LOGI(TAG, "TOPIC=%.*s", strlen(top), top); + } + else + ESP_LOGE(TAG, "MQTT client not initialized"); + + break; + + } +} + +void MQTTTaskTransmit(void *pvParameter) +{ + DATA_SEND_STRUCT DSS; + int idx = *(int*) pvParameter; + while (!mqtt[idx].mqtt_queue) + vTaskDelay(pdMS_TO_TICKS(300)); //wait for MQTT queue ready + while (1) + { + while (!mqtt[idx].is_connected) + vTaskDelay(pdMS_TO_TICKS(1000)); + xQueuePeek(mqtt[idx].mqtt_queue, &DSS, portMAX_DELAY); + MQTTPublish(&mqtt[idx], &DSS); + switch (DSS.dt) + { + case PUBLISH_SCREEN_DATA: + xQueueReceive(mqtt[idx].mqtt_queue, &DSS, 0); + free(DSS.raw_data_ptr); + break; + + case PUBLISH_CONTROL_DATA: + xQueueReceive(mqtt[idx].mqtt_queue, &DSS, 0); + free(DSS.raw_data_ptr); + break; + } + } +} + +static void start_mqtt() +{ + esp_mqtt_client_config_t mqtt_cfg = { 0 }; + + char url[40]; + char tmp[40]; + + for (int i = 0; i < MQTT_CLIENTS_NUM; ++i) + { + if (GetSysConf()->mqttStation[i].Flags1.bIsGlobalEnabled) + { + esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &reconnect_MQTT_handler, &mqtt[i].mqtt); + esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &reconnect_MQTT_handler, &mqtt[i].mqtt); + esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, &reconnect_MQTT_handler, &mqtt[i].mqtt); + + strcpy(url, "mqtt://"); + strcat(url, GetSysConf()->mqttStation[i].ServerAddr); + itoa(GetSysConf()->mqttStation[i].ServerPort, tmp, 10); + strcat(url, ":"); + strcat(url, tmp); + mqtt_cfg.uri = url; + mqtt_cfg.username = GetSysConf()->mqttStation[i].UserName; + mqtt_cfg.password = GetSysConf()->mqttStation[i].UserPass; + strcpy(tmp, GetSysConf()->mqttStation[i].ClientID); + strcat(tmp, "_"); + int len = strlen(tmp); + BytesToStr((unsigned char*) &GetSysConf()->imei, (unsigned char*) &tmp[len], 4); + mqtt_cfg.client_id = tmp; + mqtt[i].is_connected = false; + mqtt[i].mqtt_index = i; + mqtt_cfg.user_context = (void*) &mqtt[i]; + mqtt[i].mqtt = esp_mqtt_client_init(&mqtt_cfg); + /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */ + esp_mqtt_client_register_event(mqtt[i].mqtt, ESP_EVENT_ANY_ID, mqtt_event_handler, &mqtt[i].mqtt); + esp_mqtt_client_start(mqtt[i].mqtt); + xTaskCreate(MQTTTaskTransmit, "MQTTTaskTransmit", 1024 * 4, (void*) &mqtt[i].mqtt_index, 3, NULL); + } + } +} + +void MQTTRun(void) +{ + xSemaphoreMQTTHandle = xSemaphoreCreateBinaryStatic(&xSemaphoreMQTTBuf); + xSemaphoreGive(xSemaphoreMQTTHandle); + + MQTT1MessagesQueueHandle = NULL; + if (GetSysConf()->mqttStation[0].Flags1.bIsGlobalEnabled) + MQTT1MessagesQueueHandle = xQueueCreateStatic(CH_MESSAGE_BUFER_LENTH, + sizeof(DATA_SEND_STRUCT), + MQTT1MessagesQueueStorageArea, + &xStaticMQTT1MessagesQueue); + MQTT2MessagesQueueHandle = NULL; + if (GetSysConf()->mqttStation[1].Flags1.bIsGlobalEnabled) + MQTT2MessagesQueueHandle = xQueueCreateStatic(CH_MESSAGE_BUFER_LENTH, + sizeof(DATA_SEND_STRUCT), + MQTT2MessagesQueueStorageArea, + &xStaticMQTT2MessagesQueue); + + mqtt[0].mqtt_queue = MQTT1MessagesQueueHandle; + mqtt[0].wait_delivery_bit = MQTT1_WAIT_DELIVERY_BIT; + mqtt[1].mqtt_queue = MQTT2MessagesQueueHandle; + mqtt[1].wait_delivery_bit = MQTT2_WAIT_DELIVERY_BIT; + + + + start_mqtt(); +} +