diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino index 046450927..bb7555757 100644 --- a/tasmota/xdrv_23_zigbee_1_headers.ino +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -21,7 +21,22 @@ // contains some definitions for functions used before their declarations -void ZigbeeZCLSend_Raw(uint16_t dtsAddr, uint16_t groupaddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId); +class ZigbeeZCLSendMessage { +public: + uint16_t shortaddr; + uint16_t groupaddr; + uint16_t clusterId; + uint8_t endpoint; + uint8_t cmdId; + uint16_t manuf; + bool clusterSpecific; + bool needResponse; + uint8_t transacId; // ZCL transaction number + const uint8_t *msg; + size_t len; +}; + +void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl); // get the result as a string (const char*) and nullptr if there is no field or the string is empty const char * getCaseInsensitiveConstCharNull(const JsonObject &json, const char *needle) { diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 9c8d10cdf..1155a6f2e 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -1028,7 +1028,19 @@ void ZCLFrame::parseReportAttributes(Z_attribute_list& attr_list) { SBuffer buf(2); buf.add8(_cmd_id); buf.add8(0x00); // Status = OK - ZigbeeZCLSend_Raw(_srcaddr, 0x0000, 0x0000 /*cluster*/, _srcendpoint, ZCL_DEFAULT_RESPONSE, false /* not cluster specific */, _manuf_code, buf.getBuffer(), buf.len(), false /* noresponse */, _transact_seq); + + ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ + _srcaddr, + 0x0000, + _cluster_id, + _srcendpoint, + ZCL_DEFAULT_RESPONSE, + _manuf_code, + false /* not cluster specific */, + false /* noresponse */, + _transact_seq, /* zcl transaction id */ + buf.getBuffer(), buf.len() + })); } } @@ -1078,6 +1090,48 @@ void ZCLFrame::generateCallBacks(Z_attribute_list& attr_list) { } } + +// A command has been sent to a device this device, or to a group +// 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; + + 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 ((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); + 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); + } + + } + } +} + // ZCL_READ_ATTRIBUTES void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) { uint32_t i = 0; @@ -1248,7 +1302,11 @@ void ZCLFrame::parseResponse(void) { void ZCLFrame::parseClusterSpecificCommand(Z_attribute_list& attr_list) { convertClusterSpecific(attr_list, _cluster_id, _cmd_id, _frame_control.b.direction, _srcaddr, _srcendpoint, _payload); #ifndef USE_ZIGBEE_NO_READ_ATTRIBUTES // read attributes unless disabled - sendHueUpdate(_srcaddr, _groupaddr, _cluster_id, _cmd_id, _frame_control.b.direction); + if (!_frame_control.b.direction) { // only handle server->client (i.e. device->coordinator) + if (_wasbroadcast) { // only update for broadcast messages since we don't see unicast from device to device and we wouldn't know the target + sendHueUpdate(BAD_SHORTADDR, _groupaddr, _cluster_id); + } + } #endif } diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index c533eb8ed..a32f637c4 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -174,7 +174,19 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus if (groupaddr) { shortaddr = BAD_SHORTADDR; // if group address, don't send to device } - ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, 0, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(shortaddr)); + uint8_t seq = zigbee_devices.getNextSeqNumber(shortaddr); + ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ + shortaddr, + groupaddr, + cluster /*cluster*/, + endpoint, + ZCL_READ_ATTRIBUTES, + 0, /* manuf */ + false /* not cluster specific */, + true /* response */, + seq, /* zcl transaction id */ + attrs, attrs_len + })); } return 0; // Fix GCC 10.1 warning } @@ -188,31 +200,6 @@ int32_t Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, return 0; // Fix GCC 10.1 warning } -// set a timer to read back the value in the future -void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint) { - uint32_t wait_ms = 0; - - switch (cluster) { - case 0x0006: // for On/Off - case 0x0009: // for Alamrs - wait_ms = 200; // wait 0.2 s - break; - case 0x0008: // for Dimmer - case 0x0300: // for Color - wait_ms = 1050; // wait 1.0 s - break; - case 0x0102: // for Shutters - wait_ms = 10000; // wait 10.0 s - break; - } - if (wait_ms) { - zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, Z_CAT_NONE, 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); - } - } -} - // returns true if char is 'x', 'y' or 'z' inline bool isXYZ(char c) { return (c >= 'x') && (c <= 'z'); @@ -280,52 +267,6 @@ void parseXYZ(const char *model, const SBuffer &payload, struct Z_XYZ_Var *xyz) } } -// works on big endiand hex only -// Returns if found: -// - cluster number -// - command number or 0xFF if command is part of the variable part -// - the payload in the form of a HEX string with x/y/z variables -void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t cmd, bool direction) { - if (direction) { return; } // no need to update if server->client - - int32_t z_cat = -1; - uint32_t wait_ms = 0; - - 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) { - uint8_t endpoint = 0; - if (BAD_SHORTADDR != shortaddr) { - 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); - 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); - } - - } - } -} - // Parse a cluster specific command, and try to convert into human readable void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) { diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 1f5d33a8f..d800eaf71 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -969,9 +969,18 @@ void Z_SendAFInfoRequest(uint16_t shortaddr) { uint8_t InfoReq[] = { 0x04, 0x00, 0x05, 0x00 }; - ZigbeeZCLSend_Raw(shortaddr, 0x0000 /*group*/, 0x0000 /*cluster*/, endpoint, ZCL_READ_ATTRIBUTES, - false /*clusterSpecific*/, 0x0000 /*manuf*/, - InfoReq, sizeof(InfoReq), true /*needResponse*/, transacid); + ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ + shortaddr, + 0x0000, /* group */ + 0x0000 /*cluster*/, + endpoint, + ZCL_READ_ATTRIBUTES, + 0x0000, /* manuf */ + false /* not cluster specific */, + true /* response */, + transacid, /* zcl transaction id */ + InfoReq, sizeof(InfoReq) + })); } diff --git a/tasmota/xdrv_23_zigbee_9_serial.ino b/tasmota/xdrv_23_zigbee_9_serial.ino index 3fdddfedb..f5056c58c 100644 --- a/tasmota/xdrv_23_zigbee_9_serial.ino +++ b/tasmota/xdrv_23_zigbee_9_serial.ino @@ -765,96 +765,96 @@ void CmndZbEZSPSend(void) // - transacId: 8-bits, transation id of message (should be incremented at each message), used both for Zigbee message number and ZCL message number // Returns: None // -void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, uint16_t manuf, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId) { +void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) { #ifdef USE_ZIGBEE_ZNP - SBuffer buf(32+len); + SBuffer buf(32+zcl.len); buf.add8(Z_SREQ | Z_AF); // 24 buf.add8(AF_DATA_REQUEST_EXT); // 02 - if (BAD_SHORTADDR == shortaddr) { // if no shortaddr we assume group address + if (BAD_SHORTADDR == zcl.shortaddr) { // if no shortaddr we assume group address buf.add8(Z_Addr_Group); // 01 - buf.add64(groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded + buf.add64(zcl.groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded buf.add8(0xFF); // dest endpoint is not used for group addresses } else { buf.add8(Z_Addr_ShortAddress); // 02 - buf.add64(shortaddr); // dest address, only 2 LSB, upper 6 MSB are discarded - buf.add8(endpoint); // dest endpoint + buf.add64(zcl.shortaddr); // dest address, only 2 LSB, upper 6 MSB are discarded + buf.add8(zcl.endpoint); // dest endpoint } buf.add16(0x0000); // dest Pan ID, 0x0000 = intra-pan buf.add8(0x01); // source endpoint - buf.add16(clusterId); - buf.add8(transacId); // transacId + buf.add16(zcl.clusterId); + buf.add8(zcl.transacId); // transacId buf.add8(0x30); // 30 options buf.add8(0x1E); // 1E radius - buf.add16(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.add16(3 + zcl.len + (zcl.manuf ? 2 : 0)); + buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field + if (zcl.manuf) { + buf.add16(zcl.manuf); // add Manuf Id if not null } - buf.add8(transacId); // Transaction Sequence Number - buf.add8(cmdId); - if (len > 0) { - buf.addBuffer(msg, len); // add the payload + buf.add8(zcl.transacId); // Transaction Sequence Number + buf.add8(zcl.cmdId); + if (zcl.len > 0) { + buf.addBuffer(zcl.msg, zcl.len); // add the payload } ZigbeeZNPSend(buf.getBuffer(), buf.len()); #endif // USE_ZIGBEE_ZNP #ifdef USE_ZIGBEE_EZSP - SBuffer buf(32+len); + SBuffer buf(32+zcl.len); - if (BAD_SHORTADDR != shortaddr) { + if (BAD_SHORTADDR != zcl.shortaddr) { // send unicast message to an address buf.add16(EZSP_sendUnicast); // 3400 buf.add8(EMBER_OUTGOING_DIRECT); // 00 - buf.add16(shortaddr); // dest addr + buf.add16(zcl.shortaddr); // dest addr // ApsFrame buf.add16(Z_PROF_HA); // Home Automation profile - buf.add16(clusterId); // cluster + buf.add16(zcl.clusterId); // cluster buf.add8(0x01); // srcEp - buf.add8(endpoint); // dstEp + buf.add8(zcl.endpoint); // dstEp buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame - buf.add16(groupaddr); // groupId - buf.add8(transacId); + buf.add16(zcl.groupaddr); // groupId + buf.add8(zcl.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(3 + zcl.len + (zcl.manuf ? 2 : 0)); + buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field + if (zcl.manuf) { + buf.add16(zcl.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(zcl.transacId); // Transaction Sequance Number + buf.add8(zcl.cmdId); + if (zcl.len > 0) { + buf.addBuffer(zcl.msg, zcl.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.add16(zcl.clusterId); // cluster buf.add8(0x01); // srcEp - buf.add8(endpoint); // broadcast endpoint for groupcast + buf.add8(zcl.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); + buf.add16(zcl.groupaddr); // groupId + buf.add8(zcl.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(3 + zcl.len + (zcl.manuf ? 2 : 0)); + buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field + if (zcl.manuf) { + buf.add16(zcl.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(zcl.transacId); // Transaction Sequance Number + buf.add8(zcl.cmdId); + if (zcl.len > 0) { + buf.addBuffer(zcl.msg, zcl.len); // add the payload } } diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 4dd7e99ad..fff151af6 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -139,7 +139,6 @@ void CmndZbReset(void) { // High-level function // Send a command specified as an HEX string for the workload. // The target endpoint is computed if zero, i.e. sent to the first known endpoint of the device. -// If cluster-specific, a timer may be set calling `zigbeeSetCommandTimer()`, for ex to coalesce attributes or Aqara presence sensor // // Inputs: // - shortaddr: 16-bits short address, or 0x0000 if group address @@ -176,11 +175,24 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, } // everything is good, we can send the command - ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, cmd, clusterSpecific, manuf, buf.getBuffer(), buf.len(), true, zigbee_devices.getNextSeqNumber(shortaddr)); + + uint8_t seq = zigbee_devices.getNextSeqNumber(shortaddr); + ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ + shortaddr, + groupaddr, + cluster /*cluster*/, + endpoint, + cmd, + manuf, /* manuf */ + clusterSpecific /* not cluster specific */, + true /* response */, + seq, /* zcl transaction id */ + buf.getBuffer(), buf.len() + })); // now set the timer, if any, to read back the state later if (clusterSpecific) { #ifndef USE_ZIGBEE_NO_READ_ATTRIBUTES // read back attribute value unless it is disabled - zigbeeSetCommandTimer(shortaddr, groupaddr, cluster, endpoint); + sendHueUpdate(shortaddr, groupaddr, cluster, endpoint); #endif } } @@ -202,7 +214,7 @@ void ZbApplyMultiplier(double &val_d, int8_t multiplier) { // Parse "Report", "Write", "Response" or "Condig" attribute // Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01) -void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint32_t operation) { +void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) { SBuffer buf(200); // buffer to store the binary output of attibutes if (nullptr == XdrvMailbox.command) { @@ -376,7 +388,19 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t } // all good, send the packet - ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, operation, false /* not cluster specific */, manuf, buf.getBuffer(), buf.len(), false /* noresponse */, zigbee_devices.getNextSeqNumber(device)); + uint8_t seq = zigbee_devices.getNextSeqNumber(device); + ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ + device, + groupaddr, + cluster /*cluster*/, + endpoint, + operation, + manuf, /* manuf */ + false /* not cluster specific */, + false /* no response */, + seq, /* zcl transaction id */ + buf.getBuffer(), buf.len() + })); ResponseCmndDone(); } @@ -510,7 +534,7 @@ void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr, // Parse the "Send" attribute and send the command -void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint32_t operation) { +void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) { // ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":5} // ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"} // ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]} @@ -602,7 +626,19 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr } if (attrs_len > 0) { - ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, operation, false, manuf, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device)); + uint8_t seq = zigbee_devices.getNextSeqNumber(device); + ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ + device, + groupaddr, + cluster /*cluster*/, + endpoint, + operation, + manuf, /* manuf */ + false /* not cluster specific */, + true /* response */, + seq, /* zcl transaction id */ + attrs, attrs_len + })); ResponseCmndDone(); } else { ResponseCmndChar_P(PSTR("Missing parameters"));