Zigbee fixes

This commit is contained in:
Stephan Hadinger 2020-09-06 20:51:20 +02:00
parent b166d9f625
commit 8d49a4b037
6 changed files with 187 additions and 128 deletions

View File

@ -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) {

View File

@ -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
}

View File

@ -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) {

View File

@ -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)
}));
}

View File

@ -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
}
}

View File

@ -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"));