diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino
index 658d508a3..6266e8f49 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino
@@ -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
{
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_flash_fs.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_flash_fs.ino
new file mode 100644
index 000000000..034a2ff64
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_flash_fs.ino
@@ -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 .
+*/
+
+#ifdef USE_ZIGBEE
+
+#ifdef ESP32
+#include
+#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 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 "";
+ }
+
+ 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
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_7_plugin.ino
similarity index 58%
rename from tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino
rename to tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_7_plugin.ino
index 4a1ef2b59..0e8fa2ce5 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_7_plugin.ino
@@ -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");
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_8_default_plugin.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_8_default_plugin.ino
new file mode 100644
index 000000000..62e9f167f
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_8_default_plugin.ino
@@ -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 .
+*/
+
+#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(""), f)) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' loaded successfully", PSTR(""));
+ }
+}
+
+#endif // USE_ZIGBEE