mirror of https://github.com/arendst/Tasmota.git
Merge pull request #7979 from s-hadinger/zigbee_restore
Add command ``ZbRestore`` to restore device configuration dumped with ``ZbStatus 2``
This commit is contained in:
commit
dc531110bd
|
@ -3,6 +3,7 @@
|
||||||
### 8.2.0.1 20200321
|
### 8.2.0.1 20200321
|
||||||
|
|
||||||
- Change HM-10 sensor type detection and add features (#7962)
|
- Change HM-10 sensor type detection and add features (#7962)
|
||||||
|
- Add command ``ZbRestore`` to restore device configuration dumped with ``ZbStatus 2``
|
||||||
|
|
||||||
## Released
|
## Released
|
||||||
|
|
||||||
|
|
|
@ -514,6 +514,7 @@
|
||||||
#define D_JSON_ZIGBEE_STATUS_MSG "StatusMessage"
|
#define D_JSON_ZIGBEE_STATUS_MSG "StatusMessage"
|
||||||
#define D_CMND_ZIGBEE_LIGHT "Light"
|
#define D_CMND_ZIGBEE_LIGHT "Light"
|
||||||
#define D_JSON_ZIGBEE_LIGHT "Light"
|
#define D_JSON_ZIGBEE_LIGHT "Light"
|
||||||
|
#define D_CMND_ZIGBEE_RESTORE "Restore"
|
||||||
|
|
||||||
// Commands xdrv_25_A4988_Stepper.ino
|
// Commands xdrv_25_A4988_Stepper.ino
|
||||||
#define D_CMND_MOTOR "MOTOR"
|
#define D_CMND_MOTOR "MOTOR"
|
||||||
|
|
|
@ -43,6 +43,42 @@ JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) {
|
||||||
return *(JsonVariant*)nullptr;
|
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<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(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 parseHex(const char **data, size_t max_len = 8) {
|
||||||
uint32_t ret = 0;
|
uint32_t ret = 0;
|
||||||
for (uint32_t i = 0; i < max_len; i++) {
|
for (uint32_t i = 0; i < max_len; i++) {
|
||||||
|
|
|
@ -105,9 +105,7 @@ public:
|
||||||
|
|
||||||
// Add an endpoint to a device
|
// Add an endpoint to a device
|
||||||
void addEndpoint(uint16_t shortaddr, uint8_t endpoint);
|
void addEndpoint(uint16_t shortaddr, uint8_t endpoint);
|
||||||
|
void clearEndpoints(uint16_t shortaddr);
|
||||||
// Add cluster
|
|
||||||
void addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster);
|
|
||||||
|
|
||||||
void setManufId(uint16_t shortaddr, const char * str);
|
void setManufId(uint16_t shortaddr, const char * str);
|
||||||
void setModelId(uint16_t shortaddr, const char * str);
|
void setModelId(uint16_t shortaddr, const char * str);
|
||||||
|
@ -121,6 +119,7 @@ public:
|
||||||
// Dump json
|
// Dump json
|
||||||
String dumpLightState(uint16_t shortaddr) const;
|
String dumpLightState(uint16_t shortaddr) const;
|
||||||
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const;
|
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const;
|
||||||
|
int32_t deviceRestore(const JsonObject &json);
|
||||||
|
|
||||||
// Hue support
|
// Hue support
|
||||||
void setHueBulbtype(uint16_t shortaddr, int8_t bulbtype);
|
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
|
// 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) {
|
if (device.modelId) {
|
||||||
dev[F(D_JSON_MODEL D_JSON_ID)] = 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) {
|
if (device.manufacturerId) {
|
||||||
dev[F("Manufacturer")] = 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;
|
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<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);; }
|
||||||
|
|
||||||
|
// 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<JsonArray>())) {
|
||||||
|
const JsonArray &arr_ep = val_endpoints.as<const JsonArray&>();
|
||||||
|
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
|
#endif // USE_ZIGBEE
|
||||||
|
|
|
@ -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_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|"
|
||||||
D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|"
|
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_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 = {
|
void (* const ZigbeeCommand[])(void) PROGMEM = {
|
||||||
|
@ -44,7 +44,7 @@ void (* const ZigbeeCommand[])(void) PROGMEM = {
|
||||||
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive,
|
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive,
|
||||||
&CmndZbForget, &CmndZbSave, &CmndZbName,
|
&CmndZbForget, &CmndZbSave, &CmndZbName,
|
||||||
&CmndZbBind, &CmndZbPing, &CmndZbModelId,
|
&CmndZbBind, &CmndZbPing, &CmndZbModelId,
|
||||||
&CmndZbLight,
|
&CmndZbLight, CmndZbRestore,
|
||||||
};
|
};
|
||||||
|
|
||||||
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
||||||
|
@ -790,6 +790,60 @@ void CmndZbSave(void) {
|
||||||
ResponseCmndDone();
|
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<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; }
|
||||||
|
|
||||||
|
// Check is root contains `ZbStatus<x>` 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<JsonArray>()) {
|
||||||
|
const JsonArray& arr = json->as<const JsonArray&>();
|
||||||
|
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<JsonObject>()) {
|
||||||
|
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
|
// Send an attribute read command to a device, specifying cluster and list of attributes
|
||||||
void CmndZbRead(void) {
|
void CmndZbRead(void) {
|
||||||
// ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5}
|
// ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5}
|
||||||
|
|
Loading…
Reference in New Issue