diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index e1d562772..ccffecfdc 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix ``PowerDelta`` zero power detection (#7515) - Fix OTA minimal gzipped detection regression from 8.1.0.3 - Add web page sliders when ``SetOption37 128`` is active allowing control of white(s) +- Add Zigbee persistence and friendly names ### 8.1.0.3 20200106 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 0dece041a..20207bcb4 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -480,7 +480,10 @@ #define D_JSON_ZIGBEEZCL_RAW_RECEIVED "ZigbeeZCLRawReceived" #define D_JSON_ZIGBEE_DEVICE "Device" #define D_JSON_ZIGBEE_NAME "Name" +#define D_CMND_ZIGBEE_NAME "ZigbeeName" #define D_CMND_ZIGBEE_PROBE "ZigbeeProbe" +#define D_CMND_ZIGBEE_FORGET "ZigbeeForget" +#define D_CMND_ZIGBEE_SAVE "ZigbeeSave" #define D_CMND_ZIGBEE_RECEIVED "ZigbeeReceived" #define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality" #define D_CMND_ZIGBEE_READ "ZigbeeRead" diff --git a/tasmota/settings.h b/tasmota/settings.h index 684ab18af..74023ff60 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -101,8 +101,8 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu typedef union { // Restricted by MISRA-C Rule 18.4 but so useful... uint32_t data; // Allow bit manipulation using SetOption struct { // SetOption82 .. SetOption113 - uint32_t alexa_ct_range : 1; // bit 0 (v8.1.0.2) - SetOption82 - Reduced CT range for Alexa - uint32_t spare01 : 1; + uint32_t alexa_ct_range : 1; // bit 0 (v8.1.0.2) - SetOption82 - Reduced CT range for Alexa + uint32_t zigbee_use_names : 1; // bit 1 (V8.1.0.4) - SetOption83 - Use FriendlyNames instead of ShortAddresses when possible uint32_t spare02 : 1; uint32_t spare03 : 1; uint32_t spare04 : 1; diff --git a/tasmota/support_static_buffer.ino b/tasmota/support_static_buffer.ino index 512ec0db1..bec831cc9 100644 --- a/tasmota/support_static_buffer.ino +++ b/tasmota/support_static_buffer.ino @@ -80,7 +80,7 @@ public: return _buf->len; } size_t add32(const uint32_t data) { // append 32 bits value - if (_buf->len < _buf->size - 3) { // do we have room for 2 bytes + if (_buf->len < _buf->size - 3) { // do we have room for 4 bytes _buf->buf[_buf->len++] = data; _buf->buf[_buf->len++] = data >> 8; _buf->buf[_buf->len++] = data >> 16; @@ -88,6 +88,19 @@ public: } return _buf->len; } + size_t add64(const uint64_t data) { // append 64 bits value + if (_buf->len < _buf->size - 7) { // do we have room for 8 bytes + _buf->buf[_buf->len++] = data; + _buf->buf[_buf->len++] = data >> 8; + _buf->buf[_buf->len++] = data >> 16; + _buf->buf[_buf->len++] = data >> 24; + _buf->buf[_buf->len++] = data >> 32; + _buf->buf[_buf->len++] = data >> 40; + _buf->buf[_buf->len++] = data >> 48; + _buf->buf[_buf->len++] = data >> 56; + } + return _buf->len; + } size_t addBuffer(const SBuffer &buf2) { if (len() + buf2.len() <= size()) { @@ -152,6 +165,20 @@ public: return 0; } + // if no NULL is found, returns length until the end of the buffer + inline size_t strlen(const size_t offset) const { + return strnlen((const char*) &_buf->buf[offset], len() - offset); + } + + size_t strlen_s(const size_t offset) const { + size_t slen = this->strlen(offset); + if (slen == len() - offset) { + return 0; // we didn't find a NULL char + } else { + return slen; + } + } + SBuffer subBuffer(const size_t start, size_t len) const { if (start >= _buf->len) { len = 0; diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt.ino index 6096103c4..799829fe6 100644 --- a/tasmota/xdrv_02_mqtt.ino +++ b/tasmota/xdrv_02_mqtt.ino @@ -986,8 +986,10 @@ void CmndSensorRetain(void) \*********************************************************************************************/ #if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) -const static uint16_t tls_spi_start_sector = SPIFFS_END + 4; // 0xXXFF -const static uint8_t* tls_spi_start = (uint8_t*) ((tls_spi_start_sector * SPI_FLASH_SEC_SIZE) + 0x40200000); // 0x40XFF000 +// const static uint16_t tls_spi_start_sector = SPIFFS_END + 4; // 0xXXFF +// const static uint8_t* tls_spi_start = (uint8_t*) ((tls_spi_start_sector * SPI_FLASH_SEC_SIZE) + 0x40200000); // 0x40XFF000 +const static uint16_t tls_spi_start_sector = 0xFF; // Force last bank of first MB +const static uint8_t* tls_spi_start = (uint8_t*) 0x402FF000; // 0x402FF000 const static size_t tls_spi_len = 0x1000; // 4kb blocs const static size_t tls_block_offset = 0x0400; const static size_t tls_block_len = 0x0400; // 1kb diff --git a/tasmota/xdrv_23_zigbee_3_devices.ino b/tasmota/xdrv_23_zigbee_3_devices.ino index 1d793f730..97c995aaf 100644 --- a/tasmota/xdrv_23_zigbee_3_devices.ino +++ b/tasmota/xdrv_23_zigbee_3_devices.ino @@ -22,6 +22,10 @@ #include #include +#ifndef ZIGBEE_SAVE_DELAY_SECONDS +#define ZIGBEE_SAVE_DELAY_SECONDS 10; // wait for 10s before saving Zigbee info +#endif +const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value); @@ -57,6 +61,16 @@ class Z_Devices { public: Z_Devices() {}; + // Probe the existence of device keys + // Results: + // - 0x0000 = not found + // - 0xFFFF = bad parameter + // - 0x = the device's short address + uint16_t isKnownShortAddr(uint16_t shortaddr) const; + uint16_t isKnownLongAddr(uint64_t longaddr) const; + uint16_t isKnownIndex(uint32_t index) const; + uint16_t isKnownFriendlyName(const char * name) const; + // Add new device, provide ShortAddr and optional longAddr // If it is already registered, update information, otherwise create the entry void updateDevice(uint16_t shortaddr, uint64_t longaddr = 0); @@ -74,13 +88,14 @@ public: void setManufId(uint16_t shortaddr, const char * str); void setModelId(uint16_t shortaddr, const char * str); - void setFriendlyNameId(uint16_t shortaddr, const char * str); + void setFriendlyName(uint16_t shortaddr, const char * str); + const String * getFriendlyName(uint16_t) const; // device just seen on the network, update the lastSeen field void updateLastSeen(uint16_t shortaddr); // Dump json - String dump(uint32_t dump_mode, int32_t device_num = 0) const; + String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const; // Timers void resetTimer(uint32_t shortaddr); @@ -89,13 +104,32 @@ public: // Append or clear attributes Json structure void jsonClear(uint16_t shortaddr); - void jsonAppend(uint16_t shortaddr, JsonObject &values); + void jsonAppend(uint16_t shortaddr, const JsonObject &values); const JsonObject *jsonGet(uint16_t shortaddr); - const void jsonPublish(uint16_t shortaddr); // publish the json message and clear buffer + void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values); + void jsonPublishNow(uint16_t shortaddr, JsonObject &values); + + // Iterator + size_t devicesSize(void) const { + return _devices.size(); + } + const Z_Device &devicesAt(size_t i) const { + return _devices.at(i); + } + + // Remove device from list + bool removeDevice(uint16_t shortaddr); + + // Mark data as 'dirty' and requiring to save in Flash + void dirty(void); + + // Find device by name, can be short_addr, long_addr, number_in_array or name + uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const; private: std::vector _devices = {}; + uint32_t _saveTimer = 0; template < typename T> static bool findInVector(const std::vector & vecOfElements, const T & element); @@ -109,8 +143,9 @@ private: Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist - int32_t findShortAddr(uint16_t shortaddr); - int32_t findLongAddr(uint64_t longaddr); + int32_t findShortAddr(uint16_t shortaddr) const; + int32_t findLongAddr(uint64_t longaddr) const; + int32_t findFriendlyName(const char * name) const; void _updateLastSeen(Z_Device &device) { if (&device != nullptr) { @@ -187,6 +222,7 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { nullptr, nullptr }; device.json_buffer = new DynamicJsonBuffer(); _devices.push_back(device); + dirty(); return _devices.back(); } @@ -198,7 +234,7 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { // Out: // index in _devices of entry, -1 if not found // -int32_t Z_Devices::findShortAddr(uint16_t shortaddr) { +int32_t Z_Devices::findShortAddr(uint16_t shortaddr) const { if (!shortaddr) { return -1; } // does not make sense to look for 0x0000 shortaddr (localhost) int32_t found = 0; if (shortaddr) { @@ -217,7 +253,7 @@ int32_t Z_Devices::findShortAddr(uint16_t shortaddr) { // Out: // index in _devices of entry, -1 if not found // -int32_t Z_Devices::findLongAddr(uint64_t longaddr) { +int32_t Z_Devices::findLongAddr(uint64_t longaddr) const { if (!longaddr) { return -1; } int32_t found = 0; if (longaddr) { @@ -228,6 +264,66 @@ int32_t Z_Devices::findLongAddr(uint64_t longaddr) { } return -1; } +// +// Scan all devices to find a corresponding friendlyNme +// Looks info device.friendlyName entry +// In: +// friendlyName (null terminated, should not be empty) +// Out: +// index in _devices of entry, -1 if not found +// +int32_t Z_Devices::findFriendlyName(const char * name) const { + if (!name) { return -1; } // if pointer is null + size_t name_len = strlen(name); + int32_t found = 0; + if (name_len) { + for (auto &elem : _devices) { + if (elem.friendlyName == name) { return found; } + found++; + } + } + return -1; +} + +// Probe if device is already known but don't create any entry +uint16_t Z_Devices::isKnownShortAddr(uint16_t shortaddr) const { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + return shortaddr; + } else { + return 0; // unknown + } +} + +uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const { + int32_t found = findLongAddr(longaddr); + if (found >= 0) { + const Z_Device & device = devicesAt(found); + return device.shortaddr; // can be zero, if not yet registered + } else { + return 0; + } +} + +uint16_t Z_Devices::isKnownIndex(uint32_t index) const { + if (index < devicesSize()) { + const Z_Device & device = devicesAt(index); + return device.shortaddr; + } else { + return 0; + } +} + +uint16_t Z_Devices::isKnownFriendlyName(const char * name) const { + if ((!name) || (0 == strlen(name))) { return 0xFFFF; } // Error + int32_t found = findFriendlyName(name); + if (found >= 0) { + const Z_Device & device = devicesAt(found); + return device.shortaddr; // can be zero, if not yet registered + } else { + return 0; + } +} // // We have a seen a shortaddr on the network, get the corresponding @@ -252,6 +348,17 @@ Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) { return createDeviceEntry(0, longaddr); } +// Remove device from list, return true if it was known, false if it was not recorded +bool Z_Devices::removeDevice(uint16_t shortaddr) { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + _devices.erase(_devices.begin() + found); + dirty(); + return true; + } + return false; +} + // // We have just seen a device on the network, update the info based on short/long addr // In: @@ -270,15 +377,18 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) { // erase the previous shortaddr _devices.erase(_devices.begin() + s_found); updateLastSeen(shortaddr); + dirty(); } } else if (s_found >= 0) { // shortaddr already exists but longaddr not // add the longaddr to the entry _devices[s_found].longaddr = longaddr; updateLastSeen(shortaddr); + dirty(); } else if (l_found >= 0) { // longaddr entry exists, update shortaddr _devices[l_found].shortaddr = shortaddr; + dirty(); } else { // neither short/lonf addr are found. if (shortaddr || longaddr) { @@ -298,6 +408,7 @@ void Z_Devices::addEndoint(uint16_t shortaddr, uint8_t endpoint) { _updateLastSeen(device); if (findEndpointInVector(device.endpoints, ep_profile) < 0) { device.endpoints.push_back(ep_profile); + dirty(); } } @@ -310,8 +421,12 @@ void Z_Devices::addEndointProfile(uint16_t shortaddr, uint8_t endpoint, uint16_t int32_t found = findEndpointInVector(device.endpoints, ep_profile); if (found < 0) { device.endpoints.push_back(ep_profile); + dirty(); } else { - device.endpoints[found] = ep_profile; + if (device.endpoints[found] != ep_profile) { + device.endpoints[found] = ep_profile; + dirty(); + } } } @@ -324,10 +439,12 @@ void Z_Devices::addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluste if (!out) { if (!findInVector(device.clusters_in, ep_cluster)) { device.clusters_in.push_back(ep_cluster); + dirty(); } } else { // out if (!findInVector(device.clusters_out, ep_cluster)) { device.clusters_out.push_back(ep_cluster); + dirty(); } } } @@ -353,18 +470,32 @@ void Z_Devices::setManufId(uint16_t shortaddr, const char * str) { if (&device == nullptr) { return; } // don't crash if not found _updateLastSeen(device); device.manufacturerId = str; + dirty(); } void Z_Devices::setModelId(uint16_t shortaddr, const char * str) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found _updateLastSeen(device); device.modelId = str; + dirty(); } -void Z_Devices::setFriendlyNameId(uint16_t shortaddr, const char * str) { +void Z_Devices::setFriendlyName(uint16_t shortaddr, const char * str) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found _updateLastSeen(device); device.friendlyName = str; + dirty(); +} + +const String * Z_Devices::getFriendlyName(uint16_t shortaddr) const { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + const Z_Device & device = devicesAt(found); + if (device.friendlyName.length() > 0) { + return &device.friendlyName; + } + } + return nullptr; } // device just seen on the network, update the lastSeen field @@ -398,19 +529,22 @@ void Z_Devices::setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, // Run timer at each tick void Z_Devices::runTimer(void) { - uint32_t now = millis(); - for (std::vector::iterator it = _devices.begin(); it != _devices.end(); ++it) { Z_Device &device = *it; uint16_t shortaddr = device.shortaddr; uint32_t timer = device.timer; - if ((timer) && (timer <= now)) { + if ((timer) && TimeReached(timer)) { device.timer = 0; // cancel the timer before calling, so the callback can set another timer // trigger the timer (*device.func)(device.shortaddr, device.cluster, device.endpoint, device.value); } } + // save timer + if ((_saveTimer) && TimeReached(_saveTimer)) { + saveZigbeeDevices(); + _saveTimer = 0; + } } void Z_Devices::jsonClear(uint16_t shortaddr) { @@ -482,7 +616,7 @@ bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) { return false; } -void Z_Devices::jsonAppend(uint16_t shortaddr, JsonObject &values) { +void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found if (&values == nullptr) { return; } @@ -500,40 +634,103 @@ const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) { return device.json; } -const void Z_Devices::jsonPublish(uint16_t shortaddr) { - const JsonObject *json = zigbee_devices.jsonGet(shortaddr); - if (json == nullptr) { return; } // don't crash if not found +void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + JsonObject * json = device.json; + if (json == nullptr) { return; } // abort if nothing in buffer + + const String * fname = zigbee_devices.getFriendlyName(shortaddr); + bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? + + if (use_fname) { + // we need to add the Device short_addr inside the JSON + char sa[8]; + snprintf_P(sa, sizeof(sa), PSTR("0x%04X"), shortaddr); + json->set(F(D_JSON_ZIGBEE_DEVICE), sa); + } else if (fname) { + json->set(F(D_JSON_NAME), (char*) fname); + } String msg = ""; json->printTo(msg); zigbee_devices.jsonClear(shortaddr); - Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); + + if (use_fname) { + Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str()); + } else { + Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); + } MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); XdrvRulesProcess(); } +void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) { + jsonPublishFlush(shortaddr); // flush any previous buffer + jsonAppend(shortaddr, values); + jsonPublishFlush(shortaddr); // publish now +} + +void Z_Devices::dirty(void) { + _saveTimer = kZigbeeSaveDelaySeconds * 1000 + millis(); +} + +// Parse the command parameters for either: +// - a short address starting with "0x", example: 0x1234 +// - a long address starting with "0x", example: 0x7CB03EBB0A0292DD +// - 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; } + size_t param_len = strlen(param); + char dataBuf[param_len + 1]; + strcpy(dataBuf, param); + RemoveSpace(dataBuf); + uint16_t shortaddr = 0; + + if (strlen(dataBuf) < 4) { + // simple number 0..99 + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 99)) { + shortaddr = zigbee_devices.isKnownIndex(XdrvMailbox.payload - 1); + } + } else if ((dataBuf[0] == '0') && (dataBuf[1] == 'x')) { + // starts with 0x + if (strlen(dataBuf) < 18) { + // expect a short address + shortaddr = strtoull(dataBuf, nullptr, 0); + if (short_must_be_known) { + shortaddr = zigbee_devices.isKnownShortAddr(shortaddr); + } + // else we don't check if it's already registered to force unregistered devices + } else { + // expect a long address + uint64_t longaddr = strtoull(dataBuf, nullptr, 0); + shortaddr = zigbee_devices.isKnownLongAddr(longaddr); + } + } else { + // expect a Friendly Name + shortaddr = zigbee_devices.isKnownFriendlyName(dataBuf); + } + + return shortaddr; +} // Dump the internal memory of Zigbee devices -// Mode = 1: simple dump of devices addresses and names -// Mode = 2: Mode 1 + also dump the endpoints, profiles and clusters -String Z_Devices::dump(uint32_t dump_mode, int32_t device_num) const { +// Mode = 1: simple dump of devices addresses +// Mode = 2: simple dump of devices addresses and names +// Mode = 3: Mode 2 + also dump the endpoints, profiles and clusters +String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const { DynamicJsonBuffer jsonBuffer; JsonArray& json = jsonBuffer.createArray(); JsonArray& devices = json; - //JsonArray& devices = json.createNestedArray(F("ZigbeeDevices")); - - // if device_num == 0, then we show all devices. - // When no payload, the default is -99. In this case change it to 0. - if (device_num < 0) { device_num = 0; } - - uint32_t device_current = 1; - for (std::vector::const_iterator it = _devices.begin(); it != _devices.end(); ++it, ++device_current) { - // ignore non-current device, if specified device is non-zero - if ((device_num > 0) && (device_num != device_current)) { continue; } + for (std::vector::const_iterator it = _devices.begin(); it != _devices.end(); ++it) { const Z_Device& device = *it; uint16_t shortaddr = device.shortaddr; - char hex[20]; + char hex[22]; + + // ignore non-current device, if specified device is non-zero + if ((status_shortaddr) && (status_shortaddr != shortaddr)) { continue; } JsonObject& dev = devices.createNestedObject(); @@ -545,7 +742,9 @@ String Z_Devices::dump(uint32_t dump_mode, int32_t device_num) const { } if (2 <= dump_mode) { - Uint64toHex(device.longaddr, hex, 64); + hex[0] = '0'; // prefix with '0x' + hex[1] = 'x'; + Uint64toHex(device.longaddr, &hex[2], 64); dev[F("IEEEAddr")] = hex; if (device.modelId.length() > 0) { dev[F(D_JSON_MODEL D_JSON_ID)] = device.modelId; diff --git a/tasmota/xdrv_23_zigbee_4_persistence.ino b/tasmota/xdrv_23_zigbee_4_persistence.ino new file mode 100644 index 000000000..e4fae384d --- /dev/null +++ b/tasmota/xdrv_23_zigbee_4_persistence.ino @@ -0,0 +1,332 @@ +/* + xdrv_23_zigbee.ino - zigbee support for Tasmota + + Copyright (C) 2020 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 + +// Ensure persistence of devices into Flash +// +// Structure: +// (from file info): +// uint16 - start address in Flash (offset) +// uint16 - length in bytes (makes sure parsing stops) +// +// File structure: +// uint8 - number of devices, 0=none, 0xFF=invalid entry (probably Flash was erased) +// +// [Array of devices] +// [Offset = 2] +// uint8 - length of revice record +// uint16 - short address +// uint64 - long IEEE address +// uint8 - number of endpoints +// [Array of endpoints] +// uint8 - endpoint number +// uint16 - profileID of the endpoint +// Array of uint8 - clusters In codes, 0xFF end marker +// Array of uint8 - clusters Out codes, 0xFF end marker +// +// str - ModelID (null terminated C string, 32 chars max) +// str - Manuf (null terminated C string, 32 chars max) +// reserved for extensions + +// Memory footprint +const static uint16_t z_spi_start_sector = 0xFF; // Force last bank of first MB +const static uint8_t* z_spi_start = (uint8_t*) 0x402FF000; // 0x402FF000 +const static uint8_t* z_dev_start = z_spi_start + 0x0800; // 0x402FF800 - 2KB +const static size_t z_spi_len = 0x1000; // 4kb blocs +const static size_t z_block_offset = 0x0800; +const static size_t z_block_len = 0x0800; // 2kb + +class z_flashdata_t { +public: + uint32_t name; // simple 4 letters name. Currently 'skey', 'crt ', 'crt1', 'crt2' + uint16_t len; // len of object + uint16_t reserved; // align on 4 bytes boundary +}; + +const static uint32_t ZIGB_NAME = 0x3167697A; // 'zig1' little endian +const static size_t Z_MAX_FLASH = z_block_len - sizeof(z_flashdata_t); // 2040 + +// encoding for the most commonly 32 clusters, used for binary encoding +const uint16_t Z_ClusterNumber[] PROGMEM = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0100, 0x0101, 0x0102, + 0x0201, 0x0202, 0x0203, 0x0204, + 0x0300, 0x0301, + 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, + 0x0500, 0x0501, 0x0502, + 0x0700, 0x0701, 0x0702, + 0x0B00, 0x0B01, 0x0B02, 0x0B03, 0x0B04, 0x0B05, + 0x1000, + 0xFC0F, +}; + +// convert a 1 byte cluster code to the actual cluster number +uint16_t fromClusterCode(uint8_t c) { + if (c >= sizeof(Z_ClusterNumber)/sizeof(Z_ClusterNumber[0])) { + return 0xFFFF; // invalid + } + return pgm_read_word(&Z_ClusterNumber[c]); +} + +// convert a cluster number to 1 byte, or 0xFF if not in table +uint8_t toClusterCode(uint16_t c) { + for (uint32_t i = 0; i < sizeof(Z_ClusterNumber)/sizeof(Z_ClusterNumber[0]); i++) { + if (c == pgm_read_word(&Z_ClusterNumber[i])) { + return i; + } + } + return 0xFF; // not found +} + +class SBuffer hibernateDevice(const struct Z_Device &device) { + SBuffer buf(128); + + buf.add8(0x00); // overall length, will be updated later + buf.add16(device.shortaddr); + buf.add64(device.longaddr); + uint32_t endpoints = device.endpoints.size(); + if (endpoints > 254) { endpoints = 254; } + buf.add8(endpoints); + // iterate on endpoints + for (std::vector::const_iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) { + uint32_t ep_profile = *ite; + uint8_t endpoint = (ep_profile >> 16) & 0xFF; + uint16_t profileId = ep_profile & 0xFFFF; + + buf.add8(endpoint); + buf.add16(profileId); + for (std::vector::const_iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) { + uint16_t cluster = *itc & 0xFFFF; + uint8_t c_endpoint = (*itc >> 16) & 0xFF; + + if (endpoint == c_endpoint) { + uint8_t clusterCode = toClusterCode(cluster); + if (0xFF != clusterCode) { buf.add8(clusterCode); } + } + } + buf.add8(0xFF); // end of endpoint marker + + for (std::vector::const_iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) { + uint16_t cluster = *itc & 0xFFFF; + uint8_t c_endpoint = (*itc >> 16) & 0xFF; + + if (endpoint == c_endpoint) { + uint8_t clusterCode = toClusterCode(cluster); + if (0xFF != clusterCode) { buf.add8(clusterCode); } + } + } + buf.add8(0xFF); // end of endpoint marker + } + + // ModelID + size_t model_len = device.modelId.length(); + if (model_len > 32) { model_len = 32; } // max 32 chars + buf.addBuffer(device.modelId.c_str(), model_len); + buf.add8(0x00); // end of string marker + + // ManufID + size_t manuf_len = device.manufacturerId.length(); + if (manuf_len > 32) {manuf_len = 32; } // max 32 chars + buf.addBuffer(device.manufacturerId.c_str(), manuf_len); + buf.add8(0x00); // end of string marker + + // FriendlyName + size_t frname_len = device.friendlyName.length(); + if (frname_len > 32) {frname_len = 32; } // max 32 chars + buf.addBuffer(device.friendlyName.c_str(), frname_len); + buf.add8(0x00); // end of string marker + + // update overall length + buf.set8(0, buf.len()); + + return buf; +} + +class SBuffer hibernateDevices(void) { + SBuffer buf(2048); + + size_t devices_size = zigbee_devices.devicesSize(); + if (devices_size > 32) { devices_size = 32; } // arbitrarily limit to 32 devices, for now + buf.add8(devices_size); // number of devices + + for (uint32_t i = 0; i < devices_size; i++) { + const Z_Device & device = zigbee_devices.devicesAt(i); + const SBuffer buf_device = hibernateDevice(device); + buf.addBuffer(buf_device); + } + + size_t buf_len = buf.len(); + if (buf_len > 2040) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Devices list too big to fit in Flash (%d)"), buf_len); + } + + // Log + char *hex_char = (char*) malloc((buf_len * 2) + 2); + if (hex_char) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZigbeeFlashStore %s"), + ToHex_P(buf.getBuffer(), buf_len, hex_char, (buf_len * 2) + 2)); + free(hex_char); + } + + return buf; +} + +void hidrateDevices(const SBuffer &buf) { + uint32_t buf_len = buf.len(); + if (buf_len <= 10) { return; } + + uint32_t k = 0; + uint32_t num_devices = buf.get8(k++); + + for (uint32_t i = 0; (i < num_devices) && (k < buf_len); i++) { + uint32_t dev_record_len = buf.get8(k); + + SBuffer buf_d = buf.subBuffer(k, dev_record_len); + + uint32_t d = 1; // index in device buffer + uint16_t shortaddr = buf_d.get16(d); d += 2; + uint64_t longaddr = buf_d.get64(d); d += 8; + zigbee_devices.updateDevice(shortaddr, longaddr); // update device's addresses + + uint32_t endpoints = buf_d.get8(d++); + for (uint32_t j = 0; j < endpoints; j++) { + uint8_t ep = buf_d.get8(d++); + uint16_t ep_profile = buf_d.get16(d); d += 2; + zigbee_devices.addEndointProfile(shortaddr, ep, ep_profile); + + // in clusters + while (d < dev_record_len) { // safe guard against overflow + uint8_t ep_cluster = buf_d.get8(d++); + if (0xFF == ep_cluster) { break; } // end of block + zigbee_devices.addCluster(shortaddr, ep, fromClusterCode(ep_cluster), false); + } + // out clusters + while (d < dev_record_len) { // safe guard against overflow + uint8_t ep_cluster = buf_d.get8(d++); + if (0xFF == ep_cluster) { break; } // end of block + zigbee_devices.addCluster(shortaddr, ep, fromClusterCode(ep_cluster), true); + } + } + + // parse 3 strings + char empty[] = ""; + + // ManufID + uint32_t s_len = buf_d.strlen_s(d); + char *ptr = s_len ? buf_d.charptr(d) : empty; + zigbee_devices.setModelId(shortaddr, ptr); + d += s_len + 1; + + // ManufID + s_len = buf_d.strlen_s(d); + ptr = s_len ? buf_d.charptr(d) : empty; + zigbee_devices.setManufId(shortaddr, ptr); + d += s_len + 1; + + // FriendlyName + s_len = buf_d.strlen_s(d); + ptr = s_len ? buf_d.charptr(d) : empty; + zigbee_devices.setFriendlyName(shortaddr, ptr); + d += s_len + 1; + + // next iteration + k += dev_record_len; + } +} + +void loadZigbeeDevices(void) { + z_flashdata_t flashdata; + memcpy_P(&flashdata, z_dev_start, sizeof(z_flashdata_t)); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Zigbee signature in Flash: %08X - %d"), flashdata.name, flashdata.len); + + // Check the signature + if ((flashdata.name == ZIGB_NAME) && (flashdata.len > 0)) { + uint16_t buf_len = flashdata.len; + // parse what seems to be a valid entry + SBuffer buf(buf_len); + buf.addBuffer(z_dev_start + sizeof(z_flashdata_t), buf_len); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee devices data in Flash (%d bytes)"), buf_len); + hidrateDevices(buf); + } else { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "No zigbee devices data in Flash")); + } +} + +void saveZigbeeDevices(void) { + SBuffer buf = hibernateDevices(); + size_t buf_len = buf.len(); + if (buf_len > Z_MAX_FLASH) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Buffer too big to fit in Flash (%d bytes)"), buf_len); + return; + } + + // first copy SPI buffer into ram + uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len); + if (!spi_buffer) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Cannot allocate 4KB buffer")); + return; + } + // copy the flash into RAM to make local change, and write back the whole buffer + ESP.flashRead(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + + z_flashdata_t *flashdata = (z_flashdata_t*)(spi_buffer + z_block_offset); + flashdata->name = ZIGB_NAME; + flashdata->len = buf_len; + flashdata->reserved = 0; + + memcpy(spi_buffer + z_block_offset + sizeof(z_flashdata_t), buf.getBuffer(), buf_len); + + // buffer is now ready, write it back + if (ESP.flashEraseSector(z_spi_start_sector)) { + ESP.flashWrite(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + } + + free(spi_buffer); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee Devices Data store in Flash (0x%08X - %d bytes)"), z_dev_start, buf_len); +} + +// Erase the flash area containing the ZigbeeData +void eraseZigbeeDevices(void) { + // first copy SPI buffer into ram + uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len); + if (!spi_buffer) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Cannot allocate 4KB buffer")); + return; + } + // copy the flash into RAM to make local change, and write back the whole buffer + ESP.flashRead(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + + // Fill the Zigbee area with 0xFF + memset(spi_buffer + z_block_offset, 0xFF, z_block_len); + + // buffer is now ready, write it back + if (ESP.flashEraseSector(z_spi_start_sector)) { + ESP.flashWrite(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + } + + free(spi_buffer); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee Devices Data erased (0x%08X - %d bytes)"), z_dev_start, z_block_len); +} + +#endif // USE_ZIGBEE diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 58b055d2b..af691876e 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -830,10 +830,10 @@ int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& // Publish a message for `"Occupancy":0` when the timer expired int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) { - // send Occupancy:false message - Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":{\"" OCCUPANCY "\":0}}}"), shortaddr); - MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); - XdrvRulesProcess(); + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + json[F(OCCUPANCY)] = 0; + zigbee_devices.jsonPublishNow(shortaddr, json); } // Aqara Cube diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index 520dbd232..c32634f6b 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -354,7 +354,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { //ZI_LOG(LOG_LEVEL_INFO, D_LOG_ZIGBEE "starting zigbee coordinator") ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) // wait for sync ack of command - ZI_WAIT_UNTIL(5000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started + ZI_WAIT_UNTIL(10000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo) //ZI_WAIT_RECV(2000, ZBR_GETDEVICEINFO) // memorize info @@ -386,6 +386,7 @@ ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator ZI_MQTT_STATE(ZIGBEE_STATUS_OK, "Started") ZI_LOG(LOG_LEVEL_INFO, D_LOG_ZIGBEE "Zigbee started") ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages + ZI_CALL(&Z_Load_Devices, 0) ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP) ZI_WAIT_FOREVER() ZI_GOTO(ZIGBEE_LABEL_READY) diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index d396b3fc4..90d272a00 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -403,7 +403,7 @@ int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpo // Post-provess for Aqara Presence Senson Z_AqaraOccupancy(shortaddr, cluster, endpoint, json); - zigbee_devices.jsonPublish(shortaddr); + zigbee_devices.jsonPublishFlush(shortaddr); return 1; } @@ -433,8 +433,15 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { DynamicJsonBuffer jsonBuffer; JsonObject& json_root = jsonBuffer.createObject(); + JsonObject& json1 = json_root.createNestedObject(F(D_CMND_ZIGBEE_RECEIVED)); - JsonObject& json = json1.createNestedObject(shortaddr); + + const String * fname = zigbee_devices.getFriendlyName(srcaddr); + bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? + JsonObject& json = json1.createNestedObject(use_fname ? fname->c_str() : shortaddr); + if (use_fname) { + json[F(D_JSON_ZIGBEE_DEVICE)] = shortaddr; + } if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) { zcl_received.parseRawAttributes(json); @@ -449,6 +456,11 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { json_root.printTo(msg); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLRawReceived: %s"), msg.c_str()); + // Add friendly name + if ((!use_fname) && (fname)) { + json[F(D_JSON_ZIGBEE_NAME)] = (char*)fname->c_str(); // (char*) will force a copy of the string + } + zcl_received.postProcessAttributes(srcaddr, json); // Add linkquality json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality; @@ -457,7 +469,7 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { // Prepare for publish if (zigbee_devices.jsonIsConflict(srcaddr, json)) { // there is conflicting values, force a publish of the previous message now and don't coalesce - zigbee_devices.jsonPublish(srcaddr); + zigbee_devices.jsonPublishFlush(srcaddr); } else { zigbee_devices.jsonAppend(srcaddr, json); zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes); @@ -511,6 +523,12 @@ int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) { } } +int32_t Z_Load_Devices(uint8_t value) { + // try to hidrate from known devices + loadZigbeeDevices(); + return 0; // continue +} + int32_t Z_State_Ready(uint8_t value) { zigbee.init_phase = false; // initialization phase complete return 0; // continue diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 408410f9b..4464ab525 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -31,13 +31,15 @@ TasmotaSerial *ZigbeeSerial = nullptr; const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" - D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE + D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|" + D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME ; void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin, &CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend, - &CmndZigbeeProbe, &CmndZigbeeRead, &CmndZigbeeZNPReceive + &CmndZigbeeProbe, &CmndZigbeeRead, &CmndZigbeeZNPReceive, + &CmndZigbeeForget, &CmndZigbeeSave, &CmndZigbeeName }; int32_t ZigbeeProcessInput(class SBuffer &buf) { @@ -254,6 +256,7 @@ void CmndZigbeeReset(void) { switch (XdrvMailbox.payload) { case 1: ZigbeeZNPSend(ZIGBEE_FACTORY_RESET, sizeof(ZIGBEE_FACTORY_RESET)); + eraseZigbeeDevices(); restart_flag = 2; ResponseCmndChar(D_JSON_ZIGBEE_CC2530 " " D_JSON_RESET_AND_RESTARTING); break; @@ -263,13 +266,6 @@ void CmndZigbeeReset(void) { } } -void CmndZigbeeStatus(void) { - if (ZigbeeSerial) { - String dump = zigbee_devices.dump(XdrvMailbox.index, XdrvMailbox.payload); - Response_P(PSTR("{\"%s%d\":%s}"), XdrvMailbox.command, XdrvMailbox.index, dump.c_str()); - } -} - void CmndZigbeeZNPSendOrReceive(bool send) { if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) { @@ -548,20 +544,68 @@ void CmndZigbeeSend(void) { // Probe a specific device to get its endpoints and supported clusters void CmndZigbeeProbe(void) { if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } - char dataBufUc[XdrvMailbox.data_len + 1]; - UpperCase(dataBufUc, XdrvMailbox.data); - RemoveSpace(dataBufUc); - if (strlen(dataBufUc) < 3) { ResponseCmndChar("Invalid destination"); return; } - - // TODO, for now ignore friendly names - uint16_t shortaddr = strtoull(dataBufUc, nullptr, 0); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CmndZigbeeScan: short addr 0x%04X"), shortaddr); + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); + if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } + if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } // everything is good, we can send the command Z_SendActiveEpReq(shortaddr); ResponseCmndDone(); } +// Specify, read or erase a Friendly Name +void CmndZigbeeName(void) { + // Syntax is: + // ZigbeeName , - assign a friendly name + // ZigbeeName - display the current friendly name + // ZigbeeName , - remove friendly name + // + // Where can be: short_addr, long_addr, device_index, friendly_name + + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + + // check if parameters contain a comma ',' + char *p; + char *str = strtok_r(XdrvMailbox.data, ", ", &p); + + // parse first part, + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered + if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } + if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } + + if (p == nullptr) { + const String * friendlyName = zigbee_devices.getFriendlyName(shortaddr); + Response_P(PSTR("{\"0x%04X\":{\"name\":\"%s\"}}"), shortaddr, friendlyName ? friendlyName->c_str() : ""); + } else { + zigbee_devices.setFriendlyName(shortaddr, p); + Response_P(PSTR("{\"0x%04X\":{\"name\":\"%s\"}}"), shortaddr, p); + } +} + +// Remove an old Zigbee device from the list of known devices, use ZigbeeStatus to know all registered devices +void CmndZigbeeForget(void) { + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); + if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } + if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } + + // everything is good, we can send the command + if (zigbee_devices.removeDevice(shortaddr)) { + ResponseCmndDone(); + } else { + ResponseCmndChar("Unknown device"); + } +} + +// Save Zigbee information to flash +void CmndZigbeeSave(void) { + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + + saveZigbeeDevices(); + + ResponseCmndDone(); +} + // Send an attribute read command to a device, specifying cluster and list of attributes void CmndZigbeeRead(void) { // ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5} @@ -633,6 +677,20 @@ void CmndZigbeePermitJoin(void) ResponseCmndDone(); } +void CmndZigbeeStatus(void) { + if (ZigbeeSerial) { + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); + if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } + if (XdrvMailbox.payload > 0) { + if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } + } + + String dump = zigbee_devices.dump(XdrvMailbox.index, shortaddr); + Response_P(PSTR("{\"%s%d\":%s}"), XdrvMailbox.command, XdrvMailbox.index, dump.c_str()); + } +} + /*********************************************************************************************\ * Interface \*********************************************************************************************/