Zigbee auto-mapping

This commit is contained in:
Stephan Hadinger 2020-11-30 19:12:16 +01:00
parent 5892cffbda
commit 4ea69ce2ef
2 changed files with 175 additions and 142 deletions

View File

@ -20,6 +20,8 @@
#ifdef USE_ZIGBEE
#ifdef USE_ZIGBEE_EZSP
void EZ_SendZDO(uint16_t shortaddr, uint16_t cmd, const unsigned char *payload, size_t payload_len, bool retry = true);
//
// Trying to get a uniform LQI measure, we are aligning with the definition of ZNP
// I.e. a linear projection from -87dBm to +10dB over 0..255
@ -200,6 +202,44 @@ int32_t EZ_MessageSent(int32_t res, const class SBuffer &buf) {
#endif // USE_ZIGBEE_EZSP
/*********************************************************************************************\
* Handle auto-mapping
\*********************************************************************************************/
// low-level sending of packet
void Z_Send_State_or_Map(uint16_t shortaddr, uint8_t index, uint16_t zdo_cmd) {
#ifdef USE_ZIGBEE_ZNP
SBuffer buf(10);
buf.add8(Z_SREQ | Z_ZDO); // 25
buf.add8(zdo_cmd); // 33
buf.add16(shortaddr); // shortaddr
buf.add8(index); // StartIndex = 0
ZigbeeZNPSend(buf.getBuffer(), buf.len());
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
// ZDO message payload (see Zigbee spec 2.4.3.3.4)
uint8_t buf[] = { index }; // index = 0
EZ_SendZDO(shortaddr, zdo_cmd, buf, sizeof(buf), false);
#endif // USE_ZIGBEE_EZSP
}
// This callback is registered to send ZbMap(s) to each device one at a time
void Z_Map(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
if (BAD_SHORTADDR != shortaddr) {
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "sending `ZnMap 0x%04X`"), shortaddr);
#ifdef USE_ZIGBEE_ZNP
Z_Send_State_or_Map(shortaddr, value, ZDO_MGMT_LQI_REQ);
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
Z_Send_State_or_Map(shortaddr, value, ZDO_Mgmt_Lqi_req);
#endif // USE_ZIGBEE_EZSP
} else {
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbMap done"));
}
}
/*********************************************************************************************\
* Parsers for incoming EZSP messages
\*********************************************************************************************/
@ -948,80 +988,7 @@ int32_t Z_UnbindRsp(int32_t res, const class SBuffer &buf) {
// Handle MgMt Bind Rsp incoming message
//
int32_t Z_MgmtBindRsp(int32_t res, const class SBuffer &buf) {
#ifdef USE_ZIGBEE_ZNP
uint16_t shortaddr = buf.get16(2);
uint8_t status = buf.get8(4);
uint8_t bind_total = buf.get8(5);
uint8_t bind_start = buf.get8(6);
uint8_t bind_len = buf.get8(7);
const size_t prefix_len = 8;
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
uint16_t shortaddr = buf.get16(buf.len()-2);
uint8_t status = buf.get8(0);
uint8_t bind_total = buf.get8(1);
uint8_t bind_start = buf.get8(2);
uint8_t bind_len = buf.get8(3);
const size_t prefix_len = 4;
#endif // USE_ZIGBEE_EZSP
// device is reachable
zigbee_devices.deviceWasReached(shortaddr);
const char * friendlyName = zigbee_devices.getFriendlyName(shortaddr);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND_STATE "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), shortaddr);
if (friendlyName) {
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName);
}
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
",\"BindingsTotal\":%d"
",\"BindingsStart\":%d"
",\"Bindings\":["
), status, getZigbeeStatusMessage(status).c_str(), bind_total, bind_start + 1);
uint32_t idx = prefix_len;
for (uint32_t i = 0; i < bind_len; i++) {
if (idx + 14 > buf.len()) { break; } // overflow, frame size is between 14 and 21
//uint64_t srcaddr = buf.get16(idx); // unused
uint8_t srcep = buf.get8(idx + 8);
uint16_t cluster = buf.get16(idx + 9);
uint8_t addrmode = buf.get8(idx + 11);
uint16_t group = 0x0000;
uint64_t dstaddr = 0;
uint8_t dstep = 0x00;
if (Z_Addr_Group == addrmode) { // Group address mode
group = buf.get16(idx + 12);
idx += 14;
} else if (Z_Addr_IEEEAddress == addrmode) { // IEEE address mode
dstaddr = buf.get64(idx + 12);
dstep = buf.get8(idx + 20);
idx += 21;
} else {
//AddLog_P(LOG_LEVEL_INFO, PSTR("ZNP_MgmtBindRsp unknwon address mode %d"), addrmode);
break; // abort for any other value since we don't know the length of the field
}
if (i > 0) {
ResponseAppend_P(PSTR(","));
}
ResponseAppend_P(PSTR("{\"Cluster\":\"0x%04X\",\"Endpoint\":%d,"), cluster, srcep);
if (Z_Addr_Group == addrmode) { // Group address mode
ResponseAppend_P(PSTR("\"ToGroup\":%d}"), group);
} else if (Z_Addr_IEEEAddress == addrmode) { // IEEE address mode
char hex[20];
Uint64toHex(dstaddr, hex, 64);
ResponseAppend_P(PSTR("\"ToDevice\":\"0x%s\",\"ToEndpoint\":%d}"), hex, dstep);
}
}
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_BIND_STATE));
return -1;
return Z_Mgmt_Lqi_Bind_Rsp(res, buf, false);
}
// Return false, true or null (if unknown)
@ -1056,23 +1023,25 @@ const char * Z_DeviceType(uint32_t value) {
}
//
// Handle MgMt Bind Rsp incoming message
// Combined code for MgmtLqiRsp and MgmtBindRsp
//
int32_t Z_MgmtLqiRsp(int32_t res, const class SBuffer &buf) {
// If the response has a follow-up, send more requests automatically
//
int32_t Z_Mgmt_Lqi_Bind_Rsp(int32_t res, const class SBuffer &buf, boolean lqi) {
#ifdef USE_ZIGBEE_ZNP
uint16_t shortaddr = buf.get16(2);
uint8_t status = buf.get8(4);
uint8_t lqi_total = buf.get8(5);
uint8_t lqi_start = buf.get8(6);
uint8_t lqi_len = buf.get8(7);
uint8_t total = buf.get8(5);
uint8_t start = buf.get8(6);
uint8_t len = buf.get8(7);
const size_t prefix_len = 8;
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
uint16_t shortaddr = buf.get16(buf.len()-2);
uint8_t status = buf.get8(0);
uint8_t lqi_total = buf.get8(1);
uint8_t lqi_start = buf.get8(2);
uint8_t lqi_len = buf.get8(3);
uint8_t total = buf.get8(1);
uint8_t start = buf.get8(2);
uint8_t len = buf.get8(3);
const size_t prefix_len = 4;
#endif // USE_ZIGBEE_EZSP
@ -1081,62 +1050,123 @@ int32_t Z_MgmtLqiRsp(int32_t res, const class SBuffer &buf) {
const char * friendlyName = zigbee_devices.getFriendlyName(shortaddr);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_MAP "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), shortaddr);
Response_P(PSTR("{\"%s\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""),
lqi ? PSTR(D_JSON_ZIGBEE_MAP) : PSTR(D_JSON_ZIGBEE_BIND_STATE), shortaddr);
if (friendlyName) {
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName);
}
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
",\"MapTotal\":%d"
",\"MapStart\":%d"
",\"Map\":["
), status, getZigbeeStatusMessage(status).c_str(), lqi_total, lqi_start + 1);
",\"Total\":%d"
",\"Start\":%d"
",\"%s\":["
), status, getZigbeeStatusMessage(status).c_str(), total, start + 1,
lqi ? PSTR("Map") : PSTR("Bindings"));
uint32_t idx = prefix_len;
for (uint32_t i = 0; i < lqi_len; i++) {
if (idx + 22 > buf.len()) { break; } // size 22 for EZSP
if (lqi) {
uint32_t idx = prefix_len;
for (uint32_t i = 0; i < len; i++) {
if (idx + 22 > buf.len()) { break; } // size 22 for EZSP
//uint64_t extpanid = buf.get16(idx); // unused
// uint64_t m_longaddr = buf.get64(idx + 8);
uint16_t m_shortaddr = buf.get16(idx + 16);
uint8_t m_dev_type = buf.get8(idx + 18);
uint8_t m_permitjoin = buf.get8(idx + 19);
uint8_t m_depth = buf.get8(idx + 20);
uint8_t m_lqi = buf.get8(idx + 21);
idx += 22;
//uint64_t extpanid = buf.get16(idx); // unused
// uint64_t m_longaddr = buf.get64(idx + 8);
uint16_t m_shortaddr = buf.get16(idx + 16);
uint8_t m_dev_type = buf.get8(idx + 18);
uint8_t m_permitjoin = buf.get8(idx + 19);
uint8_t m_depth = buf.get8(idx + 20);
uint8_t m_lqi = buf.get8(idx + 21);
idx += 22;
if (i > 0) {
ResponseAppend_P(PSTR(","));
if (i > 0) {
ResponseAppend_P(PSTR(","));
}
ResponseAppend_P(PSTR("{\"Device\":\"0x%04X\","), m_shortaddr);
const char * friendlyName = zigbee_devices.getFriendlyName(m_shortaddr);
if (friendlyName) {
ResponseAppend_P(PSTR("\"Name\":\"%s\","), friendlyName);
}
ResponseAppend_P(PSTR("\"DeviceType\":\"%s\","
"\"RxOnWhenIdle\":%s,"
"\"Relationship\":\"%s\","
"\"PermitJoin\":%s,"
"\"Depth\":%d,"
"\"LinkQuality\":%d"
"}"
),
Z_DeviceType(m_dev_type & 0x03),
TrueFalseNull((m_dev_type & 0x0C) >> 2),
Z_DeviceRelationship((m_dev_type & 0x70) >> 4),
TrueFalseNull(m_permitjoin & 0x02),
m_depth,
m_lqi);
}
ResponseAppend_P(PSTR("{\"Device\":\"0x%04X\","), m_shortaddr);
const char * friendlyName = zigbee_devices.getFriendlyName(m_shortaddr);
if (friendlyName) {
ResponseAppend_P(PSTR("\"Name\":\"%s\","), friendlyName);
ResponseAppend_P(PSTR("]}}"));
} else { // Bind
uint32_t idx = prefix_len;
for (uint32_t i = 0; i < len; i++) {
if (idx + 14 > buf.len()) { break; } // overflow, frame size is between 14 and 21
//uint64_t srcaddr = buf.get16(idx); // unused
uint8_t srcep = buf.get8(idx + 8);
uint16_t cluster = buf.get16(idx + 9);
uint8_t addrmode = buf.get8(idx + 11);
uint16_t group = 0x0000;
uint64_t dstaddr = 0;
uint8_t dstep = 0x00;
if (Z_Addr_Group == addrmode) { // Group address mode
group = buf.get16(idx + 12);
idx += 14;
} else if (Z_Addr_IEEEAddress == addrmode) { // IEEE address mode
dstaddr = buf.get64(idx + 12);
dstep = buf.get8(idx + 20);
idx += 21;
} else {
//AddLog_P(LOG_LEVEL_INFO, PSTR("ZNP_MgmtBindRsp unknwon address mode %d"), addrmode);
break; // abort for any other value since we don't know the length of the field
}
if (i > 0) {
ResponseAppend_P(PSTR(","));
}
ResponseAppend_P(PSTR("{\"Cluster\":\"0x%04X\",\"Endpoint\":%d,"), cluster, srcep);
if (Z_Addr_Group == addrmode) { // Group address mode
ResponseAppend_P(PSTR("\"ToGroup\":%d}"), group);
} else if (Z_Addr_IEEEAddress == addrmode) { // IEEE address mode
char hex[20];
Uint64toHex(dstaddr, hex, 64);
ResponseAppend_P(PSTR("\"ToDevice\":\"0x%s\",\"ToEndpoint\":%d}"), hex, dstep);
}
}
ResponseAppend_P(PSTR("\"DeviceType\":\"%s\","
"\"RxOnWhenIdle\":%s,"
"\"Relationship\":\"%s\","
"\"PermitJoin\":%s,"
"\"Depth\":%d,"
"\"LinkQuality\":%d"
"}"
),
Z_DeviceType(m_dev_type & 0x03),
TrueFalseNull((m_dev_type & 0x0C) >> 2),
Z_DeviceRelationship((m_dev_type & 0x70) >> 4),
TrueFalseNull(m_permitjoin & 0x02),
m_depth,
m_lqi);
ResponseAppend_P(PSTR("]}}"));
}
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_MAP));
// Check if there are more values waiting, if so re-send a new request to get other values
if (start + len < total) {
// there are more values to read
#ifdef USE_ZIGBEE_ZNP
Z_Send_State_or_Map(shortaddr, start + len, lqi ? ZDO_MGMT_LQI_REQ : ZDO_MGMT_BIND_REQ);
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
Z_Send_State_or_Map(shortaddr, start + len, lqi ? ZDO_Mgmt_Lqi_req : ZDO_Mgmt_Bind_req);
#endif // USE_ZIGBEE_EZSP
}
return -1;
}
//
// Handle MgMt Bind Rsp incoming message
//
int32_t Z_MgmtLqiRsp(int32_t res, const class SBuffer &buf) {
return Z_Mgmt_Lqi_Bind_Rsp(res, buf, true);
}
#ifdef USE_ZIGBEE_EZSP
//
// Handle Parent Annonce Rsp incoming message
@ -1572,7 +1602,7 @@ void Z_IncomingMessage(class ZCLFrame &zcl_received) {
* Send ZDO Message
\*********************************************************************************************/
void EZ_SendZDO(uint16_t shortaddr, uint16_t cmd, const unsigned char *payload, size_t payload_len) {
void EZ_SendZDO(uint16_t shortaddr, uint16_t cmd, const unsigned char *payload, size_t payload_len, bool retry) {
SBuffer buf(payload_len + 22);
uint8_t seq = zigbee_devices.getNextSeqNumber(0x0000);
@ -1587,7 +1617,11 @@ void EZ_SendZDO(uint16_t shortaddr, uint16_t cmd, const unsigned char *payload,
buf.add16(cmd); // ZDO cmd in cluster
buf.add8(0); // srcEp
buf.add8(0); // dstEp
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
if (retry) {
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
} else {
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY); // APS frame
}
buf.add16(0x0000); // groupId
buf.add8(seq);
// end of ApsFrame

View File

@ -986,24 +986,7 @@ void CmndZbBindState_or_Map(uint16_t zdo_cmd) {
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
uint8_t index = XdrvMailbox.index - 1; // change default 1 to 0
#ifdef USE_ZIGBEE_ZNP
SBuffer buf(10);
buf.add8(Z_SREQ | Z_ZDO); // 25
buf.add8(zdo_cmd); // 33
buf.add16(shortaddr); // shortaddr
buf.add8(index); // StartIndex = 0
ZigbeeZNPSend(buf.getBuffer(), buf.len());
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
// ZDO message payload (see Zigbee spec 2.4.3.3.4)
uint8_t buf[] = { index }; // index = 0
EZ_SendZDO(shortaddr, zdo_cmd, buf, sizeof(buf));
#endif // USE_ZIGBEE_EZSP
Z_Send_State_or_Map(shortaddr, index, zdo_cmd);
ResponseCmndDone();
}
@ -1025,12 +1008,28 @@ void CmndZbBindState(void) {
// `ZbMap<x>` as index if it does not fit. If default, `1` starts at the beginning
//
void CmndZbMap(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
RemoveSpace(XdrvMailbox.data);
if (strlen(XdrvMailbox.data) == 0) {
// defer sending ZbMap to each device
const static uint32_t DELAY_ZBMAP = 2000; // wait for 1s between commands
uint32_t wait_ms = DELAY_ZBMAP;
zigbee_devices.setTimer(0x0000, 0, 0 /*wait_ms*/, 0, 0, Z_CAT_ALWAYS, 0 /* value = index */, &Z_Map);
for (const auto & device : zigbee_devices.getDevices()) {
zigbee_devices.setTimer(device.shortaddr, 0, wait_ms, 0, 0, Z_CAT_ALWAYS, 0 /* value = index */, &Z_Map);
wait_ms += DELAY_ZBMAP;
}
zigbee_devices.setTimer(BAD_SHORTADDR, 0, wait_ms, 0, 0, Z_CAT_ALWAYS, 0 /* value = index */, &Z_Map);
ResponseCmndDone();
} else {
#ifdef USE_ZIGBEE_ZNP
CmndZbBindState_or_Map(ZDO_MGMT_LQI_REQ);
CmndZbBindState_or_Map(ZDO_MGMT_LQI_REQ);
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
CmndZbBindState_or_Map(ZDO_Mgmt_Lqi_req);
CmndZbBindState_or_Map(ZDO_Mgmt_Lqi_req);
#endif // USE_ZIGBEE_EZSP
}
}
// Probe a specific device to get its endpoints and supported clusters