From 2c8dd288e7e0cad622749b3999bd9ec4a906fc09 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 22 Mar 2020 16:11:01 +0100 Subject: [PATCH] Add command ``ZbRestore`` to restore device configuration dumped with ``ZbStatus 2`` --- tasmota/CHANGELOG.md | 1 + tasmota/i18n.h | 1 + tasmota/xdrv_23_zigbee_1_headers.ino | 36 +++++++++++ tasmota/xdrv_23_zigbee_2_devices.ino | 95 +++++++++++++++++++++++++++- tasmota/xdrv_23_zigbee_9_impl.ino | 58 ++++++++++++++++- 5 files changed, 186 insertions(+), 5 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 66f27a53e..9851355a0 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -3,6 +3,7 @@ ### 8.2.0.1 20200321 - Change HM-10 sensor type detection and add features (#7962) +- Add command ``ZbRestore`` to restore device configuration dumped with ``ZbStatus 2`` ## Released diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 12f3164c6..47afc7307 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -514,6 +514,7 @@ #define D_JSON_ZIGBEE_STATUS_MSG "StatusMessage" #define D_CMND_ZIGBEE_LIGHT "Light" #define D_JSON_ZIGBEE_LIGHT "Light" +#define D_CMND_ZIGBEE_RESTORE "Restore" // Commands xdrv_25_A4988_Stepper.ino #define D_CMND_MOTOR "MOTOR" diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino index 50e028dc1..051858697 100644 --- a/tasmota/xdrv_23_zigbee_1_headers.ino +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -43,6 +43,42 @@ JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) { return *(JsonVariant*)nullptr; } +// 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(); + 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(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++) { diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index 0cbe9a507..7443b5457 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -105,9 +105,7 @@ public: // Add an endpoint to a device void addEndpoint(uint16_t shortaddr, uint8_t endpoint); - - // Add cluster - void addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster); + void clearEndpoints(uint16_t shortaddr); void setManufId(uint16_t shortaddr, const char * str); void setModelId(uint16_t shortaddr, const char * str); @@ -121,6 +119,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); // Hue support void setHueBulbtype(uint16_t shortaddr, int8_t bulbtype); @@ -450,6 +449,20 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) { } } +// +// Clear all endpoints +// +void Z_Devices::clearEndpoints(uint16_t shortaddr) { + if (!shortaddr) { return; } + Z_Device &device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + + for (uint32_t i = 0; i < endpoints_max; i++) { + device.endpoints[i] = 0; + // no dirty here because it doesn't make sense to store it, does it? + } +} + // // Add an endpoint to a shortaddr // @@ -994,6 +1007,9 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const { if (device.modelId) { dev[F(D_JSON_MODEL D_JSON_ID)] = device.modelId; } + if (-1 != device.bulbtype) { + dev[F(D_JSON_ZIGBEE_LIGHT)] = device.bulbtype; // sign extend, 0xFF changed as -1 + } if (device.manufacturerId) { dev[F("Manufacturer")] = device.manufacturerId; } @@ -1013,4 +1029,77 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const { return payload; } +// Restore a single device configuration based on json export +// Input: json element as expported by `ZbStatus2`` +// Mandatory attribue: `Device` +// +// Returns: +// 0 : Ok +// <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) { + + // params + uint16_t device = 0x0000; // 0x0000 is coordinator so considered invalid + uint64_t ieeeaddr = 0x0000000000000000LL; // 0 means unknown + const char * modelid = nullptr; + const char * manufid = nullptr; + const char * friendlyname = nullptr; + int8_t bulbtype = 0xFF; + size_t endpoints_len = 0; + + // read mandatory "Device" + const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); + if (nullptr != &val_device) { + device = strToUInt(val_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(), 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);; } + + // 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); } + + // read "Endpoints" + const JsonVariant &val_endpoints = getCaseInsensitive(json, PSTR("Endpoints")); + if ((nullptr != &val_endpoints) && (val_endpoints.is())) { + const JsonArray &arr_ep = val_endpoints.as(); + endpoints_len = arr_ep.size(); + 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); + } + } + } + } + + return 0; +} + #endif // USE_ZIGBEE diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index e90782a8c..674f8d655 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -35,7 +35,7 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|" D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|" - D_CMND_ZIGBEE_LIGHT + D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_RESTORE ; void (* const ZigbeeCommand[])(void) PROGMEM = { @@ -44,7 +44,7 @@ void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive, &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind, &CmndZbPing, &CmndZbModelId, - &CmndZbLight, + &CmndZbLight, CmndZbRestore, }; int32_t ZigbeeProcessInput(class SBuffer &buf) { @@ -790,6 +790,60 @@ void CmndZbSave(void) { ResponseCmndDone(); } + +// Restore a device configuration previously exported via `ZbStatus2`` +// Format: +// Either the entire `ZbStatus3` export, or an array or just the device configuration. +// If array, if can contain multiple devices +// ZbRestore {"ZbStatus3":[{"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}]} +// ZbRestore [{"Device":"0x5ADF","Name":"Petite_Lampe","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]}] +// 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; + + // check if parsing succeeded + if (json_parsed.is()) { + success = json_parsed.as().success(); + } else if (json_parsed.is()) { + success = json_parsed.as().success(); + } + if (!success) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } + + // Check is root contains `ZbStatus` key, if so change the root + const JsonVariant * zbstatus = &startsWithCaseInsensitive(*json, PSTR("ZbStatus")); + if (nullptr != zbstatus) { + json = zbstatus; + } + + // check if the root is an array + if (json->is()) { + const JsonArray& arr = json->as(); + for (auto elt : arr) { + // call restore on each item + int32_t res = zigbee_devices.deviceRestore(elt); + if (res < 0) { + ResponseCmndChar_P(PSTR("Restore failed")); + return; + } + } + } else if (json->is()) { + int32_t res = zigbee_devices.deviceRestore(*json); + if (res < 0) { + ResponseCmndChar_P(PSTR("Restore failed")); + return; + } + // call restore on a single object + } else { + ResponseCmndChar_P(PSTR("Missing parameters")); + return; + } + ResponseCmndDone(); +} + // Send an attribute read command to a device, specifying cluster and list of attributes void CmndZbRead(void) { // ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5}