added json lib and mqtt sources

This commit is contained in:
Bogdan Pilyugin 2022-08-17 13:49:09 +02:00
parent 96143f9042
commit b49073877d
7 changed files with 2520 additions and 1 deletions

View File

@ -10,9 +10,16 @@ idf_component_register(
"src/GSMTransport.c" "src/GSMTransport.c"
"src/ETHTransport.c" "src/ETHTransport.c"
"src/SNTP.c" "src/SNTP.c"
"src/MQTT.c"
"src/OTA.c" "src/OTA.c"
"extlibs/jRead.c"
"extlibs/jWrite.c"
INCLUDE_DIRS "."
"include"
"src"
"extlibs"
INCLUDE_DIRS "." "include" "src"
REQUIRES nvs_flash REQUIRES nvs_flash
libespfs libespfs
esp_http_server esp_http_server

784
extlibs/jRead.c Normal file
View File

@ -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 <jRead.h>
#include <stdio.h>
// 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<destLength; i++ )
*pdest++= *psrc++;
}
*pdest= '\0';
return destBuffer;
}
// jReadCountObject
// - used when query ends at an object, we want to return the object length
// - on entry pJson -> "{... "
// - 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 <value>
// - 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 <key> : <value> , ... }
// 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<elem.bytelen) && (i<destlen-1); i++ )
*pDest++ = ((char *)elem.pValue)[i];
*pDest= '\0';
return elem.bytelen;
}
//-------------------------------------------------
// Optional String output Functions
//
char *jReadTypeStrings[]={
"Error", // 0
"Object", // 1
"Array", // 2
"String", // 3
"Number", // 4
"Bool", // 5
"null", // 6
"Object key", // 7
"colon", // 8
"eol", // 9
"comma", // 10
"}", // 11
"]", // 12
"* parameter" // 13
};
char *jReadTypeToString( int dataType )
{
return jReadTypeStrings[ dataType ];
};
char * jReadErrorStrings[]={
"Ok", // 0
"JSON does not match Query", // 1
"Error reading JSON value", // 2
"Expected \"key\"", // 3
"Expected ':'", // 4
"Object key not found", // 5
"Expected ',' in object", // 6
"Terminal value found before end of query", // 7
"Unexpected character", // 8
"Expected ',' in array", // 9
"Array element not found (bad index)", // 10
"Object key not found (bad index)", // 11
"Bad object key", // 12
"End of array found", // 13
"End of object found" // 14
};
char * jReadErrorToString( int error )
{
if( (error >=0 ) && (error <= 14))
return jReadErrorStrings[ error ];
return "Unknown error";
};
// end of jRead.c

128
extlibs/jRead.h Normal file
View File

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

561
extlibs/jWrite.c Normal file
View File

@ -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 <stddef.h>
#include <stdio.h>
#include <string.h> // memset()
#include <jWrite.h>
#include <stdint.h> // 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<JWC(stackpos)+1; i++ )
jwPutraw( JWC_PARAM " " );
}
}
// Push / Pop node stack
//
void jwPush( JWC_DECL enum jwNodeType nodeType )
{
if( (JWC(stackpos)+1) >= 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:
/*
* <pre>
* Copyright &copy; 2007, Nick Galbreath -- nickg [at] modp [dot] com
* All rights reserved.
* http://code.google.com/p/stringencoders/
* Released under the bsd license.
* </pre>
*/
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 */

217
extlibs/jWrite.h Normal file
View File

@ -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 */

82
include/MQTT.h Normal file
View File

@ -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_ */

740
src/MQTT.c Normal file
View File

@ -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();
}