From 5384c0190b7c08a07ef950aff2cd98efc564f4c3 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 5 Jul 2020 21:01:26 +0200 Subject: [PATCH] Zigbee EZSP support for bindings and groups --- tasmota/xdrv_23_zigbee_0_constants.ino | 30 ++++++++++ tasmota/xdrv_23_zigbee_8_parsers.ino | 78 +++++++++++++++++--------- tasmota/xdrv_23_zigbee_9_serial.ino | 74 ++++++++++++++++-------- tasmota/xdrv_23_zigbee_A_impl.ino | 27 +++++++++ 4 files changed, 160 insertions(+), 49 deletions(-) diff --git a/tasmota/xdrv_23_zigbee_0_constants.ino b/tasmota/xdrv_23_zigbee_0_constants.ino index ac63d9878..da2dd5119 100644 --- a/tasmota/xdrv_23_zigbee_0_constants.ino +++ b/tasmota/xdrv_23_zigbee_0_constants.ino @@ -461,6 +461,7 @@ enum EZSP_ZDO { // Network Management Server Services Requests ZDO_Mgmt_Lqi_req = 0x0031, ZDO_Mgmt_Rtg_req = 0x0032, + ZDO_Mgmt_Bind_req = 0x0033, ZDO_Mgmt_Leave_req = 0x0034, ZDO_Mgmt_Permit_Joining_req = 0x0036, ZDO_Mgmt_NWK_Update_req = 0x0038, @@ -496,6 +497,7 @@ enum EZSP_ZDO { // Network Management Server Services Responses ZDO_Mgmt_Lqi_rsp = 0x8031, ZDO_Mgmt_Rtg_rsp = 0x8032, + ZDO_Mgmt_Bind_rsp = 0x8033, ZDO_Mgmt_Leave_rsp = 0x8034, ZDO_Mgmt_Permit_Joining_rsp = 0x8036, ZDO_Mgmt_NWK_Update_rsp = 0x8038, @@ -1106,6 +1108,34 @@ typedef struct Z_StatusLine { const char * status_msg; } Z_StatusLine; + +// ZDP Enumeration, see Zigbee spec 2.4.5 +String getZDPStatusMessage(uint8_t status) { + static const char StatusMsg[] PROGMEM = "SUCCESS|INV_REQUESTTYPE|DEVICE_NOT_FOUND|INVALID_EP|NOT_ACTIVE|NOT_SUPPORTED" + "|TIMEOUT|NO_MATCH|NO_ENTRY|NO_DESCRIPTOR|INSUFFICIENT_SPACE|NOT_PERMITTED" + "|TABLE_FULL|NOT_AUTHORIZED|DEVICE_BINDING_TABLE_FULL" + ; + static const uint8_t StatusIdx[] PROGMEM = { 0x00, 0x80, 0x81, 0x82, 0x83, 0x84, + 0x85, 0x86, 0x88, 0x89, 0x8A, 0x8B, + 0x8C, 0x8D, 0x8E }; + + char msg[32]; + int32_t idx = -1; + for (uint32_t i = 0; i < sizeof(StatusIdx); i++) { + if (status == pgm_read_byte(&StatusIdx[i])) { + idx = i; + break; + } + } + if (idx >= 0) { + GetTextIndexed(msg, sizeof(msg), idx, StatusMsg); + } else { + *msg = 0x00; // empty string + } + return String(msg); +} + + // Undocumented Zigbee ZCL code here: https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/Zigbee-Error-Codes-in-the-Log String getZigbeeStatusMessage(uint8_t status) { static const char StatusMsg[] PROGMEM = "SUCCESS|FAILURE|NOT_AUTHORIZED|RESERVED_FIELD_NOT_ZERO|MALFORMED_COMMAND|UNSUP_CLUSTER_COMMAND|UNSUP_GENERAL_COMMAND" diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 0429b82fa..14e62b603 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -670,9 +670,17 @@ int32_t ZNP_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) { // // Handle Bind Rsp incoming message // -int32_t ZNP_BindRsp(int32_t res, const class SBuffer &buf) { +int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) { +#ifdef USE_ZIGBEE_ZNP Z_ShortAddress nwkAddr = buf.get16(2); uint8_t status = buf.get8(4); + String msg = getZigbeeStatusMessage(status); +#endif // USE_ZIGBEE_ZNP +#ifdef USE_ZIGBEE_EZSP + uint8_t status = buf.get8(0); + Z_ShortAddress nwkAddr = buf.get16(buf.len()-2); // last 2 bytes + String msg = getZDPStatusMessage(status); +#endif // USE_ZIGBEE_EZSP const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr); @@ -682,7 +690,7 @@ int32_t ZNP_BindRsp(int32_t res, const class SBuffer &buf) { } ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS "\":%d" ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" - "}}"), status, getZigbeeStatusMessage(status).c_str()); + "}}"), status, msg.c_str()); MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); @@ -693,9 +701,17 @@ int32_t ZNP_BindRsp(int32_t res, const class SBuffer &buf) { // // Handle Unbind Rsp incoming message // -int32_t ZNP_UnbindRsp(int32_t res, const class SBuffer &buf) { +int32_t Z_UnbindRsp(int32_t res, const class SBuffer &buf) { +#ifdef USE_ZIGBEE_ZNP Z_ShortAddress nwkAddr = buf.get16(2); uint8_t status = buf.get8(4); + String msg = getZigbeeStatusMessage(status); +#endif // USE_ZIGBEE_ZNP +#ifdef USE_ZIGBEE_EZSP + uint8_t status = buf.get8(0); + Z_ShortAddress nwkAddr = buf.get16(buf.len()-2); // last 2 bytes + String msg = getZDPStatusMessage(status); +#endif // USE_ZIGBEE_EZSP const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr); @@ -703,8 +719,9 @@ int32_t ZNP_UnbindRsp(int32_t res, const class SBuffer &buf) { if (friendlyName) { ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName); } - ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" - "}}"), status, getZigbeeStatusMessage(status).c_str()); + ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS "\":%d" + ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" + "}}"), status, msg.c_str()); MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); @@ -714,12 +731,23 @@ int32_t ZNP_UnbindRsp(int32_t res, const class SBuffer &buf) { // // Handle MgMt Bind Rsp incoming message // -int32_t ZNP_MgmtBindRsp(int32_t res, const class SBuffer &buf) { +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 const char * friendlyName = zigbee_devices.getFriendlyName(shortaddr); @@ -733,7 +761,7 @@ int32_t ZNP_MgmtBindRsp(int32_t res, const class SBuffer &buf) { ",\"Bindings\":[" ), status, getZigbeeStatusMessage(status).c_str(), bind_total); - uint32_t idx = 8; + 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 @@ -791,7 +819,8 @@ void Z_SendIEEEAddrReq(uint16_t shortaddr) { ZigbeeZNPSend(IEEEAddrReq, sizeof(IEEEAddrReq)); #endif #ifdef USE_ZIGBEE_EZSP - // TODO + uint8_t IEEEAddrReq[] = { Z_B0(shortaddr), Z_B1(shortaddr), 0x00, 0x00 }; + EZ_SendZDO(shortaddr, ZDO_IEEE_addr_req, IEEEAddrReq, sizeof(IEEEAddrReq)); #endif } @@ -991,25 +1020,31 @@ int32_t EZ_IncomingMessage(int32_t res, const class SBuffer &buf) { uint8_t linkquality = buf.get8(14); // uint8_t linkrsssi = buf.get8(15); // probably not used as there is no equivalent in Z-Stack uint16_t srcaddr = buf.get16(16); - uint8_t bindingindex = buf.get8(18); // TODO not sure we need this one as a coordinator - uint8_t addressindex = buf.get8(19); // TODO not sure how to handle this one + // uint8_t bindingindex = buf.get8(18); // not sure we need this one as a coordinator + // uint8_t addressindex = buf.get8(19); // not sure how to handle this one // offset 20 is len, and buffer starts at offset 21 if ((0x0000 == profileid) && (0x00 == srcendpoint)) { // ZDO request // Since ZDO messages start with a sequence number, we skip it - SBuffer zdo_buf = buf.subBuffer(22, buf.get8(20) - 1); + // but we add the source address in the last 2 bytes + SBuffer zdo_buf(buf.get8(20) - 1 + 2); + zdo_buf.addBuffer(buf.buf(22), buf.get8(20) - 1); + zdo_buf.add16(srcaddr); switch (clusterid) { case ZDO_Device_annce: return Z_ReceiveEndDeviceAnnonce(res, zdo_buf); - break; case ZDO_Active_EP_rsp: return Z_ReceiveActiveEp(res, zdo_buf); - break; case ZDO_IEEE_addr_rsp: return Z_ReceiveIEEEAddr(res, zdo_buf); - break; + case ZDO_Bind_rsp: + return Z_BindRsp(res, zdo_buf); + case ZDO_Unbind_rsp: + return Z_UnbindRsp(res, zdo_buf); + case ZDO_Mgmt_Bind_rsp: + return Z_MgmtBindRsp(res, zdo_buf); } } else { bool defer_attributes = false; // do we defer attributes reporting to coalesce @@ -1130,15 +1165,6 @@ int32_t ZNP_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { return -1; } -// -// Callback for loading Zigbee configuration from Flash, called by the state machine -// -int32_t Z_Reset_Device(uint8_t value) { - // TODO - GPIO is hardwired to GPIO4 - digitalWrite(4, value ? HIGH : LOW); - return 0; // continue -} - #endif // USE_ZIGBEE_ZNP @@ -1189,9 +1215,9 @@ const Z_Dispatcher Z_DispatchTable[] PROGMEM = { { { 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_IEEE_ADDR_RSP }, &Z_ReceiveIEEEAddr }, // 4581 - { { Z_AREQ | Z_ZDO, ZDO_BIND_RSP }, &ZNP_BindRsp }, // 45A1 - { { Z_AREQ | Z_ZDO, ZDO_UNBIND_RSP }, &ZNP_UnbindRsp }, // 45A2 - { { Z_AREQ | Z_ZDO, ZDO_MGMT_BIND_RSP }, &ZNP_MgmtBindRsp }, // 45B3 + { { Z_AREQ | Z_ZDO, ZDO_BIND_RSP }, &Z_BindRsp }, // 45A1 + { { Z_AREQ | Z_ZDO, ZDO_UNBIND_RSP }, &Z_UnbindRsp }, // 45A2 + { { Z_AREQ | Z_ZDO, ZDO_MGMT_BIND_RSP }, &Z_MgmtBindRsp }, // 45B3 }; /*********************************************************************************************\ diff --git a/tasmota/xdrv_23_zigbee_9_serial.ino b/tasmota/xdrv_23_zigbee_9_serial.ino index 354bb507a..aeefe8ae4 100644 --- a/tasmota/xdrv_23_zigbee_9_serial.ino +++ b/tasmota/xdrv_23_zigbee_9_serial.ino @@ -718,33 +718,61 @@ void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterI #ifdef USE_ZIGBEE_EZSP SBuffer buf(32+len); - buf.add16(EZSP_sendUnicast); // 3400 - buf.add8(EMBER_OUTGOING_DIRECT); // 00 - buf.add16(shortaddr); // dest addr - // ApsFrame - buf.add16(Z_PROF_HA); // Home Automation profile - buf.add16(clusterId); // cluster - buf.add8(0x01); // srcEp - buf.add8(endpoint); // dstEp - buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame - buf.add16(groupaddr); // groupId - buf.add8(transacId); - // end of ApsFrame - buf.add8(0x01); // tag TODO + if (BAD_SHORTADDR != shortaddr) { + // send unicast message to an address + buf.add16(EZSP_sendUnicast); // 3400 + buf.add8(EMBER_OUTGOING_DIRECT); // 00 + buf.add16(shortaddr); // dest addr + // ApsFrame + buf.add16(Z_PROF_HA); // Home Automation profile + buf.add16(clusterId); // cluster + buf.add8(0x01); // srcEp + buf.add8(endpoint); // dstEp + buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame + buf.add16(groupaddr); // groupId + buf.add8(transacId); + // end of ApsFrame + buf.add8(0x01); // tag TODO - buf.add8(3 + len + (manuf ? 2 : 0)); - buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00) | (manuf ? 0x04 : 0x00)); // Frame Control Field - if (manuf) { - buf.add16(manuf); // add Manuf Id if not null - } - buf.add8(transacId); // Transaction Sequance Number - buf.add8(cmdId); - if (len > 0) { - buf.addBuffer(msg, len); // add the payload + buf.add8(3 + len + (manuf ? 2 : 0)); + buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00) | (manuf ? 0x04 : 0x00)); // Frame Control Field + if (manuf) { + buf.add16(manuf); // add Manuf Id if not null + } + buf.add8(transacId); // Transaction Sequance Number + buf.add8(cmdId); + if (len > 0) { + buf.addBuffer(msg, len); // add the payload + } + } else { + // send broadcast group address, aka groupcast + buf.add16(EZSP_sendMulticast); // 3800 + // ApsFrame + buf.add16(Z_PROF_HA); // Home Automation profile + buf.add16(clusterId); // cluster + buf.add8(0x01); // srcEp + buf.add8(endpoint); // broadcast endpoint for groupcast + buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame + buf.add16(groupaddr); // groupId + buf.add8(transacId); + // end of ApsFrame + buf.add8(0); // hops, 0x00 = EMBER_MAX_HOPS + buf.add8(7); // nonMemberRadius, 7 = infinite + buf.add8(0x01); // tag TODO + + buf.add8(3 + len + (manuf ? 2 : 0)); + buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00) | (manuf ? 0x04 : 0x00)); // Frame Control Field + if (manuf) { + buf.add16(manuf); // add Manuf Id if not null + } + buf.add8(transacId); // Transaction Sequance Number + buf.add8(cmdId); + if (len > 0) { + buf.addBuffer(msg, len); // add the payload + } } ZigbeeEZSPSendCmd(buf.buf(), buf.len(), true); - #endif // USE_ZIGBEE_EZSP } diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 569f522fe..7eeae7c15 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -713,6 +713,25 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind 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); + if (dstLongAddr) { + buf.add8(Z_Addr_IEEEAddress); // DstAddrMode - 0x03 = ADDRESS_64_BIT + buf.add64(dstLongAddr); + buf.add8(toendpoint); + } else { + buf.add8(Z_Addr_Group); // DstAddrMode - 0x01 = GROUP_ADDRESS + buf.add16(toGroup); + } + + EZ_SendZDO(srcDevice, unbind ? ZDO_UNBIND_REQ : ZDO_BIND_REQ, buf.buf(), buf.len()); +#endif // USE_ZIGBEE_EZSP + ResponseCmndDone(); } @@ -748,6 +767,14 @@ void CmndZbBindState(void) { 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[] = { 0x00 }; // index = 0 + + EZ_SendZDO(shortaddr, ZDO_Mgmt_Bind_req, buf, sizeof(buf)); +#endif // USE_ZIGBEE_EZSP + ResponseCmndDone(); }