Merge pull request #9357 from s-hadinger/alt_json_parser

Change replace ArduinoJson with JSMN for JSON parsing - Phase 1
This commit is contained in:
Theo Arends 2020-09-22 11:19:38 +02:00 committed by GitHub
commit a39e393a18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 2077 additions and 356 deletions

View File

@ -0,0 +1,181 @@
# JSMN lightweight JSON parser for Tasmota
Intro:
## Benefits
## How to use
`{"Device":"0x1234","Power":true,"Temperature":25.5}`
The JSON above is split into 8 tokens, and requires 32 bytes of dynamic memory.
- Token 0: `OBJECT`, size=3: `{"Device":"0x1234","Power":true,"Temperature":25.5}`
- Token 1: `KEY`: `Device`
- Token 2: `STRING`: `0x1234`
- Token 3: `KEY`: `Power`
- Token 4: `BOOL_TRUE`: `true`
- Token 5: `KEY`: `Temperature`
- Token 6: `FLOAT`: `25.5`
- Token 7: `INVALID`
If what you need is to parse a JSON Object for values with default values:
```
#include "JsonParser.h"
char json_buffer[] = "{\"Device\":\"0x1234\",\"Power\":true,\"Temperature\":25.6}";
JsonParserObject root = JsonParser(json_buffer).getRootObject();
if (!root) { ResponseCmndChar_P(PSTR("Invalid JSON")); return; }
uint16_t d = root.getUInt(PSTR("DEVICE"), 0xFFFF); // case insensitive
bool b = root.getBool(PSTR("Power"), false);
float f = root.getFloat(PSTR("Temperature), -100);
```
Alternative pattern, if you want to test the existence of the attribute first:
```
#include "JsonParser.h"
char json_buffer[] = "{\"Device\":\"0x1234\",\"Power\":true,\"Temperature\":25.6}";
JsonParserObject root = JsonParser(json_buffer).getRootObject();
if (!root) { ResponseCmndChar_P(PSTR("Invalid JSON")); return; }
JsonParserToken val = root[PSTR("DEVICE")];
if (val) {
d = val.getUInt();
}
val = root[PSTR("Power")];
if (val) {
b = val.getBool();
}
val = root[PSTR("Temperature)];
if (val) {
f = val.getFloat();
}
```
## Types and conversion
JSMN relies on the concept of JSON Tokens `JsonParserToken`. Tokens do not hold any data, but point to token boundaries in JSON string instead. Every jsmn token has a type, which indicates the type of corresponding JSON token. JSMN for Tasmota extends the type from JSMN to ease further parsing.
Types are:
- `INVALID` invalid token or end of stream, see Error Handling below
- `OBJECT` a JSON sub-object, `size()` contains the number of key/values of the object
- `ARRAY` a JSON array, `size()` contains the number of sub values
- `STRING` a JSON string, return the sub-string, unescaped, without surrounding quotes. UTF-8 is supported.
- `PRIMITIVE` an unrecognized JSON token, consider as an error
- `KEY` a JSON key in an object as a string
- `NULL` a JSON `null` value, automatically converted to `0` or `""`
- `BOOL_FALSE` a JSON `false` value, automatically converted to `0` or `""`
- `BOOL_TRUE` a JSON `true` value, automatically converted to `1` or `""`
- `UINT` a JSON unsigned int
- `INT` a JSON negative int
- `FLOAT` a JSON floating point value, i.e. a numerical value containing a decimal ot `.`
Note: values are converted in this priority: 1/ `UINT`, 2/ `INT`, 3/ `FLOAT`.
`JsonParserToken` support the following getters:
- `getBool()`: returns true if 'true' or non-zero int (default false)
- `getUInt()`: converts to unsigned int (default 0), boolean true is 1
- `getInt()`: converts to signed int (default 0), boolean true is 1
- `getULong()`: converts to unsigned 64 bits (default 0), boolean is 1
- `getStr()`: converts to string (default "")
There are variants of the same function for which you can choose the default values. Remember that using a getter if the token type is INVALID returns the default value.
Conversion to `OBJECT` or `ARRAY`:
- `getObject()`: converts token to `OBJECT` or `INVALID`
- `getArray()`: converts token to `ARRAY` or `INVALID`
For `JsonParserKey`:
- `getValue()`: returns the value token associated to the key
## Checking Token types
Type checking for `JsonParserToken`:
- `isSingleToken()` returns `true` for a single level token, `false` for `OBJECT`, `ARRAY` and `INVALID`
- `isKey()` returns `true` for a `KEY` within an `OBJECT`
- `isStr()` returns `true` for `STRING` (note: other types can still be read as strings with `getStr()`)
- `isNull()` returns `true` for `NULL`
- `isBool()` returns `true` for `BOOL_FALSE` or `BOOL_TRUE`
- `isUInt()` returns `true` for `UINT` (see below for number conversions)
- `isInt()` returns `true` for `INT` (see below for number conversions)
- `isFloat()` returns `true` for `FLOAT` (see below for number conversions)
- `isNum()` returns `true` for any numerical value, i.e. `UINT`, `INT` or `FLOAT`
- `isObject()` returns `true` for `OBJECT`
- `isArray()` returns `true` for `ARRAY`
- `isValid()` returns `true`for any type except `INVALID`
JSMN for Tasmota provides sub-classes:
- `JsonParserKey` of type `KEY` or `INVALID`, used as a return value for Object iterators
- `JsonParserObject` of type `OBJECT` or `INVALID`, providing iterators
- `JsonParserArray` of type `ARRAY` or `INVALID`, providing iterators
Converting from Token to Object or Array is done with `token.getObject()` or `token.getArray()`. If the conversion is invalid, the resulting object has type `INVALID` (see Error Handling).
## Iterators and accessors for Objects and Arrays
The `JsonParserObject` provides an easy to use iterator:
```
JsonParserToken obj = <...>
for (auto key : obj) {
// key is of type JsonParserKey
JsonParserToken valie = key.getValue(); // retrieve the value associated to key
}
```
If the object contains only one element, you can retrieve it with `obj.getFirstElement()`.
You can access any key with `obj["Key"]`. Note: the search is on purpose **case insensitive** as it is the norm in Tasmota. The search string can be in PROGMEM. If the token is not found, it is of type `INVALID`.
The `JsonParserArray` provides an easy to use iterator:
```
JsonParserArray arr = <...>
for (auto elt : arr) {
// elt is of type JsonParserToken
}
```
You can access any element in the array with the `[]` operator. Ex: `arr[0]` fof first element. If the index is invalid, the token has type `INVALID`.
## Memory
The `JsonParserToken` fits in 32 bits, so it can be freely returned or copied without any penalty of object copying. Hence it doesn't need the `const` modifier either, since it is always passed by value.
## Error handling
This library uses a `zero error` pattern. This means that calls never report any error nor throw exceptions. If something wrong happens (bad JSON, token not found...), function return an **Invalid Token**. You can call any function on an Invalid Token, they will always return the same Invalid Token (aka fixed point).
You can easily test if the current token is invalid with the following:
Short version:
```
if (token) { /* token is valid */ }
```
Long version:
```
if (token->isValiid()) { /* token is valid */ }
```
This pattern allows to cascade calls and check only the final result:
```
char json_buffer[] = "<json>";
JsonParserObject json = JsonParser(json_buffer).getRootObject();
JsonParserToken zbstatus_tok = json["ZbStatus"];
JsonParserObject zbstatus = zbstatus_tok.getObject();
if (zbstatus) { /* do some stuff */
// reaching this point means: JSON is valid, there is a root object, there is a `ZbStatus` key and it contains a sub-object
}
```
Warning: there is an explicit convert to `bool` to allow the short version. Be careful, `(bool)token` is equivalent to `token->isValid()`, it is **NOT** equivalent to `token->getBool()`.
## Limits
Please keep in mind the current limits for this library:
- Maximum JSON buffer size 2047 bytes
- Maximum 63 JSON tokens
- No support for exponent in floats (i.e. `1.0e3` is invalid)
These limits shouldn't be a problem since buffers in Tasmota are limited to 1.2KB. The support for exponent in floats is commented out and can be added if needed (slight increase in Flash size)

View File

@ -0,0 +1,8 @@
name=JSMN JSON parser customized and optimized for ESP8266 and Tasmota
version=1.0
author=Serge Zaitsev, Stephan Hadinger
maintainer=Stephan <stephan.hadinger@gmail.com>
sentence=Lightweight in-place JSON parser
paragraph=
url=https://github.com/zserge/jsmn
architectures=esp8266

View File

