mirror of https://github.com/arendst/Tasmota.git
Merge pull request #9288 from s-hadinger/zigbee_auto_config
Add Zigbee auto-config when pairing
This commit is contained in:
commit
6d24d87ff7
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue