Merge pull request #9288 from s-hadinger/zigbee_auto_config

Add Zigbee auto-config when pairing
This commit is contained in:
Theo Arends 2020-09-12 11:39:45 +02:00 committed by GitHub
commit 6d24d87ff7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 430 additions and 43 deletions

View File

@ -8,6 +8,7 @@
- Fix crash in ``ZbRestore``
- Add new shutter modes (#9244)
- Add ``#define USE_MQTT_AWS_IOT_LIGHT`` for password based AWS IoT authentication
- Add Zigbee auto-config when pairing
### 8.5.0 20200907

View File

@ -129,7 +129,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t virtual_ct_cw : 1; // bit 25 (v8.4.0.1) - SetOption107 - Virtual CT Channel - signals whether the hardware white is cold CW (true) or warm WW (false)
uint32_t teleinfo_rawdata : 1; // bit 26 (v8.4.0.2) - SetOption108 - enable Teleinfo + Tasmota Energy device (0) or Teleinfo raw data only (1)
uint32_t alexa_gen_1 : 1; // bit 27 (v8.4.0.3) - SetOption109 - Alexa gen1 mode - if you only have Echo Dot 2nd gen devices
uint32_t spare28 : 1; // bit 28
uint32_t zb_disable_autobind : 1; // bit 28 (v8.5.0.1) - SetOption110 - disable Zigbee auto-config when pairing new devices
uint32_t spare29 : 1; // bit 29
uint32_t spare30 : 1; // bit 30
uint32_t spare31 : 1; // bit 31

View File

@ -36,6 +36,10 @@ public:
char * manufacturerId;
char * modelId;
char * friendlyName;
// _defer_last_time : what was the last time an outgoing message is scheduled
// this is designed for flow control and avoid messages to be lost or unanswered
uint32_t defer_last_message_sent;
uint8_t endpoints[endpoints_max]; // static array to limit memory consumption, list of endpoints until 0x00 or end of array
// Used for attribute reporting
Z_attribute_list attr_list;
@ -78,6 +82,7 @@ public:
manufacturerId(nullptr),
modelId(nullptr),
friendlyName(nullptr),
defer_last_message_sent(0),
endpoints{ 0, 0, 0, 0, 0, 0, 0, 0 },
attr_list(),
shortaddr(_shortaddr),
@ -145,21 +150,28 @@ public:
* Structures for deferred callbacks
\*********************************************************************************************/
typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value);
typedef void (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value);
// Category for Deferred actions, this allows to selectively remove active deferred or update them
typedef enum Z_Def_Category {
Z_CAT_NONE = 0, // no category, it will happen anyways
Z_CAT_ALWAYS = 0, // no category, it will happen whatever new timers
// Below will clear any event in the same category for the same address (shortaddr / groupaddr)
Z_CLEAR_DEVICE = 0x01,
Z_CAT_READ_ATTR, // Attribute reporting, either READ_ATTRIBUTE or REPORT_ATTRIBUTE, we coalesce all attributes reported if we can
Z_CAT_VIRTUAL_OCCUPANCY, // Creation of a virtual attribute, typically after a time-out. Ex: Aqara presence sensor
Z_CAT_REACHABILITY, // timer set to measure reachability of device, i.e. if we don't get an answer after 1s, it is marked as unreachable (for Alexa)
Z_CAT_READ_0006, // Read 0x0006 cluster
Z_CAT_READ_0008, // Read 0x0008 cluster
Z_CAT_READ_0102, // Read 0x0300 cluster
Z_CAT_READ_0300, // Read 0x0300 cluster
// Below will clear based on device + cluster pair.
Z_CLEAR_DEVICE_CLUSTER,
Z_CAT_READ_CLUSTER,
// Below will clear based on device + cluster + endpoint
Z_CLEAR_DEVICE_CLUSTER_ENDPOINT,
Z_CAT_EP_DESC, // read endpoint descriptor to gather clusters
Z_CAT_BIND, // send auto-binding to coordinator
Z_CAT_CONFIG_ATTR, // send a config attribute reporting request
Z_CAT_READ_ATTRIBUTE, // read a single attribute
} Z_Def_Category;
const uint32_t Z_CAT_REACHABILITY_TIMEOUT = 1000; // 1000 ms or 1s
const uint32_t Z_CAT_REACHABILITY_TIMEOUT = 2000; // 1000 ms or 1s
typedef struct Z_Deferred {
// below are per device timers, used for example to query the new state of the device
@ -258,8 +270,9 @@ public:
bool isHueBulbHidden(uint16_t shortaddr) const ;
// Timers
void resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category);
void resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category, uint16_t cluster = 0xFFFF, uint8_t endpoint = 0xFF);
void setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func);
void queueTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func);
void runTimer(void);
// Append or clear attributes Json structure
@ -723,12 +736,17 @@ bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
// Deferred actions
// Parse for a specific category, of all deferred for a device if category == 0xFF
void Z_Devices::resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category) {
// Only with specific cluster number or for all clusters if cluster == 0xFFFF
void Z_Devices::resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category, uint16_t cluster, uint8_t endpoint) {
// iterate the list of deferred, and remove any linked to the shortaddr
for (auto & defer : _deferred) {
if ((defer.shortaddr == shortaddr) && (defer.groupaddr == groupaddr)) {
if ((0xFF == category) || (defer.category == category)) {
_deferred.remove(&defer);
if ((0xFFFF == cluster) || (defer.cluster == cluster)) {
if ((0xFF == endpoint) || (defer.endpoint == endpoint)) {
_deferred.remove(&defer);
}
}
}
}
}
@ -737,8 +755,8 @@ void Z_Devices::resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uin
// Set timer for a specific device
void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func) {
// First we remove any existing timer for same device in same category, except for category=0x00 (they need to happen anyway)
if (category) { // if category == 0, we leave all previous
resetTimersForDevice(shortaddr, groupaddr, category); // remove any cluster
if (category >= Z_CLEAR_DEVICE) { // if category == 0, we leave all previous timers
resetTimersForDevice(shortaddr, groupaddr, category, category >= Z_CLEAR_DEVICE_CLUSTER ? cluster : 0xFFFF, category >= Z_CLEAR_DEVICE_CLUSTER_ENDPOINT ? endpoint : 0xFF); // remove any cluster
}
// Now create the new timer
@ -753,6 +771,21 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
func };
}
// Set timer after the already queued events
// I.e. the wait_ms is not counted from now, but from the last event queued, which is 'now' or in the future
void Z_Devices::queueTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func) {
Z_Device & device = getShortAddr(shortaddr);
uint32_t now_millis = millis();
if (TimeReached(device.defer_last_message_sent)) {
device.defer_last_message_sent = now_millis;
}
// defer_last_message_sent equals now or a value in the future
device.defer_last_message_sent += wait_ms;
// for queueing we don't clear the backlog, so we force category to Z_CAT_ALWAYS
setTimer(shortaddr, groupaddr, (device.defer_last_message_sent - now_millis), cluster, endpoint, Z_CAT_ALWAYS, value, func);
}
// Run timer at each tick
// WARNING: don't set a new timer within a running timer, this causes memory corruption
void Z_Devices::runTimer(void) {

View File

@ -124,6 +124,16 @@ uint16_t CxToCluster(uint8_t cx) {
}
return 0xFFFF;
}
uint8_t ClusterToCx(uint16_t cluster) {
for (uint8_t i=0; i<ARRAY_SIZE(Cx_cluster); i++) {
if (pgm_read_word(&Cx_cluster[i]) == cluster) {
return i;
}
}
return 0xFF;
}
// list of post-processing directives
const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ Zuint8, Cx0000, 0x0000, Z_(ZCLVersion), 1 },
@ -544,6 +554,25 @@ const __FlashStringHelper* zigbeeFindAttributeByName(const char *command,
return nullptr;
}
//
// Find attribute details: Name, Type, Multiplier by cuslter/attr_id
//
const __FlashStringHelper* zigbeeFindAttributeById(uint16_t cluster, uint16_t attr_id,
uint8_t *attr_type, int8_t *multiplier) {
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
uint16_t conv_attr_id = pgm_read_word(&converter->attribute);
if ((conv_cluster == cluster) && (conv_attr_id == attr_id)) {
if (multiplier) { *multiplier = pgm_read_byte(&converter->multiplier); }
if (attr_type) { *attr_type = pgm_read_byte(&converter->type); }
return (const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset));
}
}
return nullptr;
}
class ZCLFrame {
public:
@ -1095,35 +1124,30 @@ void ZCLFrame::generateCallBacks(Z_attribute_list& attr_list) {
// Set timers to read back values.
// If it's a device address, also set a timer for reachability test
void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint = 0) {
int32_t z_cat = -1;
uint32_t wait_ms = 0;
uint32_t wait_ms = 0xFFFF;
switch (cluster) {
case 0x0006:
z_cat = Z_CAT_READ_0006;
wait_ms = 200; // wait 0.2 s
break;
case 0x0008:
z_cat = Z_CAT_READ_0008;
wait_ms = 1050; // wait 1.0 s
break;
case 0x0102:
z_cat = Z_CAT_READ_0102;
wait_ms = 10000; // wait 10.0 s
break;
case 0x0300:
z_cat = Z_CAT_READ_0300;
wait_ms = 1050; // wait 1.0 s
break;
default:
break;
}
if (z_cat >= 0) {
if (0xFFFF != wait_ms) {
if ((BAD_SHORTADDR != shortaddr) && (0 == endpoint)) {
endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
}
if ((BAD_SHORTADDR == shortaddr) || (endpoint)) { // send if group address or endpoint is known
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, z_cat, 0 /* value */, &Z_ReadAttrCallback);
zigbee_devices.queueTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback);
if (BAD_SHORTADDR != shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
}
@ -1170,16 +1194,27 @@ void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
// ZCL_CONFIGURE_REPORTING_RESPONSE
void ZCLFrame::parseConfigAttributes(Z_attribute_list& attr_list) {
uint32_t i = 0;
uint32_t len = _payload.len();
uint8_t status = _payload.get8(i);
Z_attribute_list attr_config_response;
attr_config_response.addAttribute(F("Status")).setUInt(status);
attr_config_response.addAttribute(F("StatusMsg")).setStr(getZigbeeStatusMessage(status).c_str());
Z_attribute_list attr_config_list;
for (uint32_t i=0; len >= i+4; i+=4) {
uint8_t status = _payload.get8(i);
uint16_t attr_id = _payload.get8(i+2);
Z_attribute_list attr_config_response;
attr_config_response.addAttribute(F("Status")).setUInt(status);
attr_config_response.addAttribute(F("StatusMsg")).setStr(getZigbeeStatusMessage(status).c_str());
const __FlashStringHelper* attr_name = zigbeeFindAttributeById(_cluster_id, attr_id, nullptr, nullptr);
if (attr_name) {
attr_config_list.addAttribute(attr_name).setStrRaw(attr_config_response.toString(true).c_str());
} else {
attr_config_list.addAttribute(_cluster_id, attr_id).setStrRaw(attr_config_response.toString(true).c_str());
}
}
Z_attribute &attr_1 = attr_list.addAttribute(F("ConfigResponse"));
attr_1.setStrRaw(attr_config_response.toString(true).c_str());
attr_1.setStrRaw(attr_config_list.toString(true).c_str());
}
// ZCL_READ_REPORTING_CONFIGURATION_RESPONSE
@ -1534,11 +1569,10 @@ void ZCLFrame::syntheticAqaraVibration(class Z_attribute_list &attr_list, class
}
/// Publish a message for `"Occupancy":0` when the timer expired
int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
void Z_OccupancyCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
Z_attribute_list attr_list;
attr_list.addAttribute(F(OCCUPANCY)).setUInt(0);
zigbee_devices.jsonPublishNow(shortaddr, attr_list);
return 0; // Fix GCC 10.1 warning
}
// ======================================================================

View File

@ -148,7 +148,7 @@ const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; // AlarmCount
const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007), ZLE(0x0008) }; // Hue, Sat, X, Y, CT, ColorMode
// This callback is registered after a cluster specific command and sends a read command for the same cluster
int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
void Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
size_t attrs_len = 0;
const uint8_t* attrs = nullptr;
@ -188,16 +188,14 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus
attrs, attrs_len
}));
}
return 0; // Fix GCC 10.1 warning
}
// This callback is registered after a an attribute read command was made to a light, and fires if we don't get any response after 1000 ms
int32_t Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
void Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
if (BAD_SHORTADDR != shortaddr) {
zigbee_devices.setReachable(shortaddr, false); // mark device as reachable
}
return 0; // Fix GCC 10.1 warning
}
// returns true if char is 'x', 'y' or 'z'
@ -349,6 +347,10 @@ void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster,
if ((0 != xyz.z) && (0xFF != xyz.z)) {
attr_list.addAttribute(command_name, PSTR("Zone")).setUInt(xyz.z);
}
// for now convert alamrs 1 and 2 to Occupancy
// TODO we may only do this conversion to ZoneType == 0x000D 'Motion Sensor'
// Occupancy is 0406/0000 of type Zmap8
attr_list.addAttribute(0x0406, 0x0000).setUInt((xyz.x) & 0x01 ? 1 : 0);
break;
case 0x00040000:
case 0x00040001:

View File

@ -533,7 +533,11 @@ int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
#endif
for (uint32_t i = 0; i < activeEpCount; i++) {
zigbee_devices.addEndpoint(nwkAddr, activeEpList[i]);
uint8_t ep = activeEpList[i];
zigbee_devices.addEndpoint(nwkAddr, ep);
if ((i < 4) && (ep < 0x10)) {
zigbee_devices.queueTimer(nwkAddr, 0 /* groupaddr */, 1500, ep /* fake cluster as ep */, ep, Z_CAT_EP_DESC, 0 /* value */, &Z_SendSimpleDescReq);
}
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
@ -546,7 +550,132 @@ int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
Z_SendAFInfoRequest(nwkAddr); // probe for ModelId and ManufId
Z_SendDeviceInfoRequest(nwkAddr); // probe for ModelId and ManufId
return -1;
}
// list of clusters that need bindings
const uint8_t Z_bindings[] PROGMEM = {
Cx0001, Cx0006, Cx0008, Cx0300,
Cx0400, Cx0402, Cx0403, Cx0405, Cx0406,
Cx0500,
};
int32_t Z_ClusterToCxBinding(uint16_t cluster) {
uint8_t cx = ClusterToCx(cluster);
for (uint32_t i=0; i<ARRAY_SIZE(Z_bindings); i++) {
if (pgm_read_byte(&Z_bindings[i]) == cx) {
return i;
}
}
return -1;
}
void Z_AutoBindDefer(uint16_t shortaddr, uint8_t endpoint, const SBuffer &buf,
size_t in_index, size_t in_len, size_t out_index, size_t out_len) {
// We use bitmaps to mark clusters that need binding and config attributes
// All clusters in 'in' and 'out' are bounded
// Only cluster in 'in' receive configure attribute requests
uint32_t cluster_map = 0; // max 32 clusters to bind
uint32_t cluster_in_map = 0; // map of clusters only in 'in' group, to be bounded
// First enumerate all clusters to bind, from in or out clusters
// scan in clusters
for (uint32_t i=0; i<in_len; i++) {
uint16_t cluster = buf.get16(in_index + i*2);
uint32_t found_cx = Z_ClusterToCxBinding(cluster); // convert to Cx of -1 if not found
if (found_cx >= 0) {
bitSet(cluster_map, found_cx);
bitSet(cluster_in_map, found_cx);
}
}
// scan out clusters
for (uint32_t i=0; i<out_len; i++) {
uint16_t cluster = buf.get16(out_index + i*2);
uint32_t found_cx = Z_ClusterToCxBinding(cluster); // convert to Cx of -1 if not found
if (found_cx >= 0) {
bitSet(cluster_map, found_cx);
}
}
// if IAS device, request the device type
if (bitRead(cluster_map, Z_ClusterToCxBinding(0x0500))) {
// send a read command to cluster 0x0500, attribute 0x0001 (ZoneType) - to read the type of sensor
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, 0x0500, endpoint, Z_CAT_READ_ATTRIBUTE, 0x0001, &Z_SendSingleAttributeRead);
}
// enqueue bind requests
for (uint32_t i=0; i<ARRAY_SIZE(Z_bindings); i++) {
if (bitRead(cluster_map, i)) {
uint16_t cluster = CxToCluster(pgm_read_byte(&Z_bindings[i]));
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, cluster, endpoint, Z_CAT_BIND, 0 /* value */, &Z_AutoBind);
}
}
// enqueue config attribute requests
for (uint32_t i=0; i<ARRAY_SIZE(Z_bindings); i++) {
if (bitRead(cluster_in_map, i)) {
uint16_t cluster = CxToCluster(pgm_read_byte(&Z_bindings[i]));
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, cluster, endpoint, Z_CAT_CONFIG_ATTR, 0 /* value */, &Z_AutoConfigReportingForCluster);
}
}
}
int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
#ifdef USE_ZIGBEE_ZNP
// Received ZDO_SIMPLE_DESC_RSP
// Z_ShortAddress srcAddr = buf.get16(2);
uint8_t status = buf.get8(4);
Z_ShortAddress nwkAddr = buf.get16(5);
uint8_t lenDescriptor = buf.get8(7);
uint8_t endpoint = buf.get8(8);
uint16_t profileId = buf.get16(9); // The profile Id for this endpoint.
uint16_t deviceId = buf.get16(11); // The Device Description Id for this endpoint.
uint8_t deviceVersion = buf.get8(13); // 0 Version 1.00
uint8_t numInCluster = buf.get8(14);
uint8_t numOutCluster = buf.get8(15 + numInCluster*2);
const size_t numInIndex = 15;
const size_t numOutIndex = 16 + numInCluster*2;
#endif
#ifdef USE_ZIGBEE_EZSP
uint8_t status = buf.get8(0);
Z_ShortAddress nwkAddr = buf.get16(1);
uint8_t lenDescriptor = buf.get8(3);
uint8_t endpoint = buf.get8(4);
uint16_t profileId = buf.get16(5); // The profile Id for this endpoint.
uint16_t deviceId = buf.get16(7); // The Device Description Id for this endpoint.
uint8_t deviceVersion = buf.get8(9); // 0 Version 1.00
uint8_t numInCluster = buf.get8(10);
uint8_t numOutCluster = buf.get8(11 + numInCluster*2);
const size_t numInIndex = 11;
const size_t numOutIndex = 12 + numInCluster*2;
#endif
if (0 == status) {
if (!Settings.flag4.zb_disable_autobind) {
Z_AutoBindDefer(nwkAddr, endpoint, buf, numInIndex, numInCluster, numOutIndex, numOutCluster);
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
"\"Status\":%d,\"Endpoint\":\"0x%02X\""
",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVersion\":%d"
"\"InClusters\":["),
ZIGBEE_STATUS_SIMPLE_DESC, endpoint,
profileId, deviceId, deviceVersion);
for (uint32_t i = 0; i < numInCluster; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(numInIndex + i*2));
}
ResponseAppend_P(PSTR("],\"OutClusters\":["));
for (uint32_t i = 0; i < numOutCluster; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(numOutIndex + i*2));
}
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
}
return -1;
}
@ -831,7 +960,7 @@ int32_t Z_MgmtBindRsp(int32_t res, const class SBuffer &buf) {
//uint64_t srcaddr = buf.get16(idx); // unused
uint8_t srcep = buf.get8(idx + 8);
uint8_t cluster = buf.get16(idx + 9);
uint16_t cluster = buf.get16(idx + 9);
uint8_t addrmode = buf.get8(idx + 11);
uint16_t group = 0x0000;
uint64_t dstaddr = 0;
@ -960,9 +1089,31 @@ void Z_SendActiveEpReq(uint16_t shortaddr) {
}
//
// Send AF Info Request
// Probe the clusters_out on the first endpoint
//
void Z_SendAFInfoRequest(uint16_t shortaddr) {
// Send ZDO_SIMPLE_DESC_REQ to get full list of supported Clusters for a specific endpoint
void Z_SendSimpleDescReq(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
#ifdef USE_ZIGBEE_ZNP
uint8_t SimpleDescReq[] = { Z_SREQ | Z_ZDO, ZDO_SIMPLE_DESC_REQ, // 2504
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr),
endpoint };
ZigbeeZNPSend(SimpleDescReq, sizeof(SimpleDescReq));
#endif
#ifdef USE_ZIGBEE_EZSP
uint8_t SimpleDescReq[] = { Z_B0(shortaddr), Z_B1(shortaddr), endpoint };
EZ_SendZDO(shortaddr, ZDO_SIMPLE_DESC_REQ, SimpleDescReq, sizeof(SimpleDescReq));
#endif
}
//
// Send AF Info Request
// Queue requests for the device
// 1. Request for 'ModelId' and 'Manufacturer': 0000/0005, 0000/0006
// 2. Auto-bind to coordinator:
// Iterate among
//
void Z_SendDeviceInfoRequest(uint16_t shortaddr) {
uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
if (0x00 == endpoint) { endpoint = 0x01; } // if we don't know the endpoint, try 0x01
uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr);
@ -983,6 +1134,170 @@ void Z_SendAFInfoRequest(uint16_t shortaddr) {
}));
}
//
// Send sing attribute read request in Timer
//
void Z_SendSingleAttributeRead(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr);
uint8_t InfoReq[2] = { Z_B0(value), Z_B1(value) }; // list of single attribute
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
shortaddr,
0x0000, /* group */
cluster /*cluster*/,
endpoint,
ZCL_READ_ATTRIBUTES,
0x0000, /* manuf */
false /* not cluster specific */,
true /* response */,
transacid, /* zcl transaction id */
InfoReq, sizeof(InfoReq)
}));
}
//
// Auto-bind some clusters to the coordinator's endpoint 0x01
//
void Z_AutoBind(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
uint64_t srcLongAddr = zigbee_devices.getDeviceLongAddr(shortaddr);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "auto-bind `ZbBind {\"Device\":\"0x%04X\",\"Endpoint\":%d,\"Cluster\":\"0x%04X\"}`"),
shortaddr, endpoint, cluster);
#ifdef USE_ZIGBEE_ZNP
SBuffer buf(34);
buf.add8(Z_SREQ | Z_ZDO);
buf.add8(ZDO_BIND_REQ);
buf.add16(shortaddr);
buf.add64(srcLongAddr);
buf.add8(endpoint);
buf.add16(cluster);
buf.add8(Z_Addr_IEEEAddress); // DstAddrMode - 0x03 = ADDRESS_64_BIT
buf.add64(localIEEEAddr);
buf.add8(0x01); // toEndpoint
ZigbeeZNPSend(buf.getBuffer(), buf.len());
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
SBuffer buf(24);
// ZDO message payload (see Zigbee spec 2.4.3.2.2)
buf.add64(srcLongAddr);
buf.add8(endpoint);
buf.add16(cluster);
buf.add8(Z_Addr_IEEEAddress); // DstAddrMode - 0x03 = ADDRESS_64_BIT
buf.add64(localIEEEAddr);
buf.add8(0x01); // toEndpoint
EZ_SendZDO(shortaddr, ZDO_BIND_REQ, buf.buf(), buf.len());
#endif // USE_ZIGBEE_EZSP
}
//
// Auto-bind some clusters to the coordinator's endpoint 0x01
//
// the structure below indicates which attributes need to be configured for attribute reporting
typedef struct Z_autoAttributeReporting_t {
uint16_t cluster;
uint16_t attr_id;
uint16_t min_interval; // minimum interval in seconds (consecutive reports won't happen before this value)
uint16_t max_interval; // maximum interval in seconds (attribut will always be reported after this interval)
float report_change; // for non discrete attributes, the value change that triggers a report
} Z_autoAttributeReporting_t;
// Note the attribute must be registered in the converter list, used to retrieve the type of the attribute
const Z_autoAttributeReporting_t Z_autoAttributeReporting[] PROGMEM = {
{ 0x0001, 0x0020, 15*60, 15*60, 0.1 }, // BatteryVoltage
{ 0x0001, 0x0021, 15*60, 15*60, 1 }, // BatteryPercentage
{ 0x0006, 0x0000, 1, 60*60, 0 }, // Power
{ 0x0008, 0x0000, 1, 60*60, 5 }, // Dimmer
{ 0x0300, 0x0000, 1, 60*60, 5 }, // Hue
{ 0x0300, 0x0001, 1, 60*60, 5 }, // Sat
{ 0x0300, 0x0003, 1, 60*60, 100 }, // X
{ 0x0300, 0x0004, 1, 60*60, 100 }, // Y
{ 0x0300, 0x0007, 1, 60*60, 5 }, // CT
{ 0x0300, 0x0008, 1, 60*60, 0 }, // ColorMode
{ 0x0400, 0x0000, 10, 60*60, 5 }, // Illuminance (5 lux)
{ 0x0402, 0x0000, 30, 60*60, 0.2 }, // Temperature (0.2 °C)
{ 0x0403, 0x0000, 30, 60*60, 1 }, // Pressure (1 hPa)
{ 0x0405, 0x0000, 30, 60*60, 1.0 }, // Humidity (1 %)
{ 0x0406, 0x0000, 10, 60*60, 0 }, // Occupancy
{ 0x0500, 0x0002, 1, 60*60, 0 }, // ZoneStatus
};
//
// Called by Device Auto-config
// Configures default values for the most common Attribute Rerporting configurations
//
// Note: must be of type `Z_DeviceTimer`
void Z_AutoConfigReportingForCluster(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
// Buffer, max 12 bytes per attribute
SBuffer buf(12*6);
Response_P(PSTR("ZbSend {\"Device\":\"0x%04X\",\"Config\":{"), shortaddr);
boolean comma = false;
for (uint32_t i=0; i<ARRAY_SIZE(Z_autoAttributeReporting); i++) {
uint16_t conv_cluster = pgm_read_word(&(Z_autoAttributeReporting[i].cluster));
uint16_t attr_id = pgm_read_word(&(Z_autoAttributeReporting[i].attr_id));
if (conv_cluster == cluster) {
uint16_t min_interval = pgm_read_word(&(Z_autoAttributeReporting[i].min_interval));
uint16_t max_interval = pgm_read_word(&(Z_autoAttributeReporting[i].max_interval));
float report_change_raw = Z_autoAttributeReporting[i].report_change;
double report_change = report_change_raw;
uint8_t attr_type;
int8_t multiplier;
const __FlashStringHelper* attr_name = zigbeeFindAttributeById(cluster, attr_id, &attr_type, &multiplier);
if (attr_name) {
if (comma) { ResponseAppend_P(PSTR(",")); }
comma = true;
ResponseAppend_P(PSTR("\"%s\":{\"MinInterval\":%d,\"MaxInterval\":%d"), attr_name, min_interval, max_interval);
buf.add8(0); // direction, always 0
buf.add16(attr_id);
buf.add8(attr_type);
buf.add16(min_interval);
buf.add16(max_interval);
if (!Z_isDiscreteDataType(attr_type)) { // report_change is only valid for non-discrete data types (numbers)
ZbApplyMultiplier(report_change, multiplier);
// encode value
int32_t res = encodeSingleAttribute(buf, report_change, "", attr_type);
if (res < 0) {
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "internal error, unsupported attribute type"));
} else {
Z_attribute attr;
attr.setKeyName(PSTR("ReportableChange"), true); // true because in PMEM
attr.setFloat(report_change_raw);
ResponseAppend_P(PSTR(",%s"), attr.toString().c_str());
}
}
ResponseAppend_P(PSTR("}"));
}
}
}
ResponseAppend_P(PSTR("}}"));
if (buf.len() > 0) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "auto-bind `%s`"), mqtt_data);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
shortaddr,
0x0000, /* group */
cluster /*cluster*/,
endpoint,
ZCL_CONFIGURE_REPORTING,
0x0000, /* manuf */
false /* not cluster specific */,
false /* no response */,
zigbee_devices.getNextSeqNumber(shortaddr), /* zcl transaction id */
buf.buf(), buf.len()
}));
}
}
//
// Handle trustCenterJoinHandler
@ -1189,6 +1504,8 @@ int32_t EZ_IncomingMessage(int32_t res, const class SBuffer &buf) {
return Z_ReceiveActiveEp(res, zdo_buf);
case ZDO_IEEE_addr_rsp:
return Z_ReceiveIEEEAddr(res, zdo_buf);
case ZDO_Simple_Desc_rsp:
return Z_ReceiveSimpleDesc(res, zdo_buf);
case ZDO_Bind_rsp:
return Z_BindRsp(res, zdo_buf);
case ZDO_Unbind_rsp:
@ -1279,9 +1596,8 @@ int32_t EZ_Recv_Default(int32_t res, const class SBuffer &buf) {
\*********************************************************************************************/
// Publish the received values once they have been coalesced
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
void Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
zigbee_devices.jsonPublishFlush(shortaddr);
return 1;
}
/*********************************************************************************************\
@ -1363,6 +1679,7 @@ const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
{ { Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND }, &ZNP_ReceivePermitJoinStatus }, // 45CB
{ { Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP }, &ZNP_ReceiveNodeDesc }, // 4582
{ { Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP }, &Z_ReceiveActiveEp }, // 4585
{ { Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP}, &Z_ReceiveSimpleDesc}, // 4584
{ { Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP }, &Z_ReceiveIEEEAddr }, // 4581
{ { Z_AREQ | Z_ZDO, ZDO_BIND_RSP }, &Z_BindRsp }, // 45A1
{ { Z_AREQ | Z_ZDO, ZDO_UNBIND_RSP }, &Z_UnbindRsp }, // 45A2
@ -1413,11 +1730,11 @@ void Z_Query_Bulb(uint16_t shortaddr, uint32_t &wait_ms) {
uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
if (endpoint) { // send only if we know the endpoint
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0006, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0006, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0008, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0008, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0300, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0300, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
zigbee_devices.setTimer(shortaddr, 0, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, 0, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
wait_ms += 1000; // wait 1 second between devices