@ -0,0 +1,527 @@
/*
JsonParser.h - lightweight JSON parser
Copyright (C) 2020 Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "JsonParser.h"
#include <Arduino.h>
/*********************************************************************************************\
* Utilities
\*********************************************************************************************/
const char * k_current_json_buffer = "";
/*********************************************************************************************\
* Lightweight String to Float, because atof() or strtof() takes 10KB
*
* To remove code, exponents are not parsed
* (commented out below, just in case we need them after all)
\*********************************************************************************************/
// Inspired from https://searchcode.com/codesearch/view/22115068/
float json_strtof(const char* s) {
const char* p = s;
float value = 0.;
int32_t sign = +1;
float factor;
// unsigned int expo;
while (isspace(*p)){ // skip any leading white-spaces
p++;
}
switch (*p) {
case '-': sign = -1;
case '+': p++;
default : break;
}
while ((unsigned int)(*p - '0') < 10u) {
value = value*10 + (*p++ - '0');
}
if (*p == '.' ) {
factor = 1.0f;
p++;
while ((unsigned int)(*p - '0') < 10u) {
factor *= 0.1f;
value += (*p++ - '0') * factor;
}
}
// if ( (*p | 32) == 'e' ) {
// expo = 0;
// factor = 10.L;
// switch (*++p) { // ja hier weiß ich nicht, was mindestens nach einem 'E' folgenden MUSS.
// case '-': factor = 0.1;
// case '+': p++;
// break;
// case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
// break;
// default : value = 0.L;
// p = s;
// goto done;
// }
// while ( (unsigned int)(*p - '0') < 10u )
// expo = 10 * expo + (*p++ - '0');
// while ( 1 ) {
// if ( expo & 1 )
// value *= factor;
// if ( (expo >>= 1) == 0 )
// break;
// factor *= factor;
// }
// }
// done:
// if ( endptr != NULL )
// *endptr = (char*)p;
return value * sign;
}
/*********************************************************************************************\
* Read-only JSON token object, fits in 32 bits
\*********************************************************************************************/
void JsonParserToken::skipToken(void) {
// printf("skipToken type = %d %s\n", t->type, json_string + t->start);
switch (t->type) {
case JSMN_OBJECT:
skipObject();
break;
case JSMN_ARRAY:
skipArray();
break;
case JSMN_STRING:
case JSMN_PRIMITIVE:
case JSMN_KEY:
case JSMN_NULL:
case JSMN_BOOL_FALSE:
case JSMN_BOOL_TRUE:
case JSMN_FLOAT:
case JSMN_INT:
case JSMN_UINT:
t++; // skip 1 token
break;
case JSMN_INVALID:
default:
break; // end of stream, stop advancing
}
}
void JsonParserToken::skipArray(void) {
if (t->type == JSMN_ARRAY) {
size_t obj_size = t->size;
t++; // array root
if (t->type == JSMN_INVALID) { return; }
for (uint32_t i=0; i<obj_size; i++) {
skipToken();
}
}
}
// skip to right after the current Object
void JsonParserToken::skipObject(void) {
if (t->type == JSMN_OBJECT) {
size_t obj_size = t->size;
t++; // object root
if (t->type == JSMN_INVALID) { return; }
for (uint32_t i=0; i<obj_size; i++) {
t++; // skip Key
if (t->type == JSMN_INVALID) { return; }
skipToken();
}
}
}
/*********************************************************************************************\
* JsonParserArray
\*********************************************************************************************/
JsonParserArray::JsonParserArray(const jsmntok_t * token) : JsonParserToken(token) {
if (t->type != JSMN_ARRAY) {
t = &token_bad;
}
}
JsonParserArray::JsonParserArray(const JsonParserToken token) : JsonParserToken(token.t) {
if (t->type != JSMN_ARRAY) {
t = &token_bad;
}
}
JsonParserArray::const_iterator::const_iterator(const JsonParserArray t): tok(t), remaining(0) {
if (tok.t == &token_bad) { tok.t = nullptr; }
if (nullptr != tok.t) {
// ASSERT type == JSMN_ARRAY by constructor
remaining = tok.t->size;
tok.nextOne(); // skip array root token
}
}
JsonParserArray::const_iterator JsonParserArray::const_iterator::const_iterator::operator++() {
if (remaining == 0) { tok.t = nullptr; }
else {
remaining--;
tok.skipToken(); // munch value
if (tok.t->type == JSMN_INVALID) { tok.t = nullptr; } // unexpected end of stream
}
return *this;
}
JsonParserToken JsonParserArray::operator[](int32_t i) const {
if ((i >= 0) && (i < t->size)) {
uint32_t index = 0;
for (const auto elt : *this) {
if (i == index) {
return elt;
}
index++;
}
}
// fallback
return JsonParserToken(&token_bad);
}
/*********************************************************************************************\
* JsonParserObject
\*********************************************************************************************/
JsonParserObject::JsonParserObject(const jsmntok_t * token) : JsonParserToken(token) {
if (t->type != JSMN_OBJECT) {
t = &token_bad;
}
}
JsonParserObject::JsonParserObject(const JsonParserToken token) : JsonParserToken(token.t) {
if (t->type != JSMN_OBJECT) {
t = &token_bad;
}
}
JsonParserKey JsonParserObject::getFirstElement(void) const {
if (t->size > 0) {
return JsonParserKey(t+1); // return next element and cast to Key
} else {
return JsonParserKey(&token_bad);
}
}
JsonParserObject::const_iterator::const_iterator(const JsonParserObject t): tok(t), remaining(0) {
if (tok.t == &token_bad) { tok.t = nullptr; }
if (nullptr != tok.t) {
// ASSERT type == JSMN_OBJECT by constructor
remaining = tok.t->size;
tok.nextOne();
}
}
JsonParserObject::const_iterator JsonParserObject::const_iterator::operator++() {
if (remaining == 0) { tok.t = nullptr; }
else {
remaining--;
tok.nextOne(); // munch key
if (tok.t->type == JSMN_INVALID) { tok.t = nullptr; } // unexpected end of stream
tok.skipToken(); // munch value
if (tok.t->type == JSMN_INVALID) { tok.t = nullptr; } // unexpected end of stream
}
return *this;
}
/*********************************************************************************************\
* JsonParserKey
\*********************************************************************************************/
JsonParserKey::JsonParserKey(const jsmntok_t * token) : JsonParserToken(token) {
if (t->type != JSMN_KEY) {
t = &token_bad;
}
}
JsonParserKey::JsonParserKey(const JsonParserToken token) : JsonParserToken(token.t) {
if (t->type != JSMN_KEY) {
t = &token_bad;
}
}
JsonParserToken JsonParserKey::getValue(void) const {
return JsonParserToken(t+1);
}
/*********************************************************************************************\
* Implementation for JSON Parser
\*********************************************************************************************/
// fall-back token object when parsing failed
const jsmntok_t token_bad = { JSMN_INVALID, 0, 0, 0 };
JsonParser::JsonParser(char * json_in) :
_size(0),
_token_len(0),
_tokens(nullptr),
_json(nullptr)
{
parse(json_in);
}
JsonParser::~JsonParser() {
this->free();
}
const JsonParserObject JsonParser::getRootObject(void) const {
return JsonParserObject(&_tokens[0]);
}
const JsonParserToken JsonParser::operator[](int32_t i) const {
if ((_token_len > 0) && (i < _token_len)) {
return JsonParserToken(&_tokens[i]);
} else {
return JsonParserToken(&token_bad);
}
}
// pointer arithmetic
// ptrdiff_t JsonParser::index(JsonParserToken token) const {
// return token.t - _tokens;
// }
bool JsonParserToken::getBool(bool val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_BOOL_TRUE) return true;
if (t->type == JSMN_BOOL_FALSE) return false;
if (isSingleToken()) return strtol(&k_current_json_buffer[t->start], nullptr, 0) != 0;
return false;
}
int32_t JsonParserToken::getInt(int32_t val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_BOOL_TRUE) return 1;
if (isSingleToken()) return strtol(&k_current_json_buffer[t->start], nullptr, 0);
return 0;
}
uint32_t JsonParserToken::getUInt(uint32_t val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_BOOL_TRUE) return 1;
if (isSingleToken()) return strtoul(&k_current_json_buffer[t->start], nullptr, 0);
return 0;
}
uint64_t JsonParserToken::getULong(uint64_t val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_BOOL_TRUE) return 1;
if (isSingleToken()) return strtoull(&k_current_json_buffer[t->start], nullptr, 0);
return 0;
}
float JsonParserToken::getFloat(float val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_BOOL_TRUE) return 1;
if (isSingleToken()) return json_strtof(&k_current_json_buffer[t->start]);
return 0;
}
const char * JsonParserToken::getStr(const char * val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_NULL) return "";
return (t->type >= JSMN_STRING) ? &k_current_json_buffer[t->start] : "";
}
JsonParserObject JsonParserToken::getObject(void) const { return JsonParserObject(*this); }
JsonParserArray JsonParserToken::getArray(void) const { return JsonParserArray(*this); }
bool JsonParserToken::getBool(void) const { return getBool(false); }
int32_t JsonParserToken::getInt(void) const { return getInt(0); }
uint32_t JsonParserToken::getUInt(void) const { return getUInt(0); }
uint64_t JsonParserToken::getULong(void) const { return getULong(0); }
float JsonParserToken::getFloat(void) const { return getFloat(0); }
const char * JsonParserToken::getStr(void) const { return getStr(""); }
int32_t JsonParserObject::getInt(const char * needle, int32_t val) const {
return (*this)[needle].getInt(val);
}
uint32_t JsonParserObject::getUInt(const char * needle, uint32_t val) const {
return (*this)[needle].getUInt(val);
}
uint64_t JsonParserObject::getULong(const char * needle, uint64_t val) const {
return (*this)[needle].getULong(val);
}
float JsonParserObject::getFloat(const char * needle, float val) const {
return (*this)[needle].getFloat(val);
}
const char * JsonParserObject::getStr(const char * needle, const char * val) const {
return (*this)[needle].getStr(val);
}
void JsonParser::parse(char * json_in) {
k_current_json_buffer = "";
if (nullptr == json_in) { return; }
_json = json_in;
k_current_json_buffer = _json;
size_t json_len = strlen(json_in);
if (_size == 0) {
// first run is used to count tokens before allocation
jsmn_init(&this->_parser);
int32_t _token_len = jsmn_parse(&this->_parser, json_in, json_len, nullptr, 0);
if (_token_len <= 0) { return; }
_size = _token_len + 1;
}
allocate();
jsmn_init(&this->_parser);
_token_len = jsmn_parse(&this->_parser, json_in, json_len, _tokens, _size);
// TODO error checking
if (_token_len >= 0) {
postProcess(json_len);
}
}
// post process the parsing by pre-munching extended types
void JsonParser::postProcess(size_t json_len) {
// add an end marker
if (_size > _token_len) {
_tokens[_token_len].type = JSMN_INVALID;
_tokens[_token_len].start = json_len;
_tokens[_token_len].len = 0;
_tokens[_token_len].size = 0;
}
for (uint32_t i=0; i<_token_len; i++) {
jsmntok_t & tok = _tokens[i];
if (tok.type >= JSMN_STRING) {
// we modify to null-terminate the primitive
_json[tok.start + tok.len] = 0;
}
if (tok.type == JSMN_STRING) {
if (tok.size == 1) { tok.type = JSMN_KEY; }
else { json_unescape(&_json[tok.start]); }
} else if (tok.type == JSMN_PRIMITIVE) {
if (tok.len >= 0) {
// non-null string
char c0 = _json[tok.start];
switch (c0) {
case 'n':
case 'N':
tok.type = JSMN_NULL;
break;
case 't':
case 'T':
tok.type = JSMN_BOOL_TRUE;
break;
case 'f':
case 'F':
tok.type = JSMN_BOOL_FALSE;
break;
case '-':
case '0'...'9':
// look if there is a '.' in the string
if (nullptr != memchr(&_json[tok.start], '.', tok.len)) {
tok.type = JSMN_FLOAT;
} else if (c0 == '-') {
tok.type = JSMN_INT;
} else {
tok.type = JSMN_UINT;
}
break;
default:
tok.type = JSMN_PRIMITIVE;
break;
}
} else {
tok.type = JSMN_PRIMITIVE;
}
}
}
}
JsonParserToken JsonParserObject::operator[](const char * needle) const {
// key can be in PROGMEM
if ((!this->isValid()) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
return JsonParserToken(&token_bad);
}
// if needle == "?" then we return the first valid key
bool wildcard = (strcmp_P("?", needle) == 0);
for (const auto key : *this) {
if (wildcard) { return key.getValue(); }
if (0 == strcasecmp_P(key.getStr(), needle)) { return key.getValue(); }
}
// if not found
return JsonParserToken(&token_bad);
}
JsonParserToken JsonParserObject::findStartsWith(const char * needle) const {
// key can be in PROGMEM
if ((!this->isValid()) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
return JsonParserToken(&token_bad);
}
String needle_s((const __FlashStringHelper *)needle);
needle_s.toLowerCase();
for (const auto key : *this) {
String key_s(key.getStr());
key_s.toLowerCase();
if (key_s.startsWith(needle_s)) {
return key.getValue();
}
}
// if not found
return JsonParserToken(&token_bad);
}
const char * JsonParserObject::findConstCharNull(const char * needle) const {
const char * r = (*this)[needle].getStr();
if (*r == 0) { r = nullptr; } // if empty string
return r;
}
// JsonParserToken JsonParser::find(JsonParserObject obj, const char *needle, bool case_sensitive) const {
// // key can be in PROGMEM
// if ((!obj.isValid()) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
// return JsonParserToken(&token_bad);
// }
// // if needle == "?" then we return the first valid key
// bool wildcard = (strcmp_P("?", needle) == 0);
// for (const auto key : obj) {
// if (wildcard) { return key.getValue(); }
// if (case_sensitive) {
// if (0 == strcmp_P(this->getStr(key), needle)) { return key.getValue(); }
// } else {
// if (0 == strcasecmp_P(this->getStr(key), needle)) { return key.getValue(); }
// }
// }
// // if not found
// return JsonParserToken(&token_bad);
// }
void JsonParser::free(void) {
if (nullptr != _tokens) {
delete[] _tokens; // TODO
_tokens = nullptr;
}
}
void JsonParser::allocate(void) {
this->free();
if (_size != 0) {
_tokens = new jsmntok_t[_size];
}
}

View File

