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 new shutter modes (#9244)
- Add Zigbee auto-config when pairing - Add Zigbee auto-config when pairing
- Add support for MLX90640 IR array temperature sensor by Christian Baars - Add support for MLX90640 IR array temperature sensor by Christian Baars
- Change replace ArduinoJson with JSMN for JSON parsing
### 8.5.0 20200907 ### 8.5.0 20200907

View File

@ -29,6 +29,7 @@ extern struct rst_info resetInfo;
\*********************************************************************************************/ \*********************************************************************************************/
#include <Ticker.h> #include <Ticker.h>
#include "JsonParser.h"
Ticker tickerOSWatch; Ticker tickerOSWatch;
@ -1412,8 +1413,9 @@ bool GetUsedInModule(uint32_t val, uint16_t *arr)
return false; 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} // {"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 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 Settings.user_template_base = base -1; // Default WEMOS
} }
return true; 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) void TemplateJson(void)

View File

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

View File

@ -27,6 +27,8 @@
#define XDRV_01 1 #define XDRV_01 1
#include "JsonParser.h"
#ifndef WIFI_SOFT_AP_CHANNEL #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 #define WIFI_SOFT_AP_CHANNEL 1 // Soft Access Point Channel number between 1 and 11 as used by WifiManager web GUI
#endif #endif
@ -3344,6 +3346,7 @@ bool JsonWebColor(const char* dataBuf)
// Default pre v7 (Light theme) // 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"]} // {"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]; char dataBufLc[strlen(dataBuf) +1];
LowerCase(dataBufLc, dataBuf); LowerCase(dataBufLc, dataBuf);
RemoveSpace(dataBufLc); 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; return true;
} }

View File

