mirror of https://github.com/arendst/Tasmota.git
Change replace ArduinoJson with JSMN for JSON parsing
This commit is contained in:
parent
75252afaeb
commit
2ff756b3cc
|
@ -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)
|
|
@ -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
|
|
@ -0,0 +1,524 @@
|
|||
/*
|
||||
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 "WSTring.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);
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
|
@ -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.
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 */
|
|
@ -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));
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
- Add new shutter modes (#9244)
|
||||
- Add Zigbee auto-config when pairing
|
||||
- Add support for MLX90640 IR array temperature sensor by Christian Baars
|
||||
- Change replace ArduinoJson with JSMN for JSON parsing
|
||||
|
||||
### 8.5.0 20200907
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ extern struct rst_info resetInfo;
|
|||
\*********************************************************************************************/
|
||||
|
||||
#include <Ticker.h>
|
||||
#include "JsonParser.h"
|
||||
|
||||
Ticker tickerOSWatch;
|
||||
|
||||
|
@ -1412,8 +1413,9 @@ bool GetUsedInModule(uint32_t val, uint16_t *arr)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool JsonTemplate(const char* dataBuf)
|
||||
bool JsonTemplate(char* dataBuf)
|
||||
{
|
||||
#if 0
|
||||
// {"NAME":"Generic","GPIO":[17,254,29,254,7,254,254,254,138,254,139,254,254],"FLAG":1,"BASE":255}
|
||||
|
||||
if (strlen(dataBuf) < 9) { return false; } // Workaround exception if empty JSON like {} - Needs checks
|
||||
|
@ -1454,6 +1456,46 @@ bool JsonTemplate(const char* dataBuf)
|
|||
Settings.user_template_base = base -1; // Default WEMOS
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
// {"NAME":"Generic","GPIO":[17,254,29,254,7,254,254,254,138,254,139,254,254],"FLAG":1,"BASE":255}
|
||||
|
||||
if (strlen(dataBuf) < 9) { return false; } // Workaround exception if empty JSON like {} - Needs checks
|
||||
|
||||
JsonParserObject root = JsonParser((char*) dataBuf).getRootObject();
|
||||
if (!root) { return false; }
|
||||
|
||||
// All parameters are optional allowing for partial changes
|
||||
JsonParserToken val = root[PSTR(D_JSON_NAME)];
|
||||
if (val) {
|
||||
SettingsUpdateText(SET_TEMPLATE_NAME, val.getStr());
|
||||
}
|
||||
JsonParserArray arr = root[PSTR(D_JSON_GPIO)];
|
||||
if (arr) {
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(Settings.user_template.gp.io); i++) {
|
||||
#ifdef ESP8266
|
||||
Settings.user_template.gp.io[i] = arr[i].getUInt();
|
||||
#else // ESP32
|
||||
uint16_t gpio = arr[i].getUInt();
|
||||
if (gpio == (AGPIO(GPIO_NONE) +1)) {
|
||||
gpio = AGPIO(GPIO_USER);
|
||||
}
|
||||
Settings.user_template.gp.io[i] = gpio;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
val = root[PSTR(D_JSON_FLAG)];
|
||||
if (val) {
|
||||
uint32_t flag = val.getUInt();
|
||||
memcpy(&Settings.user_template.flag, &flag, sizeof(gpio_flag));
|
||||
}
|
||||
val = root[PSTR(D_JSON_BASE)];
|
||||
if (val) {
|
||||
uint32_t base = val.getUInt();
|
||||
if ((0 == base) || !ValidTemplateModule(base -1)) { base = 18; }
|
||||
Settings.user_template_base = base -1; // Default WEMOS
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TemplateJson(void)
|
||||
|
|
|
@ -88,6 +88,7 @@ String EscapeJSONString(const char *str) {
|
|||
//
|
||||
// If the key is not found, returns a nullptr
|
||||
// Input: needle cannot be NULL but may be PROGMEM
|
||||
#if 0
|
||||
const JsonVariant &GetCaseInsensitive(const JsonObject &json, const char *needle) {
|
||||
// key can be in PROGMEM
|
||||
// if needle == "?" then we return the first valid key
|
||||
|
@ -113,3 +114,4 @@ const JsonVariant &GetCaseInsensitive(const JsonObject &json, const char *needle
|
|||
bool HasKeyCaseInsensitive(const JsonObject &json, const char *needle) {
|
||||
return &GetCaseInsensitive(json, needle) != nullptr;
|
||||
}
|
||||
#endif
|
|
@ -27,6 +27,8 @@
|
|||
|
||||
#define XDRV_01 1
|
||||
|
||||
#include "JsonParser.h"
|
||||
|
||||
#ifndef WIFI_SOFT_AP_CHANNEL
|
||||
#define WIFI_SOFT_AP_CHANNEL 1 // Soft Access Point Channel number between 1 and 11 as used by WifiManager web GUI
|
||||
#endif
|
||||
|
@ -3344,6 +3346,7 @@ bool JsonWebColor(const char* dataBuf)
|
|||
// Default pre v7 (Light theme)
|
||||
// {"WebColor":["#000","#fff","#f2f2f2","#000","#fff","#000","#fff","#f00","#008000","#fff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#fff","#999","#000"]} // {"WebColor":["#000000","#ffffff","#f2f2f2","#000000","#ffffff","#000000","#ffffff","#ff0000","#008000","#ffffff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#ffffff","#999999","#000000"]}
|
||||
|
||||
#if 0
|
||||
char dataBufLc[strlen(dataBuf) +1];
|
||||
LowerCase(dataBufLc, dataBuf);
|
||||
RemoveSpace(dataBufLc);
|
||||
|
@ -3362,6 +3365,21 @@ bool JsonWebColor(const char* dataBuf)
|
|||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
JsonParserObject root = JsonParser((char*) dataBuf).getRootObject();
|
||||
JsonParserArray arr = root[PSTR(D_CMND_WEBCOLOR)].getArray();
|
||||
if (arr) { // if arr is valid, i.e. json is valid, the key D_CMND_WEBCOLOR was found and the token is an arra
|
||||
uint32_t i = 0;
|
||||
for (auto color : arr) {
|
||||
if (i < COL_LAST) {
|
||||
WebHexCode(i, color.getStr());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#define XDRV_05 5
|
||||
|
||||
#include <IRremoteESP8266.h>
|
||||
#include "JsonParser.h"
|
||||
|
||||
enum IrErrors { IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND };
|
||||
|
||||
|
@ -180,6 +181,7 @@ uint32_t IrRemoteCmndIrSendJson(void)
|
|||
// IRsend { "protocol": "RC5", "bits": 12, "data":"0xC86" }
|
||||
// IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
|
||||
|
||||
#if 0
|
||||
char dataBufUc[XdrvMailbox.data_len + 1];
|
||||
UpperCase(dataBufUc, XdrvMailbox.data);
|
||||
RemoveSpace(dataBufUc);
|
||||
|
@ -200,6 +202,18 @@ uint32_t IrRemoteCmndIrSendJson(void)
|
|||
uint16_t bits = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_BITS))];
|
||||
uint64_t data = strtoull(root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_DATA))], nullptr, 0);
|
||||
uint16_t repeat = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_REPEAT))];
|
||||
#else
|
||||
RemoveSpace(XdrvMailbox.data); // TODO is this really needed?
|
||||
JsonParserObject root = JsonParser((chat*) 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));
|
||||
uint64_t data = root.getULong(PSTR(D_JSON_IR_DATA));
|
||||
uint16_t repeat = root.getUInt(PSTR(D_JSON_IR_REPEAT));
|
||||
#endif
|
||||
// check if the IRSend<x> is great than repeat
|
||||
if (XdrvMailbox.index > repeat + 1) {
|
||||
repeat = XdrvMailbox.index - 1;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <IRrecv.h>
|
||||
#include <IRutils.h>
|
||||
#include <IRac.h>
|
||||
#include "JsonParser.h"
|
||||
|
||||
enum IrErrors { IE_RESPONSE_PROVIDED, IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND, IE_SYNTAX_IRHVAC,
|
||||
IE_UNSUPPORTED_HVAC, IE_UNSUPPORTED_PROTOCOL };
|
||||
|
@ -267,6 +268,16 @@ String listSupportedProtocols(bool hvac) {
|
|||
return l;
|
||||
}
|
||||
|
||||
bool strToBool(class JsonParserToken token, bool def) {
|
||||
if (token.isBool() || token.isNum()) {
|
||||
return token.getBool();
|
||||
} else if (token.isStr()) {
|
||||
return IRac::strToBool(token.getStr());
|
||||
} else {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
// used to convert values 0-5 to fanspeed_t
|
||||
const stdAc::fanspeed_t IrHvacFanSpeed[] PROGMEM = { stdAc::fanspeed_t::kAuto,
|
||||
stdAc::fanspeed_t::kMin, stdAc::fanspeed_t::kLow,stdAc::fanspeed_t::kMedium,
|
||||
|
@ -275,17 +286,10 @@ const stdAc::fanspeed_t IrHvacFanSpeed[] PROGMEM = { stdAc::fanspeed_t::kAuto,
|
|||
uint32_t IrRemoteCmndIrHvacJson(void)
|
||||
{
|
||||
stdAc::state_t state, prev;
|
||||
char parm_uc[12];
|
||||
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRHVAC: Received %s"), XdrvMailbox.data);
|
||||
char dataBufUc[XdrvMailbox.data_len + 1];
|
||||
UpperCase(dataBufUc, XdrvMailbox.data);
|
||||
RemoveSpace(dataBufUc);
|
||||
if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; }
|
||||
|
||||
DynamicJsonBuffer jsonBuf;
|
||||
JsonObject &json = jsonBuf.parseObject(dataBufUc);
|
||||
if (!json.success()) { return IE_INVALID_JSON; }
|
||||
JsonParserObject root = JsonParser((char*) XdrvMailbox.data).getRootObject();
|
||||
if (!root) { return IE_INVALID_JSON; }
|
||||
|
||||
// from: https://github.com/crankyoldgit/IRremoteESP8266/blob/master/examples/CommonAcControl/CommonAcControl.ino
|
||||
state.protocol = decode_type_t::UNKNOWN;
|
||||
|
@ -307,60 +311,47 @@ uint32_t IrRemoteCmndIrHvacJson(void)
|
|||
state.clean = false; // Turn off any Cleaning options if we can.
|
||||
state.clock = -1; // Don't set any current time if we can avoid it.
|
||||
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR));
|
||||
if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_PROTOCOL));
|
||||
if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); } // also support 'protocol'
|
||||
if (root[PSTR(D_JSON_IRHVAC_VENDOR)]) { state.protocol = strToDecodeType(root.getStr(PSTR(D_JSON_IRHVAC_VENDOR))); }
|
||||
if (root[PSTR(D_JSON_IRHVAC_PROTOCOL)]) { state.protocol = strToDecodeType(root.getStr(PSTR(D_JSON_IRHVAC_PROTOCOL))); }
|
||||
// UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR));
|
||||
// if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); }
|
||||
// UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_PROTOCOL));
|
||||
// if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); } // also support 'protocol'
|
||||
if (decode_type_t::UNKNOWN == state.protocol) { return IE_UNSUPPORTED_HVAC; }
|
||||
if (!IRac::isProtocolSupported(state.protocol)) { return IE_UNSUPPORTED_HVAC; }
|
||||
|
||||
// for fan speed, we also support 1-5 values
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FANSPEED));
|
||||
if (json.containsKey(parm_uc)) {
|
||||
uint32_t fan_speed = json[parm_uc];
|
||||
JsonParserToken tok_fan_speed = root[PSTR(D_JSON_IRHVAC_FANSPEED)];
|
||||
if (tok_fan_speed) {
|
||||
uint32_t fan_speed = tok_fan_speed.getUInt();
|
||||
if ((fan_speed >= 1) && (fan_speed <= 5)) {
|
||||
state.fanspeed = (stdAc::fanspeed_t) pgm_read_byte(&IrHvacFanSpeed[fan_speed]);
|
||||
} else {
|
||||
state.fanspeed = IRac::strToFanspeed(json[parm_uc]);
|
||||
state.fanspeed = IRac::strToFanspeed(tok_fan_speed.getStr());
|
||||
}
|
||||
}
|
||||
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODEL));
|
||||
if (json.containsKey(parm_uc)) { state.model = IRac::strToModel(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODE));
|
||||
if (json.containsKey(parm_uc)) { state.mode = IRac::strToOpmode(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGV));
|
||||
if (json.containsKey(parm_uc)) { state.swingv = IRac::strToSwingV(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGH));
|
||||
if (json.containsKey(parm_uc)) { state.swingh = IRac::strToSwingH(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TEMP));
|
||||
if (json.containsKey(parm_uc)) { state.degrees = json[parm_uc]; }
|
||||
if (root[PSTR(D_JSON_IRHVAC_MODEL)]) { state.model = IRac::strToModel(PSTR(D_JSON_IRHVAC_MODEL)]); }
|
||||
if (root[PSTR(D_JSON_IRHVAC_MODE)]) { state.mode = IRac::strToOpmode(PSTR(D_JSON_IRHVAC_MODE)]); }
|
||||
if (root[PSTR(D_JSON_IRHVAC_SWINGV)]) { state.swingv = IRac::strToSwingV(PSTR(D_JSON_IRHVAC_SWINGV)]); }
|
||||
if (root[PSTR(D_JSON_IRHVAC_SWINGH)]) { state.swingh = IRac::strToSwingV(PSTR(D_JSON_IRHVAC_SWINGH)]); }
|
||||
state.degrees = root.getFloat(PSTR(D_JSON_IRHVAC_TEMP), state.degrees);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("model %d, mode %d, fanspeed %d, swingv %d, swingh %d"),
|
||||
// state.model, state.mode, state.fanspeed, state.swingv, state.swingh);
|
||||
|
||||
// decode booleans
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_POWER));
|
||||
if (json.containsKey(parm_uc)) { state.power = IRac::strToBool(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CELSIUS));
|
||||
if (json.containsKey(parm_uc)) { state.celsius = IRac::strToBool(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_LIGHT));
|
||||
if (json.containsKey(parm_uc)) { state.light = IRac::strToBool(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_BEEP));
|
||||
if (json.containsKey(parm_uc)) { state.beep = IRac::strToBool(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_ECONO));
|
||||
if (json.containsKey(parm_uc)) { state.econo = IRac::strToBool(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FILTER));
|
||||
if (json.containsKey(parm_uc)) { state.filter = IRac::strToBool(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TURBO));
|
||||
if (json.containsKey(parm_uc)) { state.turbo = IRac::strToBool(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_QUIET));
|
||||
if (json.containsKey(parm_uc)) { state.quiet = IRac::strToBool(json[parm_uc]); }
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CLEAN));
|
||||
if (json.containsKey(parm_uc)) { state.clean = IRac::strToBool(json[parm_uc]); }
|
||||
state.power = strToBool(root[PSTR(D_JSON_IRHVAC_POWER)], state.power);
|
||||
state.celsius = strToBool(root[PSTR(D_JSON_IRHVAC_CELSIUS)], state.celsius);
|
||||
state.light = strToBool(root[PSTR(D_JSON_IRHVAC_LIGHT)], state.light);
|
||||
state.beep = strToBool(root[PSTR(D_JSON_IRHVAC_BEEP)], state.beep);
|
||||
state.econo = strToBool(root[PSTR(D_JSON_IRHVAC_ECONO)], state.econo);
|
||||
state.filter = strToBool(root[PSTR(D_JSON_IRHVAC_FILTER)], state.filter);
|
||||
state.turbo = strToBool(root[PSTR(D_JSON_IRHVAC_TURBO)], state.turbo);
|
||||
state.quiet = strToBool(root[PSTR(D_JSON_IRHVAC_QUIET)], state.quiet);
|
||||
state.clean = strToBool(root[PSTR(D_JSON_IRHVAC_CLEAN)], state.clean);
|
||||
|
||||
// optional timer and clock
|
||||
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SLEEP));
|
||||
if (json[parm_uc]) { state.sleep = json[parm_uc]; }
|
||||
state.sleep = root.getInt(PSTR(D_JSON_IRHVAC_SLEEP), state.sleep);
|
||||
//if (json[D_JSON_IRHVAC_CLOCK]) { state.clock = json[D_JSON_IRHVAC_CLOCK]; } // not sure it's useful to support 'clock'
|
||||
|
||||
IRac ac(Pin(GPIO_IRSEND));
|
||||
|
|
|
@ -337,6 +337,7 @@ void CmndTimer(void)
|
|||
Settings.timer[index -1].data = Settings.timer[XdrvMailbox.payload -1].data; // Copy timer
|
||||
}
|
||||
} else {
|
||||
#if 0
|
||||
//#ifndef USE_RULES
|
||||
#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
|
||||
if (devices_present) {
|
||||
|
@ -419,6 +420,95 @@ void CmndTimer(void)
|
|||
index++;
|
||||
}
|
||||
//#ifndef USE_RULES
|
||||
#else
|
||||
//#ifndef USE_RULES
|
||||
#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
|
||||
if (devices_present) {
|
||||
#endif
|
||||
JsonParserObject root = JsonParser(XdrvMailbox.data).getRootObject();
|
||||
if (!root) {
|
||||
Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_INVALID_JSON "\"}"), index); // JSON decode failed
|
||||
error = 1;
|
||||
}
|
||||
else {
|
||||
char parm_uc[10];
|
||||
index--;
|
||||
JsonParserToken val = root[PSTR(D_JSON_TIMER_ARM)];
|
||||
if (val) {
|
||||
Settings.timer[index].arm = (val.getInt() != 0);
|
||||
}
|
||||
#ifdef USE_SUNRISE
|
||||
val = root[PSTR(D_JSON_TIMER_MODE)];
|
||||
if (val) {
|
||||
Settings.timer[index].mode = val.getUInt() & 0x03;
|
||||
}
|
||||
#endif
|
||||
val = root[PSTR(D_JSON_TIMER_TIME)];
|
||||
if (val) {
|
||||
uint16_t itime = 0;
|
||||
int8_t value = 0;
|
||||
uint8_t sign = 0;
|
||||
char time_str[10];
|
||||
|
||||
strlcpy(time_str, val.getStr(), sizeof(time_str));
|
||||
const char *substr = strtok(time_str, ":");
|
||||
if (substr != nullptr) {
|
||||
if (strchr(substr, '-')) {
|
||||
sign = 1;
|
||||
substr++;
|
||||
}
|
||||
value = atoi(substr);
|
||||
if (sign) { value += 12; } // Allow entering timer offset from -11:59 to -00:01 converted to 12:01 to 23:59
|
||||
if (value > 23) { value = 23; }
|
||||
itime = value * 60;
|
||||
substr = strtok(nullptr, ":");
|
||||
if (substr != nullptr) {
|
||||
value = atoi(substr);
|
||||
if (value < 0) { value = 0; }
|
||||
if (value > 59) { value = 59; }
|
||||
itime += value;
|
||||
}
|
||||
}
|
||||
Settings.timer[index].time = itime;
|
||||
}
|
||||
val = root[PSTR(D_JSON_TIMER_WINDOW)];
|
||||
if (val) {
|
||||
Settings.timer[index].window = val.getUInt() & 0x0F;
|
||||
TimerSetRandomWindow(index);
|
||||
}
|
||||
val = root[PSTR(D_JSON_TIMER_DAYS)];
|
||||
if (val) {
|
||||
// SMTWTFS = 1234567 = 0011001 = 00TW00S = --TW--S
|
||||
Settings.timer[index].days = 0;
|
||||
const char *tday = val.getStr();
|
||||
uint8_t i = 0;
|
||||
char ch = *tday++;
|
||||
while ((ch != '\0') && (i < 7)) {
|
||||
if (ch == '-') { ch = '0'; }
|
||||
uint8_t mask = 1 << i++;
|
||||
Settings.timer[index].days |= (ch == '0') ? 0 : mask;
|
||||
ch = *tday++;
|
||||
}
|
||||
}
|
||||
val = root[PSTR(D_JSON_TIMER_REPEAT)];
|
||||
if (val) {
|
||||
Settings.timer[index].repeat = (val.getUInt() != 0);
|
||||
}
|
||||
val = root[PSTR(D_JSON_TIMER_OUTPUT)];
|
||||
if (val) {
|
||||
uint8_t device = (val.getUInt() -1) & 0x0F;
|
||||
Settings.timer[index].device = (device < devices_present) ? device : 0;
|
||||
}
|
||||
val = root[PSTR(D_JSON_TIMER_ACTION)];
|
||||
if (val) {
|
||||
uint8_t action = val.getUInt() & 0x03;
|
||||
Settings.timer[index].power = (devices_present) ? action : 3; // If no devices than only allow rules
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
//#ifndef USE_RULES
|
||||
#endif
|
||||
#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
|
||||
} else {
|
||||
Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_TIMER_NO_DEVICE "\"}"), index); // No outputs defined so nothing to control
|
||||
|
|
|
@ -497,6 +497,7 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
|
|||
rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT"
|
||||
}
|
||||
|
||||
#if 0
|
||||
// StaticJsonBuffer<1280> jsonBuf; // Was 1024 until 20200811
|
||||
DynamicJsonBuffer jsonBuf; // Was static until 20200812
|
||||
JsonObject &root = jsonBuf.parseObject(event);
|
||||
|
@ -528,6 +529,43 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
|
|||
} else {
|
||||
str_value = (*obj)[rule_name]; // "CURRENT"
|
||||
}
|
||||
#else
|
||||
|
||||
char buf[event.length()+1];
|
||||
strcpy(buf, event.c_str());
|
||||
JsonParser parser = JsonParser(buf);
|
||||
JsonParserObject obj = parser.getRootObject();
|
||||
if (!obj) {
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event too long (%d)"), event.length());
|
||||
return false; // No valid JSON data
|
||||
}
|
||||
String subtype;
|
||||
uint32_t i = 0;
|
||||
while ((pos = rule_name.indexOf("#")) > 0) { // "SUBTYPE1#SUBTYPE2#CURRENT"
|
||||
subtype = rule_name.substring(0, pos);
|
||||
obj = obj[subtype.c_str()].getObject();
|
||||
if (!obj) { return false; } // not found
|
||||
|
||||
rule_name = rule_name.substring(pos +1);
|
||||
if (i++ > 10) { return false; } // Abandon possible loop
|
||||
|
||||
yield();
|
||||
}
|
||||
|
||||
JsonParserToken val = obj[rule_name.c_str()];
|
||||
if (!val) { return false; } // last level not found
|
||||
const char* str_value;
|
||||
if (rule_name_idx) {
|
||||
if (val.isArray()) {
|
||||
str_value = (val.getArray())[rule_name_idx -1].getStr();
|
||||
} else {
|
||||
str_value = val.getStr();
|
||||
}
|
||||
} else {
|
||||
str_value = val.getStr(); // "CURRENT"
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"),
|
||||
// rule_name.c_str(), rule_svalue, Rules.trigger_count[rule_set], bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set]), event.c_str(), (str_value) ? str_value : "none");
|
||||
|
@ -1034,20 +1072,28 @@ bool RulesMqttData(void)
|
|||
if (event_item.Key.length() == 0) { //If did not specify Key
|
||||
value = sData;
|
||||
} else { //If specified Key, need to parse Key/Value from JSON data
|
||||
StaticJsonBuffer<500> jsonBuf;
|
||||
JsonObject& jsonData = jsonBuf.parseObject(sData);
|
||||
JsonParserObject jsonData = JsonParser((char*)sData.c_str()).getRootObject();
|
||||
|
||||
// StaticJsonBuffer<500> jsonBuf;
|
||||
// JsonObject& jsonData = jsonBuf.parseObject(sData);
|
||||
String key1 = event_item.Key;
|
||||
String key2;
|
||||
if (!jsonData.success()) break; //Failed to parse JSON data, ignore this message.
|
||||
if (!jsonData) break; //Failed to parse JSON data, ignore this message.
|
||||
int dot;
|
||||
if ((dot = key1.indexOf('.')) > 0) {
|
||||
key2 = key1.substring(dot+1);
|
||||
key1 = key1.substring(0, dot);
|
||||
if (!jsonData[key1][key2].success()) break; //Failed to get the key/value, ignore this message.
|
||||
value = (const char *)jsonData[key1][key2];
|
||||
JsonParserToken value_tok = jsonData[key1.c_str()][key2.c_str()];
|
||||
if (!value_tok) break; //Failed to get the key/value, ignore this message.
|
||||
value = value_tok.getStr();
|
||||
// if (!jsonData[key1][key2].success()) break; //Failed to get the key/value, ignore this message.
|
||||
// value = (const char *)jsonData[key1][key2];
|
||||
} else {
|
||||
if (!jsonData[key1].success()) break;
|
||||
value = (const char *)jsonData[key1];
|
||||
JsonParserToken value_tok = jsonData[key1.c_str()];
|
||||
if (!value_tok) break; //Failed to get the key/value, ignore this message.
|
||||
value = value_tok.getStr();
|
||||
// if (!jsonData[key1].success()) break;
|
||||
// value = (const char *)jsonData[key1];
|
||||
}
|
||||
}
|
||||
value.trim();
|
||||
|
|
|
@ -569,10 +569,12 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
|
|||
if (Webserver->args()) {
|
||||
response = "[";
|
||||
|
||||
StaticJsonBuffer<300> jsonBuffer;
|
||||
JsonObject &hue_json = jsonBuffer.parseObject(Webserver->arg((Webserver->args())-1));
|
||||
if (hue_json.containsKey("on")) {
|
||||
on = hue_json["on"];
|
||||
JsonParser parser = JsonParser((char*) Webserver->arg((Webserver->args())-1).c_str());
|
||||
JsonParserObject root = parser.getRootObject();
|
||||
|
||||
JsonParserToken hue_on = root[PSTR("on")];
|
||||
if (hue_on) {
|
||||
on = hue_on.getBool();
|
||||
snprintf_P(buf, buf_size,
|
||||
PSTR("{\"success\":{\"/lights/%d/state/on\":%s}}"),
|
||||
device_id, on ? "true" : "false");
|
||||
|
@ -587,15 +589,6 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
|
|||
}
|
||||
} else {
|
||||
#endif
|
||||
/*
|
||||
switch(on)
|
||||
{
|
||||
case false : ExecuteCommandPower(device, POWER_OFF, SRC_HUE);
|
||||
break;
|
||||
case true : ExecuteCommandPower(device, POWER_ON, SRC_HUE);
|
||||
break;
|
||||
}
|
||||
*/
|
||||
ExecuteCommandPower(device, (on) ? POWER_ON : POWER_OFF, SRC_HUE);
|
||||
response += buf;
|
||||
resp = true;
|
||||
|
@ -619,8 +612,10 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
|
|||
}
|
||||
prev_x_str[0] = prev_y_str[0] = 0; // reset xy string
|
||||
|
||||
if (hue_json.containsKey("bri")) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
|
||||
bri = hue_json["bri"];
|
||||
parser.setCurrent();
|
||||
JsonParserToken hue_bri = root[PSTR("bri")];
|
||||
if (hue_bri) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
|
||||
bri = hue_bri.getUInt();
|
||||
prev_bri = bri; // store command value
|
||||
if (resp) { response += ","; }
|
||||
snprintf_P(buf, buf_size,
|
||||
|
@ -634,15 +629,19 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
|
|||
}
|
||||
resp = true;
|
||||
}
|
||||
|
||||
// handle xy before Hue/Sat
|
||||
// If the request contains both XY and HS, we wan't to give priority to HS
|
||||
if (hue_json.containsKey("xy")) {
|
||||
float x = hue_json["xy"][0];
|
||||
float y = hue_json["xy"][1];
|
||||
const String &x_str = hue_json["xy"][0];
|
||||
const String &y_str = hue_json["xy"][1];
|
||||
x_str.toCharArray(prev_x_str, sizeof(prev_x_str));
|
||||
y_str.toCharArray(prev_y_str, sizeof(prev_y_str));
|
||||
parser.setCurrent();
|
||||
JsonParserToken hue_xy = root[PSTR("xy")];
|
||||
if (hue_xy) {
|
||||
JsonParserArray arr_xy = JsonParserArray(hue_xy);
|
||||
JsonParserToken tok_x = arr_xy[0];
|
||||
JsonParserToken tok_y = arr_xy[1];
|
||||
float x = tok_x.getFloat();
|
||||
float y = tok_y.getFloat();
|
||||
strlcpy(prev_x_str, tok_x.getStr(), sizeof(prev_x_str));
|
||||
strlcpy(prev_y_str, tok_y.getStr(), sizeof(prev_y_str));
|
||||
uint8_t rr,gg,bb;
|
||||
LightStateClass::XyToRgb(x, y, &rr, &gg, &bb);
|
||||
LightStateClass::RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
|
||||
|
@ -658,8 +657,11 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
|
|||
resp = true;
|
||||
change = true;
|
||||
}
|
||||
if (hue_json.containsKey("hue")) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
|
||||
hue = hue_json["hue"];
|
||||
|
||||
parser.setCurrent();
|
||||
JsonParserToken hue_hue = root[PSTR("hue")];
|
||||
if (hue_hue) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
|
||||
hue = hue_hue.getUInt();
|
||||
prev_hue = hue;
|
||||
if (resp) { response += ","; }
|
||||
snprintf_P(buf, buf_size,
|
||||
|
@ -674,8 +676,11 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
|
|||
}
|
||||
resp = true;
|
||||
}
|
||||
if (hue_json.containsKey("sat")) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
|
||||
sat = hue_json["sat"];
|
||||
|
||||
parser.setCurrent();
|
||||
JsonParserToken hue_sat = root[PSTR("sat")];
|
||||
if (hue_sat) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
|
||||
sat = hue_sat.getUInt();
|
||||
prev_sat = sat; // store command value
|
||||
if (resp) { response += ","; }
|
||||
snprintf_P(buf, buf_size,
|
||||
|
@ -690,8 +695,11 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
|
|||
}
|
||||
resp = true;
|
||||
}
|
||||
if (hue_json.containsKey("ct")) { // Color temperature 153 (Cold) to 500 (Warm)
|
||||
ct = hue_json["ct"];
|
||||
|
||||
parser.setCurrent();
|
||||
JsonParserToken hue_ct = root[PSTR("ct")];
|
||||
if (hue_ct) { // Color temperature 153 (Cold) to 500 (Warm)
|
||||
ct = hue_ct.getUInt();
|
||||
prev_ct = ct; // store commande value
|
||||
if (resp) { response += ","; }
|
||||
snprintf_P(buf, buf_size,
|
||||
|
@ -704,6 +712,7 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
|
|||
}
|
||||
resp = true;
|
||||
}
|
||||
|
||||
if (change) {
|
||||
#ifdef USE_SHUTTER
|
||||
if (ShutterState(device)) {
|
||||
|
|
|
@ -39,42 +39,6 @@ public:
|
|||
void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl);
|
||||
bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_status_ok = false);
|
||||
|
||||
// get the result as a string (const char*) and nullptr if there is no field or the string is empty
|
||||
const char * getCaseInsensitiveConstCharNull(const JsonObject &json, const char *needle) {
|
||||
const JsonVariant &val = GetCaseInsensitive(json, needle);
|
||||
if (&val) {
|
||||
const char *val_cs = val.as<const char*>();
|
||||
if (strlen(val_cs)) {
|
||||
return val_cs;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get an JSON attribute, with case insensitive key search starting with *needle
|
||||
JsonVariant &startsWithCaseInsensitive(const JsonObject &json, const char *needle) {
|
||||
// key can be in PROGMEM
|
||||
if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
|
||||
return *(JsonVariant*)nullptr;
|
||||
}
|
||||
|
||||
String needle_s((const __FlashStringHelper *)needle);
|
||||
needle_s.toLowerCase();
|
||||
|
||||
for (auto kv : json) {
|
||||
String key_s(kv.key);
|
||||
key_s.toLowerCase();
|
||||
JsonVariant &value = kv.value;
|
||||
|
||||
if (key_s.startsWith(needle_s)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
// if not found
|
||||
return *(JsonVariant*)nullptr;
|
||||
}
|
||||
|
||||
|
||||
uint32_t parseHex(const char **data, size_t max_len = 8) {
|
||||
uint32_t ret = 0;
|
||||
for (uint32_t i = 0; i < max_len; i++) {
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
#include "JsonParser.h"
|
||||
|
||||
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
|
||||
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
|
||||
#endif
|
||||
|
@ -261,7 +263,7 @@ public:
|
|||
// Dump json
|
||||
String dumpLightState(uint16_t shortaddr) const;
|
||||
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const;
|
||||
int32_t deviceRestore(const JsonObject &json);
|
||||
int32_t deviceRestore(JsonParserObject json);
|
||||
|
||||
// General Zigbee device profile support
|
||||
void setZbProfile(uint16_t shortaddr, uint8_t zb_profile);
|
||||
|
@ -936,7 +938,7 @@ void Z_Devices::clean(void) {
|
|||
// - a number 0..99, the index number in ZigbeeStatus
|
||||
// - a friendly name, between quotes, example: "Room_Temp"
|
||||
uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_known) const {
|
||||
if (nullptr == param) { return 0; }
|
||||
if (nullptr == param) { return BAD_SHORTADDR; }
|
||||
size_t param_len = strlen(param);
|
||||
char dataBuf[param_len + 1];
|
||||
strcpy(dataBuf, param);
|
||||
|
@ -1070,7 +1072,7 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
|
|||
// <0 : Error
|
||||
//
|
||||
// Ex: {"Device":"0x5ADF","Name":"IKEA_Light","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}
|
||||
int32_t Z_Devices::deviceRestore(const JsonObject &json) {
|
||||
int32_t Z_Devices::deviceRestore(JsonParserObject json) {
|
||||
|
||||
// params
|
||||
uint16_t device = 0x0000; // 0x0000 is coordinator so considered invalid
|
||||
|
@ -1078,56 +1080,38 @@ int32_t Z_Devices::deviceRestore(const JsonObject &json) {
|
|||
const char * modelid = nullptr;
|
||||
const char * manufid = nullptr;
|
||||
const char * friendlyname = nullptr;
|
||||
int8_t bulbtype = 0xFF;
|
||||
int8_t bulbtype = -1;
|
||||
size_t endpoints_len = 0;
|
||||
|
||||
// read mandatory "Device"
|
||||
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR("Device"));
|
||||
if (nullptr != &val_device) {
|
||||
device = strToUInt(val_device);
|
||||
JsonParserToken val_device = json[PSTR("Device")];
|
||||
if (val_device) {
|
||||
device = (uint32_t) val_device.getUInt(device);
|
||||
} else {
|
||||
return -1; // missing "Device" attribute
|
||||
}
|
||||
|
||||
// read "IEEEAddr" 64 bits in format "0x0000000000000000"
|
||||
const JsonVariant &val_ieeeaddr = GetCaseInsensitive(json, PSTR("IEEEAddr"));
|
||||
if (nullptr != &val_ieeeaddr) {
|
||||
ieeeaddr = strtoull(val_ieeeaddr.as<const char*>(), nullptr, 0);
|
||||
}
|
||||
|
||||
// read "Name"
|
||||
friendlyname = getCaseInsensitiveConstCharNull(json, PSTR("Name"));
|
||||
|
||||
// read "ModelId"
|
||||
modelid = getCaseInsensitiveConstCharNull(json, PSTR("ModelId"));
|
||||
|
||||
// read "Manufacturer"
|
||||
manufid = getCaseInsensitiveConstCharNull(json, PSTR("Manufacturer"));
|
||||
|
||||
// read "Light"
|
||||
const JsonVariant &val_bulbtype = GetCaseInsensitive(json, PSTR(D_JSON_ZIGBEE_LIGHT));
|
||||
if (nullptr != &val_bulbtype) { bulbtype = strToUInt(val_bulbtype);; }
|
||||
ieeeaddr = json.getULong(PSTR("IEEEAddr"), ieeeaddr); // read "IEEEAddr" 64 bits in format "0x0000000000000000"
|
||||
friendlyname = json.getStr(PSTR("Name"), nullptr); // read "Name"
|
||||
modelid = json.getStr(PSTR("ModelId"), nullptr);
|
||||
manufid = json.getStr(PSTR("Manufacturer"), nullptr);
|
||||
JsonParserToken tok_bulbtype = json[PSTR(D_JSON_ZIGBEE_LIGHT)];
|
||||
|
||||
// update internal device information
|
||||
updateDevice(device, ieeeaddr);
|
||||
if (modelid) { setModelId(device, modelid); }
|
||||
if (manufid) { setManufId(device, manufid); }
|
||||
if (friendlyname) { setFriendlyName(device, friendlyname); }
|
||||
if (&val_bulbtype) { setHueBulbtype(device, bulbtype); }
|
||||
if (tok_bulbtype) { setHueBulbtype(device, tok_bulbtype.getInt()); }
|
||||
|
||||
// read "Endpoints"
|
||||
const JsonVariant &val_endpoints = GetCaseInsensitive(json, PSTR("Endpoints"));
|
||||
if ((nullptr != &val_endpoints) && (val_endpoints.is<JsonArray>())) {
|
||||
const JsonArray &arr_ep = val_endpoints.as<const JsonArray&>();
|
||||
endpoints_len = arr_ep.size();
|
||||
JsonParserToken val_endpoints = json[PSTR("Endpoints")];
|
||||
if (val_endpoints.isArray()) {
|
||||
JsonParserArray arr_ep = JsonParserArray(val_endpoints);
|
||||
clearEndpoints(device); // clear even if array is empty
|
||||
if (endpoints_len) {
|
||||
for (auto ep_elt : arr_ep) {
|
||||
uint8_t ep = strToUInt(ep_elt);
|
||||
if (ep) {
|
||||
addEndpoint(device, ep);
|
||||
}
|
||||
}
|
||||
for (auto ep_elt : arr_ep) {
|
||||
uint8_t ep = ep_elt.getUInt();
|
||||
if (ep) { addEndpoint(device, ep); }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -218,10 +218,12 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
|
|||
if (Webserver->args()) {
|
||||
response = "[";
|
||||
|
||||
StaticJsonBuffer<300> jsonBuffer;
|
||||
JsonObject &hue_json = jsonBuffer.parseObject(Webserver->arg((Webserver->args())-1));
|
||||
if (hue_json.containsKey("on")) {
|
||||
on = hue_json["on"];
|
||||
JsonParser parser = JsonParser((char*) Webserver->arg((Webserver->args())-1).c_str());
|
||||
JsonParserObject root = parser.getRootObject();
|
||||
|
||||
JsonParserToken hue_on = root[PSTR("on")];
|
||||
if (hue_on) {
|
||||
on = hue_on.getBool();
|
||||
snprintf_P(buf, buf_size,
|
||||
PSTR("{\"success\":{\"/lights/%d/state/on\":%s}}"),
|
||||
device_id, on ? "true" : "false");
|
||||
|
@ -235,8 +237,10 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
|
|||
resp = true;
|
||||
}
|
||||
|
||||
if (hue_json.containsKey("bri")) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
|
||||
bri = hue_json["bri"];
|
||||
parser.setCurrent();
|
||||
JsonParserToken hue_bri = root[PSTR("bri")];
|
||||
if (hue_bri) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
|
||||
bri = hue_bri.getUInt();
|
||||
prev_bri = bri; // store command value
|
||||
if (resp) { response += ","; }
|
||||
snprintf_P(buf, buf_size,
|
||||
|
@ -252,13 +256,16 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
|
|||
}
|
||||
// handle xy before Hue/Sat
|
||||
// If the request contains both XY and HS, we wan't to give priority to HS
|
||||
if (hue_json.containsKey("xy")) {
|
||||
float x = hue_json["xy"][0];
|
||||
float y = hue_json["xy"][1];
|
||||
const String &x_str = hue_json["xy"][0];
|
||||
const String &y_str = hue_json["xy"][1];
|
||||
x_str.toCharArray(prev_x_str, sizeof(prev_x_str));
|
||||
y_str.toCharArray(prev_y_str, sizeof(prev_y_str));
|
||||
parser.setCurrent();
|
||||
JsonParserToken hue_xy = root[PSTR("xy")];
|
||||
if (hue_xy) {
|
||||
JsonParserArray arr_xy = JsonParserArray(hue_xy);
|
||||
JsonParserToken tok_x = arr_xy[0];
|
||||
JsonParserToken tok_y = arr_xy[1];
|
||||
float x = tok_x.getFloat();
|
||||
float y = tok_y.getFloat();
|
||||
strlcpy(prev_x_str, tok_x.getStr(), sizeof(prev_x_str));
|
||||
strlcpy(prev_y_str, tok_y.getStr(), sizeof(prev_y_str));
|
||||
if (resp) { response += ","; }
|
||||
snprintf_P(buf, buf_size,
|
||||
PSTR("{\"success\":{\"/lights/%d/state/xy\":[%s,%s]}}"),
|
||||
|
@ -270,8 +277,11 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
|
|||
ZigbeeHueXY(shortaddr, xi, yi);
|
||||
}
|
||||
bool huesat_changed = false;
|
||||
if (hue_json.containsKey("hue")) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
|
||||
hue = hue_json["hue"];
|
||||
|
||||
parser.setCurrent();
|
||||
JsonParserToken hue_hue = root[PSTR("hue")];
|
||||
if (hue_hue) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
|
||||
hue = hue_hue.getUInt();
|
||||
prev_hue = hue;
|
||||
if (resp) { response += ","; }
|
||||
snprintf_P(buf, buf_size,
|
||||
|
@ -285,8 +295,11 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
|
|||
}
|
||||
resp = true;
|
||||
}
|
||||
if (hue_json.containsKey("sat")) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
|
||||
sat = hue_json["sat"];
|
||||
|
||||
parser.setCurrent();
|
||||
JsonParserToken hue_sat = root[PSTR("sat")];
|
||||
if (hue_sat) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
|
||||
sat = hue_sat.getUInt();
|
||||
prev_sat = sat; // store command value
|
||||
if (resp) { response += ","; }
|
||||
snprintf_P(buf, buf_size,
|
||||
|
@ -303,8 +316,11 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) {
|
|||
}
|
||||
resp = true;
|
||||
}
|
||||
if (hue_json.containsKey("ct")) { // Color temperature 153 (Cold) to 500 (Warm)
|
||||
ct = hue_json["ct"];
|
||||
|
||||
parser.setCurrent();
|
||||
JsonParserToken hue_ct = root[PSTR("ct")];
|
||||
if (hue_ct) { // Color temperature 153 (Cold) to 500 (Warm)
|
||||
ct = hue_ct.getUInt();
|
||||
prev_ct = ct; // store commande value
|
||||
if (resp) { response += ","; }
|
||||
snprintf_P(buf, buf_size,
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#define XDRV_23 23
|
||||
|
||||
#include "JsonParser.h"
|
||||
|
||||
const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix
|
||||
#ifdef USE_ZIGBEE_ZNP
|
||||
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEEZNPRECEIVE "|"
|
||||
|
@ -97,19 +99,6 @@ void ZigbeeInit(void)
|
|||
* Commands
|
||||
\*********************************************************************************************/
|
||||
|
||||
uint32_t strToUInt(const JsonVariant &val) {
|
||||
// if the string starts with 0x, it is considered Hex, otherwise it is an int
|
||||
if (val.is<unsigned int>()) {
|
||||
return val.as<unsigned int>();
|
||||
} else {
|
||||
if (val.is<const char*>()) {
|
||||
String sval = val.as<String>();
|
||||
return strtoull(sval.c_str(), nullptr, 0);
|
||||
}
|
||||
}
|
||||
return 0; // couldn't parse anything
|
||||
}
|
||||
|
||||
#ifdef USE_ZIGBEE_ZNP
|
||||
// Do a factory reset of the CC2530
|
||||
const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM =
|
||||
|
@ -242,7 +231,7 @@ bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_stat
|
|||
|
||||
// Parse "Report", "Write", "Response" or "Condig" attribute
|
||||
// Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01)
|
||||
void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
|
||||
void ZbSendReportWrite(class JsonParserToken val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
|
||||
SBuffer buf(200); // buffer to store the binary output of attibutes
|
||||
|
||||
if (nullptr == XdrvMailbox.command) {
|
||||
|
@ -250,12 +239,11 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
|
|||
}
|
||||
|
||||
// iterate on keys
|
||||
for (JsonObject::const_iterator it=val_pubwrite.begin(); it!=val_pubwrite.end(); ++it) {
|
||||
const char *key = it->key;
|
||||
const JsonVariant &value = it->value;
|
||||
for (auto key : val_pubwrite.getObject()) {
|
||||
JsonParserToken value = key.getValue();
|
||||
|
||||
Z_attribute attr;
|
||||
attr.setKeyName(key);
|
||||
attr.setKeyName(key.getStr());
|
||||
if (Z_parseAttributeKey(attr)) {
|
||||
// Buffer ready, do some sanity checks
|
||||
if (0xFFFF == cluster) {
|
||||
|
@ -276,10 +264,10 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
|
|||
}
|
||||
}
|
||||
|
||||
if (value.is<const char*>()) {
|
||||
attr.setStr(value.as<const char*>());
|
||||
} else if (value.is<double>()) {
|
||||
attr.setFloat(value.as<float>());
|
||||
if (value.isStr()) {
|
||||
attr.setStr(value.getStr());
|
||||
} else if (value.isNum()) {
|
||||
attr.setFloat(value.getFloat());
|
||||
}
|
||||
|
||||
double val_d = 0; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss
|
||||
|
@ -293,41 +281,30 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
|
|||
} else {
|
||||
// ////////////////////////////////////////////////////////////////////////////////
|
||||
// ZCL_CONFIGURE_REPORTING
|
||||
if (!value.is<JsonObject>()) {
|
||||
if (!value.isObject()) {
|
||||
ResponseCmndChar_P(PSTR("Config requires JSON objects"));
|
||||
return;
|
||||
}
|
||||
JsonObject &attr_config = value.as<JsonObject>();
|
||||
JsonParserObject attr_config = value.getObject();
|
||||
bool attr_direction = false;
|
||||
|
||||
const JsonVariant &val_attr_direction = GetCaseInsensitive(attr_config, PSTR("DirectionReceived"));
|
||||
if (nullptr != &val_attr_direction) {
|
||||
uint32_t dir = strToUInt(val_attr_direction);
|
||||
if (dir) {
|
||||
attr_direction = true;
|
||||
}
|
||||
}
|
||||
uint32_t dir = attr_config.getUInt(PSTR("DirectionReceived"), 0);
|
||||
if (dir) { attr_direction = true; }
|
||||
|
||||
// read MinInterval and MaxInterval, default to 0xFFFF if not specified
|
||||
uint16_t attr_min_interval = 0xFFFF;
|
||||
uint16_t attr_max_interval = 0xFFFF;
|
||||
const JsonVariant &val_attr_min = GetCaseInsensitive(attr_config, PSTR("MinInterval"));
|
||||
if (nullptr != &val_attr_min) { attr_min_interval = strToUInt(val_attr_min); }
|
||||
const JsonVariant &val_attr_max = GetCaseInsensitive(attr_config, PSTR("MaxInterval"));
|
||||
if (nullptr != &val_attr_max) { attr_max_interval = strToUInt(val_attr_max); }
|
||||
uint16_t attr_min_interval = attr_config.getUInt(PSTR("MinInterval"), 0xFFFF);
|
||||
uint16_t attr_max_interval = attr_config.getUInt(PSTR("MaxInterval"), 0xFFFF);
|
||||
|
||||
// read ReportableChange
|
||||
const JsonVariant &val_attr_rc = GetCaseInsensitive(attr_config, PSTR("ReportableChange"));
|
||||
if (nullptr != &val_attr_rc) {
|
||||
val_d = val_attr_rc.as<double>();
|
||||
val_str = val_attr_rc.as<const char*>();
|
||||
JsonParserToken val_attr_rc = attr_config[PSTR("ReportableChange")];
|
||||
if (val_attr_rc) {
|
||||
val_d = val_attr_rc.getFloat();
|
||||
val_str = val_attr_rc.getStr();
|
||||
ZbApplyMultiplier(val_d, attr.attr_multiplier);
|
||||
}
|
||||
|
||||
// read TimeoutPeriod
|
||||
uint16_t attr_timeout = 0x0000;
|
||||
const JsonVariant &val_attr_timeout = GetCaseInsensitive(attr_config, PSTR("TimeoutPeriod"));
|
||||
if (nullptr != &val_attr_timeout) { attr_timeout = strToUInt(val_attr_timeout); }
|
||||
uint16_t attr_timeout = attr_config.getUInt(PSTR("TimeoutPeriod"), 0x0000);
|
||||
|
||||
bool attr_discrete = Z_isDiscreteDataType(attr.attr_type);
|
||||
|
||||
|
@ -376,37 +353,36 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
|
|||
}
|
||||
|
||||
// Parse the "Send" attribute and send the command
|
||||
void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf) {
|
||||
void ZbSendSend(class JsonParserToken val_cmd, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf) {
|
||||
uint8_t cmd = 0;
|
||||
String cmd_str = ""; // the actual low-level command, either specified or computed
|
||||
const char *cmd_s; // pointer to payload string
|
||||
const char *cmd_s = ""; // pointer to payload string
|
||||
bool clusterSpecific = true;
|
||||
|
||||
static char delim[] = ", "; // delimiters for parameters
|
||||
// probe the type of the argument
|
||||
// If JSON object, it's high level commands
|
||||
// If String, it's a low level command
|
||||
if (val_cmd.is<JsonObject>()) {
|
||||
if (val_cmd.isObject()) {
|
||||
// we have a high-level command
|
||||
const JsonObject &cmd_obj = val_cmd.as<const JsonObject&>();
|
||||
JsonParserObject cmd_obj = val_cmd.getObject();
|
||||
int32_t cmd_size = cmd_obj.size();
|
||||
if (cmd_size > 1) {
|
||||
Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size);
|
||||
return;
|
||||
} else if (1 == cmd_size) {
|
||||
// We have exactly 1 command, parse it
|
||||
JsonObject::const_iterator it = cmd_obj.begin(); // just get the first key/value
|
||||
String key = it->key;
|
||||
const JsonVariant& value = it->value;
|
||||
JsonParserKey key = cmd_obj.getFirstElement();
|
||||
JsonParserToken value = key.getValue();
|
||||
uint32_t x = 0, y = 0, z = 0;
|
||||
uint16_t cmd_var;
|
||||
uint16_t local_cluster_id;
|
||||
|
||||
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str(), &local_cluster_id, &cmd_var);
|
||||
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.getStr(), &local_cluster_id, &cmd_var);
|
||||
if (tasmota_cmd) {
|
||||
cmd_str = tasmota_cmd;
|
||||
} else {
|
||||
Response_P(PSTR("Unrecognized zigbee command: %s"), key.c_str());
|
||||
Response_P(PSTR("Unrecognized zigbee command: %s"), key.getStr());
|
||||
return;
|
||||
}
|
||||
// check cluster
|
||||
|
@ -418,13 +394,17 @@ void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr,
|
|||
}
|
||||
|
||||
// parse the JSON value, depending on its type fill in x,y,z
|
||||
if (value.is<bool>()) {
|
||||
x = value.as<bool>() ? 1 : 0;
|
||||
} else if (value.is<unsigned int>()) {
|
||||
x = value.as<unsigned int>();
|
||||
if (value.isNum()) {
|
||||
x = value.getUInt(); // automatic conversion to 0/1
|
||||
// if (value.is<bool>()) {
|
||||
// // x = value.as<bool>() ? 1 : 0;
|
||||
// } else if
|
||||
// } else if (value.is<unsigned int>()) {
|
||||
// x = value.as<unsigned int>();
|
||||
} else {
|
||||
// if non-bool or non-int, trying char*
|
||||
const char *s_const = value.as<const char*>();
|
||||
const char *s_const = value.getStr(nullptr);
|
||||
// const char *s_const = value.as<const char*>();
|
||||
if (s_const != nullptr) {
|
||||
char s[strlen(s_const)+1];
|
||||
strcpy(s, s_const);
|
||||
|
@ -459,14 +439,13 @@ void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr,
|
|||
} else {
|
||||
// we have zero command, pass through until last error for missing command
|
||||
}
|
||||
} else if (val_cmd.is<const char*>()) {
|
||||
} else if (val_cmd.isStr()) {
|
||||
// low-level command
|
||||
cmd_str = val_cmd.as<String>();
|
||||
// Now parse the string to extract cluster, command, and payload
|
||||
// Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC"
|
||||
// where AA is the cluster number, BBBB the command number, CCCC... the payload
|
||||
// First delimiter is '_' for a global command, or '!' for a cluster specific command
|
||||
const char * data = cmd_str.c_str();
|
||||
const char * data = val_cmd.getStr();
|
||||
uint16_t local_cluster_id = parseHex(&data, 4);
|
||||
|
||||
// check cluster
|
||||
|
@ -505,7 +484,7 @@ void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr,
|
|||
|
||||
|
||||
// Parse the "Send" attribute and send the command
|
||||
void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
|
||||
void ZbSendRead(JsonParserToken val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
|
||||
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":5}
|
||||
// ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"}
|
||||
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]}
|
||||
|
@ -525,32 +504,30 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
|
|||
attr_item_offset = 1;
|
||||
}
|
||||
|
||||
uint16_t val = strToUInt(val_attr);
|
||||
if (val_attr.is<JsonArray>()) {
|
||||
if (val_attr.isArray()) {
|
||||
// value is an array []
|
||||
const JsonArray& attr_arr = val_attr.as<const JsonArray&>();
|
||||
JsonParserArray attr_arr = val_attr.getArray();
|
||||
attrs_len = attr_arr.size() * attr_item_len;
|
||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||
|
||||
uint32_t i = 0;
|
||||
for (auto value : attr_arr) {
|
||||
uint16_t val = strToUInt(value);
|
||||
uint16_t val = value.getUInt();
|
||||
i += attr_item_offset;
|
||||
attrs[i++] = val & 0xFF;
|
||||
attrs[i++] = val >> 8;
|
||||
i += attr_item_len - 2 - attr_item_offset; // normally 0
|
||||
}
|
||||
} else if (val_attr.is<JsonObject>()) {
|
||||
} else if (val_attr.isObject()) {
|
||||
// value is an object {}
|
||||
const JsonObject& attr_obj = val_attr.as<const JsonObject&>();
|
||||
JsonParserObject attr_obj = val_attr.getObject();
|
||||
attrs_len = attr_obj.size() * attr_item_len;
|
||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||
uint32_t actual_attr_len = 0;
|
||||
|
||||
// iterate on keys
|
||||
for (JsonObject::const_iterator it=attr_obj.begin(); it!=attr_obj.end(); ++it) {
|
||||
const char *key = it->key;
|
||||
const JsonVariant &value = it->value; // we don't need the value here, only keys are relevant
|
||||
for (auto key : attr_obj) {
|
||||
JsonParserToken value = key.getValue();
|
||||
|
||||
bool found = false;
|
||||
// scan attributes to find by name, and retrieve type
|
||||
|
@ -561,11 +538,11 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
|
|||
uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
|
||||
// uint8_t local_type_id = pgm_read_byte(&converter->type);
|
||||
|
||||
if ((pgm_read_word(&converter->name_offset)) && (0 == strcasecmp_P(key, Z_strings + pgm_read_word(&converter->name_offset)))) {
|
||||
if ((pgm_read_word(&converter->name_offset)) && (0 == strcasecmp_P(key.getStr(), Z_strings + pgm_read_word(&converter->name_offset)))) {
|
||||
// match name
|
||||
// check if there is a conflict with cluster
|
||||
// TODO
|
||||
if (!value && attr_item_offset) {
|
||||
if (!(value.getBool()) && attr_item_offset) {
|
||||
// If value is false (non-default) then set direction to 1 (for ReadConfig)
|
||||
attrs[actual_attr_len] = 0x01;
|
||||
}
|
||||
|
@ -594,6 +571,7 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
|
|||
} else {
|
||||
// value is a literal
|
||||
if (0xFFFF != cluster) {
|
||||
uint16_t val = val_attr.getUInt();
|
||||
attrs_len = attr_item_len;
|
||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||
attrs[0 + attr_item_offset] = val & 0xFF; // little endian
|
||||
|
@ -648,9 +626,8 @@ void CmndZbSend(void) {
|
|||
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} }
|
||||
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} }
|
||||
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
|
||||
DynamicJsonBuffer jsonBuf;
|
||||
const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
|
||||
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||
JsonParserObject root = JsonParser(XdrvMailbox.data).getRootObject();
|
||||
if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||
|
||||
// params
|
||||
uint16_t device = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
|
||||
|
@ -661,15 +638,15 @@ void CmndZbSend(void) {
|
|||
|
||||
|
||||
// parse "Device" and "Group"
|
||||
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_DEVICE));
|
||||
if (nullptr != &val_device) {
|
||||
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
|
||||
JsonParserToken val_device = root[PSTR(D_CMND_ZIGBEE_DEVICE)];
|
||||
if (val_device) {
|
||||
device = zigbee_devices.parseDeviceParam(val_device.getStr());
|
||||
if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
|
||||
}
|
||||
if (BAD_SHORTADDR == device) { // if not found, check if we have a group
|
||||
const JsonVariant &val_group = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_GROUP));
|
||||
if (nullptr != &val_group) {
|
||||
groupaddr = strToUInt(val_group);
|
||||
JsonParserToken val_group = root[PSTR(D_CMND_ZIGBEE_GROUP)];
|
||||
if (val_group) {
|
||||
groupaddr = val_group.getUInt();
|
||||
} else { // no device nor group
|
||||
ResponseCmndChar_P(PSTR("Unknown device"));
|
||||
return;
|
||||
|
@ -679,12 +656,9 @@ void CmndZbSend(void) {
|
|||
// Note: groupaddr == 0 is valid
|
||||
|
||||
// read other parameters
|
||||
const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CLUSTER));
|
||||
if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); }
|
||||
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_ENDPOINT));
|
||||
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
|
||||
const JsonVariant &val_manuf = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_MANUF));
|
||||
if (nullptr != &val_manuf) { manuf = strToUInt(val_manuf); }
|
||||
cluster = root.getUInt(PSTR(D_CMND_ZIGBEE_CLUSTER), cluster);
|
||||
endpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), endpoint);
|
||||
manuf = root.getUInt(PSTR(D_CMND_ZIGBEE_MANUF), manuf);
|
||||
|
||||
// infer endpoint
|
||||
if (BAD_SHORTADDR == device) {
|
||||
|
@ -700,61 +674,61 @@ void CmndZbSend(void) {
|
|||
// from here endpoint is valid and non-zero
|
||||
// cluster may be already specified or 0xFFFF
|
||||
|
||||
const JsonVariant &val_cmd = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_SEND));
|
||||
const JsonVariant &val_read = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_READ));
|
||||
const JsonVariant &val_write = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_WRITE));
|
||||
const JsonVariant &val_publish = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_REPORT));
|
||||
const JsonVariant &val_response = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_RESPONSE));
|
||||
const JsonVariant &val_read_config = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_READ_CONFIG));
|
||||
const JsonVariant &val_config = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CONFIG));
|
||||
uint32_t multi_cmd = (nullptr != &val_cmd) + (nullptr != &val_read) + (nullptr != &val_write) + (nullptr != &val_publish)
|
||||
+ (nullptr != &val_response) + (nullptr != &val_read_config) + (nullptr != &val_config);
|
||||
JsonParserToken val_cmd = root[PSTR(D_CMND_ZIGBEE_SEND)];
|
||||
JsonParserToken val_read = root[PSTR(D_CMND_ZIGBEE_READ)];
|
||||
JsonParserToken val_write = root[PSTR(D_CMND_ZIGBEE_WRITE)];
|
||||
JsonParserToken val_publish = root[PSTR(D_CMND_ZIGBEE_REPORT)];
|
||||
JsonParserToken val_response = root[PSTR(D_CMND_ZIGBEE_RESPONSE)];
|
||||
JsonParserToken val_read_config = root[PSTR(D_CMND_ZIGBEE_READ_CONFIG)];
|
||||
JsonParserToken val_config = root[PSTR(D_CMND_ZIGBEE_CONFIG)];
|
||||
uint32_t multi_cmd = ((bool)val_cmd) + ((bool)val_read) + ((bool)val_write) + ((bool)val_publish)
|
||||
+ ((bool)val_response) + ((bool)val_read_config) + ((bool)val_config);
|
||||
if (multi_cmd > 1) {
|
||||
ResponseCmndChar_P(PSTR("Can only have one of: 'Send', 'Read', 'Write', 'Report', 'Reponse', 'ReadConfig' or 'Config'"));
|
||||
return;
|
||||
}
|
||||
// from here we have one and only one command
|
||||
|
||||
if (nullptr != &val_cmd) {
|
||||
if (val_cmd) {
|
||||
// "Send":{...commands...}
|
||||
// we accept either a string or a JSON object
|
||||
ZbSendSend(val_cmd, device, groupaddr, cluster, endpoint, manuf);
|
||||
} else if (nullptr != &val_read) {
|
||||
} else if (val_read) {
|
||||
// "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...]
|
||||
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
|
||||
ZbSendRead(val_read, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_ATTRIBUTES);
|
||||
} else if (nullptr != &val_write) {
|
||||
} else if (val_write) {
|
||||
// only KSON object
|
||||
if (!val_write.is<JsonObject>()) {
|
||||
if (!val_write.isObject()) {
|
||||
ResponseCmndChar_P(PSTR("Missing parameters"));
|
||||
return;
|
||||
}
|
||||
// "Write":{...attributes...}
|
||||
ZbSendReportWrite(val_write, device, groupaddr, cluster, endpoint, manuf, ZCL_WRITE_ATTRIBUTES);
|
||||
} else if (nullptr != &val_publish) {
|
||||
} else if (val_publish) {
|
||||
// "Publish":{...attributes...}
|
||||
// only KSON object
|
||||
if (!val_publish.is<JsonObject>()) {
|
||||
if (!val_publish.isObject()) {
|
||||
ResponseCmndChar_P(PSTR("Missing parameters"));
|
||||
return;
|
||||
}
|
||||
ZbSendReportWrite(val_publish, device, groupaddr, cluster, endpoint, manuf, ZCL_REPORT_ATTRIBUTES);
|
||||
} else if (nullptr != &val_response) {
|
||||
} else if (val_response) {
|
||||
// "Report":{...attributes...}
|
||||
// only KSON object
|
||||
if (!val_response.is<JsonObject>()) {
|
||||
if (!val_response.isObject()) {
|
||||
ResponseCmndChar_P(PSTR("Missing parameters"));
|
||||
return;
|
||||
}
|
||||
ZbSendReportWrite(val_response, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_ATTRIBUTES_RESPONSE);
|
||||
} else if (nullptr != &val_read_config) {
|
||||
} else if (val_read_config) {
|
||||
// "ReadConfg":{...attributes...}, "ReadConfg":attribute or "ReadConfg":[...attributes...]
|
||||
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
|
||||
ZbSendRead(val_read_config, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_REPORTING_CONFIGURATION);
|
||||
} else if (nullptr != &val_config) {
|
||||
} else if (val_config) {
|
||||
// "Config":{...attributes...}
|
||||
// only JSON object
|
||||
if (!val_config.is<JsonObject>()) {
|
||||
if (!val_config.isObject()) {
|
||||
ResponseCmndChar_P(PSTR("Missing parameters"));
|
||||
return;
|
||||
}
|
||||
|
@ -774,61 +748,56 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
|
|||
|
||||
// local endpoint is always 1, IEEE addresses are calculated
|
||||
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
|
||||
DynamicJsonBuffer jsonBuf;
|
||||
const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
|
||||
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||
JsonParserObject root = JsonParser(XdrvMailbox.data).getRootObject();
|
||||
if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||
|
||||
// params
|
||||
uint16_t srcDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
|
||||
uint16_t srcDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
|
||||
uint16_t dstDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
|
||||
uint64_t dstLongAddr = 0;
|
||||
uint8_t endpoint = 0x00; // 0x00 is invalid for the src endpoint
|
||||
uint8_t toendpoint = 0x00; // 0x00 is invalid for the dst endpoint
|
||||
uint8_t toendpoint = 0x01; // default dest endpoint to 0x01
|
||||
uint16_t toGroup = 0x0000; // group address
|
||||
uint16_t cluster = 0; // 0xFFFF is invalid
|
||||
uint32_t group = 0xFFFFFFFF; // 16 bits values, otherwise 0xFFFFFFFF is unspecified
|
||||
|
||||
// Information about source device: "Device", "Endpoint", "Cluster"
|
||||
// - the source endpoint must have a known IEEE address
|
||||
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_DEVICE));
|
||||
if (nullptr != &val_device) {
|
||||
srcDevice = zigbee_devices.parseDeviceParam(val_device.as<char*>());
|
||||
}
|
||||
if ((nullptr == &val_device) || (BAD_SHORTADDR == srcDevice)) { ResponseCmndChar_P(PSTR("Unknown source device")); return; }
|
||||
srcDevice = zigbee_devices.parseDeviceParam(root.getStr(PSTR(D_CMND_ZIGBEE_DEVICE), nullptr));
|
||||
if (BAD_SHORTADDR == srcDevice) { ResponseCmndChar_P(PSTR("Unknown source device")); return; }
|
||||
// check if IEEE address is known
|
||||
uint64_t srcLongAddr = zigbee_devices.getDeviceLongAddr(srcDevice);
|
||||
if (0 == srcLongAddr) { ResponseCmndChar_P(PSTR("Unknown source IEEE address")); return; }
|
||||
// look for source endpoint
|
||||
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_ENDPOINT));
|
||||
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
|
||||
else { endpoint = zigbee_devices.findFirstEndpoint(srcDevice); }
|
||||
endpoint = root.getUInt(PSTR(D_CMND_ZIGBEE_ENDPOINT), endpoint);
|
||||
if (0 == endpoint) { endpoint = zigbee_devices.findFirstEndpoint(srcDevice); }
|
||||
// look for source cluster
|
||||
const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CLUSTER));
|
||||
if (nullptr != &val_cluster) {
|
||||
cluster = strToUInt(val_cluster); // first convert as number
|
||||
JsonParserToken val_cluster = root[PSTR(D_CMND_ZIGBEE_CLUSTER)];
|
||||
if (val_cluster) {
|
||||
cluster = val_cluster.getUInt(cluster); // first convert as number
|
||||
if (0 == cluster) {
|
||||
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr);
|
||||
zigbeeFindAttributeByName(val_cluster.getStr(), &cluster, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Or Group Address - we don't need a dstEndpoint in this case
|
||||
const JsonVariant &to_group = GetCaseInsensitive(json, PSTR("ToGroup"));
|
||||
if (nullptr != &to_group) { toGroup = strToUInt(to_group); }
|
||||
JsonParserToken to_group = root[PSTR("ToGroup")];
|
||||
if (to_group) { toGroup = to_group.getUInt(toGroup); }
|
||||
|
||||
// Either Device address
|
||||
// In this case the following parameters are mandatory
|
||||
// - "ToDevice" and the device must have a known IEEE address
|
||||
// - "ToEndpoint"
|
||||
const JsonVariant &dst_device = GetCaseInsensitive(json, PSTR("ToDevice"));
|
||||
JsonParserToken dst_device = root[PSTR("ToDevice")];
|
||||
|
||||
// If no target is specified, we default to coordinator 0x0000
|
||||
if ((nullptr == &to_group) && (nullptr == &dst_device)) {
|
||||
if ((!to_group) && (!dst_device)) {
|
||||
dstDevice = 0x0000;
|
||||
}
|
||||
|
||||
if ((nullptr != &dst_device) || (BAD_SHORTADDR != dstDevice)) {
|
||||
if ((dst_device) || (BAD_SHORTADDR != dstDevice)) {
|
||||
if (BAD_SHORTADDR == dstDevice) {
|
||||
dstDevice = zigbee_devices.parseDeviceParam(dst_device.as<char*>());
|
||||
dstDevice = zigbee_devices.parseDeviceParam(dst_device.getStr(nullptr));
|
||||
if (BAD_SHORTADDR == dstDevice) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
|
||||
}
|
||||
if (0x0000 == dstDevice) {
|
||||
|
@ -838,14 +807,12 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
|
|||
}
|
||||
if (0 == dstLongAddr) { ResponseCmndChar_P(PSTR("Unknown dest IEEE address")); return; }
|
||||
|
||||
const JsonVariant &val_toendpoint = GetCaseInsensitive(json, PSTR("ToEndpoint"));
|
||||
if (nullptr != &val_toendpoint) { toendpoint = strToUInt(val_toendpoint); }
|
||||
else { toendpoint = 0x01; } // default to endpoint 1
|
||||
toendpoint = root.getUInt(PSTR("ToEndpoint"), toendpoint);
|
||||
}
|
||||
|
||||
// make sure we don't have conflicting parameters
|
||||
if (&to_group && dstLongAddr) { ResponseCmndChar_P(PSTR("Cannot have both \"ToDevice\" and \"ToGroup\"")); return; }
|
||||
if (!&to_group && !dstLongAddr) { ResponseCmndChar_P(PSTR("Missing \"ToDevice\" or \"ToGroup\"")); return; }
|
||||
if (to_group && dstLongAddr) { ResponseCmndChar_P(PSTR("Cannot have both \"ToDevice\" and \"ToGroup\"")); return; }
|
||||
if (!to_group && !dstLongAddr) { ResponseCmndChar_P(PSTR("Missing \"ToDevice\" or \"ToGroup\"")); return; }
|
||||
|
||||
#ifdef USE_ZIGBEE_ZNP
|
||||
SBuffer buf(34);
|
||||
|
@ -1097,38 +1064,32 @@ void CmndZbSave(void) {
|
|||
// ZbRestore {"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}
|
||||
void CmndZbRestore(void) {
|
||||
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
|
||||
DynamicJsonBuffer jsonBuf;
|
||||
const JsonVariant json_parsed = jsonBuf.parse((const char*) XdrvMailbox.data); // const to force a copy of parameter
|
||||
const JsonVariant * json = &json_parsed; // root of restore, to be changed if needed
|
||||
bool success = false;
|
||||
JsonParser p(XdrvMailbox.data);
|
||||
JsonParserToken root = p.getRoot();
|
||||
|
||||
// check if parsing succeeded
|
||||
if (json_parsed.is<JsonObject>()) {
|
||||
success = json_parsed.as<const JsonObject&>().success();
|
||||
} else if (json_parsed.is<JsonArray>()) {
|
||||
success = json_parsed.as<const JsonArray&>().success();
|
||||
}
|
||||
if (!success) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||
if (!p || !(root.isObject() || root.isArray())) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||
|
||||
// Check is root contains `ZbStatus<x>` key, if so change the root
|
||||
const JsonVariant * zbstatus = &startsWithCaseInsensitive(*json, PSTR("ZbStatus"));
|
||||
if (nullptr != zbstatus) {
|
||||
json = zbstatus;
|
||||
JsonParserToken zbstatus = root.getObject().findStartsWith(PSTR("ZbStatus"));
|
||||
if (zbstatus) {
|
||||
root = zbstatus;
|
||||
}
|
||||
|
||||
// check if the root is an array
|
||||
if (json->is<JsonArray>()) {
|
||||
const JsonArray& arr = json->as<const JsonArray&>();
|
||||
for (auto elt : arr) {
|
||||
if (root.isArray()) {
|
||||
JsonParserArray arr = JsonParserArray(root);
|
||||
for (const auto elt : arr) {
|
||||
// call restore on each item
|
||||
int32_t res = zigbee_devices.deviceRestore(elt);
|
||||
if (res < 0) {
|
||||
ResponseCmndChar_P(PSTR("Restore failed"));
|
||||
return;
|
||||
if (elt.isObject()) {
|
||||
int32_t res = zigbee_devices.deviceRestore(JsonParserObject(elt));
|
||||
if (res < 0) {
|
||||
ResponseCmndChar_P(PSTR("Restore failed"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (json->is<JsonObject>()) {
|
||||
int32_t res = zigbee_devices.deviceRestore(*json);
|
||||
} else if (root.isObject()) {
|
||||
int32_t res = zigbee_devices.deviceRestore(JsonParserObject(root));
|
||||
if (res < 0) {
|
||||
ResponseCmndChar_P(PSTR("Restore failed"));
|
||||
return;
|
||||
|
@ -1258,31 +1219,19 @@ void CmndZbConfig(void) {
|
|||
// if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
|
||||
RemoveAllSpaces(XdrvMailbox.data);
|
||||
if (strlen(XdrvMailbox.data) > 0) {
|
||||
DynamicJsonBuffer jsonBuf;
|
||||
const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
|
||||
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||
|
||||
JsonParserObject root = JsonParser(XdrvMailbox.data).getRootObject();
|
||||
if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||
// Channel
|
||||
const JsonVariant &val_channel = GetCaseInsensitive(json, PSTR("Channel"));
|
||||
if (nullptr != &val_channel) { zb_channel = strToUInt(val_channel); }
|
||||
|
||||
zb_channel = root.getUInt(PSTR("Channel"), zb_channel);
|
||||
zb_pan_id = root.getUInt(PSTR("PanID"), zb_pan_id);
|
||||
zb_ext_panid = root.getULong(PSTR("ExtPanID"), zb_ext_panid);
|
||||
zb_precfgkey_l = root.getULong(PSTR("KeyL"), zb_precfgkey_l);
|
||||
zb_precfgkey_h = root.getULong(PSTR("KeyH"), zb_precfgkey_h);
|
||||
zb_txradio_dbm = root.getUInt(PSTR("TxRadio"), zb_txradio_dbm);
|
||||
|
||||
if (zb_channel < 11) { zb_channel = 11; }
|
||||
if (zb_channel > 26) { zb_channel = 26; }
|
||||
// PanID
|
||||
const JsonVariant &val_pan_id = GetCaseInsensitive(json, PSTR("PanID"));
|
||||
if (nullptr != &val_pan_id) { zb_pan_id = strToUInt(val_pan_id); }
|
||||
// ExtPanID
|
||||
const JsonVariant &val_ext_pan_id = GetCaseInsensitive(json, PSTR("ExtPanID"));
|
||||
if (nullptr != &val_ext_pan_id) { zb_ext_panid = strtoull(val_ext_pan_id.as<const char*>(), nullptr, 0); }
|
||||
// KeyL
|
||||
const JsonVariant &val_key_l = GetCaseInsensitive(json, PSTR("KeyL"));
|
||||
if (nullptr != &val_key_l) { zb_precfgkey_l = strtoull(val_key_l.as<const char*>(), nullptr, 0); }
|
||||
// KeyH
|
||||
const JsonVariant &val_key_h = GetCaseInsensitive(json, PSTR("KeyH"));
|
||||
if (nullptr != &val_key_h) { zb_precfgkey_h = strtoull(val_key_h.as<const char*>(), nullptr, 0); }
|
||||
// TxRadio dBm
|
||||
const JsonVariant &val_txradio = GetCaseInsensitive(json, PSTR("TxRadio"));
|
||||
if (nullptr != &val_txradio) { zb_txradio_dbm = strToUInt(val_txradio); }
|
||||
|
||||
// if network key is zero, we generate a truly random key with a hardware generator from ESP
|
||||
if ((0 == zb_precfgkey_l) && (0 == zb_precfgkey_h)) {
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "generating random Zigbee network key"));
|
||||
|
|
Loading…
Reference in New Issue