@ -0,0 +1,260 @@
/*
JsonParser.h - lightweight JSON parser
Copyright (C) 2020 Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __JSON_PARSER__
#define __JSON_PARSER__
#include "jsmn.h"
#include <string.h>
#include <stdlib.h>
// #define strcmp_P(x, y) strcmp(x,y)
// #define strcasecmp_P(x,y) strcasecmp(x,y)
// #define pgm_read_byte(x) (*(uint8_t*)(x))
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
/*********************************************************************************************\
* Utilities
\*********************************************************************************************/
// The code uses a zero-error approach. Functions never return an error code nor an exception.
// In case an operation fails, it returns an "Invalid Token".
// To know if a token is valid, use the `isValid()` method or just use it in an if statement.
//
// Internally, the bad token is a pointer to a constant token of type JSMN_INVALID
// fall-back token object when parsing failed
const extern jsmntok_t token_bad;
// To reduce code size, the current buffer is stored in a global variable.
// This prevents all calls to add this parameter on the stack and reduces code size.
// The caveat is that code is not re-entrant.
// If you ever intermix two or more JSON parsers, use `parser->setCurrent()` before further calls
//
// the current json buffer being used, for convenience
// Warning: this makes code non-reentrant.
extern const char * k_current_json_buffer;
/*********************************************************************************************\
* Read-only JSON token object, fits in 32 bits
\*********************************************************************************************/
// forward class declarations
class JsonParserObject;
class JsonParserArray;
class JsonParserToken {
public:
// constructor
// If parameter is null, we use the Invalid Token instead.
JsonParserToken(const jsmntok_t * token) : t(token) {
if (nullptr == t) { t = &token_bad; }
}
// no explicit destructor (not needed)
inline bool isValid(void) const { return t->type != JSMN_INVALID; }
inline size_t size(void) const { return t->size; }
inline bool isSingleToken(void) const { return (t->type >= JSMN_STRING); }
inline bool isKey(void) const { return (t->type == JSMN_KEY); }
inline bool isStr(void) const { return (t->type == JSMN_STRING); }
inline bool isNull(void) const { return (t->type == JSMN_NULL); }
inline bool isBool(void) const { return (t->type == JSMN_BOOL_TRUE) || (t->type == JSMN_BOOL_FALSE); }
inline bool isFloat(void) const { return (t->type == JSMN_FLOAT); }
inline bool isInt(void) const { return (t->type == JSMN_INT); }
inline bool isUint(void) const { return (t->type == JSMN_UINT); }
inline bool isNum(void) const { return (t->type >= JSMN_FLOAT) && (t->type <= JSMN_UINT); }
inline bool isObject(void) const { return (t->type == JSMN_OBJECT); }
inline bool isArray(void) const { return (t->type == JSMN_ARRAY); }
// move to token immediately after in the buffer
void nextOne(void) { if (t->type != JSMN_INVALID) { t++; } }
// conversion operators
// Warning - bool does not test for Boolean value but for validity, i.e. equivalent to token.valid()
inline explicit operator bool() const { return t->type != JSMN_INVALID; };
// all the following conversion will try to give a meaninful value
// if the content is not of the right type or the token is invalid, returns the 'default'
bool getBool(void) const; // true if 'true' or non-zero int (default false)
int32_t getInt(void) const; // convert to int (default 0)
uint32_t getUInt(void) const; // convert to unsigned int (default 0)
uint64_t getULong(void) const; // convert to unsigned 64 bits (default 0)
float getFloat(void) const; // convert to float (default 0), does not support exponent
const char * getStr(void) const; // convert to string (default "")
// same as above, but you can choose the default value
bool getBool(bool val) const;
int32_t getInt(int32_t val) const;
uint32_t getUInt(uint32_t val) const;
uint64_t getULong(uint64_t val) const;
float getFloat(float val) const;
const char * getStr(const char * val) const;
// convert to JsonParserObject or JsonParserArray, or Invalid Token if not allowed
JsonParserObject getObject(void) const;
JsonParserArray getArray(void) const;
public:
// the following should be 'protected' but then it can't be accessed by iterators
const jsmntok_t * t;
// skip the next Token as a whole (i.e. skip an entire array)
void skipToken(void);
protected:
// skip the next token knowing it's an array
void skipArray(void);
// skip the next token knowing it's an object
void skipObject(void);
};
/*********************************************************************************************\
* Subclass for Key
\*********************************************************************************************/
class JsonParserKey : public JsonParserToken {
public:
JsonParserKey(const jsmntok_t * token);
explicit JsonParserKey(const JsonParserToken token);
// get the value token associated to the key
JsonParserToken getValue(void) const;
};
/*********************************************************************************************\
* Subclass for Object
\*********************************************************************************************/
class JsonParserObject : public JsonParserToken {
public:
JsonParserObject(const jsmntok_t * token);
explicit JsonParserObject(const JsonParserToken token);
// find key with name, case-insensitive, '?' matches any key. Returns Invalid Token if not found
JsonParserToken operator[](const char * needle) const;
// find a key starting with `needle`, case insensitive
JsonParserToken findStartsWith(const char * needle) const;
// find a key, case-insensitive, return nullptr if not found (instead of "")
const char * findConstCharNull(const char * needle) const;
// all-in-one methods: search for key (case insensitive), convert value and set default
int32_t getInt(const char *, int32_t) const;
uint32_t getUInt(const char *, uint32_t) const;
uint64_t getULong(const char *, uint64_t) const;
float getFloat(const char *, float) const;
const char * getStr(const char *, const char *) const;
// get first element (key)
JsonParserKey getFirstElement(void) const;
//
// const iterator
//
class const_iterator {
public:
const_iterator(const JsonParserObject t);
const_iterator operator++();
bool operator!=(const_iterator & other) const { return tok.t != other.tok.t; }
const JsonParserKey operator*() const { return JsonParserKey(tok); }
private:
JsonParserToken tok;
size_t remaining;
};
const_iterator begin() const { return const_iterator(*this); } // start with 'head'
const_iterator end() const { return const_iterator(JsonParserObject(&token_bad)); } // end with null pointer
};
/*********************************************************************************************\
* Subclass for Array
\*********************************************************************************************/
class JsonParserArray : public JsonParserToken {
public:
JsonParserArray(const jsmntok_t * token);
JsonParserArray(const JsonParserToken token);
// get the element if index `i` from 0 to `size() - 1`
JsonParserToken operator[](int32_t i) const;
//
// const iterator
//
class const_iterator {
public:
const_iterator(const JsonParserArray t);
const_iterator operator++();
bool operator!=(const_iterator & other) const { return tok.t != other.tok.t; }
const JsonParserToken operator*() const { return tok; }
private:
JsonParserToken tok;
size_t remaining;
};
const_iterator begin() const { return const_iterator(*this); } // start with 'head'
const_iterator end() const { return const_iterator(JsonParserArray(&token_bad)); } // end with null pointer
};
/*********************************************************************************************\
* JSON Parser
\*********************************************************************************************/
class JsonParser {
public:
// constructor, parse the json buffer
// Warning: the buffer is modified in the process (in-place parsing)
// Input: `json_in` can be nullptr, but CANNOT be in PROGMEM (remember we need to change characters in-place)
JsonParser(char * json_in);
// destructor
~JsonParser();
// set the current buffer for attribute access (i.e. set the global)
void setCurrent(void) { k_current_json_buffer = _json; }
// test if the parsing was successful
inline explicit operator bool() const { return _token_len > 0; }
const JsonParserToken getRoot(void) { return JsonParserToken(&_tokens[0]); }
// const JsonParserObject getRootObject(void) { return JsonParserObject(&_tokens[0]); }
const JsonParserObject getRootObject(void) const;
// pointer arithmetic
// ptrdiff_t index(JsonParserToken token) const;
protected:
uint16_t _size; // size of tokens buffer
int16_t _token_len; // how many tokens have been parsed
jsmntok_t * _tokens; // pointer to token buffer
jsmn_parser _parser; // jmsn_parser structure
char * _json; // json buffer
// disallocate token buffer
void free(void);
// allocate token buffer of size _size
void allocate(void);
// access tokens by index
const JsonParserToken operator[](int32_t i) const;
// parse
void parse(char * json_in);
// post-process parsing: insert NULL chars to split strings, compute a more precise token type
void postProcess(size_t json_len);
};
#endif // __JSON_PARSER__

Binary file not shown.

View File

@ -0,0 +1,449 @@
/*
* MIT License
*
* Copyright (c) 2010 Serge Zaitsev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "jsmn.h"
#define JSMN_STRICT // force strict mode
const uint32_t JSMN_START_MAX = (1U << JSMN_START_B) - 1;
const uint32_t JSMN_LEN_MAX = (1U << JSMN_LEN_B) - 1;
/**
* Allocates a fresh unused token from the token pool.
*/
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *tok;
if (parser->toknext >= num_tokens) {
return NULL;
}
tok = &tokens[parser->toknext++];
tok->start = JSMN_START_MAX;
tok->len = JSMN_LEN_MAX;
tok->size = 0;
return tok;
}
/**
* Fills token type and boundaries.
*/
static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
const int start, const int len) {
token->type = type;
token->start = start;
token->len = len;
token->size = 0;
}
/**
* Fills next available token with JSON primitive.
*/
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
const size_t len, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *token;
int start;
start = parser->pos;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
switch (js[parser->pos]) {
#ifndef JSMN_STRICT
/* In strict mode primitive must be followed by "," or "}" or "]" */
case ':':
#endif
case '\t':
case '\r':
case '\n':
case ' ':
case ',':
case ']':
case '}':
goto found;
default:
/* to quiet a warning from gcc*/
break;
}
if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
#ifdef JSMN_STRICT
/* In strict mode primitive must be followed by a comma/object/array */
parser->pos = start;
return JSMN_ERROR_PART;
#endif
found:
if (tokens == NULL) {
parser->pos--;
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos - start);
parser->pos--;
return 0;
}
/**
* Fills next token with JSON string.
*/
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
const size_t len, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *token;
int start = parser->pos;
parser->pos++;
/* Skip starting quote */
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c = js[parser->pos];
/* Quote: end of string */
if (c == '\"') {
if (tokens == NULL) {
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos - start - 1);
return 0;
}
/* Backslash: Quoted symbol expected */
if (c == '\\' && parser->pos + 1 < len) {
int i;
parser->pos++;
switch (js[parser->pos]) {
/* Allowed escaped symbols */
case '\"':
case '/':
case '\\':
case 'b':
case 'f':
case 'r':
case 'n':
case 't':
break;
/* Allows escaped symbol \uXXXX */
case 'u':
parser->pos++;
for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
i++) {
/* If it isn't a hex character we have an error */
if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
(js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
parser->pos = start;
return JSMN_ERROR_INVAL;
}
parser->pos++;
}
parser->pos--;
break;
/* Unexpected symbol */
default:
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
}
parser->pos = start;
return JSMN_ERROR_PART;
}
/**
* Parse JSON string and fill tokens.
*/
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
jsmntok_t *tokens, const unsigned int num_tokens) {
int r;
int i;
jsmntok_t *token;
int count = parser->toknext;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c;
jsmntype_t type;
c = js[parser->pos];
switch (c) {
case '{':
case '[':
count++;
if (tokens == NULL) {
break;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
return JSMN_ERROR_NOMEM;
}
if (parser->toksuper != -1) {
jsmntok_t *t = &tokens[parser->toksuper];
#ifdef JSMN_STRICT
/* In strict mode an object or array can't become a key */
if (t->type == JSMN_OBJECT) {
return JSMN_ERROR_INVAL;
}
#endif
t->size++;
}
token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
token->start = parser->pos;
parser->toksuper = parser->toknext - 1;
break;
case '}':
case ']':
if (tokens == NULL) {
break;
}
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
for (i = parser->toknext - 1; i >= 0; i--) {
token = &tokens[i];
if ((token->start != JSMN_START_MAX) && (token->len == JSMN_LEN_MAX)) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
token->len = parser->pos + 1 - token->start;
break;
}
}
/* Error if unmatched closing bracket */
if (i == -1) {
return JSMN_ERROR_INVAL;
}
for (; i >= 0; i--) {
token = &tokens[i];
if ((token->start != JSMN_START_MAX) && (token->len == JSMN_LEN_MAX)) {
parser->toksuper = i;
break;
}
}
break;
case '\"':
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
if (r < 0) {
return r;
}
count++;
if (parser->toksuper != -1 && tokens != NULL) {
tokens[parser->toksuper].size++;
}
break;
case '\t':
case '\r':
case '\n':
case ' ':
break;
case ':':
parser->toksuper = parser->toknext - 1;
break;
case ',':
if (tokens != NULL && parser->toksuper != -1 &&
tokens[parser->toksuper].type != JSMN_ARRAY &&
tokens[parser->toksuper].type != JSMN_OBJECT) {
for (i = parser->toknext - 1; i >= 0; i--) {
if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
if ((tokens[i].start != JSMN_START_MAX) && (tokens[i].len == JSMN_LEN_MAX)) {
parser->toksuper = i;
break;
}
}
}
}
break;
#ifdef JSMN_STRICT
/* In strict mode primitives are: numbers and booleans */
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 't':
case 'f':
case 'n':
/* And they must not be keys of the object */
if (tokens != NULL && parser->toksuper != -1) {
const jsmntok_t *t = &tokens[parser->toksuper];
if (t->type == JSMN_OBJECT ||
(t->type == JSMN_STRING && t->size != 0)) {
return JSMN_ERROR_INVAL;
}
}
#else
/* In non-strict mode every unquoted value is a primitive */
default:
#endif
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
if (r < 0) {
return r;
}
count++;
if (parser->toksuper != -1 && tokens != NULL) {
tokens[parser->toksuper].size++;
}
break;
#ifdef JSMN_STRICT
/* Unexpected char in strict mode */
default:
return JSMN_ERROR_INVAL;
#endif
}
}
if (tokens != NULL) {
for (i = parser->toknext - 1; i >= 0; i--) {
/* Unmatched opened object or array */
if ((tokens[i].start != JSMN_START_MAX) && (tokens[i].len == JSMN_LEN_MAX)) {
return JSMN_ERROR_PART;
}
}
}
return count;
}
/**
* Creates a new parser based over a given buffer with an array of tokens
* available.
*/
JSMN_API void jsmn_init(jsmn_parser *parser) {
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
}
//
// Json in-place string unescape
// inpired from https://github.com/mjansson/json/blob/master/json.h
//
//! Define a bitmask with the given number of bits set to 1
#define JSON_BITMASK(numbits) ((1U << (numbits)) - 1)
static uint32_t json_get_num_bytes_as_utf8(uint32_t val) {
if (val >= 0x04000000) return 6;
else if (val >= 0x00200000) return 5;
else if (val >= 0x00010000) return 4;
else if (val >= 0x00000800) return 3;
else if (val >= 0x00000080) return 2;
return 1;
}
static uint32_t json_encode_utf8(char* str, uint32_t val) {
if (val < 0x80) {
*str = (char)val;
return 1;
}
//Get number of _extra_ bytes
uint32_t num = json_get_num_bytes_as_utf8(val) - 1;
*str++ = (char)((0x80U | (JSON_BITMASK(num) << (7U - num))) |
((val >> (6U * num)) & JSON_BITMASK(6U - num)));
for (uint32_t j = 1; j <= num; ++j)
*str++ = (char)(0x80U | ((val >> (6U * (num - j))) & 0x3F));
return num + 1;
}
void json_unescape(char* string) {
size_t outlength = 0;
uint32_t hexval, numbytes;
char c;
for (uint32_t i = 0; (c = string[i]) != 0; i++) {
if ('\\' == c) {
c = string[++i];
switch (c) {
case 0:
return; // end of stream
case '\"':
case '/':
case '\\':
string[outlength++] = c;
break;
case 'b':
string[outlength++] = '\b';
break;
case 'f':
string[outlength++] = '\f';
break;
case 'r':
string[outlength++] = '\r';
break;
case 'n':
string[outlength++] = '\n';
break;
case 't':
string[outlength++] = '\t';
break;
case 'u':
{
uint32_t hexval = 0;
for (uint32_t j = 0; j < 4; ++j) {
char val = string[++i];
if (0 == val) { return; } // we reached end of string
uint32_t uival = 0;
if ((val >= 'a') && (val <= 'f'))
uival = 10 + (val - 'a');
else if ((val >= 'A') && (val <= 'F'))
uival = 10 + (val - 'A');
else if ((val >= '0') && (val <= '9'))
uival = val - '0';
hexval |= uival << (3 - j);
}
numbytes = json_get_num_bytes_as_utf8(hexval);
outlength += json_encode_utf8(string + outlength, hexval);
}
break;
default:
break;
}
}
else {
string[outlength++] = c;
}
}
}

