Zigbee flash storage refactor

This commit is contained in:
Stephan Hadinger 2020-10-28 10:08:15 +01:00
parent b4dc2600c5
commit afb5839b6c
8 changed files with 407 additions and 292 deletions

View File

@ -194,14 +194,10 @@ 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) {
size_t strlen(const size_t offset) const {
if (offset >= len()) { return 0; }
size_t slen = strnlen((const char*) &_buf->buf[offset], len() - offset);
if (slen == (len() - offset)) {
return 0; // we didn't find a NULL char
} else {
return slen;

View File

@ -64,46 +64,6 @@ uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
/*********************************************************************************************\
*
* Class for attribute array of values
* This is a helper function to generate a clean list of unsigned ints
*
\*********************************************************************************************/
class Z_json_array {
public:
Z_json_array(): val("[]") {} // start with empty array
void add(uint32_t uval32) {
// remove trailing ']'
val.remove(val.length()-1);
if (val.length() > 1) { // if not empty, prefix with comma
val += ',';
}
val += uval32;
val += ']';
}
void addStrRaw(const char * sval) {
// remove trailing ']'
val.remove(val.length()-1);
if (val.length() > 1) { // if not empty, prefix with comma
val += ',';
}
val += sval;
val += ']';
}
void addStr(const char * sval) {
addStrRaw(EscapeJSONString(sval).c_str());
}
String &toString(void) {
return val;
}
private :
String val;
};
/*********************************************************************************************\
*
* Class for single attribute
@ -143,8 +103,8 @@ public:
float fval;
SBuffer* bval;
char* sval;
class Z_attribute_list * objval;
class Z_json_array * arrval;
class Z_attribute_list * objval;
class JsonGeneratorArray * arrval;
} val;
Za_type type; // uint8_t in size, type of attribute, see above
bool key_is_str; // is the key a string?
@ -217,7 +177,7 @@ public:
}
Z_attribute_list & newAttrList(void);
Z_json_array & newJsonArray(void);
JsonGeneratorArray & newJsonArray(void);
inline bool isNum(void) const { return (type >= Za_type::Za_bool) && (type <= Za_type::Za_float); }
inline bool isNone(void) const { return (type == Za_type::Za_none);}
@ -446,9 +406,9 @@ Z_attribute_list & Z_attribute::newAttrList(void) {
return *val.objval;
}
Z_json_array & Z_attribute::newJsonArray(void) {
JsonGeneratorArray & Z_attribute::newJsonArray(void) {
freeVal();
val.arrval = new Z_json_array();
val.arrval = new JsonGeneratorArray();
type = Za_type::Za_arr;
return *val.arrval;
}

View File

@ -36,31 +36,110 @@ enum class Z_Data_Type : uint8_t {
Z_Device = 0xFF // special value when parsing Device level attributes
};
const uint8_t Z_Data_Type_char[] PROGMEM = {
'?', // 0x00 Z_Data_Type::Z_Unknown
'L', // 0x01 Z_Data_Type::Z_Light
'P', // 0x02 Z_Data_Type::Z_Plug
'I', // 0x03 Z_Data_Type::Z_PIR
'A', // 0x04 Z_Data_Type::Z_Alarm
'T', // 0x05 Z_Data_Type::Z_Thermo
'O', // 0x05 Z_Data_Type::Z_OnOff
'\0', // 0x06
'\0', // 0x07
'\0', // 0x08
'\0', // 0x09
'\0', // 0x0A
'\0', // 0x0B
'\0', // 0x0C
'\0', // 0x0D
'\0', // 0x0E
'E', // 0x05 Z_Data_Type::Z_Ext
// '_' maps to 0xFF Z_Data_Type::Z_Device
};
class Z_Data_Set;
/*********************************************************************************************\
* Device specific data, sensors...
\*********************************************************************************************/
class Z_Data {
public:
Z_Data(Z_Data_Type type = Z_Data_Type::Z_Unknown, uint8_t endpoint = 0) : _type(type), _endpoint(endpoint), _config(-1), _power(0) {}
Z_Data(Z_Data_Type type = Z_Data_Type::Z_Unknown, uint8_t endpoint = 0) : _type(type), _endpoint(endpoint), _config(0xF), _power(0) {}
inline Z_Data_Type getType(void) const { return _type; }
inline int8_t getConfig(void) const { return _config; }
inline bool validConfig(void) const { return _config != 0xF; }
inline void setConfig(int8_t config) { _config = config; }
uint8_t getConfigByte(void) const { return ( ((uint8_t)_type) << 4) | ((_config & 0xF) & 0x0F); }
inline uint8_t getEndpoint(void) const { return _endpoint; }
void toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const;
static const Z_Data_Type type = Z_Data_Type::Z_Unknown;
static bool ConfigToZData(const char * config_str, Z_Data_Type * type, uint8_t * ep, uint8_t * config);
static Z_Data_Type CharToDataType(char c);
static char DataTypeToChar(Z_Data_Type t);
friend class Z_Data_Set;
protected:
Z_Data_Type _type; // encoded on 4 bits, type of the device
uint8_t _endpoint; // source endpoint, or 0x00 if any endpoint
int8_t _config; // encoded on 4 bits, customize behavior
uint8_t _config : 4; // encoded on 4 bits, customize behavior
uint8_t _power; // power state if the type supports it
};
Z_Data_Type Z_Data::CharToDataType(char c) {
if (c) {
if (c == '_') {
return Z_Data_Type::Z_Device;
} else {
for (uint32_t i=0; i<ARRAY_SIZE(Z_Data_Type_char); i++) {
if (pgm_read_byte(&Z_Data_Type_char[i]) == c) {
return (Z_Data_Type) i;
}
}
}
}
return Z_Data_Type::Z_Unknown;
}
char Z_Data::DataTypeToChar(Z_Data_Type t) {
if (t == Z_Data_Type::Z_Device) {
return '_';
} else {
uint8_t tt = (uint8_t) t;
if (tt < ARRAY_SIZE(Z_Data_Type_char)) {
return pgm_read_byte(&Z_Data_Type_char[tt]);
}
}
return '\0';
}
// Parse configuration
// Either '_'
// or 'T01' or 'L01.2'
// result: true if parsing ok
bool Z_Data::ConfigToZData(const char * config_str, Z_Data_Type * type, uint8_t * ep, uint8_t * config) {
if (config_str == nullptr) { return false; }
Z_Data_Type data_type = Z_Data::CharToDataType(config_str[0]);
if (data_type == Z_Data_Type::Z_Unknown) {
return false;
}
if (type != nullptr) {
*type = data_type;
}
if (ep != nullptr) {
*ep = strtoul(&config_str[1], nullptr, 16); // hex base 16
}
if ((config != nullptr) && (config_str[3] == '.')) {
// we have a config attribute
*config = strtoul(&config_str[4], nullptr, 16) & 0x0F; // hex base 16
}
return true;
}
/*********************************************************************************************\
* Device specific: On/Off, power up to 8 relays
\*********************************************************************************************/
@ -157,8 +236,6 @@ public:
inline void setCT(uint16_t _ct) { ct = _ct; }
inline void setX(uint16_t _x) { x = _x; }
inline void setY(uint16_t _y) { y = _y; }
void toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const;
static const Z_Data_Type type = Z_Data_Type::Z_Light;
// 12 bytes
@ -330,14 +407,6 @@ const Z_Data & Z_Data_Set::find(Z_Data_Type type, uint8_t ep) const {
return *(Z_Data*)nullptr;
}
// Low-level
// Add light attributes, used by dumpLightState and by SbData
//
void Z_Data_Light::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const {
attr_list.addAttribute(PSTR(D_JSON_ZIGBEE_LIGHT)).setInt(getConfig()); // special case, since type is 0x00 we can assume getConfig() is good
Z_Data::toAttributes(attr_list, type);
}
void Z_Data_OnOff::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const {
if (validPower()) { attr_list.addAttribute(PSTR("Power")).setUInt(getPower() ? 1 : 0); }
}
@ -370,7 +439,7 @@ public:
// Light information for Hue integration integration, last known values
// New version of device data handling
Z_Data_Set data; // Linkedlist of device data per endpoint
Z_Data_Set data; // Linkedlist of device data per endpoint
// other status
uint8_t lqi; // lqi from last message, 0xFF means unknown
uint8_t batterypercent; // battery percentage (0..100), 0xFF means unknwon
@ -390,7 +459,7 @@ public:
seqNumber(0),
hidden(false),
reachable(false),
// Hue support
data(),
lqi(0xFF),
batterypercent(0xFF),
last_seen(0)
@ -533,7 +602,7 @@ public:
// 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);
Z_Device & updateDevice(uint16_t shortaddr, uint64_t longaddr = 0);
// Add an endpoint to a device
void addEndpoint(uint16_t shortaddr, uint8_t endpoint);

View File

@ -29,10 +29,12 @@
//
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return (Z_Device&) device_unk; } // it is not legal to create this entry
Z_Device device(shortaddr, longaddr);
Z_Device & device = _devices.addToLast();
device.shortaddr = shortaddr;
device.longaddr = longaddr;
dirty();
return _devices.addHead(device);
return device;
}
void Z_Devices::freeDeviceEntry(Z_Device *device) {
@ -178,7 +180,7 @@ bool Z_Devices::removeDevice(uint16_t shortaddr) {
// In:
// shortaddr
// longaddr (both can't be null at the same time)
void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
Z_Device & Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
Z_Device * s_found = &findShortAddr(shortaddr); // is there already a shortaddr entry
Z_Device * l_found = &findLongAddr(longaddr); // is there already a longaddr entry
@ -191,21 +193,25 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
freeDeviceEntry(s_found);
_devices.remove(s_found);
dirty();
return *l_found;
}
} else if (foundDevice(*s_found)) {
// shortaddr already exists but longaddr not
// add the longaddr to the entry
s_found->longaddr = longaddr;
dirty();
return *s_found;
} else if (foundDevice(*l_found)) {
// longaddr entry exists, update shortaddr
l_found->shortaddr = shortaddr;
dirty();
return *l_found;
} else {
// neither short/lonf addr are found.
if ((BAD_SHORTADDR != shortaddr) || longaddr) {
createDeviceEntry(shortaddr, longaddr);
return createDeviceEntry(shortaddr, longaddr);
}
return (Z_Device&) device_unk;
}
}
@ -224,7 +230,7 @@ void Z_Devices::clearEndpoints(uint16_t shortaddr) {
// Add an endpoint to a shortaddr
//
void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) {
if (0x00 == endpoint) { return; }
if ((0x00 == endpoint) || (endpoint > 240)) { return; }
Z_Device &device = getShortAddr(shortaddr);
for (uint32_t i = 0; i < endpoints_max; i++) {
@ -278,6 +284,7 @@ void Z_Devices::setStringAttribute(char*& attr, const char * str) {
}
}
if (str_len) {
if (str_len > 31) { str_len = 31; }
attr = (char*) malloc(str_len + 1);
strlcpy(attr, str, str_len + 1);
}
@ -652,7 +659,7 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const {
// Mode = 1: simple dump of devices addresses
// Mode = 2: simple dump of devices addresses and names, endpoints, light
String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
Z_json_array json_arr;
JsonGeneratorArray json_arr;
for (const auto & device : _devices) {
uint16_t shortaddr = device.shortaddr;
@ -678,20 +685,30 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
if (device.modelId) {
attr_list.addAttribute(F(D_JSON_MODEL D_JSON_ID)).setStr(device.modelId);
}
int8_t bulbtype = getHueBulbtype(shortaddr);
if (bulbtype >= 0) {
attr_list.addAttribute(F(D_JSON_ZIGBEE_LIGHT)).setInt(bulbtype); // sign extend, 0xFF changed as -1
}
if (device.manufacturerId) {
attr_list.addAttribute(F("Manufacturer")).setStr(device.manufacturerId);
}
Z_json_array arr_ep;
JsonGeneratorArray arr_ep;
for (uint32_t i = 0; i < endpoints_max; i++) {
uint8_t endpoint = device.endpoints[i];
if (0x00 == endpoint) { break; }
arr_ep.add(endpoint);
}
attr_list.addAttribute(F("Endpoints")).setStrRaw(arr_ep.toString().c_str());
JsonGeneratorArray arr_data;
for (auto & data_elt : device.data) {
char key[8];
if (data_elt.validConfig()) {
snprintf_P(key, sizeof(key), "?%02X.%1X", data_elt.getEndpoint(), data_elt.getConfig());
} else {
snprintf_P(key, sizeof(key), "?%02X", data_elt.getEndpoint());
}
key[0] = Z_Data::DataTypeToChar(data_elt.getType());
arr_data.addStr(key);
}
attr_list.addAttribute(F("Config")).setStrRaw(arr_data.toString().c_str());
}
json_arr.addStrRaw(attr_list.toString(true).c_str());
}
@ -710,18 +727,17 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
int32_t Z_Devices::deviceRestore(JsonParserObject json) {
// params
uint16_t device = 0x0000; // 0x0000 is coordinator so considered invalid
uint16_t shortaddr = 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 = -1;
size_t endpoints_len = 0;
// read mandatory "Device"
JsonParserToken val_device = json[PSTR("Device")];
if (val_device) {
device = (uint32_t) val_device.getUInt(device);
shortaddr = (uint32_t) val_device.getUInt(shortaddr);
} else {
return -1; // missing "Device" attribute
}
@ -730,23 +746,41 @@ int32_t Z_Devices::deviceRestore(JsonParserObject json) {
friendlyname = json.getStr(PSTR("Name"), nullptr); // read "Name"
modelid = json.getStr(PSTR("ModelId"), nullptr);
manufid = json.getStr(PSTR("Manufacturer"), nullptr);
JsonParserToken tok_bulbtype = json[PSTR(D_JSON_ZIGBEE_LIGHT)];
// update internal device information
updateDevice(device, ieeeaddr);
if (modelid) { setModelId(device, modelid); }
if (manufid) { setManufId(device, manufid); }
if (friendlyname) { setFriendlyName(device, friendlyname); }
if (tok_bulbtype) { setLightProfile(device, tok_bulbtype.getInt()); }
updateDevice(shortaddr, ieeeaddr);
if (modelid) { setModelId(shortaddr, modelid); }
if (manufid) { setManufId(shortaddr, manufid); }
if (friendlyname) { setFriendlyName(shortaddr, friendlyname); }
// read "Endpoints"
JsonParserToken val_endpoints = json[PSTR("Endpoints")];
if (val_endpoints.isArray()) {
JsonParserArray arr_ep = JsonParserArray(val_endpoints);
clearEndpoints(device); // clear even if array is empty
clearEndpoints(shortaddr); // clear even if array is empty
for (auto ep_elt : arr_ep) {
uint8_t ep = ep_elt.getUInt();
if (ep) { addEndpoint(device, ep); }
if (ep) { addEndpoint(shortaddr, ep); }
}
}
// read "Config"
JsonParserToken val_config = json[PSTR("Config")];
Z_Device & device = getShortAddr(shortaddr);
if (val_config.isArray()) {
JsonParserArray arr_config = JsonParserArray(val_config);
device.data.reset(); // remove existing configuration
for (auto config_elt : arr_config) {
const char * conf_str = config_elt.getStr();
Z_Data_Type data_type;
uint8_t ep, config;
if (Z_Data::ConfigToZData(conf_str, &data_type, &ep, &config)) {
Z_Data & data = device.data.getByType(data_type, ep);
if (&data != nullptr) {
data.setConfig(config);
}
}
}
}

View File

@ -26,6 +26,14 @@
// uint16 - start address in Flash (offset)
// uint16 - length in bytes (makes sure parsing stops)
//
// First byte:
// 0x00 - Empty or V3 format
// 0x01-0xFE - Legacy format
// 0xFF - invalid
//
//
// V1 Legacy
// =========
// File structure:
// uint8 - number of devices, 0=none, 0xFF=invalid entry (probably Flash was erased)
//
@ -47,6 +55,29 @@
// reserved for extensions
// -- V2 --
// int8_t - zigbee profile of the device
//
// =======================
// v3 with version number
// File structure:
//
// uint8 - number of devices, 0=none, 0xFF=invalid entry (probably Flash was erased)
//
// [Array of devices]
// [Offset = 2]
// uint8 - length of device record
// uint16 - short address
// uint64 - long IEEE address
//
// str - ModelID (null terminated C string, 32 chars max)
// str - Manuf (null terminated C string, 32 chars max)
// str - FriendlyName (null terminated C string, 32 chars max)
//
// [Array of endpoints]
// uint8 - endpoint number, 0xFF marks the end of endpoints
// uint8[] - list of configuration bytes, 0xFF marks the end
// i.e. 0xFF-0xFF marks the end of the array of endpoints
//
// Memory footprint
#ifdef ESP8266
@ -63,71 +94,81 @@ const static size_t z_block_offset = 0x0000; // No offset needed
const static size_t z_block_len = 0x1000; // 4kb
#endif
class z_flashdata_t {
// Each entry consumes 8 bytes
class Z_Flashentry {
public:
uint32_t name; // simple 4 letters name. Currently 'zig1', 'zig2'. 0xFFFFFFFF if not entry
uint16_t len; // len of object in bytes, 0xFFFF if no entry
uint16_t start; // address of start, 0xFFFF if empty, must be aligned on 128 bytes boundaries
};
class Z_Flashdirectory {
public:
// 8 bytes header
uint32_t magic; // magic value 'Tsmt' to check that the block is initialized
uint32_t clock; // clock vector to discard entries that are made before this one. This should be incremented by 1 for each new entry (future anti-weavering)
// entries, 14*8 = 112 bytes
Z_Flashentry entries[14];
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
// link to next entry, none for now, but may be used for anti-weavering
uint16_t next_dir; // 0xFFFF if none
uint16_t reserved1; // must be 0xFFFF
uint32_t reserved2; // must be 0xFFFFFFFF
};
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
const static uint32_t ZIGB_NAME1 = 0x3167697A; // 'zig1' little endian
const static uint32_t ZIGB_NAME2 = 0x3267697A; // 'zig2' little endian, v2
const static size_t Z_MAX_FLASH = z_block_len - sizeof(Z_Flashentry); // 2040
class SBuffer hibernateDevice(const struct Z_Device &device) {
bool hibernateDeviceConfiguration(SBuffer & buf, const class Z_Data_Set & data, uint8_t endpoint) {
bool found = false;
for (auto & elt : data) {
if (endpoint == elt.getEndpoint()) {
buf.add8(elt.getConfigByte());
found = true;
}
}
return found;
}
class SBuffer hibernateDevicev2(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_count = 0;
for (endpoints_count = 0; endpoints_count < endpoints_max; endpoints_count++) {
if (0x00 == device.endpoints[endpoints_count]) { break; }
char *names[3] = { device.modelId, device.manufacturerId, device.friendlyName };
for (uint32_t i=0; i<ARRAY_SIZE(names); i++) {
char *p = names[i];
if (p) {
size_t len = strlen(p);
if (len > 32) { len = 32; } // max 32 chars
buf.addBuffer(p, len);
}
buf.add8(0x00); // end of string marker
}
buf.add8(endpoints_count);
// iterate on endpoints
for (uint32_t i = 0; i < endpoints_max; i++) {
// check if we need to write fake endpoint 0x00
buf.add8(0x00);
if (hibernateDeviceConfiguration(buf, device.data, 0)) {
buf.add8(0xFF); // end of configuration
} else {
buf.setLen(buf.len()-1); // remove 1 byte header
}
// scan endpoints
for (uint32_t i=0; i<endpoints_max; i++) {
uint8_t endpoint = device.endpoints[i];
if (0x00 == endpoint) { break; } // stop
if (0x00 == endpoint) { break; }
buf.add8(endpoint);
buf.add16(0x0000); // profile_id, not used anymore
// removed clusters_in
buf.add8(0xFF); // end of endpoint marker
// no more storage of clusters_out
buf.add8(0xFF); // end of endpoint marker
hibernateDeviceConfiguration(buf, device.data, endpoint);
buf.add8(0xFF); // end of configuration
}
// ModelID
if (device.modelId) {
size_t model_len = strlen(device.modelId);
if (model_len > 32) { model_len = 32; } // max 32 chars
buf.addBuffer(device.modelId, model_len);
}
buf.add8(0x00); // end of string marker
// ManufID
if (device.manufacturerId) {
size_t manuf_len = strlen(device.manufacturerId);
if (manuf_len > 32) { manuf_len = 32; } // max 32 chars
buf.addBuffer(device.manufacturerId, manuf_len);
}
buf.add8(0x00); // end of string marker
// FriendlyName
if (device.friendlyName) {
size_t frname_len = strlen(device.friendlyName);
if (frname_len > 32) {frname_len = 32; } // max 32 chars
buf.addBuffer(device.friendlyName, frname_len);
}
buf.add8(0x00); // end of string marker
// Zigbee Profile
buf.add8(device.getLightChannels());
buf.add8(0xFF); // end of endpoints
// update overall length
buf.set8(0, buf.len());
@ -144,7 +185,7 @@ class SBuffer hibernateDevices(void) {
for (uint32_t i = 0; i < devices_size; i++) {
const Z_Device & device = zigbee_devices.devicesAt(i);
const SBuffer buf_device = hibernateDevice(device);
const SBuffer buf_device = hibernateDevicev2(device);
buf.addBuffer(buf_device);
}
@ -164,22 +205,24 @@ class SBuffer hibernateDevices(void) {
return buf;
}
void hydrateDevices(const SBuffer &buf) {
uint32_t buf_len = buf.len();
if (buf_len <= 10) { return; }
// parse a single string from the saved data
// if something wrong happens, returns nullptr to ignore the string
// Index d is incremented to just after the string
const char * hydrateSingleString(const SBuffer & buf, uint32_t *d) {
size_t s_len = buf.strlen(*d);
const char * ptr = s_len ? buf.charptr(*d) : "";
*d += s_len + 1;
return ptr;
}
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
void hydrateSingleDevice(const SBuffer & buf_d, uint32_t version) {
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;
size_t buf_len = buf_d.len();
Z_Device & device = zigbee_devices.updateDevice(shortaddr, longaddr); // update device's addresses
if (1 == version) {
uint32_t endpoints = buf_d.get8(d++);
for (uint32_t j = 0; j < endpoints; j++) {
uint8_t ep = buf_d.get8(d++);
@ -187,51 +230,77 @@ void hydrateDevices(const SBuffer &buf) {
zigbee_devices.addEndpoint(shortaddr, ep);
// in clusters
while (d < dev_record_len) { // safe guard against overflow
while (d < buf_len) { // safe guard against overflow
uint8_t ep_cluster = buf_d.get8(d++);
if (0xFF == ep_cluster) { break; } // end of block
// ignore
}
// out clusters
while (d < dev_record_len) { // safe guard against overflow
while (d < buf_len) { // safe guard against overflow
uint8_t ep_cluster = buf_d.get8(d++);
if (0xFF == ep_cluster) { break; } // end of block
// ignore
}
}
}
// parse 3 strings
char empty[] = "";
// ModelId
zigbee_devices.setModelId(shortaddr, hydrateSingleString(buf_d, &d));
// 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
zigbee_devices.setManufId(shortaddr, hydrateSingleString(buf_d, &d));
// 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
zigbee_devices.setFriendlyName(shortaddr, hydrateSingleString(buf_d, &d));
// 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;
if (d >= buf_len) { return; }
// Hue bulbtype - if present
if (d < dev_record_len) {
zigbee_devices.setLightProfile(shortaddr, buf_d.get8(d));
d++;
// Hue bulbtype - if present
if (1 == version) {
zigbee_devices.setLightProfile(shortaddr, buf_d.get8(d));
d++;
} else if (2 == version) {
// v2 parser
while (d < buf_len) {
uint8_t ep = buf_d.get8(d++);
if (0xFF == ep) { break; } // ep 0xFF marks the end of the endpoints
if (ep > 240) { ep = 0xFF; } // ep == 0xFF means ignore
if ((ep > 0) && (ep != 0xFF)) { zigbee_devices.addEndpoint(shortaddr, ep); } // don't add endpoint if it is 0x00
while (d < buf_len) {
uint8_t config_type = buf_d.get8(d++);
if (0xFF == config_type) { break; } // 0xFF marks the end of congiguration
uint8_t config = config_type & 0x0F;
Z_Data_Type type = (Z_Data_Type) (config_type >> 4);
// set the configuration
if (ep != 0xFF) {
Z_Data & z_data = device.data.getByType(type, ep);
if (&z_data != nullptr) {
z_data.setConfig(config);
}
}
}
}
}
}
void hydrateDevices(const SBuffer &buf, uint32_t version) {
uint32_t buf_len = buf.len();
if (buf_len <= 10) { return; }
uint32_t k = 0; // byte index in global buffer
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);
hydrateSingleDevice(buf_d, version);
// next iteration
k += dev_record_len;
}
}
void loadZigbeeDevices(void) {
#ifdef ESP32
// first copy SPI buffer into ram
@ -243,19 +312,24 @@ void loadZigbeeDevices(void) {
ZigbeeRead(&spi_buffer, z_spi_len);
z_dev_start = spi_buffer;
#endif // ESP32
z_flashdata_t flashdata;
memcpy_P(&flashdata, z_dev_start, sizeof(z_flashdata_t));
Z_Flashentry flashdata;
memcpy_P(&flashdata, z_dev_start, sizeof(Z_Flashentry));
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Memory %d"), ESP_getFreeHeap());
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)) {
if ( ((flashdata.name == ZIGB_NAME1) || (flashdata.name == ZIGB_NAME2))
&& (flashdata.len > 0)) {
uint16_t buf_len = flashdata.len;
uint32_t version = (flashdata.name == ZIGB_NAME2) ? 2 : 1;
// 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);
hydrateDevices(buf);
buf.addBuffer(z_dev_start + sizeof(Z_Flashentry), buf_len);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee devices data in Flash v%d (%d bytes)"), version, buf_len);
// Serial.printf(">> Buffer=");
// for (uint32_t i=0; i<buf.len(); i++) Serial.printf("%02X ", buf.get8(i));
// Serial.printf("\n");
hydrateDevices(buf, version);
zigbee_devices.clean(); // don't write back to Flash what we just loaded
} else {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "No zigbee devices data in Flash"));
@ -287,12 +361,12 @@ void saveZigbeeDevices(void) {
ZigbeeRead(&spi_buffer, z_spi_len);
#endif // ESP8266 - ESP32
z_flashdata_t *flashdata = (z_flashdata_t*)(spi_buffer + z_block_offset);
flashdata->name = ZIGB_NAME;
Z_Flashentry *flashdata = (Z_Flashentry*)(spi_buffer + z_block_offset);
flashdata->name = ZIGB_NAME2; // v2
flashdata->len = buf_len;
flashdata->reserved = 0;
flashdata->start = 0;
memcpy(spi_buffer + z_block_offset + sizeof(z_flashdata_t), buf.getBuffer(), buf_len);
memcpy(spi_buffer + z_block_offset + sizeof(Z_Flashentry), buf.getBuffer(), buf_len);
// buffer is now ready, write it back
#ifdef ESP8266

View File

@ -1332,7 +1332,7 @@ void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
attr_list.addAttribute(F(D_CMND_ZIGBEE_CLUSTER)).setUInt(_cluster_id);
Z_json_array attr_numbers;
JsonGeneratorArray attr_numbers;
Z_attribute_list attr_names;
while (len >= 2 + i) {
uint16_t attrid = _payload.get16(i);
@ -1803,6 +1803,8 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, Z_attribute_list& attr_
// First we find or instantiate the correct Z_Data_XXX accorfing to the endpoint
// Then store the attribute at the attribute addres (via offset) and according to size 8/16/32 bits
// add the endpoint if it was not already known
zigbee_devices.addEndpoint(shortaddr, src_ep);
// we don't apply the multiplier, but instead store in Z_Data_XXX object
Z_Data & data = device.data.getByType(map_type, src_ep);
uint8_t *attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + map_offset;

View File

@ -370,7 +370,7 @@ void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster,
attr_list.addAttribute(command_name, PSTR("Count")).setUInt(xyz.y);
{
Z_json_array group_list;
JsonGeneratorArray group_list;
for (uint32_t i = 0; i < xyz.y; i++) {
group_list.add(payload.get16(2 + 2*i));
}
@ -441,7 +441,7 @@ void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster,
attr_list.addAttribute(command_name, command_suffix).setUInt(xyz.x);
} else {
// multiple answers, create an array
Z_json_array arr;
JsonGeneratorArray arr;
arr.add(xyz.x);
arr.add(xyz.y);
if (xyz.z_type) {

View File

@ -1314,17 +1314,12 @@ bool parseDeviceInnerData(class Z_Device & device, JsonParserObject root) {
// Parse key in format "L02":....
const char * data_type_str = data_elt.getStr();
Z_Data_Type data_type;
uint8_t endpoint;
uint8_t config = 0xFF; // unspecified
// parse key in the form "L01.5"
if (!Z_Data::ConfigToZData(data_type_str, &data_type, &endpoint, &config)) { data_type = Z_Data_Type::Z_Unknown; }
switch (data_type_str[0]) {
case 'P': data_type = Z_Data_Type::Z_Plug; break;
case 'L': data_type = Z_Data_Type::Z_Light; break;
case 'O': data_type = Z_Data_Type::Z_OnOff; break;
case 'T': data_type = Z_Data_Type::Z_Thermo; break;
case 'A': data_type = Z_Data_Type::Z_Alarm; break;
case '_': data_type = Z_Data_Type::Z_Device; break;
default: data_type = Z_Data_Type::Z_Unknown; break;
}
// The format should be a valid Code Lette followed by '-'
if (data_type == Z_Data_Type::Z_Unknown) {
Response_P(PSTR("{\"%s\":\"%s \"%s\"\"}"), XdrvMailbox.command, PSTR("Invalid Parameters"), data_type_str);
return false;
@ -1333,79 +1328,81 @@ bool parseDeviceInnerData(class Z_Device & device, JsonParserObject root) {
JsonParserObject data_values = data_elt.getValue().getObject();
if (!data_values) { return false; }
// Decode the endpoint number
uint8_t endpoint = strtoul(&data_type_str[1], nullptr, 16); // hex base 16
JsonParserToken val;
if (data_type == Z_Data_Type::Z_Device) {
if (val = data_values[PSTR(D_CMND_ZIGBEE_LINKQUALITY)]) { device.lqi = val.getUInt(); }
if (val = data_values[PSTR("BatteryPercentage")]) { device.batterypercent = val.getUInt(); }
if (val = data_values[PSTR("LastSeen")]) { device.last_seen = val.getUInt(); }
} else {
// Import generic attributes first
Z_Data & data = device.data.getByType(data_type, endpoint);
// Import generic attributes first
Z_Data & data = device.data.getByType(data_type, endpoint);
// scan through attributes
if (&data != nullptr) {
if (config != 0xFF) {
data.setConfig(config);
}
// scan through attributes
for (auto attr : data_values) {
JsonParserToken attr_value = attr.getValue();
uint8_t conv_zigbee_type;
Z_Data_Type conv_data_type;
uint8_t conv_map_offset;
if (zigbeeFindAttributeByName(attr.getStr(), nullptr, nullptr, nullptr, &conv_zigbee_type, &conv_data_type, &conv_map_offset) != nullptr) {
// found an attribute matching the name, does is fit the type?
if (conv_data_type == data_type) {
// we got a match. Bear in mind that a zero value is not a valid 'data_type'
for (auto attr : data_values) {
JsonParserToken attr_value = attr.getValue();
uint8_t conv_zigbee_type;
Z_Data_Type conv_data_type;
uint8_t conv_map_offset;
if (zigbeeFindAttributeByName(attr.getStr(), nullptr, nullptr, nullptr, &conv_zigbee_type, &conv_data_type, &conv_map_offset) != nullptr) {
// found an attribute matching the name, does is fit the type?
if (conv_data_type == data_type) {
// we got a match. Bear in mind that a zero value is not a valid 'data_type'
uint8_t *attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + conv_map_offset;
uint32_t uval32 = attr_value.getUInt(); // call converter to uint only once
int32_t ival32 = attr_value.getInt(); // call converter to int only once
switch (conv_zigbee_type) {
case Zenum8:
case Zuint8: *(uint8_t*)attr_address = uval32; break;
case Zenum16:
case Zuint16: *(uint16_t*)attr_address = uval32; break;
case Zuint32: *(uint32_t*)attr_address = uval32; break;
case Zint8: *(int8_t*)attr_address = ival32; break;
case Zint16: *(int16_t*)attr_address = ival32; break;
case Zint32: *(int32_t*)attr_address = ival32; break;
uint8_t *attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + conv_map_offset;
uint32_t uval32 = attr_value.getUInt(); // call converter to uint only once
int32_t ival32 = attr_value.getInt(); // call converter to int only once
switch (conv_zigbee_type) {
case Zenum8:
case Zuint8: *(uint8_t*)attr_address = uval32; break;
case Zenum16:
case Zuint16: *(uint16_t*)attr_address = uval32; break;
case Zuint32: *(uint32_t*)attr_address = uval32; break;
case Zint8: *(int8_t*)attr_address = ival32; break;
case Zint16: *(int16_t*)attr_address = ival32; break;
case Zint32: *(int32_t*)attr_address = ival32; break;
}
} else if (conv_data_type != Z_Data_Type::Z_Unknown) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "attribute %s is wrong type %d (expected %d)"), attr.getStr(), (uint8_t)data_type, (uint8_t)conv_data_type);
}
}
} else if (conv_data_type != Z_Data_Type::Z_Unknown) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "attribute %s is wrong type %d (expected %d)"), attr.getStr(), (uint8_t)data_type, (uint8_t)conv_data_type);
}
}
}
// Import specific attributes that are not handled with the generic method
switch (data_type) {
// case Z_Data_Type::Z_Plug:
// {
// Z_Data_Plug & plug = (Z_Data_Plug&) data;
// }
// break;
// case Z_Data_Type::Z_Light:
// {
// Z_Data_Light & light = (Z_Data_Light&) data;
// }
// break;
case Z_Data_Type::Z_OnOff:
{
Z_Data_OnOff & onoff = (Z_Data_OnOff&) data;
// Import specific attributes that are not handled with the generic method
switch (data_type) {
// case Z_Data_Type::Z_Plug:
// {
// Z_Data_Plug & plug = (Z_Data_Plug&) data;
// }
// break;
// case Z_Data_Type::Z_Light:
// {
// Z_Data_Light & light = (Z_Data_Light&) data;
// }
// break;
case Z_Data_Type::Z_OnOff:
{
Z_Data_OnOff & onoff = (Z_Data_OnOff&) data;
if (val = data_values[PSTR("Power")]) { onoff.setPower(val.getUInt() ? true : false); }
if (val = data_values[PSTR("Power")]) { onoff.setPower(val.getUInt() ? true : false); }
}
break;
// case Z_Data_Type::Z_Thermo:
// {
// Z_Data_Thermo & thermo = (Z_Data_Thermo&) data;
// }
// break;
// case Z_Data_Type::Z_Alarm:
// {
// Z_Data_Alarm & alarm = (Z_Data_Alarm&) data;
// }
// break;
}
break;
// case Z_Data_Type::Z_Thermo:
// {
// Z_Data_Thermo & thermo = (Z_Data_Thermo&) data;
// }
// break;
// case Z_Data_Type::Z_Alarm:
// {
// Z_Data_Alarm & alarm = (Z_Data_Alarm&) data;
// }
// break;
case Z_Data_Type::Z_Device:
{
if (val = data_values[PSTR(D_CMND_ZIGBEE_LINKQUALITY)]) { device.lqi = val.getUInt(); }
if (val = data_values[PSTR("BatteryPercentage")]) { device.batterypercent = val.getUInt(); }
if (val = data_values[PSTR("LastSeen")]) { device.last_seen = val.getUInt(); }
}
break;
}
}
return true;
@ -1454,56 +1451,39 @@ void CmndZbData(void) {
{ // scope to force object deallocation
Z_attribute_list device_attr;
device.toAttributes(device_attr);
attr_data.addAttribute(F("_00")).setStrRaw(device_attr.toString(true).c_str());
attr_data.addAttribute(F("_")).setStrRaw(device_attr.toString(true).c_str());
}
// Iterate on data objects
for (auto & data_elt : device.data) {
Z_attribute_list inner_attr;
char key[4];
snprintf_P(key, sizeof(key), "?%02X", data_elt.getEndpoint());
// The key is in the form "L01", where 'L' is the type and '01' the endpoint in hex format
// 'P' = Power
// 'L' = Light
// 'O' = OnOff
// 'T' = Thermo & sensors
// 'A' = Alarm
// '?' = Device wide
//
char key[8];
if (data_elt.validConfig()) {
snprintf_P(key, sizeof(key), "?%02X.%1X", data_elt.getEndpoint(), data_elt.getConfig());
} else {
snprintf_P(key, sizeof(key), "?%02X", data_elt.getEndpoint());
}
Z_Data_Type data_type = data_elt.getType();
key[0] = Z_Data::DataTypeToChar(data_type);
switch (data_type) {
case Z_Data_Type::Z_Plug:
{
key[0] = 'P';
((Z_Data_Plug&)data_elt).toAttributes(inner_attr, data_type);
}
((Z_Data_Plug&)data_elt).toAttributes(inner_attr, data_type);
break;
case Z_Data_Type::Z_Light:
{
key[0] = 'L';
((Z_Data_Light&)data_elt).toAttributes(inner_attr, data_type);
}
((Z_Data_Light&)data_elt).toAttributes(inner_attr, data_type);
break;
case Z_Data_Type::Z_OnOff:
{
key[0] = 'O';
((Z_Data_OnOff&)data_elt).toAttributes(inner_attr, data_type);
}
((Z_Data_OnOff&)data_elt).toAttributes(inner_attr, data_type);
break;
case Z_Data_Type::Z_Thermo:
{
key[0] = 'T';
((Z_Data_Thermo&)data_elt).toAttributes(inner_attr, data_type);
}
((Z_Data_Thermo&)data_elt).toAttributes(inner_attr, data_type);
break;
case Z_Data_Type::Z_Alarm:
{
key[0] = 'A';
((Z_Data_Alarm&)data_elt).toAttributes(inner_attr, data_type);
}
((Z_Data_Alarm&)data_elt).toAttributes(inner_attr, data_type);
break;
}
if (key[0] != '?') {
if ((key[0] != '\0') && (key[0] != '?')) {
attr_data.addAttribute(key).setStrRaw(inner_attr.toString(true).c_str());
}
}