@ -25,6 +25,7 @@
#define XDRV_05 5 #define XDRV_05 5
#include <IRremoteESP8266.h> #include <IRremoteESP8266.h>
#include "JsonParser.h"
enum IrErrors { IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND }; 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": "RC5", "bits": 12, "data":"0xC86" }
// IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 } // IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
#if 0
char dataBufUc[XdrvMailbox.data_len + 1]; char dataBufUc[XdrvMailbox.data_len + 1];
UpperCase(dataBufUc, XdrvMailbox.data); UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc); RemoveSpace(dataBufUc);
@ -200,6 +202,18 @@ uint32_t IrRemoteCmndIrSendJson(void)
uint16_t bits = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_BITS))]; 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); 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))]; 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 // check if the IRSend<x> is great than repeat
if (XdrvMailbox.index > repeat + 1) { if (XdrvMailbox.index > repeat + 1) {
repeat = XdrvMailbox.index - 1; repeat = XdrvMailbox.index - 1;

View File

@ -29,6 +29,7 @@
#include <IRrecv.h> #include <IRrecv.h>
#include <IRutils.h> #include <IRutils.h>
#include <IRac.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, 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 }; IE_UNSUPPORTED_HVAC, IE_UNSUPPORTED_PROTOCOL };
@ -267,6 +268,16 @@ String listSupportedProtocols(bool hvac) {
return l; 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 // used to convert values 0-5 to fanspeed_t
const stdAc::fanspeed_t IrHvacFanSpeed[] PROGMEM = { stdAc::fanspeed_t::kAuto, const stdAc::fanspeed_t IrHvacFanSpeed[] PROGMEM = { stdAc::fanspeed_t::kAuto,
stdAc::fanspeed_t::kMin, stdAc::fanspeed_t::kLow,stdAc::fanspeed_t::kMedium, 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) uint32_t IrRemoteCmndIrHvacJson(void)
{ {
stdAc::state_t state, prev; stdAc::state_t state, prev;
char parm_uc[12];
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRHVAC: Received %s"), XdrvMailbox.data); //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRHVAC: Received %s"), XdrvMailbox.data);
char dataBufUc[XdrvMailbox.data_len + 1]; JsonParserObject root = JsonParser((char*) XdrvMailbox.data).getRootObject();
UpperCase(dataBufUc, XdrvMailbox.data); if (!root) { return IE_INVALID_JSON; }
RemoveSpace(dataBufUc);
if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; }
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject(dataBufUc);
if (!json.success()) { return IE_INVALID_JSON; }
// from: https://github.com/crankyoldgit/IRremoteESP8266/blob/master/examples/CommonAcControl/CommonAcControl.ino // from: https://github.com/crankyoldgit/IRremoteESP8266/blob/master/examples/CommonAcControl/CommonAcControl.ino
state.protocol = decode_type_t::UNKNOWN; 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.clean = false; // Turn off any Cleaning options if we can.
state.clock = -1; // Don't set any current time if we can avoid it. state.clock = -1; // Don't set any current time if we can avoid it.
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR)); if (root[PSTR(D_JSON_IRHVAC_VENDOR)]) { state.protocol = strToDecodeType(root.getStr(PSTR(D_JSON_IRHVAC_VENDOR), "")); }
if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); } 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_PROTOCOL)); // UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR));
if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); } // also support 'protocol' // 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 (decode_type_t::UNKNOWN == state.protocol) { return IE_UNSUPPORTED_HVAC; }
if (!IRac::isProtocolSupported(state.protocol)) { return IE_UNSUPPORTED_HVAC; } if (!IRac::isProtocolSupported(state.protocol)) { return IE_UNSUPPORTED_HVAC; }
// for fan speed, we also support 1-5 values // for fan speed, we also support 1-5 values
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FANSPEED)); JsonParserToken tok_fan_speed = root[PSTR(D_JSON_IRHVAC_FANSPEED)];
if (json.containsKey(parm_uc)) { if (tok_fan_speed) {
uint32_t fan_speed = json[parm_uc]; uint32_t fan_speed = tok_fan_speed.getUInt();
if ((fan_speed >= 1) && (fan_speed <= 5)) { if ((fan_speed >= 1) && (fan_speed <= 5)) {
state.fanspeed = (stdAc::fanspeed_t) pgm_read_byte(&IrHvacFanSpeed[fan_speed]); state.fanspeed = (stdAc::fanspeed_t) pgm_read_byte(&IrHvacFanSpeed[fan_speed]);
} else { } 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 (root[PSTR(D_JSON_IRHVAC_MODEL)]) { state.model = IRac::strToModel(PSTR(D_JSON_IRHVAC_MODEL)); }
if (json.containsKey(parm_uc)) { state.model = IRac::strToModel(json[parm_uc]); } if (root[PSTR(D_JSON_IRHVAC_MODE)]) { state.mode = IRac::strToOpmode(PSTR(D_JSON_IRHVAC_MODE)); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODE)); if (root[PSTR(D_JSON_IRHVAC_SWINGV)]) { state.swingv = IRac::strToSwingV(PSTR(D_JSON_IRHVAC_SWINGV)); }
if (json.containsKey(parm_uc)) { state.mode = IRac::strToOpmode(json[parm_uc]); } if (root[PSTR(D_JSON_IRHVAC_SWINGH)]) { state.swingh = IRac::strToSwingH(PSTR(D_JSON_IRHVAC_SWINGH)); }
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGV)); state.degrees = root.getFloat(PSTR(D_JSON_IRHVAC_TEMP), state.degrees);
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]; }
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("model %d, mode %d, fanspeed %d, swingv %d, swingh %d"), // 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); // state.model, state.mode, state.fanspeed, state.swingv, state.swingh);
// decode booleans // decode booleans
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_POWER)); state.power = strToBool(root[PSTR(D_JSON_IRHVAC_POWER)], state.power);
if (json.containsKey(parm_uc)) { state.power = IRac::strToBool(json[parm_uc]); } state.celsius = strToBool(root[PSTR(D_JSON_IRHVAC_CELSIUS)], state.celsius);
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CELSIUS)); state.light = strToBool(root[PSTR(D_JSON_IRHVAC_LIGHT)], state.light);
if (json.containsKey(parm_uc)) { state.celsius = IRac::strToBool(json[parm_uc]); } state.beep = strToBool(root[PSTR(D_JSON_IRHVAC_BEEP)], state.beep);
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_LIGHT)); state.econo = strToBool(root[PSTR(D_JSON_IRHVAC_ECONO)], state.econo);
if (json.containsKey(parm_uc)) { state.light = IRac::strToBool(json[parm_uc]); } state.filter = strToBool(root[PSTR(D_JSON_IRHVAC_FILTER)], state.filter);
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_BEEP)); state.turbo = strToBool(root[PSTR(D_JSON_IRHVAC_TURBO)], state.turbo);
if (json.containsKey(parm_uc)) { state.beep = IRac::strToBool(json[parm_uc]); } state.quiet = strToBool(root[PSTR(D_JSON_IRHVAC_QUIET)], state.quiet);
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_ECONO)); state.clean = strToBool(root[PSTR(D_JSON_IRHVAC_CLEAN)], state.clean);
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]); }
// optional timer and clock // optional timer and clock
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SLEEP)); state.sleep = root.getInt(PSTR(D_JSON_IRHVAC_SLEEP), state.sleep);
if (json[parm_uc]) { state.sleep = json[parm_uc]; }
//if (json[D_JSON_IRHVAC_CLOCK]) { state.clock = json[D_JSON_IRHVAC_CLOCK]; } // not sure it's useful to support 'clock' //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)); 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 Settings.timer[index -1].data = Settings.timer[XdrvMailbox.payload -1].data; // Copy timer
} }
} else { } else {
#if 0
//#ifndef USE_RULES //#ifndef USE_RULES
#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0 #if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
if (devices_present) { if (devices_present) {
@ -419,6 +420,95 @@ void CmndTimer(void)
index++; index++;
} }
//#ifndef USE_RULES //#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 #if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
} else { } else {
Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_TIMER_NO_DEVICE "\"}"), index); // No outputs defined so nothing to control 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" rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT"
} }
#if 0
// StaticJsonBuffer<1280> jsonBuf; // Was 1024 until 20200811 // StaticJsonBuffer<1280> jsonBuf; // Was 1024 until 20200811
DynamicJsonBuffer jsonBuf; // Was static until 20200812 DynamicJsonBuffer jsonBuf; // Was static until 20200812
JsonObject &root = jsonBuf.parseObject(event); JsonObject &root = jsonBuf.parseObject(event);
@ -528,6 +529,42 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
} else { } else {
str_value = (*obj)[rule_name]; // "CURRENT" 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"), //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"); // 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 if (event_item.Key.length() == 0) { //If did not specify Key
value = sData; value = sData;
} else { //If specified Key, need to parse Key/Value from JSON data } else { //If specified Key, need to parse Key/Value from JSON data
StaticJsonBuffer<500> jsonBuf; JsonParserObject jsonData = JsonParser((char*)sData.c_str()).getRootObject();
JsonObject& jsonData = jsonBuf.parseObject(sData);
String key1 = event_item.Key; String key1 = event_item.Key;
String key2; 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; int dot;
if ((dot = key1.indexOf('.')) > 0) { if ((dot = key1.indexOf('.')) > 0) {
key2 = key1.substring(dot+1); key2 = key1.substring(dot+1);
key1 = key1.substring(0, dot); key1 = key1.substring(0, dot);
if (!jsonData[key1][key2].success()) break; //Failed to get the key/value, ignore this message. JsonParserToken value_tok = jsonData[key1.c_str()][key2.c_str()];
value = (const char *)jsonData[key1][key2]; 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 { } else {
if (!jsonData[key1].success()) break; JsonParserToken value_tok = jsonData[key1.c_str()];
value = (const char *)jsonData[key1]; 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(); value.trim();

View File

@ -569,10 +569,12 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
if (Webserver->args()) { if (Webserver->args()) {
response = "["; response = "[";
StaticJsonBuffer<300> jsonBuffer; JsonParser parser = JsonParser((char*) Webserver->arg((Webserver->args())-1).c_str());
JsonObject &hue_json = jsonBuffer.parseObject(Webserver->arg((Webserver->args())-1)); JsonParserObject root = parser.getRootObject();
if (hue_json.containsKey("on")) {
on = hue_json["on"]; JsonParserToken hue_on = root[PSTR("on")];
if (hue_on) {
on = hue_on.getBool();
snprintf_P(buf, buf_size, snprintf_P(buf, buf_size,
PSTR("{\"success\":{\"/lights/%d/state/on\":%s}}"), PSTR("{\"success\":{\"/lights/%d/state/on\":%s}}"),
device_id, on ? "true" : "false"); device_id, on ? "true" : "false");
@ -587,15 +589,6 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
} }
} else { } else {
#endif #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); ExecuteCommandPower(device, (on) ? POWER_ON : POWER_OFF, SRC_HUE);
response += buf; response += buf;
resp = true; 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 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. parser.setCurrent();
bri = hue_json["bri"]; 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 prev_bri = bri; // store command value
if (resp) { response += ","; } if (resp) { response += ","; }
snprintf_P(buf, buf_size, snprintf_P(buf, buf_size,
@ -634,15 +629,19 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
} }
resp = true; resp = true;
} }
// handle xy before Hue/Sat // handle xy before Hue/Sat
// If the request contains both XY and HS, we wan't to give priority to HS // If the request contains both XY and HS, we wan't to give priority to HS
if (hue_json.containsKey("xy")) { parser.setCurrent();
float x = hue_json["xy"][0]; JsonParserToken hue_xy = root[PSTR("xy")];
float y = hue_json["xy"][1]; if (hue_xy) {
const String &x_str = hue_json["xy"][0]; JsonParserArray arr_xy = JsonParserArray(hue_xy);
const String &y_str = hue_json["xy"][1]; JsonParserToken tok_x = arr_xy[0];
x_str.toCharArray(prev_x_str, sizeof(prev_x_str)); JsonParserToken tok_y = arr_xy[1];
y_str.toCharArray(prev_y_str, sizeof(prev_y_str)); 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; uint8_t rr,gg,bb;
LightStateClass::XyToRgb(x, y, &rr, &gg, &bb); LightStateClass::XyToRgb(x, y, &rr, &gg, &bb);
LightStateClass::RgbToHsb(rr, gg, bb, &hue, &sat, nullptr); 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; resp = true;
change = 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; prev_hue = hue;
if (resp) { response += ","; } if (resp) { response += ","; }
snprintf_P(buf, buf_size, snprintf_P(buf, buf_size,
@ -674,8 +676,11 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
} }
resp = true; 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 prev_sat = sat; // store command value
if (resp) { response += ","; } if (resp) { response += ","; }
snprintf_P(buf, buf_size, snprintf_P(buf, buf_size,
@ -690,8 +695,11 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
} }
resp = true; 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 prev_ct = ct; // store commande value
if (resp) { response += ","; } if (resp) { response += ","; }
snprintf_P(buf, buf_size, snprintf_P(buf, buf_size,
@ -704,6 +712,7 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
} }
resp = true; resp = true;
} }
if (change) { if (change) {
#ifdef USE_SHUTTER #ifdef USE_SHUTTER
if (ShutterState(device)) { if (ShutterState(device)) {

View File

@ -39,42 +39,6 @@ public:
void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl); void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl);
bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_status_ok = false); 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 parseHex(const char **data, size_t max_len = 8) {
uint32_t ret = 0; uint32_t ret = 0;
for (uint32_t i = 0; i < max_len; i++) { for (uint32_t i = 0; i < max_len; i++) {

View File

@ -19,6 +19,8 @@
#ifdef USE_ZIGBEE #ifdef USE_ZIGBEE
#include "JsonParser.h"
#ifndef ZIGBEE_SAVE_DELAY_SECONDS #ifndef ZIGBEE_SAVE_DELAY_SECONDS
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info #define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
#endif #endif
@ -261,7 +263,7 @@ public:
// Dump json // Dump json
String dumpLightState(uint16_t shortaddr) const; String dumpLightState(uint16_t shortaddr) const;
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) 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 // General Zigbee device profile support
void setZbProfile(uint16_t shortaddr, uint8_t zb_profile); 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 number 0..99, the index number in ZigbeeStatus
// - a friendly name, between quotes, example: "Room_Temp" // - a friendly name, between quotes, example: "Room_Temp"
uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_known) const { 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); size_t param_len = strlen(param);
char dataBuf[param_len + 1]; char dataBuf[param_len + 1];
strcpy(dataBuf, param); strcpy(dataBuf, param);
@ -1070,7 +1072,7 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
// <0 : Error // <0 : Error
// //
// Ex: {"Device":"0x5ADF","Name":"IKEA_Light","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]} // 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 // params
uint16_t device = 0x0000; // 0x0000 is coordinator so considered invalid 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 * modelid = nullptr;
const char * manufid = nullptr; const char * manufid = nullptr;
const char * friendlyname = nullptr; const char * friendlyname = nullptr;
int8_t bulbtype = 0xFF; int8_t bulbtype = -1;
size_t endpoints_len = 0; size_t endpoints_len = 0;
// read mandatory "Device" // read mandatory "Device"
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR("Device")); JsonParserToken val_device = json[PSTR("Device")];
if (nullptr != &val_device) { if (val_device) {
device = strToUInt(val_device); device = (uint32_t) val_device.getUInt(device);
} else { } else {
return -1; // missing "Device" attribute return -1; // missing "Device" attribute
} }
// read "IEEEAddr" 64 bits in format "0x0000000000000000" ieeeaddr = json.getULong(PSTR("IEEEAddr"), ieeeaddr); // read "IEEEAddr" 64 bits in format "0x0000000000000000"
const JsonVariant &val_ieeeaddr = GetCaseInsensitive(json, PSTR("IEEEAddr")); friendlyname = json.getStr(PSTR("Name"), nullptr); // read "Name"
if (nullptr != &val_ieeeaddr) { modelid = json.getStr(PSTR("ModelId"), nullptr);
ieeeaddr = strtoull(val_ieeeaddr.as<const char*>(), nullptr, 0); manufid = json.getStr(PSTR("Manufacturer"), nullptr);
} JsonParserToken tok_bulbtype = json[PSTR(D_JSON_ZIGBEE_LIGHT)];
// 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);; }
// update internal device information // update internal device information
updateDevice(device, ieeeaddr); updateDevice(device, ieeeaddr);
if (modelid) { setModelId(device, modelid); } if (modelid) { setModelId(device, modelid); }
if (manufid) { setManufId(device, manufid); } if (manufid) { setManufId(device, manufid); }
if (friendlyname) { setFriendlyName(device, friendlyname); } if (friendlyname) { setFriendlyName(device, friendlyname); }
if (&val_bulbtype) { setHueBulbtype(device, bulbtype); } if (tok_bulbtype) { setHueBulbtype(device, tok_bulbtype.getInt()); }
// read "Endpoints" // read "Endpoints"
const JsonVariant &val_endpoints = GetCaseInsensitive(json, PSTR("Endpoints")); JsonParserToken val_endpoints = json[PSTR("Endpoints")];
if ((nullptr != &val_endpoints) && (val_endpoints.is<JsonArray>())) { if (val_endpoints.isArray()) {
const JsonArray &arr_ep = val_endpoints.as<const JsonArray&>(); JsonParserArray arr_ep = JsonParserArray(val_endpoints);
endpoints_len = arr_ep.size();
clearEndpoints(device); // clear even if array is empty clearEndpoints(device); // clear even if array is empty
if (endpoints_len) { for (auto ep_elt : arr_ep) {
for (auto ep_elt : arr_ep) { uint8_t ep = ep_elt.getUInt();
uint8_t ep = strToUInt(ep_elt); if (ep) { addEndpoint(device, ep); }
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()) { if (Webserver->args()) {
response = "["; response = "[";
StaticJsonBuffer<300> jsonBuffer; JsonParser parser = JsonParser((char*) Webserver->arg((Webserver->args())-1).c_str());
JsonObject &hue_json = jsonBuffer.parseObject(Webserver->arg((Webserver->args())-1)); JsonParserObject root = parser.getRootObject();
if (hue_json.containsKey("on")) {
on = hue_json["on"]; JsonParserToken hue_on = root[PSTR("on")];
if (hue_on) {
on = hue_on.getBool();
snprintf_P(buf, buf_size, snprintf_P(buf, buf_size,
PSTR("{\"success\":{\"/lights/%d/state/on\":%s}}"), PSTR("{\"success\":{\"/lights/%d/state/on\":%s}}"),
device_id, on ? "true" : "false"); device_id, on ? "true" : "false");
@ -235,8 +237,10 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
resp = true; 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. parser.setCurrent();
bri = hue_json["bri"]; 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 prev_bri = bri; // store command value
if (resp) { response += ","; } if (resp) { response += ","; }
snprintf_P(buf, buf_size, 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 // handle xy before Hue/Sat
// If the request contains both XY and HS, we wan't to give priority to HS // If the request contains both XY and HS, we wan't to give priority to HS
if (hue_json.containsKey("xy")) { parser.setCurrent();
float x = hue_json["xy"][0]; JsonParserToken hue_xy = root[PSTR("xy")];
float y = hue_json["xy"][1]; if (hue_xy) {
const String &x_str = hue_json["xy"][0]; JsonParserArray arr_xy = JsonParserArray(hue_xy);
const String &y_str = hue_json["xy"][1]; JsonParserToken tok_x = arr_xy[0];
x_str.toCharArray(prev_x_str, sizeof(prev_x_str)); JsonParserToken tok_y = arr_xy[1];
y_str.toCharArray(prev_y_str, sizeof(prev_y_str)); 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 += ","; } if (resp) { response += ","; }
snprintf_P(buf, buf_size, snprintf_P(buf, buf_size,
PSTR("{\"success\":{\"/lights/%d/state/xy\":[%s,%s]}}"), 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); ZigbeeHueXY(shortaddr, xi, yi);
} }
bool huesat_changed = false; 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; prev_hue = hue;
if (resp) { response += ","; } if (resp) { response += ","; }
snprintf_P(buf, buf_size, snprintf_P(buf, buf_size,
@ -285,8 +295,11 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
} }
resp = true; 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 prev_sat = sat; // store command value
if (resp) { response += ","; } if (resp) { response += ","; }
snprintf_P(buf, buf_size, snprintf_P(buf, buf_size,
@ -303,8 +316,11 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
} }
resp = true; 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 prev_ct = ct; // store commande value
if (resp) { response += ","; } if (resp) { response += ","; }
snprintf_P(buf, buf_size, snprintf_P(buf, buf_size,

View File

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