Merge pull request #17127 from s-hadinger/zigbee_plugin_default

Zigbee add default plugin in flash
This commit is contained in:
s-hadinger 2022-11-18 00:34:47 +01:00 committed by GitHub
commit d46f6ea6b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 391 additions and 201 deletions

View File

@ -761,17 +761,6 @@ void ZCLFrame::computeSyntheticAttributes(Z_attribute_list& attr_list) {
attr_list.addAttribute(0x0001, 0x0021).setUInt(toPercentageCR2032(mv) * 2);
}
break;
case 0x00010021: // BatteryPercentage
if (modelId.startsWith(F("TRADFRI")) ||
modelId.startsWith(F("SYMFONISK"))) {
attr.setUInt(attr.getUInt() * 2); // bug in IKEA remotes battery, need to double the value
}
break;
case 0x00060000: // "Power" for lumi Door/Window is converted to "Contact"
if (modelId.startsWith(F("lumi.sensor_magnet"))) {
attr.setKeyId(0x0500, 0xFFF0 + ZA_Contact); // change cluster and attribute to 0500/FFF0
}
break;
case 0x02010008: // Pi Heating Demand - solve Eutotronic bug
case 0x02014008: // Eurotronic Host Flags decoding
{

View File

@ -0,0 +1,142 @@
/*
xdrv_23_zigbee_7_6_flash_fs.ino - implement a Flash based read-only triviall file-system
Copyright (C) 2022 Theo Arends and 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/>.
*/
#ifdef USE_ZIGBEE
#ifdef ESP32
#include <vfs_api.h>
#else
#include "FSImpl.h"
#endif
/********************************************************************
** Subfile implementation
**
** Takes a string point in Flash and turn it to a read-only file
********************************************************************/
class FlashFileImpl;
typedef std::shared_ptr<FlashFileImpl> FlashFileImplPtr;
class FlashFileImpl : public FileImpl {
public:
FlashFileImpl(const char* str) {
_buf = str;
_len = strlen_P(str);
_seek = 0;
}
virtual ~FlashFileImpl() {}
size_t write(const uint8_t *buf, size_t size) {
return 0; // not accepted
}
size_t read(uint8_t* buf, size_t size) {
if (_seek < _len) {
if (size + _seek > _len) {
size = _len - _seek; // always > 0 because of guarding test
}
memcpy_P(buf, _buf + _seek, size);
_seek += size;
return size;
}
return 0; // abort
}
void flush() {
// do nothing
}
bool setBufferSize(size_t size) {
return true;
}
bool seek(uint32_t pos, SeekMode mode) {
// AddLog(LOG_LEVEL_DEBUG, "ZIP: seek pos=%i mode=%i", pos, mode);
if (SeekSet == mode) {
if (pos <= _len) {
_seek = pos;
return true;
}
} else if (SeekCur == mode) {
if (_seek + pos <= _len) {
_seek += pos;
return true;
}
} else if (SeekEnd == mode) {
_seek = _len;
return true;
}
return false;
}
size_t position() const {
return _seek;
}
size_t size() const {
return _len;
}
void close() {
// do nothing
}
time_t getLastWrite() {
return 0;
}
const char* path() const {
return "";
}
const char* name() const {
return "<internal>";
}
boolean isDirectory(void) {
return false; // no directory allowed
}
FileImplPtr openNextFile(const char* mode) {
return nullptr; // TODO
}
void rewindDirectory(void) {
// ignore
}
operator bool() {
return true;
}
// ESP8266 specific?
bool truncate(uint32_t size) { return false; }
const char* fullName() const { return ""; }
bool isFile() const { return true; }
bool isDirectory() const { return false; }
protected:
const char * _buf;
size_t _len;
uint32_t _seek;
};
#endif // USE_ZIGBEE

View File

@ -125,6 +125,200 @@ bool Zb_readline(class File *f, char* buf, size_t size) {
extern FS *ffsp;
#endif
bool ZbLoad_inner(const char *filename, File &fp) {
char * filename_imported = nullptr;
Z_plugin_template * tmpl = nullptr; // current template with matchers and attributes
bool new_matchers = false; // indicates that we have finished the current matchers
char buf_line[96]; // max line is 96 bytes (comments don't count)
// read the first 6 chars
bool invalid_header = false;
static const char Z2T_HEADER_V1[] PROGMEM = "#Z2Tv1";
for (uint32_t i = 0; i < 6; i++) {
int c = fp.read();
if (c < 0) {
invalid_header = true;
break;
}
buf_line[i] = c;
buf_line[i+1] = 0;
}
if (!invalid_header) {
if (strcmp_P(buf_line, Z2T_HEADER_V1) != 0) {
invalid_header = true;
}
}
if (invalid_header) {
AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' invalid header", filename);
return false;
}
// parse line by line
while (1) {
if (!Zb_readline(&fp, buf_line, sizeof(buf_line))) {
// EOF
break;
}
// at first valid line, we instanciate a new plug-in instance and assign a filemane
if (filename_imported == nullptr) {
// allocate only once the filename for multiple entries
// freed only by `ZbUnload`
filename_imported = (char*) malloc(strlen(filename)+1);
strcpy(filename_imported, filename);
}
// there is a non-empty line, containing no space/tab/crlf
// depending on the first char, parse either device name or cluster/attribute+name
if (buf_line[0] == ':') {
if (tmpl == nullptr || new_matchers) {
tmpl = &g_plugin_templates.addToLast();
tmpl->filename = filename_imported;
new_matchers = false;
}
// parse device name
char *rest = buf_line + 1; // skip first char ':'
char *token = strtok_r(rest, ",", &rest);
Z_plugin_matcher & matcher = tmpl->matchers.addToLast();
if (token != nullptr) {
matcher.setModel(token);
}
token = strtok_r(rest, ",", &rest);
if (token != nullptr) {
matcher.setManuf(token);
}
} else {
if (tmpl == nullptr) {
continue;
}
new_matchers = true;
char *rest = buf_line;
char *token = strtok_r(rest, ",", &rest);
if (token == nullptr) {
continue;
}
// detect if token contains '=', if yes it is a synonym
char * delimiter_equal = strchr(token, '=');
if (delimiter_equal == nullptr) {
// NORMAL ATTRIBUTE
// token is of from '0000/0000' or '0000/0000%00'
char * delimiter_slash = strchr(token, '/');
char * delimiter_percent = strchr(token, '%');
if (delimiter_slash == nullptr || (delimiter_percent != nullptr && delimiter_slash > delimiter_percent)) {
AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' wrong delimiter '%s'", filename, token);
}
uint16_t attr_id = 0xFFFF;
uint16_t cluster_id = 0xFFFF;
uint8_t type_id = Zunk;
int8_t multiplier = 1;
int8_t divider = 1;
int16_t base = 0;
char * name = nullptr;
uint16_t manuf = 0;
cluster_id = strtoul(token, &delimiter_slash, 16);
if (!delimiter_percent) {
attr_id = strtoul(delimiter_slash+1, nullptr, 16);
} else {
attr_id = strtoul(delimiter_slash+1, &delimiter_percent, 16);
type_id = Z_getTypeByName(delimiter_percent+1);
}
// NAME of the attribute
token = strtok_r(rest, ",", &rest);
if (token == nullptr) {
AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' ignore missing name '%s'", filename, buf_line);
continue;
}
name = token;
// ADDITIONAL ELEMENTS?
// Ex: `manuf:1037`
while (token = strtok_r(rest, ",", &rest)) {
char * sub_token;
// look for multiplier
if (sub_token = Z_subtoken(token, Z_MUL)) {
multiplier = strtol(sub_token, nullptr, 10);
}
// look for divider
else if (sub_token = Z_subtoken(token, Z_DIV)) {
divider = strtol(sub_token, nullptr, 10); // negative to indicate divider
}
// look for offset (base)
else if (sub_token = Z_subtoken(token, Z_ADD)) {
base = strtol(sub_token, nullptr, 10); // negative to indicate divider
}
// look for `manuf:HHHH`
else if (sub_token = Z_subtoken(token, Z_MANUF)) {
manuf = strtoul(sub_token, nullptr, 16);
}
else {
AddLog(LOG_LEVEL_DEBUG, "ZIG: ZbLoad unrecognized modifier '%s'", token);
}
}
// token contains the name of the attribute
Z_plugin_attribute & plugin_attr = tmpl->attributes.addToLast();
plugin_attr.cluster = cluster_id;
plugin_attr.attribute = attr_id;
plugin_attr.type = type_id;
plugin_attr.setName(name);
plugin_attr.multiplier = multiplier;
plugin_attr.divider = divider;
plugin_attr.base = base;
plugin_attr.manuf = manuf;
} else {
// ATTRIBUTE SYNONYM
// token is of from '0000/0000=0000/0000,1'
char * rest2 = token;
char * tok2 = strtok_r(rest2, "=", &rest2);
char * delimiter_slash = strchr(tok2, '/');
uint16_t cluster_id = strtoul(tok2, &delimiter_slash, 16);
uint16_t attr_id = strtoul(delimiter_slash+1, nullptr, 16);
tok2 = strtok_r(rest2, "=", &rest2);
char * delimiter_slash2 = strchr(tok2, '/');
uint16_t new_cluster_id = strtoul(tok2, &delimiter_slash2, 16);
uint16_t new_attr_id = strtoul(delimiter_slash2+1, nullptr, 16);
int8_t multiplier = 1;
int8_t divider = 1;
int16_t base = 0;
// ADDITIONAL ELEMENTS?
while (token = strtok_r(rest, ",", &rest)) {
char * sub_token;
// look for multiplier
if (sub_token = Z_subtoken(token, Z_MUL)) {
multiplier = strtol(sub_token, nullptr, 10);
}
// look for divider
else if (sub_token = Z_subtoken(token, Z_DIV)) {
divider = strtol(sub_token, nullptr, 10); // negative to indicate divider
}
// look for offset (base)
else if (sub_token = Z_subtoken(token, Z_ADD)) {
base = strtol(sub_token, nullptr, 10); // negative to indicate divider
}
else {
AddLog(LOG_LEVEL_DEBUG, "ZIG: ZbLoad unrecognized modifier '%s'", token);
}
}
// create the synonym
Z_attribute_synonym & syn = tmpl->synonyms.addToLast();
syn.cluster = cluster_id;
syn.attribute = attr_id;
syn.new_cluster = new_cluster_id;
syn.new_attribute = new_attr_id;
syn.multiplier = multiplier;
syn.divider = divider;
syn.base = base;
}
}
}
return true;
}
// load a file from filesystem
// returns `true` if success
bool ZbLoad(const char *filename_raw) {
@ -147,196 +341,7 @@ bool ZbLoad(const char *filename_raw) {
return false;
}
char * filename_imported = nullptr;
Z_plugin_template * tmpl = nullptr; // current template with matchers and attributes
bool new_matchers = false; // indicates that we have finished the current matchers
char buf_line[96]; // max line is 96 bytes (comments don't count)
// read the first 6 chars
bool invalid_header = false;
static const char Z2T_HEADER_V1[] PROGMEM = "#Z2Tv1";
for (uint32_t i = 0; i < 6; i++) {
int c = fp.read();
if (c < 0) {
invalid_header = true;
break;
}
buf_line[i] = c;
buf_line[i+1] = 0;
}
if (!invalid_header) {
if (strcmp_P(buf_line, Z2T_HEADER_V1) != 0) {
invalid_header = true;
}
}
if (invalid_header) {
AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' invalid header", filename_raw);
return false;
}
// parse line by line
while (1) {
if (!Zb_readline(&fp, buf_line, sizeof(buf_line))) {
// EOF
break;
}
// at first valid line, we instanciate a new plug-in instance and assign a filemane
if (filename_imported == nullptr) {
// allocate only once the filename for multiple entries
// freed only by `ZbUnload`
filename_imported = (char*) malloc(strlen(filename.c_str())+1);
strcpy(filename_imported, filename.c_str());
}
// there is a non-empty line, containing no space/tab/crlf
// depending on the first char, parse either device name or cluster/attribute+name
if (buf_line[0] == ':') {
if (tmpl == nullptr || new_matchers) {
tmpl = &g_plugin_templates.addToLast();
tmpl->filename = filename_imported;
new_matchers = false;
}
// parse device name
char *rest = buf_line + 1; // skip first char ':'
char *token = strtok_r(rest, ",", &rest);
Z_plugin_matcher & matcher = tmpl->matchers.addToLast();
if (token != nullptr) {
matcher.setModel(token);
}
token = strtok_r(rest, ",", &rest);
if (token != nullptr) {
matcher.setManuf(token);
}
} else {
if (tmpl == nullptr) {
continue;
}
new_matchers = true;
char *rest = buf_line;
char *token = strtok_r(rest, ",", &rest);
if (token == nullptr) {
continue;
}
// detect if token contains '=', if yes it is a synonym
char * delimiter_equal = strchr(token, '=');
if (delimiter_equal == nullptr) {
// NORMAL ATTRIBUTE
// token is of from '0000/0000' or '0000/0000%00'
char * delimiter_slash = strchr(token, '/');
char * delimiter_percent = strchr(token, '%');
if (delimiter_slash == nullptr || (delimiter_percent != nullptr && delimiter_slash > delimiter_percent)) {
AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' wrong delimiter '%s'", filename_raw, token);
}
uint16_t attr_id = 0xFFFF;
uint16_t cluster_id = 0xFFFF;
uint8_t type_id = Zunk;
int8_t multiplier = 1;
int8_t divider = 1;
int16_t base = 0;
char * name = nullptr;
uint16_t manuf = 0;
cluster_id = strtoul(token, &delimiter_slash, 16);
if (!delimiter_percent) {
attr_id = strtoul(delimiter_slash+1, nullptr, 16);
} else {
attr_id = strtoul(delimiter_slash+1, &delimiter_percent, 16);
type_id = Z_getTypeByName(delimiter_percent+1);
}
// NAME of the attribute
token = strtok_r(rest, ",", &rest);
if (token == nullptr) {
AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' ignore missing name '%s'", filename_raw, buf_line);
continue;
}
name = token;
// ADDITIONAL ELEMENTS?
// Ex: `manuf:1037`
while (token = strtok_r(rest, ",", &rest)) {
char * sub_token;
// look for multiplier
if (sub_token = Z_subtoken(token, Z_MUL)) {
multiplier = strtol(sub_token, nullptr, 10);
}
// look for divider
else if (sub_token = Z_subtoken(token, Z_DIV)) {
divider = strtol(sub_token, nullptr, 10); // negative to indicate divider
}
// look for offset (base)
else if (sub_token = Z_subtoken(token, Z_ADD)) {
base = strtol(sub_token, nullptr, 10); // negative to indicate divider
}
// look for `manuf:HHHH`
else if (sub_token = Z_subtoken(token, Z_MANUF)) {
manuf = strtoul(sub_token, nullptr, 16);
}
else {
AddLog(LOG_LEVEL_DEBUG, "ZIG: ZbLoad unrecognized modifier '%s'", token);
}
}
// token contains the name of the attribute
Z_plugin_attribute & plugin_attr = tmpl->attributes.addToLast();
plugin_attr.cluster = cluster_id;
plugin_attr.attribute = attr_id;
plugin_attr.type = type_id;
plugin_attr.setName(name);
plugin_attr.multiplier = multiplier;
plugin_attr.divider = divider;
plugin_attr.base = base;
plugin_attr.manuf = manuf;
} else {
// ATTRIBUTE SYNONYM
// token is of from '0000/0000=0000/0000,1'
char * rest2 = token;
char * tok2 = strtok_r(rest2, "=", &rest2);
char * delimiter_slash = strchr(tok2, '/');
uint16_t cluster_id = strtoul(tok2, &delimiter_slash, 16);
uint16_t attr_id = strtoul(delimiter_slash+1, nullptr, 16);
tok2 = strtok_r(rest2, "=", &rest2);
char * delimiter_slash2 = strchr(tok2, '/');
uint16_t new_cluster_id = strtoul(tok2, &delimiter_slash2, 16);
uint16_t new_attr_id = strtoul(delimiter_slash2+1, nullptr, 16);
int8_t multiplier = 1;
int8_t divider = 1;
int16_t base = 0;
// ADDITIONAL ELEMENTS?
while (token = strtok_r(rest, ",", &rest)) {
char * sub_token;
// look for multiplier
if (sub_token = Z_subtoken(token, Z_MUL)) {
multiplier = strtol(sub_token, nullptr, 10);
}
// look for divider
else if (sub_token = Z_subtoken(token, Z_DIV)) {
divider = strtol(sub_token, nullptr, 10); // negative to indicate divider
}
// look for offset (base)
else if (sub_token = Z_subtoken(token, Z_ADD)) {
base = strtol(sub_token, nullptr, 10); // negative to indicate divider
}
else {
AddLog(LOG_LEVEL_DEBUG, "ZIG: ZbLoad unrecognized modifier '%s'", token);
}
}
// create the synonym
Z_attribute_synonym & syn = tmpl->synonyms.addToLast();
syn.cluster = cluster_id;
syn.attribute = attr_id;
syn.new_cluster = new_cluster_id;
syn.new_attribute = new_attr_id;
syn.multiplier = multiplier;
syn.divider = divider;
syn.base = base;
}
}
}
return ZbLoad_inner(filename.c_str(), fp);
} else {
AddLog(LOG_LEVEL_ERROR, "ZIG: filesystem not enabled");
}
@ -447,6 +452,7 @@ void ZbLoadDump(void) {
// Auto-load all files ending with '.zb'
void ZbAutoload(void) {
ZbAutoLoadFromFlash();
#ifdef USE_UFILESYS
if (ffsp) {
File dir = ffsp->open("/", "r");

View File

@ -0,0 +1,53 @@
/*
xdrv_23_zigbee_7_8_default_plugin.ino - default plugin stored in Flash
Copyright (C) 2021 Theo Arends and 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/>.
*/
#ifdef USE_ZIGBEE
/********************************************************************
** Default plugin
**
** Below is a the template loaded at boot
** We simulate a read-only file from the filesystem
********************************************************************/
const char Z_DEF_PLUGIN[] PROGMEM =
"#Z2Tv1" "\n"
// bug in IKEA remotes battery, need to double the value
":TRADFRI*," "\n"
":SYMFONISK*," "\n"
"0001/0021=0001/0021,mul:2" "\n" // BatteryPercentage
// "Power" for lumi Door/Window is converted to "Contact"
":lumi.sensor_magnet*," "\n"
"0006/0000=0500/FFF2" "\n" // 0xFFF0 + ZA_Contact
;
/********************************************************************
** Load from flash
********************************************************************/
void ZbAutoLoadFromFlash(void) {
FlashFileImplPtr fp = FlashFileImplPtr(new FlashFileImpl(Z_DEF_PLUGIN));
File f = File(fp);
if (ZbLoad_inner(PSTR("<internal_plugin>"), f)) {
AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' loaded successfully", PSTR("<internal_plugin>"));
}
}
#endif // USE_ZIGBEE