From 49ebd870ca256febaf74f7dbd33310cad2d4991e Mon Sep 17 00:00:00 2001 From: Hadinger Date: Sun, 2 Feb 2020 20:53:49 +0100 Subject: [PATCH] Add ``ZbBind`` (experimental) and bug fixes --- tasmota/CHANGELOG.md | 1 + tasmota/i18n.h | 1 + tasmota/xdrv_23_zigbee_3_devices.ino | 22 ++++- tasmota/xdrv_23_zigbee_5_converters.ino | 19 ++-- tasmota/xdrv_23_zigbee_8_parsers.ino | 3 + tasmota/xdrv_23_zigbee_9_impl.ino | 125 ++++++++++++++++++------ 6 files changed, 134 insertions(+), 37 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index b5036467c..4d2834a4e 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -4,6 +4,7 @@ - Change wifi connectivity stability (#7602) - Add ``SetOption84 1`` sends AWS IoT device shadow updates (alternative to retained) +- Add ``ZbBind`` (experimental) and bug fixes ### 8.1.0.4 20200116 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index fdf07acd6..d44030569 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -493,6 +493,7 @@ #define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent" #define D_JSON_ZIGBEE_RECEIVED "ZbReceived" #define D_JSON_ZIGBEE_RECEIVED_LEGACY "ZigbeeReceived" +#define D_CMND_ZIGBEE_BIND "Bind" // Commands xdrv_25_A4988_Stepper.ino #define D_CMND_MOTOR "MOTOR" diff --git a/tasmota/xdrv_23_zigbee_3_devices.ino b/tasmota/xdrv_23_zigbee_3_devices.ino index 5db3986e4..984842380 100644 --- a/tasmota/xdrv_23_zigbee_3_devices.ino +++ b/tasmota/xdrv_23_zigbee_3_devices.ino @@ -71,6 +71,8 @@ public: uint16_t isKnownIndex(uint32_t index) const; uint16_t isKnownFriendlyName(const char * name) const; + uint64_t getDeviceLongAddr(uint16_t shortaddr) const; + // Add new device, provide ShortAddr and optional longAddr // If it is already registered, update information, otherwise create the entry void updateDevice(uint16_t shortaddr, uint64_t longaddr = 0); @@ -142,6 +144,7 @@ private: static int32_t findClusterEndpoint(const std::vector & vecOfElements, uint16_t element); Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist + const Z_Device & getShortAddrConst(uint16_t shortaddr) const ; // find Device from shortAddr, creates it if does not exist Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist int32_t findShortAddr(uint16_t shortaddr) const; @@ -160,6 +163,9 @@ private: Z_Devices zigbee_devices = Z_Devices(); +// Local coordinator information +uint64_t localIEEEAddr = 0; + // https://thispointer.com/c-how-to-find-an-element-in-vector-and-get-its-index/ template < typename T> bool Z_Devices::findInVector(const std::vector & vecOfElements, const T & element) { @@ -326,6 +332,11 @@ uint16_t Z_Devices::isKnownFriendlyName(const char * name) const { } } +uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const { + const Z_Device & device = getShortAddrConst(shortaddr); + return device.longaddr; +} + // // We have a seen a shortaddr on the network, get the corresponding // @@ -335,9 +346,18 @@ Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) { if (found >= 0) { return _devices[found]; } -//Serial.printf("Device entry created for shortaddr = 0x%02X, found = %d\n", shortaddr, found); + //Serial.printf("Device entry created for shortaddr = 0x%02X, found = %d\n", shortaddr, found); return createDeviceEntry(shortaddr, 0); } +// Same version but Const +const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const { + if (!shortaddr) { return *(Z_Device*) nullptr; } // this is not legal + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + return _devices[found]; + } + return *((Z_Device*)nullptr); +} // find the Device object by its longaddr (unique key if not null) Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) { diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 0518ec6be..fa79a0796 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -39,10 +39,10 @@ class ZCLFrame { public: ZCLFrame(uint8_t frame_control, uint16_t manuf_code, uint8_t transact_seq, uint8_t cmd_id, - const char *buf, size_t buf_len, uint16_t clusterid = 0, uint16_t groupid = 0, - uint16_t srcaddr = 0, uint8_t srcendpoint = 0, uint8_t dstendpoint = 0, uint8_t wasbroadcast = 0, - uint8_t linkquality = 0, uint8_t securityuse = 0, uint8_t seqnumber = 0, - uint32_t timestamp = 0): + const char *buf, size_t buf_len, uint16_t clusterid, uint16_t groupid, + uint16_t srcaddr, uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast, + uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber, + uint32_t timestamp): _cmd_id(cmd_id), _manuf_code(manuf_code), _transact_seq(transact_seq), _payload(buf_len ? buf_len : 250), // allocate the data frame from source or preallocate big enough _cluster_id(clusterid), _group_id(groupid), @@ -74,9 +74,9 @@ public: } static ZCLFrame parseRawFrame(const SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid, - uint16_t srcaddr = 0, uint8_t srcendpoint = 0, uint8_t dstendpoint = 0, uint8_t wasbroadcast = 0, - uint8_t linkquality = 0, uint8_t securityuse = 0, uint8_t seqnumber = 0, - uint32_t timestamp = 0) { // parse a raw frame and build the ZCL frame object + uint16_t srcaddr, uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast, + uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber, + uint32_t timestamp) { // parse a raw frame and build the ZCL frame object uint32_t i = offset; ZCLHeaderFrameControl_t frame_control; uint16_t manuf_code = 0; @@ -92,7 +92,10 @@ public: cmd_id = buf.get8(i++); ZCLFrame zcl_frame(frame_control.d8, manuf_code, transact_seq, cmd_id, (const char *)(buf.buf() + i), len + offset - i, - clusterid, groupid); + clusterid, groupid, + srcaddr, srcendpoint, dstendpoint, wasbroadcast, + linkquality, securityuse, seqnumber, + timestamp); return zcl_frame; } diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 74a900f00..668f85471 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -33,6 +33,9 @@ int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) { uint8_t device_state = buf.get8(14); uint8_t device_associated = buf.get8(15); + // keep track of the local IEEE address + localIEEEAddr = long_adr; + char hex[20]; Uint64toHex(long_adr, hex, 64); Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 2b529c8d6..43f7a2ef4 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -33,19 +33,19 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|" - D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME ; + D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND ; const char kZigbeeCommands[] PROGMEM = D_PRFX_ZIGBEE "|" // legacy prefix -- deprecated D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|" - D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME ; + D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND ; void (* const ZigbeeCommand[])(void) PROGMEM = { - &CmndZigbeeZNPSend, &CmndZigbeePermitJoin, - &CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend, - &CmndZigbeeProbe, &CmndZigbeeRead, &CmndZigbeeZNPReceive, - &CmndZigbeeForget, &CmndZigbeeSave, &CmndZigbeeName + &CmndZbZNPSend, &CmndZbPermitJoin, + &CmndZbStatus, &CmndZbReset, &CmndZbSend, + &CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive, + &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind }; int32_t ZigbeeProcessInput(class SBuffer &buf) { @@ -257,7 +257,7 @@ const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM = { Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x01 /* STARTOPT_CLEAR_CONFIG */}; //"2605030101"; // Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 len, 0x01 STARTOPT_CLEAR_CONFIG // Do a factory reset of the CC2530 -void CmndZigbeeReset(void) { +void CmndZbReset(void) { if (ZigbeeSerial) { switch (XdrvMailbox.payload) { case 1: @@ -272,7 +272,7 @@ void CmndZigbeeReset(void) { } } -void CmndZigbeeZNPSendOrReceive(bool send) +void CmndZbZNPSendOrReceive(bool send) { if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) { uint8_t code; @@ -300,14 +300,14 @@ void CmndZigbeeZNPSendOrReceive(bool send) } // For debug purposes only, simulates a message received -void CmndZigbeeZNPReceive(void) +void CmndZbZNPReceive(void) { - CmndZigbeeZNPSendOrReceive(false); + CmndZbZNPSendOrReceive(false); } -void CmndZigbeeZNPSend(void) +void CmndZbZNPSend(void) { - CmndZigbeeZNPSendOrReceive(true); + CmndZbZNPSendOrReceive(true); } void ZigbeeZNPSend(const uint8_t *msg, size_t len) { @@ -442,7 +442,7 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) { ResponseCmndDone(); } -void CmndZigbeeSend(void) { +void CmndZbSend(void) { // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} } // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} } // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"0xFF"} } @@ -465,9 +465,14 @@ void CmndZigbeeSend(void) { uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint String cmd_str = ""; // the actual low-level command, either specified or computed - const JsonVariant &val_device = getCaseInsensitive(json, PSTR("device")); - if (nullptr != &val_device) { device = strToUInt(val_device); } - const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("endpoint")); + const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); + if (nullptr != &val_device) { + device = zigbee_devices.parseDeviceParam(val_device.as()); + if (0xFFFF == device) { ResponseCmndChar("Invalid parameter"); return; } + } + if ((nullptr == &val_device) || (0x000 == device)) { ResponseCmndChar("Unknown device"); return; } + + const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint")); if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } const JsonVariant &val_cmd = getCaseInsensitive(json, PSTR("Send")); if (nullptr != &val_cmd) { @@ -547,8 +552,64 @@ void CmndZigbeeSend(void) { } +ZBM(ZBS_BIND_REQ, Z_SREQ | Z_ZDO, ZDO_BIND_REQ, + 0,0, // dstAddr - 16 bits, device to send the bind to + 0,0,0,0,0,0,0,0, // srcAddr - 64 bits, IEEE binding source + 0x00, // source endpoint + 0x00, 0x00, // cluster + 0x03, // DstAddrMode - 0x03 = ADDRESS_64_BIT + 0,0,0,0,0,0,0,0, // dstAddr - 64 bits, IEEE binding destination, i.e. coordinator + 0x01 // dstEndpoint - 0x01 for coordinator +) + +void CmndZbBind(void) { + // ZbBind { "device":"0x1234", "endpoint":1, "cluster":6 } + + // local endpoint is always 1, IEEE addresses are calculated + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + DynamicJsonBuffer jsonBuf; + JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data); + if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; } + + // params + // static char delim[] = ", "; // delimiters for parameters + uint16_t device = 0xFFFF; // 0xFFFF is broadcast, so considered valid + uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint + uint16_t cluster = 0; // 0xFFFF is invalid + uint32_t group = 0xFFFFFFFF; // 16 bits values, otherwise 0xFFFFFFFF is unspecified + + const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); + if (nullptr != &val_device) { + device = zigbee_devices.parseDeviceParam(val_device.as()); + if (0xFFFF == device) { ResponseCmndChar("Invalid parameter"); return; } + } + if ((nullptr == &val_device) || (0x000 == device)) { ResponseCmndChar("Unknown device"); return; } + + const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint")); + if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } + const JsonVariant &val_cluster = getCaseInsensitive(json, PSTR("Cluster")); + if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); } + + // TODO compute endpoint from cluster + + SBuffer buf(sizeof(ZBS_BIND_REQ)); + buf.add8(Z_SREQ | Z_ZDO); + buf.add8(ZDO_BIND_REQ); + buf.add16(device); + buf.add64(zigbee_devices.getDeviceLongAddr(device)); + buf.add8(endpoint); + buf.add16(cluster); + buf.add8(0x03); // DstAddrMode - 0x03 = ADDRESS_64_BIT + buf.add64(localIEEEAddr); // coordinatore IEEE address + buf.add8(0x01); // local endpoint = 1 + + ZigbeeZNPSend(buf.getBuffer(), buf.len()); + + ResponseCmndDone(); +} + // Probe a specific device to get its endpoints and supported clusters -void CmndZigbeeProbe(void) { +void CmndZbProbe(void) { if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } @@ -560,7 +621,7 @@ void CmndZigbeeProbe(void) { } // Specify, read or erase a Friendly Name -void CmndZigbeeName(void) { +void CmndZbName(void) { // Syntax is: // ZigbeeName , - assign a friendly name // ZigbeeName - display the current friendly name @@ -589,7 +650,7 @@ void CmndZigbeeName(void) { } // Remove an old Zigbee device from the list of known devices, use ZigbeeStatus to know all registered devices -void CmndZigbeeForget(void) { +void CmndZbForget(void) { if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } @@ -604,7 +665,7 @@ void CmndZigbeeForget(void) { } // Save Zigbee information to flash -void CmndZigbeeSave(void) { +void CmndZbSave(void) { if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } saveZigbeeDevices(); @@ -613,7 +674,7 @@ void CmndZigbeeSave(void) { } // Send an attribute read command to a device, specifying cluster and list of attributes -void CmndZigbeeRead(void) { +void CmndZbRead(void) { // ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5} // ZigbeeRead {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Attr":"0x0005"} // ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":[5,6,7,4]} @@ -629,10 +690,14 @@ void CmndZigbeeRead(void) { size_t attrs_len = 0; uint8_t* attrs = nullptr; // empty string is valid - const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); - if (nullptr != &val_device) { device = strToUInt(val_device); } - const JsonVariant val_cluster = getCaseInsensitive(json, PSTR("Cluster")); + if (nullptr != &val_device) { + device = zigbee_devices.parseDeviceParam(val_device.as()); + if (0xFFFF == device) { ResponseCmndChar("Invalid parameter"); return; } + } + if ((nullptr == &val_device) || (0x000 == device)) { ResponseCmndChar("Unknown device"); return; } + + const JsonVariant &val_cluster = getCaseInsensitive(json, PSTR("Cluster")); if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); } const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint")); if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } @@ -659,14 +724,18 @@ void CmndZigbeeRead(void) { } } - ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */); + if ((0 != endpoint) && (attrs_len > 0)) { + ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */); + ResponseCmndDone(); + } else { + ResponseCmndChar("Missing parameters"); + } if (attrs) { delete[] attrs; } - ResponseCmndDone(); } // Allow or Deny pairing of new Zigbee devices -void CmndZigbeePermitJoin(void) +void CmndZbPermitJoin(void) { if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } uint32_t payload = XdrvMailbox.payload; @@ -683,7 +752,7 @@ void CmndZigbeePermitJoin(void) ResponseCmndDone(); } -void CmndZigbeeStatus(void) { +void CmndZbStatus(void) { if (ZigbeeSerial) { if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);