Add Zigbee persistence and friendly names

This commit is contained in:
Hadinger 2020-01-18 00:02:01 +01:00
parent 50a25aeb7c
commit 18ce64f813
11 changed files with 704 additions and 63 deletions

View File

@ -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

View File

@ -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"

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -22,6 +22,10 @@
#include <vector>
#include <map>
#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<shortaddr> = 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<Z_Device> _devices = {};
uint32_t _saveTimer = 0;
template < typename T>
static bool findInVector(const std::vector<T> & 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<Z_Device>::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<Z_Device>::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<Z_Device>::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;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<uint32_t>::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<uint32_t>::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<uint32_t>::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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 <device_id>,<friendlyname> - assign a friendly name
// ZigbeeName <device_id> - display the current friendly name
// ZigbeeName <device_id>, - remove friendly name
//
// Where <device_id> 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, <device_id>
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
\*********************************************************************************************/