View File

@ -0,0 +1,118 @@
/*
* MIT License
*
* Copyright (c) 2010 Serge Zaitsev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef JSMN_H
#define JSMN_H
#include <stddef.h>
#include <stdint.h>
// #ifdef JSMN_STATIC
// #define JSMN_API static
// #else
#define JSMN_API extern
// #endif
/**
* JSON type identifier. Basic types are:
* o Object
* o Array
* o String
* o Other primitive: number, boolean (true/false) or null
*/
typedef enum {
// end market
JSMN_INVALID = 0, // type == 0 is invalid
JSMN_OBJECT = 1,
JSMN_ARRAY = 2,
JSMN_STRING = 3,
JSMN_PRIMITIVE = 4,
// new types created during post-processing
JSMN_KEY = 5, // JSMN_STRING with size 1
JSMN_NULL = 6, // JSMN_PRIMITIVE starting with 'n'
JSMN_BOOL_FALSE = 7, // JSMN_PRIMITIVE starting with 'f' or 'F'
JSMN_BOOL_TRUE = 8, // JSMN_PRIMITIVE starting with 't' or 'T'
JSMN_FLOAT = 9, // JSMN_PRIMITIVE starting with '.', '-', '0-9' and containing a '.'
JSMN_INT = 10, // JSMN_PRIMITIVE starting with '-', '0-9' and not containing a '.'
JSMN_UINT = 11, // JSMN_PRIMITIVE starting with '0-9' and not containing a '.'
} jsmntype_t;
enum jsmnerr {
/* Not enough tokens were provided */
JSMN_ERROR_NOMEM = -1,
/* Invalid character inside JSON string */
JSMN_ERROR_INVAL = -2,
/* The string is not a full JSON packet, more bytes expected */
JSMN_ERROR_PART = -3
};
/**
* JSON token description.
* type type (object, array, string etc.)
* start start position in JSON data string
* end end position in JSON data string
*/
// size of bitfield, sum is 32
#define JSMN_TYPE_B 4
#define JSMN_SIZE_B 6 // max 63 items per level (ex: max 63 keys per object)
#define JSMN_START_B 11 // max 2KB input buffer
#define JSMN_LEN_B 11 // max 2KB per item
typedef struct jsmntok {
jsmntype_t type : JSMN_TYPE_B;
unsigned int size : JSMN_SIZE_B;
unsigned int start : JSMN_START_B;
unsigned int len : JSMN_LEN_B;
} jsmntok_t;
/**
* JSON parser. Contains an array of token blocks available. Also stores
* the string being parsed now and current position in that string.
*/
typedef struct jsmn_parser {
unsigned int pos; /* offset in the JSON string */
unsigned int toknext; /* next token to allocate */
int toksuper; /* superior token node, e.g. parent object or array */
} jsmn_parser;
/**
* Create JSON parser over an array of tokens
*/
JSMN_API void jsmn_init(jsmn_parser *parser);
/**
* Run JSON parser. It parses a JSON data string into and array of tokens, each
* describing
* a single JSON object.
*/
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
jsmntok_t *tokens, const unsigned int num_tokens);
/**
*
* In-place json unescape
*
*/
void json_unescape(char* string);
#endif /* JSMN_H */

View File

@ -0,0 +1,55 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "../src/JsonParser.h"
static char test_simple[] = "{\"Device\":\"0x9C33\",\"Illuminance\":42,\"Occupancy\":1,\"Endpoint\":1,\"LinkQuality\":59}";
static char test_moderate[] = "{\"ZbReceived\":{\"Prez\":{\"Device\":\"0x9C33\",\"Illuminance\":42,\"Occupancy\":1,\"Endpoint\":1,\"LinkQuality\":59}}}";
static char test_complex[] = "{\"ZbStatus3\":[{\"Device\":\"0x7869\",\"INT\":-3,\"Name\":\"Tilt\",\"IEEEAddr\":\"0x00158D00031310F4\",\"ModelId\":\"lumi.vibration.aq1\",\"Manufacturer\":\"LUMI\",\"Endpoints\":{\"0x01\":{\"ProfileId\":\"0x0104\",\"ClustersIn\":[\"0x0000\",\"0x0003\",\"0x0019\",\"0x0101\"],\"ClustersOut\":[\"0x0000\",\"0x0004\",\"0x0003\",\"0x0005\",\"0x0019\",\"0x0101\"]},\"0x02\":{\"ProfileId\":\"0x0000\\ta\",\"ClustersIn\":[2],\"ClustersOut\":[-3,0.4,5.8]}}}]}";
int main(int argc, char* argv[]) {
printf("Starting... sizeof = %lu / %lu\n", sizeof(jsmntok_t), sizeof(JsonParserToken));
// char * json_str = test_complex;
char * json_str = test_simple;
JsonParser parser(64); // size for 64 tokens
int r = parser.parse(json_str);
printf("r = %d\n", r);
for (uint32_t i=0; i<r; i++) {
JsonParserToken token = parser[i];
uint32_t start = token.t->start;
uint32_t len = token.t->len;
printf("Tok[%2d]= type=%s, start=%d, len=%d, size=%d, str ='%s'\n", i, JSMNTypeName(token.t->type), start, len, token.t->size, (token.t->type >= JSMN_STRING || 1) ? &json_str[start] : "");
}
printf("==================\n");
JsonParserObject root = parser.getRootObject();
for (const auto key : root) {
// printf("Index = %ld\n", parser.index(key));
JsonParserToken value = key.getValue();
printf("Key = %s, Val type = %s\n", parser.getStr(key), JSMNTypeName(value.t->type));
if (value.isArray()) {
for (const auto arr_val : JsonParserArray(value)) {
printf("Array = %s, type = %s\n", parser.getStr(arr_val), JSMNTypeName(arr_val.t->type));
}
} else {
printf("Value = %s\n", parser.getStr(value));
}
}
// root.nextOne();
// printf("Index = %ld\n", parser.index(root));
// root.skipObject();
// printf("Index = %ld\n", parser.index(root));
JsonParserToken oc = parser.GetCaseInsensitive(root, "occupancy");
printf("Looking for 'Occupancy': %s, %d\n", parser.getStr(oc), parser.getInt(oc));
JsonParserToken oc2 = parser.GetCaseInsensitive(root, "occupanc");
printf("Looking for 'Occupanc': %s, %d\n", parser.getStr(oc2), parser.getInt(oc2));
}

View File

@ -13,6 +13,7 @@
- Add new shutter modes (#9244)
- Add Zigbee auto-config when pairing
- Add support for MLX90640 IR array temperature sensor by Christian Baars
- Change replace ArduinoJson with JSMN for JSON parsing
### 8.5.0 20200907

View File

@ -29,6 +29,7 @@ extern struct rst_info resetInfo;
\*********************************************************************************************/
#include <Ticker.h>
#include "JsonParser.h"
Ticker tickerOSWatch;
@ -1412,8 +1413,9 @@ bool GetUsedInModule(uint32_t val, uint16_t *arr)
return false;
}
bool JsonTemplate(const char* dataBuf)
bool JsonTemplate(char* dataBuf)
{
#if 0
// {"NAME":"Generic","GPIO":[17,254,29,254,7,254,254,254,138,254,139,254,254],"FLAG":1,"BASE":255}
if (strlen(dataBuf) < 9) { return false; } // Workaround exception if empty JSON like {} - Needs checks
@ -1454,6 +1456,46 @@ bool JsonTemplate(const char* dataBuf)
Settings.user_template_base = base -1; // Default WEMOS
}
return true;
#else
// {"NAME":"Generic","GPIO":[17,254,29,254,7,254,254,254,138,254,139,254,254],"FLAG":1,"BASE":255}
if (strlen(dataBuf) < 9) { return false; } // Workaround exception if empty JSON like {} - Needs checks
JsonParserObject root = JsonParser((char*) dataBuf).getRootObject();
if (!root) { return false; }
// All parameters are optional allowing for partial changes
JsonParserToken val = root[PSTR(D_JSON_NAME)];
if (val) {
SettingsUpdateText(SET_TEMPLATE_NAME, val.getStr());
}
JsonParserArray arr = root[PSTR(D_JSON_GPIO)];
if (arr) {
for (uint32_t i = 0; i < ARRAY_SIZE(Settings.user_template.gp.io); i++) {
#ifdef ESP8266
Settings.user_template.gp.io[i] = arr[i].getUInt();
#else // ESP32
uint16_t gpio = arr[i].getUInt();
if (gpio == (AGPIO(GPIO_NONE) +1)) {
gpio = AGPIO(GPIO_USER);
}
Settings.user_template.gp.io[i] = gpio;
#endif
}
}
val = root[PSTR(D_JSON_FLAG)];
if (val) {
uint32_t flag = val.getUInt();
memcpy(&Settings.user_template.flag, &flag, sizeof(gpio_flag));
}
val = root[PSTR(D_JSON_BASE)];
if (val) {
uint32_t base = val.getUInt();
if ((0 == base) || !ValidTemplateModule(base -1)) { base = 18; }
Settings.user_template_base = base -1; // Default WEMOS
}
return true;
#endif
}
void TemplateJson(void)

View File

@ -88,6 +88,7 @@ String EscapeJSONString(const char *str) {
//
// If the key is not found, returns a nullptr
// Input: needle cannot be NULL but may be PROGMEM
#if 0
const JsonVariant &GetCaseInsensitive(const JsonObject &json, const char *needle) {
// key can be in PROGMEM
// if needle == "?" then we return the first valid key
@ -113,3 +114,4 @@ const JsonVariant &GetCaseInsensitive(const JsonObject &json, const char *needle
bool HasKeyCaseInsensitive(const JsonObject &json, const char *needle) {
return &GetCaseInsensitive(json, needle) != nullptr;
}
#endif

View File

@ -27,6 +27,8 @@
#define XDRV_01 1
#include "JsonParser.h"
#ifndef WIFI_SOFT_AP_CHANNEL
#define WIFI_SOFT_AP_CHANNEL 1 // Soft Access Point Channel number between 1 and 11 as used by WifiManager web GUI
#endif
@ -3344,6 +3346,7 @@ bool JsonWebColor(const char* dataBuf)
// Default pre v7 (Light theme)
// {"WebColor":["#000","#fff","#f2f2f2","#000","#fff","#000","#fff","#f00","#008000","#fff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#fff","#999","#000"]} // {"WebColor":["#000000","#ffffff","#f2f2f2","#000000","#ffffff","#000000","#ffffff","#ff0000","#008000","#ffffff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#ffffff","#999999","#000000"]}
#if 0
char dataBufLc[strlen(dataBuf) +1];
LowerCase(dataBufLc, dataBuf);
RemoveSpace(dataBufLc);
@ -3362,6 +3365,21 @@ bool JsonWebColor(const char* dataBuf)
}
}
}
#else
JsonParserObject root = JsonParser((char*) dataBuf).getRootObject();
JsonParserArray arr = root[PSTR(D_CMND_WEBCOLOR)].getArray();
if (arr) { // if arr is valid, i.e. json is valid, the key D_CMND_WEBCOLOR was found and the token is an arra
uint32_t i = 0;
for (auto color : arr) {
if (i < COL_LAST) {
WebHexCode(i, color.getStr());
} else {
break;
}
i++;
}
}
#endif
return true;
}

View File

