diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index faaa2ae17..d07791a2e 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -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 diff --git a/tasmota/settings.h b/tasmota/settings.h index 2ceadd523..a9c8680c1 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -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 diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index 8ad36f861..1e2c29704 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -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) { diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 1155a6f2e..e70e6702d 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -124,6 +124,16 @@ uint16_t CxToCluster(uint8_t cx) { } return 0xFFFF; } + +uint8_t ClusterToCx(uint16_t cluster) { + for (uint8_t i=0; icluster_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 } // ====================================================================== diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index a32f637c4..947e2ffe0 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -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: diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index d800eaf71..788c72e88 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -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= 0) { + bitSet(cluster_map, found_cx); + bitSet(cluster_in_map, found_cx); + } + } + // scan out clusters + for (uint32_t i=0; i= 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 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 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