added json lib and mqtt sources
This commit is contained in:
parent
96143f9042
commit
b49073877d
|
|
@ -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
|
||||
|
|
|
|||
784
extlibs/jRead.c
Normal file
784
extlibs/jRead.c
Normal 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
128
extlibs/jRead.h
Normal 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
561
extlibs/jWrite.c
Normal 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 © 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
217
extlibs/jWrite.h
Normal 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
82
include/MQTT.h
Normal 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
740
src/MQTT.c
Normal 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();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user