@ -25,6 +25,7 @@
#define XDRV_05 5
#include <IRremoteESP8266.h>
#include "JsonParser.h"
enum IrErrors { IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND };
@ -180,6 +181,7 @@ uint32_t IrRemoteCmndIrSendJson(void)
// IRsend { "protocol": "RC5", "bits": 12, "data":"0xC86" }
// IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
#if 0
char dataBufUc[XdrvMailbox.data_len + 1];
UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc);
@ -200,6 +202,18 @@ uint32_t IrRemoteCmndIrSendJson(void)
uint16_t bits = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_BITS))];
uint64_t data = strtoull(root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_DATA))], nullptr, 0);
uint16_t repeat = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_REPEAT))];
#else
RemoveSpace(XdrvMailbox.data); // TODO is this really needed?
JsonParserObject root = JsonParser((char*) XdrvMailbox.data).getRootObject();
if (!root) { return IE_INVALID_JSON; }
// IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
// IRsend { "protocol": "NEC", "bits": 32, "data":"0x02FDFE80", "repeat": 2 }
const char *protocol = root.getStr(PSTR(D_JSON_IR_PROTOCOL), "");
uint16_t bits = root.getUInt(PSTR(D_JSON_IR_BITS), 0);
uint64_t data = root.getULong(PSTR(D_JSON_IR_DATA), 0);
uint16_t repeat = root.getUInt(PSTR(D_JSON_IR_REPEAT), 0);
#endif
// check if the IRSend<x> is great than repeat
if (XdrvMailbox.index > repeat + 1) {
repeat = XdrvMailbox.index - 1;

View File

@ -29,6 +29,7 @@
#include <IRrecv.h>
#include <IRutils.h>
#include <IRac.h>
#include "JsonParser.h"
enum IrErrors { IE_RESPONSE_PROVIDED, IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND, IE_SYNTAX_IRHVAC,
IE_UNSUPPORTED_HVAC, IE_UNSUPPORTED_PROTOCOL };
@ -267,6 +268,16 @@ String listSupportedProtocols(bool hvac) {
return l;
}
bool strToBool(class JsonParserToken token, bool def) {
if (token.isBool() || token.isNum()) {
return token.getBool();
} else if (token.isStr()) {
return IRac::strToBool(token.getStr());
} else {
return def;
}
}
// used to convert values 0-5 to fanspeed_t
const stdAc::fanspeed_t IrHvacFanSpeed[] PROGMEM = { stdAc::fanspeed_t::kAuto,
stdAc::fanspeed_t::kMin, stdAc::fanspeed_t::kLow,stdAc::fanspeed_t::kMedium,
@ -275,17 +286,10 @@ const stdAc::fanspeed_t IrHvacFanSpeed[] PROGMEM = { stdAc::fanspeed_t::kAuto,
uint32_t IrRemoteCmndIrHvacJson(void)
{
stdAc::state_t state, prev;
char parm_uc[12];
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRHVAC: Received %s"), XdrvMailbox.data);
char dataBufUc[XdrvMailbox.data_len + 1];
UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc);
if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; }
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject(dataBufUc);
if (!json.success()) { return IE_INVALID_JSON; }
JsonParserObject root = JsonParser((char*) XdrvMailbox.data).getRootObject();
if (!root) { return IE_INVALID_JSON; }
// from: https://github.com/crankyoldgit/IRremoteESP8266/blob/master/examples/CommonAcControl/CommonAcControl.ino
state.protocol = decode_type_t::UNKNOWN;
@ -307,60 +311,47 @@ uint32_t IrRemoteCmndIrHvacJson(void)
state.clean = false; // Turn off any Cleaning options if we can.
state.clock = -1; // Don't set any current time if we can avoid it.
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR));
if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_PROTOCOL));
if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); } // also support 'protocol'
if (root[PSTR(D_JSON_IRHVAC_VENDOR)]) { state.protocol = strToDecodeType(root.getStr(PSTR(D_JSON_IRHVAC_VENDOR), "")); }
if (root[PSTR(D_JSON_IRHVAC_PROTOCOL)]) { state.protocol = strToDecodeType(root.getStr(PSTR(D_JSON_IRHVAC_PROTOCOL), "")); }
// UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR));
// if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); }
// UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_PROTOCOL));
// if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); } // also support 'protocol'
if (decode_type_t::UNKNOWN == state.protocol) { return IE_UNSUPPORTED_HVAC; }
if (!IRac::isProtocolSupported(state.protocol)) { return IE_UNSUPPORTED_HVAC; }
// for fan speed, we also support 1-5 values
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FANSPEED));
if (json.containsKey(parm_uc)) {
uint32_t fan_speed = json[parm_uc];
JsonParserToken tok_fan_speed = root[PSTR(D_JSON_IRHVAC_FANSPEED)];
if (tok_fan_speed) {
uint32_t fan_speed = tok_fan_speed.getUInt();
if ((fan_speed >= 1) && (fan_speed <= 5)) {
state.fanspeed = (stdAc::fanspeed_t) pgm_read_byte(&IrHvacFanSpeed[fan_speed]);
} else {
state.fanspeed = IRac::strToFanspeed(json[parm_uc]);
state.fanspeed = IRac::strToFanspeed(tok_fan_speed.getStr());
}
}
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODEL));
if (json.containsKey(parm_uc)) { state.model = IRac::strToModel(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODE));
if (json.containsKey(parm_uc)) { state.mode = IRac::strToOpmode(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGV));
if (json.containsKey(parm_uc)) { state.swingv = IRac::strToSwingV(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGH));
if (json.containsKey(parm_uc)) { state.swingh = IRac::strToSwingH(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TEMP));
if (json.containsKey(parm_uc)) { state.degrees = json[parm_uc]; }
if (root[PSTR(D_JSON_IRHVAC_MODEL)]) { state.model = IRac::strToModel(PSTR(D_JSON_IRHVAC_MODEL)); }
if (root[PSTR(D_JSON_IRHVAC_MODE)]) { state.mode = IRac::strToOpmode(PSTR(D_JSON_IRHVAC_MODE)); }
if (root[PSTR(D_JSON_IRHVAC_SWINGV)]) { state.swingv = IRac::strToSwingV(PSTR(D_JSON_IRHVAC_SWINGV)); }
if (root[PSTR(D_JSON_IRHVAC_SWINGH)]) { state.swingh = IRac::strToSwingH(PSTR(D_JSON_IRHVAC_SWINGH)); }
state.degrees = root.getFloat(PSTR(D_JSON_IRHVAC_TEMP), state.degrees);
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("model %d, mode %d, fanspeed %d, swingv %d, swingh %d"),
// state.model, state.mode, state.fanspeed, state.swingv, state.swingh);
// decode booleans
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_POWER));
if (json.containsKey(parm_uc)) { state.power = IRac::strToBool(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CELSIUS));
if (json.containsKey(parm_uc)) { state.celsius = IRac::strToBool(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_LIGHT));
if (json.containsKey(parm_uc)) { state.light = IRac::strToBool(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_BEEP));
if (json.containsKey(parm_uc)) { state.beep = IRac::strToBool(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_ECONO));
if (json.containsKey(parm_uc)) { state.econo = IRac::strToBool(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FILTER));
if (json.containsKey(parm_uc)) { state.filter = IRac::strToBool(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TURBO));
if (json.containsKey(parm_uc)) { state.turbo = IRac::strToBool(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_QUIET));
if (json.containsKey(parm_uc)) { state.quiet = IRac::strToBool(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CLEAN));
if (json.containsKey(parm_uc)) { state.clean = IRac::strToBool(json[parm_uc]); }
state.power = strToBool(root[PSTR(D_JSON_IRHVAC_POWER)], state.power);
state.celsius = strToBool(root[PSTR(D_JSON_IRHVAC_CELSIUS)], state.celsius);
state.light = strToBool(root[PSTR(D_JSON_IRHVAC_LIGHT)], state.light);
state.beep = strToBool(root[PSTR(D_JSON_IRHVAC_BEEP)], state.beep);
state.econo = strToBool(root[PSTR(D_JSON_IRHVAC_ECONO)], state.econo);
state.filter = strToBool(root[PSTR(D_JSON_IRHVAC_FILTER)], state.filter);
state.turbo = strToBool(root[PSTR(D_JSON_IRHVAC_TURBO)], state.turbo);
state.quiet = strToBool(root[PSTR(D_JSON_IRHVAC_QUIET)], state.quiet);
state.clean = strToBool(root[PSTR(D_JSON_IRHVAC_CLEAN)], state.clean);
// optional timer and clock
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SLEEP));
if (json[parm_uc]) { state.sleep = json[parm_uc]; }
state.sleep = root.getInt(PSTR(D_JSON_IRHVAC_SLEEP), state.sleep);
//if (json[D_JSON_IRHVAC_CLOCK]) { state.clock = json[D_JSON_IRHVAC_CLOCK]; } // not sure it's useful to support 'clock'
IRac ac(Pin(GPIO_IRSEND));

View File

@ -337,6 +337,7 @@ void CmndTimer(void)
Settings.timer[index -1].data = Settings.timer[XdrvMailbox.payload -1].data; // Copy timer
}
} else {
#if 0
//#ifndef USE_RULES
#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
if (devices_present) {
@ -419,6 +420,95 @@ void CmndTimer(void)
index++;
}
//#ifndef USE_RULES
#else
//#ifndef USE_RULES
#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
if (devices_present) {
#endif
JsonParserObject root = JsonParser(XdrvMailbox.data).getRootObject();
if (!root) {
Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_INVALID_JSON "\"}"), index); // JSON decode failed
error = 1;
}
else {
char parm_uc[10];
index--;
JsonParserToken val = root[PSTR(D_JSON_TIMER_ARM)];
if (val) {
Settings.timer[index].arm = (val.getInt() != 0);
}
#ifdef USE_SUNRISE
val = root[PSTR(D_JSON_TIMER_MODE)];
if (val) {
Settings.timer[index].mode = val.getUInt() & 0x03;
}
#endif
val = root[PSTR(D_JSON_TIMER_TIME)];
if (val) {
uint16_t itime = 0;
int8_t value = 0;
uint8_t sign = 0;
char time_str[10];
strlcpy(time_str, val.getStr(), sizeof(time_str));
const char *substr = strtok(time_str, ":");
if (substr != nullptr) {
if (strchr(substr, '-')) {
sign = 1;
substr++;
}
value = atoi(substr);
if (sign) { value += 12; } // Allow entering timer offset from -11:59 to -00:01 converted to 12:01 to 23:59
if (value > 23) { value = 23; }
itime = value * 60;
substr = strtok(nullptr, ":");
if (substr != nullptr) {
value = atoi(substr);
if (value < 0) { value = 0; }
if (value > 59) { value = 59; }
itime += value;
}
}
Settings.timer[index].time = itime;
}
val = root[PSTR(D_JSON_TIMER_WINDOW)];
if (val) {
Settings.timer[index].window = val.getUInt() & 0x0F;
TimerSetRandomWindow(index);
}
val = root[PSTR(D_JSON_TIMER_DAYS)];
if (val) {
// SMTWTFS = 1234567 = 0011001 = 00TW00S = --TW--S
Settings.timer[index].days = 0;
const char *tday = val.getStr();
uint8_t i = 0;
char ch = *tday++;
while ((ch != '\0') && (i < 7)) {
if (ch == '-') { ch = '0'; }
uint8_t mask = 1 << i++;
Settings.timer[index].days |= (ch == '0') ? 0 : mask;
ch = *tday++;
}
}
val = root[PSTR(D_JSON_TIMER_REPEAT)];
if (val) {
Settings.timer[index].repeat = (val.getUInt() != 0);
}
val = root[PSTR(D_JSON_TIMER_OUTPUT)];
if (val) {
uint8_t device = (val.getUInt() -1) & 0x0F;
Settings.timer[index].device = (device < devices_present) ? device : 0;
}
val = root[PSTR(D_JSON_TIMER_ACTION)];
if (val) {
uint8_t action = val.getUInt() & 0x03;
Settings.timer[index].power = (devices_present) ? action : 3; // If no devices than only allow rules
}
index++;
}
//#ifndef USE_RULES
#endif
#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
} else {
Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_TIMER_NO_DEVICE "\"}"), index); // No outputs defined so nothing to control

View File

@ -497,6 +497,7 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT"
}
#if 0
// StaticJsonBuffer<1280> jsonBuf; // Was 1024 until 20200811
DynamicJsonBuffer jsonBuf; // Was static until 20200812
JsonObject &root = jsonBuf.parseObject(event);
@ -528,6 +529,42 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
} else {
str_value = (*obj)[rule_name]; // "CURRENT"
}
#else
String buf = event; // copy the string into a new buffer that will be modified
JsonParser parser = JsonParser((char*)buf.c_str());
JsonParserObject obj = parser.getRootObject();
if (!obj) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event too long (%d)"), event.length());
return false; // No valid JSON data
}
String subtype;
uint32_t i = 0;
while ((pos = rule_name.indexOf("#")) > 0) { // "SUBTYPE1#SUBTYPE2#CURRENT"
subtype = rule_name.substring(0, pos);
obj = obj[subtype.c_str()].getObject();
if (!obj) { return false; } // not found
rule_name = rule_name.substring(pos +1);
if (i++ > 10) { return false; } // Abandon possible loop
yield();
}
JsonParserToken val = obj[rule_name.c_str()];
if (!val) { return false; } // last level not found
const char* str_value;
if (rule_name_idx) {
if (val.isArray()) {
str_value = (val.getArray())[rule_name_idx -1].getStr();
} else {
str_value = val.getStr();
}
} else {
str_value = val.getStr(); // "CURRENT"
}
#endif
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"),
// rule_name.c_str(), rule_svalue, Rules.trigger_count[rule_set], bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set]), event.c_str(), (str_value) ? str_value : "none");
@ -1034,20 +1071,26 @@ bool RulesMqttData(void)
if (event_item.Key.length() == 0) { //If did not specify Key
value = sData;
} else { //If specified Key, need to parse Key/Value from JSON data
StaticJsonBuffer<500> jsonBuf;
JsonObject& jsonData = jsonBuf.parseObject(sData);
JsonParserObject jsonData = JsonParser((char*)sData.c_str()).getRootObject();
String key1 = event_item.Key;
String key2;
if (!jsonData.success()) break; //Failed to parse JSON data, ignore this message.
if (!jsonData) break; //Failed to parse JSON data, ignore this message.
int dot;
if ((dot = key1.indexOf('.')) > 0) {
key2 = key1.substring(dot+1);
key1 = key1.substring(0, dot);
if (!jsonData[key1][key2].success()) break; //Failed to get the key/value, ignore this message.
value = (const char *)jsonData[key1][key2];
JsonParserToken value_tok = jsonData[key1.c_str()][key2.c_str()];
if (!value_tok) break; //Failed to get the key/value, ignore this message.
value = value_tok.getStr();
// if (!jsonData[key1][key2].success()) break; //Failed to get the key/value, ignore this message.
// value = (const char *)jsonData[key1][key2];
} else {
if (!jsonData[key1].success()) break;
value = (const char *)jsonData[key1];
JsonParserToken value_tok = jsonData[key1.c_str()];
if (!value_tok) break; //Failed to get the key/value, ignore this message.
value = value_tok.getStr();
// if (!jsonData[key1].success()) break;
// value = (const char *)jsonData[key1];
}
}
value.trim();

View File

@ -569,10 +569,12 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
if (Webserver->args()) {
response = "[";
StaticJsonBuffer<300> jsonBuffer;
JsonObject &hue_json = jsonBuffer.parseObject(Webserver->arg((Webserver->args())-1));
if (hue_json.containsKey("on")) {
on = hue_json["on"];
JsonParser parser = JsonParser((char*) Webserver->arg((Webserver->args())-1).c_str());
JsonParserObject root = parser.getRootObject();
JsonParserToken hue_on = root[PSTR("on")];
if (hue_on) {
on = hue_on.getBool();
snprintf_P(buf, buf_size,
PSTR("{\"success\":{\"/lights/%d/state/on\":%s}}"),
device_id, on ? "true" : "false");
@ -587,15 +589,6 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
}
} else {
#endif
/*
switch(on)
{
case false : ExecuteCommandPower(device, POWER_OFF, SRC_HUE);
break;
case true : ExecuteCommandPower(device, POWER_ON, SRC_HUE);
break;
}
*/
ExecuteCommandPower(device, (on) ? POWER_ON : POWER_OFF, SRC_HUE);
response += buf;
resp = true;
@ -619,8 +612,10 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
}
prev_x_str[0] = prev_y_str[0] = 0; // reset xy string
if (hue_json.containsKey("bri")) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
bri = hue_json["bri"];
parser.setCurrent();
JsonParserToken hue_bri = root[PSTR("bri")];
if (hue_bri) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
bri = hue_bri.getUInt();
prev_bri = bri; // store command value
if (resp) { response += ","; }
snprintf_P(buf, buf_size,
@ -634,15 +629,19 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
}
resp = true;
}
// handle xy before Hue/Sat
// If the request contains both XY and HS, we wan't to give priority to HS
if (hue_json.containsKey("xy")) {
float x = hue_json["xy"][0];
float y = hue_json["xy"][1];
const String &x_str = hue_json["xy"][0];
const String &y_str = hue_json["xy"][1];
x_str.toCharArray(prev_x_str, sizeof(prev_x_str));
y_str.toCharArray(prev_y_str, sizeof(prev_y_str));
parser.setCurrent();
JsonParserToken hue_xy = root[PSTR("xy")];
if (hue_xy) {
JsonParserArray arr_xy = JsonParserArray(hue_xy);
JsonParserToken tok_x = arr_xy[0];
JsonParserToken tok_y = arr_xy[1];
float x = tok_x.getFloat();
float y = tok_y.getFloat();
strlcpy(prev_x_str, tok_x.getStr(), sizeof(prev_x_str));
strlcpy(prev_y_str, tok_y.getStr(), sizeof(prev_y_str));
uint8_t rr,gg,bb;
LightStateClass::XyToRgb(x, y, &rr, &gg, &bb);
LightStateClass::RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
@ -658,8 +657,11 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
resp = true;
change = true;
}
if (hue_json.containsKey("hue")) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
hue = hue_json["hue"];
parser.setCurrent();
JsonParserToken hue_hue = root[PSTR("hue")];
if (hue_hue) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
hue = hue_hue.getUInt();
prev_hue = hue;
if (resp) { response += ","; }
snprintf_P(buf, buf_size,
@ -674,8 +676,11 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
}
resp = true;
}
if (hue_json.containsKey("sat")) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
sat = hue_json["sat"];
parser.setCurrent();
JsonParserToken hue_sat = root[PSTR("sat")];
if (hue_sat) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
sat = hue_sat.getUInt();
prev_sat = sat; // store command value
if (resp) { response += ","; }
snprintf_P(buf, buf_size,
@ -690,8 +695,11 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
}
resp = true;
}
if (hue_json.containsKey("ct")) { // Color temperature 153 (Cold) to 500 (Warm)
ct = hue_json["ct"];
parser.setCurrent();
JsonParserToken hue_ct = root[PSTR("ct")];
if (hue_ct) { // Color temperature 153 (Cold) to 500 (Warm)
ct = hue_ct.getUInt();
prev_ct = ct; // store commande value
if (resp) { response += ","; }
snprintf_P(buf, buf_size,
@ -704,6 +712,7 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
}
resp = true;
}
if (change) {
#ifdef USE_SHUTTER
if (ShutterState(device)) {

View File

@ -39,42 +39,6 @@ public:
void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl);
bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_status_ok = false);
// get the result as a string (const char*) and nullptr if there is no field or the string is empty
const char * getCaseInsensitiveConstCharNull(const JsonObject &json, const char *needle) {
const JsonVariant &val = GetCaseInsensitive(json, needle);
if (&val) {
const char *val_cs = val.as<const char*>();
if (strlen(val_cs)) {
return val_cs;
}
}
return nullptr;
}
// Get an JSON attribute, with case insensitive key search starting with *needle
JsonVariant &startsWithCaseInsensitive(const JsonObject &json, const char *needle) {
// key can be in PROGMEM
if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
return *(JsonVariant*)nullptr;
}
String needle_s((const __FlashStringHelper *)needle);
needle_s.toLowerCase();
for (auto kv : json) {
String key_s(kv.key);
key_s.toLowerCase();
JsonVariant &value = kv.value;
if (key_s.startsWith(needle_s)) {
return value;
}
}
// if not found
return *(JsonVariant*)nullptr;
}
uint32_t parseHex(const char **data, size_t max_len = 8) {
uint32_t ret = 0;
for (uint32_t i = 0; i < max_len; i++) {

View File

@ -19,6 +19,8 @@
#ifdef USE_ZIGBEE
#include "JsonParser.h"
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
#endif
@ -261,7 +263,7 @@ public:
// Dump json
String dumpLightState(uint16_t shortaddr) const;
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const;
int32_t deviceRestore(const JsonObject &json);
int32_t deviceRestore(JsonParserObject json);
// General Zigbee device profile support
void setZbProfile(uint16_t shortaddr, uint8_t zb_profile);
@ -936,7 +938,7 @@ void Z_Devices::clean(void) {
// - a number 0..99, the index number in ZigbeeStatus
// - a friendly name, between quotes, example: "Room_Temp"
uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_known) const {
if (nullptr == param) { return 0; }
if (nullptr == param) { return BAD_SHORTADDR; }
size_t param_len = strlen(param);
char dataBuf[param_len + 1];
strcpy(dataBuf, param);
@ -1070,7 +1072,7 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
// <0 : Error
//
// Ex: {"Device":"0x5ADF","Name":"IKEA_Light","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}
int32_t Z_Devices::deviceRestore(const JsonObject &json) {
int32_t Z_Devices::deviceRestore(JsonParserObject json) {
// params
uint16_t device = 0x0000; // 0x0000 is coordinator so considered invalid
@ -1078,56 +1080,38 @@ int32_t Z_Devices::deviceRestore(const JsonObject &json) {
const char * modelid = nullptr;
const char * manufid = nullptr;
const char * friendlyname = nullptr;
int8_t bulbtype = 0xFF;
int8_t bulbtype = -1;
size_t endpoints_len = 0;
// read mandatory "Device"
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = strToUInt(val_device);
JsonParserToken val_device = json[PSTR("Device")];
if (val_device) {
device = (uint32_t) val_device.getUInt(device);
} else {
return -1; // missing "Device" attribute
}
// read "IEEEAddr" 64 bits in format "0x0000000000000000"
const JsonVariant &val_ieeeaddr = GetCaseInsensitive(json, PSTR("IEEEAddr"));
if (nullptr != &val_ieeeaddr) {
ieeeaddr = strtoull(val_ieeeaddr.as<const char*>(), nullptr, 0);
}
// read "Name"
friendlyname = getCaseInsensitiveConstCharNull(json, PSTR("Name"));
// read "ModelId"
modelid = getCaseInsensitiveConstCharNull(json, PSTR("ModelId"));
// read "Manufacturer"
manufid = getCaseInsensitiveConstCharNull(json, PSTR("Manufacturer"));
// read "Light"
const JsonVariant &val_bulbtype = GetCaseInsensitive(json, PSTR(D_JSON_ZIGBEE_LIGHT));
if (nullptr != &val_bulbtype) { bulbtype = strToUInt(val_bulbtype);; }
ieeeaddr = json.getULong(PSTR("IEEEAddr"), ieeeaddr); // read "IEEEAddr" 64 bits in format "0x0000000000000000"
friendlyname = json.getStr(PSTR("Name"), nullptr); // read "Name"
modelid = json.getStr(PSTR("ModelId"), nullptr);
manufid = json.getStr(PSTR("Manufacturer"), nullptr);
JsonParserToken tok_bulbtype = json[PSTR(D_JSON_ZIGBEE_LIGHT)];
// update internal device information
updateDevice(device, ieeeaddr);
if (modelid) { setModelId(device, modelid); }
if (manufid) { setManufId(device, manufid); }
if (friendlyname) { setFriendlyName(device, friendlyname); }
if (&val_bulbtype) { setHueBulbtype(device, bulbtype); }
if (tok_bulbtype) { setHueBulbtype(device, tok_bulbtype.getInt()); }
// read "Endpoints"
const JsonVariant &val_endpoints = GetCaseInsensitive(json, PSTR("Endpoints"));
if ((nullptr != &val_endpoints) && (val_endpoints.is<JsonArray>())) {
const JsonArray &arr_ep = val_endpoints.as<const JsonArray&>();
endpoints_len = arr_ep.size();
JsonParserToken val_endpoints = json[PSTR("Endpoints")];
if (val_endpoints.isArray()) {
JsonParserArray arr_ep = JsonParserArray(val_endpoints);
clearEndpoints(device); // clear even if array is empty
if (endpoints_len) {
for (auto ep_elt : arr_ep) {
uint8_t ep = strToUInt(ep_elt);
if (ep) {
addEndpoint(device, ep);
}
}
for (auto ep_elt : arr_ep) {
uint8_t ep = ep_elt.getUInt();
if (ep) { addEndpoint(device, ep); }
}
}

View File

@ -218,10 +218,12 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
if (Webserver->args()) {
response = "[";
StaticJsonBuffer<300> jsonBuffer;
JsonObject &hue_json = jsonBuffer.parseObject(Webserver->arg((Webserver->args())-1));
if (hue_json.containsKey("on")) {
on = hue_json["on"];
JsonParser parser = JsonParser((char*) Webserver->arg((Webserver->args())-1).c_str());
JsonParserObject root = parser.getRootObject();
JsonParserToken hue_on = root[PSTR("on")];
if (hue_on) {
on = hue_on.getBool();
snprintf_P(buf, buf_size,
PSTR("{\"success\":{\"/lights/%d/state/on\":%s}}"),
device_id, on ? "true" : "false");
@ -235,8 +237,10 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
resp = true;
}
if (hue_json.containsKey("bri")) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
bri = hue_json["bri"];
parser.setCurrent();
JsonParserToken hue_bri = root[PSTR("bri")];
if (hue_bri) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
bri = hue_bri.getUInt();
prev_bri = bri; // store command value
if (resp) { response += ","; }
snprintf_P(buf, buf_size,
@ -252,13 +256,16 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
}
// handle xy before Hue/Sat
// If the request contains both XY and HS, we wan't to give priority to HS
if (hue_json.containsKey("xy")) {
float x = hue_json["xy"][0];
float y = hue_json["xy"][1];
const String &x_str = hue_json["xy"][0];
const String &y_str = hue_json["xy"][1];
x_str.toCharArray(prev_x_str, sizeof(prev_x_str));
y_str.toCharArray(prev_y_str, sizeof(prev_y_str));
parser.setCurrent();
JsonParserToken hue_xy = root[PSTR("xy")];
if (hue_xy) {
JsonParserArray arr_xy = JsonParserArray(hue_xy);
JsonParserToken tok_x = arr_xy[0];
JsonParserToken tok_y = arr_xy[1];
float x = tok_x.getFloat();
float y = tok_y.getFloat();
strlcpy(prev_x_str, tok_x.getStr(), sizeof(prev_x_str));
strlcpy(prev_y_str, tok_y.getStr(), sizeof(prev_y_str));
if (resp) { response += ","; }
snprintf_P(buf, buf_size,
PSTR("{\"success\":{\"/lights/%d/state/xy\":[%s,%s]}}"),
@ -270,8 +277,11 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
ZigbeeHueXY(shortaddr, xi, yi);
}
bool huesat_changed = false;
if (hue_json.containsKey("hue")) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
hue = hue_json["hue"];
parser.setCurrent();
JsonParserToken hue_hue = root[PSTR("hue")];
if (hue_hue) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
hue = hue_hue.getUInt();
prev_hue = hue;
if (resp) { response += ","; }
snprintf_P(buf, buf_size,
@ -285,8 +295,11 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
}
resp = true;
}
if (hue_json.containsKey("sat")) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
sat = hue_json["sat"];
parser.setCurrent();
JsonParserToken hue_sat = root[PSTR("sat")];
if (hue_sat) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
sat = hue_sat.getUInt();
prev_sat = sat; // store command value
if (resp) { response += ","; }
snprintf_P(buf, buf_size,
@ -303,8 +316,11 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
}
resp = true;
}
if (hue_json.containsKey("ct")) { // Color temperature 153 (Cold) to 500 (Warm)
ct = hue_json["ct"];
parser.setCurrent();
JsonParserToken hue_ct = root[PSTR("ct")];
if (hue_ct) { // Color temperature 153 (Cold) to 500 (Warm)
ct = hue_ct.getUInt();
prev_ct = ct; // store commande value
if (resp) { response += ","; }
snprintf_P(buf, buf_size,

View File

@ -21,6 +21,8 @@
#define XDRV_23 23
#include "JsonParser.h"
const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix
#ifdef USE_ZIGBEE_ZNP
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEEZNPRECEIVE "|"
@ -97,19 +99,6 @@ void ZigbeeInit(void)
* Commands
\*********************************************************************************************/
uint32_t strToUInt(const JsonVariant &val) {
// if the string starts with 0x, it is considered Hex, otherwise it is an int
if (val.is<unsigned int>()) {
return val.as<unsigned int>();
} else {
if (val.is<const char*>()) {
String sval = val.as<String>();
return strtoull(sval.c_str(), nullptr, 0);
}
}
return 0; // couldn't parse anything
}
#ifdef USE_ZIGBEE_ZNP
// Do a factory reset of the CC2530
const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM =
@ -242,7 +231,7 @@ bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_stat
// Parse "Report", "Write", "Response" or "Condig" attribute
// Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01)
void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
void ZbSendReportWrite(class JsonParserToken val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
SBuffer buf(200); // buffer to store the binary output of attibutes
if (nullptr == XdrvMailbox.command) {
@ -250,12 +239,11 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
}
// iterate on keys
for (JsonObject::const_iterator it=val_pubwrite.begin(); it!=val_pubwrite.end(); ++it) {
const char *key = it->key;
const JsonVariant &value = it->value;
for (auto key : val_pubwrite.getObject()) {
JsonParserToken value = key.getValue();
Z_attribute attr;
attr.setKeyName(key);
attr.setKeyName(key.getStr());
if (Z_parseAttributeKey(attr)) {
// Buffer ready, do some sanity checks
if (0xFFFF == cluster) {
@ -276,10 +264,10 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
}
}
if (value.is<const char*>()) {
attr.setStr(value.as<const char*>());
} else if (value.is<double>()) {
attr.setFloat(value.as<float>());
if (value.isStr()) {
attr.setStr(value.getStr());
} else if (value.isNum()) {
attr.setFloat(value.getFloat());
}
double val_d = 0; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss
@ -293,41 +281,30 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
} else {
// ////////////////////////////////////////////////////////////////////////////////
// ZCL_CONFIGURE_REPORTING
if (!value.is<JsonObject>()) {
if (!value.isObject()) {
ResponseCmndChar_P(PSTR("Config requires JSON objects"));
return;
}
JsonObject &attr_config = value.as<JsonObject>();
JsonParserObject attr_config = value.getObject();
bool attr_direction = false;
const JsonVariant &val_attr_direction = GetCaseInsensitive(attr_config, PSTR("DirectionReceived"));
if (nullptr != &val_attr_direction) {
uint32_t dir = strToUInt(val_attr_direction);
if (dir) {
attr_direction = true;
}
}
uint32_t dir = attr_config.getUInt(PSTR("DirectionReceived"), 0);
if (dir) { attr_direction = true; }
// read MinInterval and MaxInterval, default to 0xFFFF if not specified
uint16_t attr_min_interval = 0xFFFF;
uint16_t attr_max_interval = 0xFFFF;
const JsonVariant &val_attr_min = GetCaseInsensitive(attr_config, PSTR("MinInterval"));
if (nullptr != &val_attr_min) { attr_min_interval = strToUInt(val_attr_min); }
const JsonVariant &val_attr_max = GetCaseInsensitive(attr_config, PSTR("MaxInterval"));
if (nullptr != &val_attr_max) { attr_max_interval = strToUInt(val_attr_max); }
uint16_t attr_min_interval = attr_config.getUInt(PSTR("MinInterval"), 0xFFFF);
uint16_t attr_max_interval = attr_config.getUInt(PSTR("MaxInterval"), 0xFFFF);
// read ReportableChange
const JsonVariant &val_attr_rc = GetCaseInsensitive(attr_config, PSTR("ReportableChange"));
if (nullptr != &val_attr_rc) {
val_d = val_attr_rc.as<double>();
val_str = val_attr_rc.as<const char*>();
JsonParserToken val_attr_rc = attr_config[PSTR("ReportableChange")];
if (val_attr_rc) {
val_d = val_attr_rc.getFloat();
val_str = val_attr_rc.getStr();
ZbApplyMultiplier(val_d, attr.attr_multiplier);
}
// read TimeoutPeriod
uint16_t attr_timeout = 0x0000;
const JsonVariant &val_attr_timeout = GetCaseInsensitive(attr_config, PSTR("TimeoutPeriod"));
if (nullptr != &val_attr_timeout) { attr_timeout = strToUInt(val_attr_timeout); }
uint16_t attr_timeout = attr_config.getUInt(PSTR("TimeoutPeriod"), 0x0000);
bool attr_discrete = Z_isDiscreteDataType(attr.attr_type);
@ -376,37 +353,36 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
}
// Parse the "Send" attribute and send the command
void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf) {
void ZbSendSend(class JsonParserToken val_cmd, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf) {
uint8_t cmd = 0;
String cmd_str = ""; // the actual low-level command, either specified or computed
const char *cmd_s; // pointer to payload string
const char *cmd_s = ""; // pointer to payload string
bool clusterSpecific = true;
static char delim[] = ", "; // delimiters for parameters
// probe the type of the argument
// If JSON object, it's high level commands
// If String, it's a low level command
if (val_cmd.is<JsonObject>()) {
if (val_cmd.isObject()) {
// we have a high-level command
const JsonObject &cmd_obj = val_cmd.as<const JsonObject&>();
JsonParserObject cmd_obj = val_cmd.getObject();
int32_t cmd_size = cmd_obj.size();
if (cmd_size > 1) {
Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size);
return;
} else if (1 == cmd_size) {
// We have exactly 1 command, parse it
JsonObject::const_iterator it = cmd_obj.begin(); // just get the first key/value
String key = it->key;
const JsonVariant& value = it->value;
JsonParserKey key = cmd_obj.getFirstElement();
JsonParserToken value = key.getValue();
uint32_t x = 0, y = 0, z = 0;
uint16_t cmd_var;
uint16_t local_cluster_id;
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str(), &local_cluster_id, &cmd_var);
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.getStr(), &local_cluster_id, &cmd_var);
if (tasmota_cmd) {
cmd_str = tasmota_cmd;
} else {
Response_P(PSTR("Unrecognized zigbee command: %s"), key.c_str());
Response_P(PSTR("Unrecognized zigbee command: %s"), key.getStr());
return;
}
// check cluster
@ -418,13 +394,17 @@ void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr,
}
// parse the JSON value, depending on its type fill in x,y,z
if (value.is<bool>()) {
x = value.as<bool>() ? 1 : 0;
} else if (value.is<unsigned int>()) {
x = value.as<unsigned int>();
if (value.isNum()) {
x = value.getUInt(); // automatic conversion to 0/1
// if (value.is<bool>()) {
// // x = value.as<bool>() ? 1 : 0;
// } else if
// } else if (value.is<unsigned int>()) {
// x = value.as<unsigned int>();
} else {
// if non-bool or non-int, trying char*
const char *s_const = value.as<const char*>();
const char *s_const = value.getStr(nullptr);
// const char *s_const = value.as<const char*>();
if (s_const != nullptr) {
char s[strlen(s_const)+1];
strcpy(s, s_const);
@ -459,14 +439,13 @@ void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr,
} else {
// we have zero command, pass through until last error for missing command
}
} else if (val_cmd.is<const char*>()) {
} else if (val_cmd.isStr()) {
// low-level command
cmd_str = val_cmd.as<String>();
// Now parse the string to extract cluster, command, and payload
// Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC"
// where AA is the cluster number, BBBB the command number, CCCC... the payload
// First delimiter is '_' for a global command, or '!' for a cluster specific command
const char * data = cmd_str.c_str();
const char * data = val_cmd.getStr();
uint16_t local_cluster_id = parseHex(&data, 4);
// check cluster
@ -505,7 +484,7 @@ void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr,
// Parse the "Send" attribute and send the command
void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
void ZbSendRead(JsonParserToken val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":5}
// ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"}
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]}
@ -525,32 +504,30 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
attr_item_offset = 1;
}
uint16_t val = strToUInt(val_attr);
if (val_attr.is<JsonArray>()) {
if (val_attr.isArray()) {
// value is an array []
const JsonArray& attr_arr = val_attr.as<const JsonArray&>();
JsonParserArray attr_arr = val_attr.getArray();
attrs_len = attr_arr.size() * attr_item_len;
attrs = (uint8_t*) calloc(attrs_len, 1);
uint32_t i = 0;
for (auto value : attr_arr) {
uint16_t val = strToUInt(value);
uint16_t val = value.getUInt();
i += attr_item_offset;
attrs[i++] = val & 0xFF;
attrs[i++] = val >> 8;
i += attr_item_len - 2 - attr_item_offset; // normally 0
}
} else if (val_attr.is<JsonObject>()) {
} else if (val_attr.isObject()) {
// value is an object {}
const JsonObject& attr_obj = val_attr.as<const JsonObject&>();
JsonParserObject attr_obj = val_attr.getObject();
attrs_len = attr_obj.size() * attr_item_len;
attrs = (uint8_t*) calloc(attrs_len, 1);
uint32_t actual_attr_len = 0;
// iterate on keys
for (JsonObject::const_iterator it=attr_obj.begin(); it!=attr_obj.end(); ++it) {
const char *key = it->key;
const JsonVariant &value = it->value; // we don't need the value here, only keys are relevant
for (auto key : attr_obj) {
JsonParserToken value = key.getValue();
bool found = false;
// scan attributes to find by name, and retrieve type
@ -561,11 +538,11 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
// uint8_t local_type_id = pgm_read_byte(&converter->type);
if ((pgm_read_word(&converter->name_offset)) && (0 == strcasecmp_P(key, Z_strings + pgm_read_word(&converter->name_offset)))) {
if ((pgm_read_word(&converter->name_offset)) && (0 == strcasecmp_P(key.getStr(), Z_strings + pgm_read_word(&converter->name_offset)))) {
// match name
// check if there is a conflict with cluster
// TODO
if (!value && attr_item_offset) {
if (!(value.getBool()) && attr_item_offset) {
// If value is false (non-default) then set direction to 1 (for ReadConfig)
attrs[actual_attr_len] = 0x01;
}
@ -594,6 +571,7 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
} else {
// value is a literal
if (0xFFFF != cluster) {
uint16_t val = val_attr.getUInt();
attrs_len = attr_item_len;
attrs = (uint8_t*) calloc(attrs_len, 1);
attrs[0 + attr_item_offset] = val & 0xFF; // little endian
@ -648,9 +626,8 @@ void CmndZbSend(void) {
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} }
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
DynamicJsonBuffer jsonBuf;
const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
JsonParserObject root = JsonParser(XdrvMailbox.data).getRootObject();
if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params
uint16_t device = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
@ -661,15 +638,15 @@ void CmndZbSend(void) {
// parse "Device" and "Group"
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_DEVICE));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
JsonParserToken val_device = root[PSTR(D_CMND_ZIGBEE_DEVICE)];
if (val_device) {
device = zigbee_devices.parseDeviceParam(val_device.getStr());
if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
}
if (BAD_SHORTADDR == device) { // if not found, check if we have a group
const JsonVariant &val_group = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_GROUP));
if (nullptr != &val_group) {
groupaddr = strToUInt(val_group);
JsonParserToken val_group = root[PSTR(D_CMND_ZIGBEE_GROUP)];
if (val_group) {
groupaddr = val_group.getUInt();
} else { // no device nor group
ResponseCmndChar_P(PSTR("Unknown device"));
return;
@ -679,12 +656,9 @@ void CmndZbSend(void) {
// Note: groupaddr == 0 is valid
// read other parameters
const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CLUSTER));
if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); }
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_ENDPOINT));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
const JsonVariant &val_manuf = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_MANUF));
if (nullptr != &val_manuf) { manuf = strToUInt(val_manuf); }
cluster = root.getUInt(PSTR(D_CMND_ZIGBEE_CLUSTER), cluster);
endpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), endpoint);
manuf = root.getUInt(PSTR(D_CMND_ZIGBEE_MANUF), manuf);
// infer endpoint
if (BAD_SHORTADDR == device) {
@ -700,61 +674,61 @@ void CmndZbSend(void) {
// from here endpoint is valid and non-zero
// cluster may be already specified or 0xFFFF
const JsonVariant &val_cmd = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_SEND));
const JsonVariant &val_read = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_READ));
const JsonVariant &val_write = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_WRITE));
const JsonVariant &val_publish = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_REPORT));
const JsonVariant &val_response = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_RESPONSE));
const JsonVariant &val_read_config = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_READ_CONFIG));
const JsonVariant &val_config = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CONFIG));
uint32_t multi_cmd = (nullptr != &val_cmd) + (nullptr != &val_read) + (nullptr != &val_write) + (nullptr != &val_publish)
+ (nullptr != &val_response) + (nullptr != &val_read_config) + (nullptr != &val_config);
JsonParserToken val_cmd = root[PSTR(D_CMND_ZIGBEE_SEND)];
JsonParserToken val_read = root[PSTR(D_CMND_ZIGBEE_READ)];
JsonParserToken val_write = root[PSTR(D_CMND_ZIGBEE_WRITE)];
JsonParserToken val_publish = root[PSTR(D_CMND_ZIGBEE_REPORT)];
JsonParserToken val_response = root[PSTR(D_CMND_ZIGBEE_RESPONSE)];
JsonParserToken val_read_config = root[PSTR(D_CMND_ZIGBEE_READ_CONFIG)];
JsonParserToken val_config = root[PSTR(D_CMND_ZIGBEE_CONFIG)];
uint32_t multi_cmd = ((bool)val_cmd) + ((bool)val_read) + ((bool)val_write) + ((bool)val_publish)
+ ((bool)val_response) + ((bool)val_read_config) + ((bool)val_config);
if (multi_cmd > 1) {
ResponseCmndChar_P(PSTR("Can only have one of: 'Send', 'Read', 'Write', 'Report', 'Reponse', 'ReadConfig' or 'Config'"));
return;
}
// from here we have one and only one command
if (nullptr != &val_cmd) {
if (val_cmd) {
// "Send":{...commands...}
// we accept either a string or a JSON object
ZbSendSend(val_cmd, device, groupaddr, cluster, endpoint, manuf);
} else if (nullptr != &val_read) {
} else if (val_read) {
// "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...]
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
ZbSendRead(val_read, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_ATTRIBUTES);
} else if (nullptr != &val_write) {
} else if (val_write) {
// only KSON object
if (!val_write.is<JsonObject>()) {
if (!val_write.isObject()) {
ResponseCmndChar_P(PSTR("Missing parameters"));
return;
}
// "Write":{...attributes...}
ZbSendReportWrite(val_write, device, groupaddr, cluster, endpoint, manuf, ZCL_WRITE_ATTRIBUTES);
} else if (nullptr != &val_publish) {
} else if (val_publish) {
// "Publish":{...attributes...}
// only KSON object
if (!val_publish.is<JsonObject>()) {
if (!val_publish.isObject()) {
ResponseCmndChar_P(PSTR("Missing parameters"));
return;
}
ZbSendReportWrite(val_publish, device, groupaddr, cluster, endpoint, manuf, ZCL_REPORT_ATTRIBUTES);
} else if (nullptr != &val_response) {
} else if (val_response) {
// "Report":{...attributes...}
// only KSON object
if (!val_response.is<JsonObject>()) {
if (!val_response.isObject()) {
ResponseCmndChar_P(PSTR("Missing parameters"));
return;
}
ZbSendReportWrite(val_response, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_ATTRIBUTES_RESPONSE);
} else if (nullptr != &val_read_config) {
} else if (val_read_config) {
// "ReadConfg":{...attributes...}, "ReadConfg":attribute or "ReadConfg":[...attributes...]
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
ZbSendRead(val_read_config, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_REPORTING_CONFIGURATION);
} else if (nullptr != &val_config) {
} else if (val_config) {
// "Config":{...attributes...}
// only JSON object
if (!val_config.is<JsonObject>()) {
if (!val_config.isObject()) {
ResponseCmndChar_P(PSTR("Missing parameters"));
return;
}
@ -774,61 +748,56 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
// local endpoint is always 1, IEEE addresses are calculated
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
DynamicJsonBuffer jsonBuf;
const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
JsonParserObject root = JsonParser(XdrvMailbox.data).getRootObject();
if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params
uint16_t srcDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
uint16_t srcDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
uint16_t dstDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
uint64_t dstLongAddr = 0;
uint8_t endpoint = 0x00; // 0x00 is invalid for the src endpoint
uint8_t toendpoint = 0x00; // 0x00 is invalid for the dst endpoint
uint8_t toendpoint = 0x01; // default dest endpoint to 0x01
uint16_t toGroup = 0x0000; // group address
uint16_t cluster = 0; // 0xFFFF is invalid
uint32_t group = 0xFFFFFFFF; // 16 bits values, otherwise 0xFFFFFFFF is unspecified
// Information about source device: "Device", "Endpoint", "Cluster"
// - the source endpoint must have a known IEEE address
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_DEVICE));
if (nullptr != &val_device) {
srcDevice = zigbee_devices.parseDeviceParam(val_device.as<char*>());
}
if ((nullptr == &val_device) || (BAD_SHORTADDR == srcDevice)) { ResponseCmndChar_P(PSTR("Unknown source device")); return; }
srcDevice = zigbee_devices.parseDeviceParam(root.getStr(PSTR(D_CMND_ZIGBEE_DEVICE), nullptr));
if (BAD_SHORTADDR == srcDevice) { ResponseCmndChar_P(PSTR("Unknown source device")); return; }
// check if IEEE address is known
uint64_t srcLongAddr = zigbee_devices.getDeviceLongAddr(srcDevice);
if (0 == srcLongAddr) { ResponseCmndChar_P(PSTR("Unknown source IEEE address")); return; }
// look for source endpoint
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_ENDPOINT));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
else { endpoint = zigbee_devices.findFirstEndpoint(srcDevice); }
endpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), endpoint);
if (0 == endpoint) { endpoint = zigbee_devices.findFirstEndpoint(srcDevice); }
// look for source cluster
const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CLUSTER));
if (nullptr != &val_cluster) {
cluster = strToUInt(val_cluster); // first convert as number
JsonParserToken val_cluster = root[PSTR(D_CMND_ZIGBEE_CLUSTER)];
if (val_cluster) {
cluster = val_cluster.getUInt(cluster); // first convert as number
if (0 == cluster) {
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr);
zigbeeFindAttributeByName(val_cluster.getStr(), &cluster, nullptr, nullptr);
}
}
// Or Group Address - we don't need a dstEndpoint in this case
const JsonVariant &to_group = GetCaseInsensitive(json, PSTR("ToGroup"));
if (nullptr != &to_group) { toGroup = strToUInt(to_group); }
JsonParserToken to_group = root[PSTR("ToGroup")];
if (to_group) { toGroup = to_group.getUInt(toGroup); }
// Either Device address
// In this case the following parameters are mandatory
// - "ToDevice" and the device must have a known IEEE address
// - "ToEndpoint"
const JsonVariant &dst_device = GetCaseInsensitive(json, PSTR("ToDevice"));
JsonParserToken dst_device = root[PSTR("ToDevice")];
// If no target is specified, we default to coordinator 0x0000
if ((nullptr == &to_group) && (nullptr == &dst_device)) {
if ((!to_group) && (!dst_device)) {
dstDevice = 0x0000;
}
if ((nullptr != &dst_device) || (BAD_SHORTADDR != dstDevice)) {
if ((dst_device) || (BAD_SHORTADDR != dstDevice)) {
if (BAD_SHORTADDR == dstDevice) {
dstDevice = zigbee_devices.parseDeviceParam(dst_device.as<char*>());
dstDevice = zigbee_devices.parseDeviceParam(dst_device.getStr(nullptr));
if (BAD_SHORTADDR == dstDevice) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
}
if (0x0000 == dstDevice) {
@ -838,14 +807,12 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
}
if (0 == dstLongAddr) { ResponseCmndChar_P(PSTR("Unknown dest IEEE address")); return; }
const JsonVariant &val_toendpoint = GetCaseInsensitive(json, PSTR("ToEndpoint"));
if (nullptr != &val_toendpoint) { toendpoint = strToUInt(val_toendpoint); }
else { toendpoint = 0x01; } // default to endpoint 1
toendpoint = root.getUInt(PSTR("ToEndpoint"), toendpoint);
}
// make sure we don't have conflicting parameters
if (&to_group && dstLongAddr) { ResponseCmndChar_P(PSTR("Cannot have both \"ToDevice\" and \"ToGroup\"")); return; }
if (!&to_group && !dstLongAddr) { ResponseCmndChar_P(PSTR("Missing \"ToDevice\" or \"ToGroup\"")); return; }
if (to_group && dstLongAddr) { ResponseCmndChar_P(PSTR("Cannot have both \"ToDevice\" and \"ToGroup\"")); return; }
if (!to_group && !dstLongAddr) { ResponseCmndChar_P(PSTR("Missing \"ToDevice\" or \"ToGroup\"")); return; }
#ifdef USE_ZIGBEE_ZNP
SBuffer buf(34);
@ -1097,38 +1064,32 @@ void CmndZbSave(void) {
// ZbRestore {"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}
void CmndZbRestore(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
DynamicJsonBuffer jsonBuf;
const JsonVariant json_parsed = jsonBuf.parse((const char*) XdrvMailbox.data); // const to force a copy of parameter
const JsonVariant * json = &json_parsed; // root of restore, to be changed if needed
bool success = false;
JsonParser p(XdrvMailbox.data);
JsonParserToken root = p.getRoot();
// check if parsing succeeded
if (json_parsed.is<JsonObject>()) {
success = json_parsed.as<const JsonObject&>().success();
} else if (json_parsed.is<JsonArray>()) {
success = json_parsed.as<const JsonArray&>().success();
}
if (!success) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
if (!p || !(root.isObject() || root.isArray())) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// Check is root contains `ZbStatus<x>` key, if so change the root
const JsonVariant * zbstatus = &startsWithCaseInsensitive(*json, PSTR("ZbStatus"));
if (nullptr != zbstatus) {
json = zbstatus;
JsonParserToken zbstatus = root.getObject().findStartsWith(PSTR("ZbStatus"));
if (zbstatus) {
root = zbstatus;
}
// check if the root is an array
if (json->is<JsonArray>()) {
const JsonArray& arr = json->as<const JsonArray&>();
for (auto elt : arr) {
if (root.isArray()) {
JsonParserArray arr = JsonParserArray(root);
for (const auto elt : arr) {
// call restore on each item
int32_t res = zigbee_devices.deviceRestore(elt);
if (res < 0) {
ResponseCmndChar_P(PSTR("Restore failed"));
return;
if (elt.isObject()) {
int32_t res = zigbee_devices.deviceRestore(JsonParserObject(elt));
if (res < 0) {
ResponseCmndChar_P(PSTR("Restore failed"));
return;
}
}
}
} else if (json->is<JsonObject>()) {
int32_t res = zigbee_devices.deviceRestore(*json);
} else if (root.isObject()) {
int32_t res = zigbee_devices.deviceRestore(JsonParserObject(root));
if (res < 0) {
ResponseCmndChar_P(PSTR("Restore failed"));
return;
@ -1258,31 +1219,19 @@ void CmndZbConfig(void) {
// if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
RemoveAllSpaces(XdrvMailbox.data);
if (strlen(XdrvMailbox.data) > 0) {
DynamicJsonBuffer jsonBuf;
const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
JsonParserObject root = JsonParser(XdrvMailbox.data).getRootObject();
if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// Channel
const JsonVariant &val_channel = GetCaseInsensitive(json, PSTR("Channel"));
if (nullptr != &val_channel) { zb_channel = strToUInt(val_channel); }
zb_channel = root.getUInt(PSTR("Channel"), zb_channel);
zb_pan_id = root.getUInt(PSTR("PanID"), zb_pan_id);
zb_ext_panid = root.getULong(PSTR("ExtPanID"), zb_ext_panid);
zb_precfgkey_l = root.getULong(PSTR("KeyL"), zb_precfgkey_l);
zb_precfgkey_h = root.getULong(PSTR("KeyH"), zb_precfgkey_h);
zb_txradio_dbm = root.getUInt(PSTR("TxRadio"), zb_txradio_dbm);
if (zb_channel < 11) { zb_channel = 11; }
if (zb_channel > 26) { zb_channel = 26; }
// PanID
const JsonVariant &val_pan_id = GetCaseInsensitive(json, PSTR("PanID"));
if (nullptr != &val_pan_id) { zb_pan_id = strToUInt(val_pan_id); }
// ExtPanID
const JsonVariant &val_ext_pan_id = GetCaseInsensitive(json, PSTR("ExtPanID"));
if (nullptr != &val_ext_pan_id) { zb_ext_panid = strtoull(val_ext_pan_id.as<const char*>(), nullptr, 0); }
// KeyL
const JsonVariant &val_key_l = GetCaseInsensitive(json, PSTR("KeyL"));
if (nullptr != &val_key_l) { zb_precfgkey_l = strtoull(val_key_l.as<const char*>(), nullptr, 0); }
// KeyH
const JsonVariant &val_key_h = GetCaseInsensitive(json, PSTR("KeyH"));
if (nullptr != &val_key_h) { zb_precfgkey_h = strtoull(val_key_h.as<const char*>(), nullptr, 0); }
// TxRadio dBm
const JsonVariant &val_txradio = GetCaseInsensitive(json, PSTR("TxRadio"));
if (nullptr != &val_txradio) { zb_txradio_dbm = strToUInt(val_txradio); }
// if network key is zero, we generate a truly random key with a hardware generator from ESP
if ((0 == zb_precfgkey_l) && (0 == zb_precfgkey_h)) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "generating random Zigbee network key"));