2019-09-29 14:38:26 +01:00
|
|
|
/*
|
2019-10-27 10:13:24 +00:00
|
|
|
xdrv_23_zigbee.ino - zigbee support for Tasmota
|
2019-09-29 14:38:26 +01:00
|
|
|
|
2019-12-31 13:23:34 +00:00
|
|
|
Copyright (C) 2020 Theo Arends and Stephan Hadinger
|
2019-09-29 14:38:26 +01:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef USE_ZIGBEE
|
|
|
|
|
|
|
|
int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) {
|
|
|
|
// Ex= 6700.00.6263151D004B1200.0000.07.09.02.83869991
|
|
|
|
// IEEE Adr (8 bytes) = 0x00124B001D156362
|
|
|
|
// Short Addr (2 bytes) = 0x0000
|
|
|
|
// Device Type (1 byte) = 0x07 (coord?)
|
|
|
|
// Device State (1 byte) = 0x09 (coordinator started)
|
|
|
|
// NumAssocDevices (1 byte) = 0x02
|
|
|
|
// List of devices: 0x8683, 0x9199
|
|
|
|
Z_IEEEAddress long_adr = buf.get64(3);
|
|
|
|
Z_ShortAddress short_adr = buf.get16(11);
|
|
|
|
uint8_t device_type = buf.get8(13);
|
|
|
|
uint8_t device_state = buf.get8(14);
|
|
|
|
uint8_t device_associated = buf.get8(15);
|
|
|
|
|
2020-02-02 19:53:49 +00:00
|
|
|
// keep track of the local IEEE address
|
|
|
|
localIEEEAddr = long_adr;
|
|
|
|
|
2019-09-29 14:38:26 +01:00
|
|
|
char hex[20];
|
|
|
|
Uint64toHex(long_adr, hex, 64);
|
2019-11-09 08:25:15 +00:00
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
2020-03-01 10:25:59 +00:00
|
|
|
"\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\""
|
2019-09-29 14:38:26 +01:00
|
|
|
",\"DeviceType\":%d,\"DeviceState\":%d"
|
|
|
|
",\"NumAssocDevices\":%d"),
|
|
|
|
ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state,
|
|
|
|
device_associated);
|
|
|
|
|
|
|
|
if (device_associated > 0) {
|
|
|
|
uint idx = 16;
|
|
|
|
ResponseAppend_P(PSTR(",\"AssocDevicesList\":["));
|
|
|
|
for (uint32_t i = 0; i < device_associated; i++) {
|
|
|
|
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
|
|
|
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(idx));
|
|
|
|
idx += 2;
|
|
|
|
}
|
|
|
|
ResponseAppend_P(PSTR("]"));
|
|
|
|
}
|
|
|
|
|
|
|
|
ResponseJsonEnd(); // append '}'
|
|
|
|
ResponseJsonEnd(); // append '}'
|
2019-11-09 08:25:15 +00:00
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
|
2019-09-29 14:38:26 +01:00
|
|
|
XdrvRulesProcess();
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf) {
|
|
|
|
// Check the status after NV Init "ZNP Has Configured"
|
|
|
|
// Good response should be 610700 or 610709 (Success or Created)
|
|
|
|
// We only filter the response on 6107 and check the code in this function
|
|
|
|
uint8_t status = buf.get8(2);
|
|
|
|
if ((0x00 == status) || (0x09 == status)) {
|
|
|
|
return 0; // Ok, continue
|
|
|
|
} else {
|
|
|
|
return -2; // Error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-13 11:56:52 +01:00
|
|
|
const char Z_RebootReason[] PROGMEM = "Power-up|External|Watchdog";
|
|
|
|
|
|
|
|
int32_t Z_Reboot(int32_t res, class SBuffer &buf) {
|
|
|
|
// print information about the reboot of device
|
|
|
|
// 4180.02.02.00.02.06.03
|
2019-10-27 10:13:24 +00:00
|
|
|
//
|
2019-10-13 11:56:52 +01:00
|
|
|
uint8_t reason = buf.get8(2);
|
|
|
|
uint8_t transport_rev = buf.get8(3);
|
|
|
|
uint8_t product_id = buf.get8(4);
|
|
|
|
uint8_t major_rel = buf.get8(5);
|
|
|
|
uint8_t minor_rel = buf.get8(6);
|
|
|
|
uint8_t hw_rev = buf.get8(7);
|
|
|
|
char reason_str[12];
|
|
|
|
|
|
|
|
if (reason > 3) { reason = 3; }
|
|
|
|
GetTextIndexed(reason_str, sizeof(reason_str), reason, Z_RebootReason);
|
|
|
|
|
2019-11-09 08:25:15 +00:00
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
2020-03-16 17:55:58 +00:00
|
|
|
"\"Status\":%d,\"Message\":\"CC2530 booted\",\"RestartReason\":\"%s\""
|
2019-10-13 11:56:52 +01:00
|
|
|
",\"MajorRel\":%d,\"MinorRel\":%d}}"),
|
2020-03-16 17:55:58 +00:00
|
|
|
ZIGBEE_STATUS_BOOT, reason_str,
|
2019-10-13 11:56:52 +01:00
|
|
|
major_rel, minor_rel);
|
|
|
|
|
2019-11-09 08:25:15 +00:00
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
|
2019-10-13 11:56:52 +01:00
|
|
|
XdrvRulesProcess();
|
|
|
|
|
|
|
|
if ((0x02 == major_rel) && (0x06 == minor_rel)) {
|
|
|
|
return 0; // version 2.6.x is ok
|
|
|
|
} else {
|
|
|
|
return ZIGBEE_LABEL_UNSUPPORTED_VERSION; // abort
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-29 14:38:26 +01:00
|
|
|
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
|
|
|
|
// check that the version is supported
|
|
|
|
// typical version for ZNP 1.2
|
|
|
|
// 61020200-02.06.03.D9143401.0200000000
|
|
|
|
// TranportRev = 02
|
|
|
|
// Product = 00
|
|
|
|
// MajorRel = 2
|
|
|
|
// MinorRel = 6
|
|
|
|
// MaintRel = 3
|
|
|
|
// Revision = 20190425 d (0x013414D9)
|
|
|
|
uint8_t major_rel = buf.get8(4);
|
|
|
|
uint8_t minor_rel = buf.get8(5);
|
|
|
|
uint8_t maint_rel = buf.get8(6);
|
|
|
|
uint32_t revision = buf.get32(7);
|
|
|
|
|
2019-11-09 08:25:15 +00:00
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
2019-09-29 14:38:26 +01:00
|
|
|
"\"Status\":%d,\"MajorRel\":%d,\"MinorRel\":%d"
|
|
|
|
",\"MaintRel\":%d,\"Revision\":%d}}"),
|
|
|
|
ZIGBEE_STATUS_CC_VERSION, major_rel, minor_rel,
|
|
|
|
maint_rel, revision);
|
|
|
|
|
2019-11-09 08:25:15 +00:00
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
|
2019-09-29 14:38:26 +01:00
|
|
|
XdrvRulesProcess();
|
|
|
|
|
|
|
|
if ((0x02 == major_rel) && (0x06 == minor_rel)) {
|
|
|
|
return 0; // version 2.6.x is ok
|
|
|
|
} else {
|
|
|
|
return ZIGBEE_LABEL_UNSUPPORTED_VERSION; // abort
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match) {
|
|
|
|
if ( (pgm_read_byte(&match[0]) == buf.get8(0)) &&
|
|
|
|
(pgm_read_byte(&match[1]) == buf.get8(1)) ) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) {
|
|
|
|
// we received a PermitJoin status change
|
|
|
|
uint8_t duration = buf.get8(2);
|
|
|
|
uint8_t status_code;
|
|
|
|
const char* message;
|
|
|
|
|
|
|
|
if (0xFF == duration) {
|
|
|
|
status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_XX;
|
|
|
|
message = PSTR("Enable Pairing mode until next boot");
|
|
|
|
} else if (duration > 0) {
|
|
|
|
status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_60;
|
|
|
|
message = PSTR("Enable Pairing mode for %d seconds");
|
|
|
|
} else {
|
|
|
|
status_code = ZIGBEE_STATUS_PERMITJOIN_CLOSE;
|
|
|
|
message = PSTR("Disable Pairing mode");
|
|
|
|
}
|
2019-11-09 08:25:15 +00:00
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
2019-09-29 14:38:26 +01:00
|
|
|
"\"Status\":%d,\"Message\":\""),
|
|
|
|
status_code);
|
|
|
|
ResponseAppend_P(message, duration);
|
|
|
|
ResponseAppend_P(PSTR("\"}}"));
|
|
|
|
|
2019-11-09 08:25:15 +00:00
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
|
2019-09-29 14:38:26 +01:00
|
|
|
XdrvRulesProcess();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-02-23 15:46:00 +00:00
|
|
|
// Send ZDO_IEEE_ADDR_REQ request to get IEEE long address
|
|
|
|
void Z_SendIEEEAddrReq(uint16_t shortaddr) {
|
|
|
|
uint8_t IEEEAddrReq[] = { Z_SREQ | Z_ZDO, ZDO_IEEE_ADDR_REQ,
|
|
|
|
Z_B0(shortaddr), Z_B1(shortaddr), 0x00, 0x00 };
|
|
|
|
|
|
|
|
ZigbeeZNPSend(IEEEAddrReq, sizeof(IEEEAddrReq));
|
|
|
|
}
|
|
|
|
|
2019-09-29 14:38:26 +01:00
|
|
|
// Send ACTIVE_EP_REQ to collect active endpoints for this address
|
|
|
|
void Z_SendActiveEpReq(uint16_t shortaddr) {
|
|
|
|
uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ,
|
|
|
|
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
|
|
|
|
|
|
|
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* Z_DeviceType[] = { "Coordinator", "Router", "End Device", "Unknown" };
|
|
|
|
int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) {
|
|
|
|
// Received ZDO_NODE_DESC_RSP
|
|
|
|
Z_ShortAddress srcAddr = buf.get16(2);
|
|
|
|
uint8_t status = buf.get8(4);
|
|
|
|
Z_ShortAddress nwkAddr = buf.get16(5);
|
|
|
|
uint8_t logicalType = buf.get8(7);
|
|
|
|
uint8_t apsFlags = buf.get8(8);
|
|
|
|
uint8_t MACCapabilityFlags = buf.get8(9);
|
|
|
|
uint16_t manufacturerCapabilities = buf.get16(10);
|
|
|
|
uint8_t maxBufferSize = buf.get8(12);
|
|
|
|
uint16_t maxInTransferSize = buf.get16(13);
|
|
|
|
uint16_t serverMask = buf.get16(15);
|
|
|
|
uint16_t maxOutTransferSize = buf.get16(17);
|
|
|
|
uint8_t descriptorCapabilities = buf.get8(19);
|
|
|
|
|
|
|
|
if (0 == status) {
|
|
|
|
uint8_t deviceType = logicalType & 0x7; // 0=coordinator, 1=router, 2=end device
|
|
|
|
if (deviceType > 3) { deviceType = 3; }
|
|
|
|
bool complexDescriptorAvailable = (logicalType & 0x08) ? 1 : 0;
|
|
|
|
|
2019-11-09 08:25:15 +00:00
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
2019-09-29 14:38:26 +01:00
|
|
|
"\"Status\":%d,\"NodeType\":\"%s\",\"ComplexDesc\":%s}}"),
|
|
|
|
ZIGBEE_STATUS_NODE_DESC, Z_DeviceType[deviceType],
|
|
|
|
complexDescriptorAvailable ? "true" : "false"
|
|
|
|
);
|
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
2019-09-29 14:38:26 +01:00
|
|
|
XdrvRulesProcess();
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
|
|
|
|
// Received ZDO_ACTIVE_EP_RSP
|
|
|
|
Z_ShortAddress srcAddr = buf.get16(2);
|
|
|
|
uint8_t status = buf.get8(4);
|
|
|
|
Z_ShortAddress nwkAddr = buf.get16(5);
|
|
|
|
uint8_t activeEpCount = buf.get8(7);
|
|
|
|
uint8_t* activeEpList = (uint8_t*) buf.charptr(8);
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < activeEpCount; i++) {
|
2020-03-17 17:46:05 +00:00
|
|
|
zigbee_devices.addEndpoint(nwkAddr, activeEpList[i]);
|
2019-09-29 14:38:26 +01:00
|
|
|
}
|
|
|
|
|
2019-11-09 08:25:15 +00:00
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
2019-09-29 14:38:26 +01:00
|
|
|
"\"Status\":%d,\"ActiveEndpoints\":["),
|
|
|
|
ZIGBEE_STATUS_ACTIVE_EP);
|
|
|
|
for (uint32_t i = 0; i < activeEpCount; i++) {
|
|
|
|
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
|
|
|
ResponseAppend_P(PSTR("\"0x%02X\""), activeEpList[i]);
|
|
|
|
}
|
|
|
|
ResponseAppend_P(PSTR("]}}"));
|
2019-10-06 11:40:58 +01:00
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
2019-09-29 14:38:26 +01:00
|
|
|
XdrvRulesProcess();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
void Z_SendAFInfoRequest(uint16_t shortaddr, uint8_t endpoint, uint16_t clusterid, uint8_t transacid) {
|
|
|
|
SBuffer buf(100);
|
|
|
|
buf.add8(Z_SREQ | Z_AF); // 24
|
|
|
|
buf.add8(AF_DATA_REQUEST); // 01
|
|
|
|
buf.add16(shortaddr);
|
|
|
|
buf.add8(endpoint); // dest endpoint
|
|
|
|
buf.add8(0x01); // source endpoint
|
|
|
|
buf.add16(clusterid);
|
|
|
|
buf.add8(transacid);
|
|
|
|
buf.add8(0x30); // 30 options
|
|
|
|
buf.add8(0x1E); // 1E radius
|
|
|
|
|
|
|
|
buf.add8(3 + 2*sizeof(uint16_t)); // Len = 0x07
|
|
|
|
buf.add8(0x00); // Frame Control Field
|
2019-10-20 15:43:50 +01:00
|
|
|
buf.add8(transacid); // Transaction Sequence Number
|
2019-10-06 11:40:58 +01:00
|
|
|
buf.add8(ZCL_READ_ATTRIBUTES); // 00 Command
|
|
|
|
buf.add16(0x0004); // 0400 ManufacturerName
|
|
|
|
buf.add16(0x0005); // 0500 ModelIdentifier
|
|
|
|
|
|
|
|
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
|
|
|
}
|
|
|
|
|
2020-02-23 15:46:00 +00:00
|
|
|
int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) {
|
|
|
|
uint8_t status = buf.get8(2);
|
|
|
|
Z_IEEEAddress ieeeAddr = buf.get64(3);
|
|
|
|
Z_ShortAddress nwkAddr = buf.get16(11);
|
|
|
|
// uint8_t startIndex = buf.get8(13);
|
|
|
|
// uint8_t numAssocDev = buf.get8(14);
|
|
|
|
|
|
|
|
if (0 == status) { // SUCCESS
|
|
|
|
zigbee_devices.updateDevice(nwkAddr, ieeeAddr);
|
|
|
|
char hex[20];
|
|
|
|
Uint64toHex(ieeeAddr, hex, 64);
|
|
|
|
// Ping response
|
2020-03-14 13:17:30 +00:00
|
|
|
const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
|
2020-02-23 15:46:00 +00:00
|
|
|
if (friendlyName) {
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
|
2020-03-01 10:25:59 +00:00
|
|
|
",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\""
|
2020-03-14 13:17:30 +00:00
|
|
|
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), nwkAddr, hex, friendlyName);
|
2020-02-23 15:46:00 +00:00
|
|
|
} else {
|
2020-03-01 10:25:59 +00:00
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
|
|
|
|
",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\""
|
|
|
|
"}}"), nwkAddr, hex);
|
2020-02-23 15:46:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
|
|
|
XdrvRulesProcess();
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-03-01 10:25:59 +00:00
|
|
|
int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) {
|
|
|
|
Z_ShortAddress nwkAddr = buf.get16(2);
|
|
|
|
uint8_t status = buf.get8(4);
|
2020-03-14 13:17:30 +00:00
|
|
|
char status_message[32];
|
|
|
|
|
|
|
|
strncpy_P(status_message, (const char*) getZigbeeStatusMessage(status), sizeof(status_message));
|
|
|
|
status_message[sizeof(status_message)-1] = 0; // truncate if needed, strlcpy is safer but strlcpy_P does not exist
|
2020-03-01 10:25:59 +00:00
|
|
|
|
2020-03-14 13:17:30 +00:00
|
|
|
const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
|
2020-03-01 10:25:59 +00:00
|
|
|
if (friendlyName) {
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
|
|
|
|
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""
|
2020-03-14 13:17:30 +00:00
|
|
|
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
|
|
|
|
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
|
|
|
|
"}}"), nwkAddr, friendlyName, status, status_message);
|
2020-03-01 10:25:59 +00:00
|
|
|
} else {
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
|
2020-03-14 13:17:30 +00:00
|
|
|
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
|
|
|
|
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
|
|
|
|
"}}"), nwkAddr, status, status_message);
|
2020-03-01 10:25:59 +00:00
|
|
|
}
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
|
|
|
XdrvRulesProcess();
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-03-14 13:17:30 +00:00
|
|
|
//
|
|
|
|
// Report any AF_DATA_CONFIRM message
|
|
|
|
// Ex: {"ZbConfirm":{"Endpoint":1,"Status":0,"StatusMessage":"SUCCESS"}}
|
|
|
|
//
|
|
|
|
int32_t Z_DataConfirm(int32_t res, const class SBuffer &buf) {
|
|
|
|
uint8_t status = buf.get8(2);
|
|
|
|
uint8_t endpoint = buf.get8(3);
|
|
|
|
//uint8_t transId = buf.get8(4);
|
|
|
|
char status_message[32];
|
|
|
|
|
|
|
|
if (status) { // only report errors
|
2020-03-17 22:00:40 +00:00
|
|
|
const char * statm = (const char*) getZigbeeStatusMessage(status);
|
|
|
|
if (nullptr == statm) { statm = PSTR(""); }
|
|
|
|
strncpy_P(status_message, statm, sizeof(status_message));
|
2020-03-14 13:17:30 +00:00
|
|
|
status_message[sizeof(status_message)-1] = 0; // truncate if needed, strlcpy is safer but strlcpy_P does not exist
|
|
|
|
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_CONFIRM "\":{\"" D_CMND_ZIGBEE_ENDPOINT "\":%d"
|
|
|
|
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
|
|
|
|
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
|
|
|
|
"}}"), endpoint, status, status_message);
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
|
|
|
XdrvRulesProcess();
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-09-29 14:38:26 +01:00
|
|
|
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
|
|
|
|
Z_ShortAddress srcAddr = buf.get16(2);
|
|
|
|
Z_ShortAddress nwkAddr = buf.get16(4);
|
|
|
|
Z_IEEEAddress ieeeAddr = buf.get64(6);
|
|
|
|
uint8_t capabilities = buf.get8(14);
|
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
zigbee_devices.updateDevice(nwkAddr, ieeeAddr);
|
2019-09-29 14:38:26 +01:00
|
|
|
|
|
|
|
char hex[20];
|
|
|
|
Uint64toHex(ieeeAddr, hex, 64);
|
2019-11-09 08:25:15 +00:00
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
2020-03-17 17:46:05 +00:00
|
|
|
"\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\""
|
2019-09-29 14:38:26 +01:00
|
|
|
",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"),
|
|
|
|
ZIGBEE_STATUS_DEVICE_ANNOUNCE, hex, nwkAddr,
|
|
|
|
(capabilities & 0x04) ? "true" : "false",
|
|
|
|
(capabilities & 0x08) ? "true" : "false",
|
|
|
|
(capabilities & 0x40) ? "true" : "false"
|
|
|
|
);
|
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
2019-09-29 14:38:26 +01:00
|
|
|
XdrvRulesProcess();
|
|
|
|
Z_SendActiveEpReq(nwkAddr);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-12-23 15:53:54 +00:00
|
|
|
// 45CA
|
|
|
|
int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) {
|
|
|
|
Z_ShortAddress srcAddr = buf.get16(2);
|
|
|
|
Z_IEEEAddress ieeeAddr = buf.get64(4);
|
|
|
|
Z_ShortAddress parentNw = buf.get16(12);
|
|
|
|
|
|
|
|
zigbee_devices.updateDevice(srcAddr, ieeeAddr);
|
|
|
|
|
|
|
|
char hex[20];
|
|
|
|
Uint64toHex(ieeeAddr, hex, 64);
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
2020-03-17 17:46:05 +00:00
|
|
|
"\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\""
|
2019-12-23 15:53:54 +00:00
|
|
|
",\"ParentNetwork\":\"0x%04X\"}}"),
|
|
|
|
ZIGBEE_STATUS_DEVICE_INDICATION, hex, srcAddr, parentNw
|
|
|
|
);
|
|
|
|
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
|
|
|
XdrvRulesProcess();
|
|
|
|
//Z_SendActiveEpReq(srcAddr);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-12-22 16:47:45 +00:00
|
|
|
// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
|
|
|
|
// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
|
|
|
|
const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
|
|
|
|
|
2020-03-14 13:17:30 +00:00
|
|
|
void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint8_t endpoint, const JsonObject *json) {
|
2019-12-22 16:47:45 +00:00
|
|
|
// Read OCCUPANCY value if any
|
|
|
|
const JsonVariant &val_endpoint = getCaseInsensitive(*json, PSTR(OCCUPANCY));
|
|
|
|
if (nullptr != &val_endpoint) {
|
|
|
|
uint32_t occupancy = strToUInt(val_endpoint);
|
|
|
|
|
|
|
|
if (occupancy) {
|
2020-03-14 13:17:30 +00:00
|
|
|
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, OCCUPANCY_TIMEOUT, cluster, endpoint, Z_CAT_VIRTUAL_ATTR, 0, &Z_OccupancyCallback);
|
2019-12-22 16:47:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Publish the received values once they have been coalesced
|
2020-03-14 13:17:30 +00:00
|
|
|
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
|
2019-12-22 16:47:45 +00:00
|
|
|
const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
|
|
|
|
if (json == nullptr) { return 0; } // don't crash if not found
|
|
|
|
// Post-provess for Aqara Presence Senson
|
|
|
|
Z_AqaraOccupancy(shortaddr, cluster, endpoint, json);
|
|
|
|
|
2020-01-17 23:02:01 +00:00
|
|
|
zigbee_devices.jsonPublishFlush(shortaddr);
|
2019-12-23 15:53:54 +00:00
|
|
|
return 1;
|
2019-12-22 16:47:45 +00:00
|
|
|
}
|
|
|
|
|
2019-09-29 14:38:26 +01:00
|
|
|
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
|
|
|
|
uint16_t groupid = buf.get16(2);
|
|
|
|
uint16_t clusterid = buf.get16(4);
|
|
|
|
Z_ShortAddress srcaddr = buf.get16(6);
|
|
|
|
uint8_t srcendpoint = buf.get8(8);
|
|
|
|
uint8_t dstendpoint = buf.get8(9);
|
|
|
|
uint8_t wasbroadcast = buf.get8(10);
|
|
|
|
uint8_t linkquality = buf.get8(11);
|
|
|
|
uint8_t securityuse = buf.get8(12);
|
|
|
|
uint32_t timestamp = buf.get32(13);
|
|
|
|
uint8_t seqnumber = buf.get8(17);
|
|
|
|
|
2019-12-22 16:47:45 +00:00
|
|
|
bool defer_attributes = false; // do we defer attributes reporting to coalesce
|
|
|
|
|
2019-12-15 18:39:27 +00:00
|
|
|
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid,
|
|
|
|
srcaddr,
|
|
|
|
srcendpoint, dstendpoint, wasbroadcast,
|
|
|
|
linkquality, securityuse, seqnumber,
|
|
|
|
timestamp);
|
|
|
|
zcl_received.log();
|
2019-09-29 14:38:26 +01:00
|
|
|
char shortaddr[8];
|
|
|
|
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
|
|
|
|
|
|
|
|
DynamicJsonBuffer jsonBuffer;
|
2020-01-19 21:59:02 +00:00
|
|
|
JsonObject& json = jsonBuffer.createObject();
|
2020-03-14 13:17:30 +00:00
|
|
|
|
2020-03-01 10:25:59 +00:00
|
|
|
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) {
|
|
|
|
zcl_received.parseResponse();
|
|
|
|
} else {
|
|
|
|
// Build the ZbReceive json
|
|
|
|
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
|
|
|
|
zcl_received.parseRawAttributes(json);
|
|
|
|
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
|
|
|
|
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
|
|
|
|
zcl_received.parseReadAttributes(json);
|
2020-03-14 13:17:30 +00:00
|
|
|
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
|
2020-03-01 10:25:59 +00:00
|
|
|
} else if (zcl_received.isClusterSpecificCommand()) {
|
|
|
|
zcl_received.parseClusterSpecificCommand(json);
|
|
|
|
}
|
|
|
|
String msg("");
|
|
|
|
msg.reserve(100);
|
|
|
|
json.printTo(msg);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str());
|
|
|
|
|
|
|
|
zcl_received.postProcessAttributes(srcaddr, json);
|
|
|
|
// Add Endpoint
|
|
|
|
json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint;
|
|
|
|
// Add Group if non-zero
|
|
|
|
if (groupid) {
|
|
|
|
json[F(D_CMND_ZIGBEE_GROUP)] = groupid;
|
|
|
|
}
|
|
|
|
// Add linkquality
|
|
|
|
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
|
|
|
|
|
|
|
|
if (defer_attributes) {
|
|
|
|
// Prepare for publish
|
|
|
|
if (zigbee_devices.jsonIsConflict(srcaddr, json)) {
|
|
|
|
// there is conflicting values, force a publish of the previous message now and don't coalesce
|
|
|
|
zigbee_devices.jsonPublishFlush(srcaddr);
|
|
|
|
}
|
|
|
|
zigbee_devices.jsonAppend(srcaddr, json);
|
2020-03-14 13:17:30 +00:00
|
|
|
zigbee_devices.setTimer(srcaddr, 0 /* groupaddr */, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, Z_CAT_READ_ATTR, 0, &Z_PublishAttributes);
|
2020-03-01 10:25:59 +00:00
|
|
|
} else {
|
|
|
|
// Publish immediately
|
|
|
|
zigbee_devices.jsonPublishNow(srcaddr, json);
|
2019-12-23 15:53:54 +00:00
|
|
|
}
|
2019-12-22 16:47:45 +00:00
|
|
|
}
|
2019-09-29 14:38:26 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-10-06 11:40:58 +01:00
|
|
|
typedef struct Z_Dispatcher {
|
|
|
|
const uint8_t* match;
|
|
|
|
ZB_RecvMsgFunc func;
|
|
|
|
} Z_Dispatcher;
|
|
|
|
|
|
|
|
// Filters for ZCL frames
|
2020-03-14 13:17:30 +00:00
|
|
|
ZBM(AREQ_AF_DATA_CONFIRM, Z_AREQ | Z_AF, AF_DATA_CONFIRM) // 4480
|
2019-10-06 11:40:58 +01:00
|
|
|
ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
|
|
|
|
ZBM(AREQ_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1
|
2019-12-23 15:53:54 +00:00
|
|
|
ZBM(AREQ_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND) // 45CA
|
2019-10-06 11:40:58 +01:00
|
|
|
ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB
|
|
|
|
ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585
|
|
|
|
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
|
2020-02-23 15:46:00 +00:00
|
|
|
ZBM(AREQ_ZDO_IEEE_ADDR_RSP, Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP) // 4581
|
2020-03-01 10:25:59 +00:00
|
|
|
ZBM(AREQ_ZDO_BIND_RSP, Z_AREQ | Z_ZDO, ZDO_BIND_RSP) // 45A1
|
2019-10-06 11:40:58 +01:00
|
|
|
|
|
|
|
const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
|
2020-03-14 13:17:30 +00:00
|
|
|
{ AREQ_AF_DATA_CONFIRM, &Z_DataConfirm },
|
2019-10-06 11:40:58 +01:00
|
|
|
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
|
|
|
|
{ AREQ_END_DEVICE_ANNCE_IND, &Z_ReceiveEndDeviceAnnonce },
|
2019-12-23 15:53:54 +00:00
|
|
|
{ AREQ_END_DEVICE_TC_DEV_IND, &Z_ReceiveTCDevInd },
|
2019-10-06 11:40:58 +01:00
|
|
|
{ AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus },
|
|
|
|
{ AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc },
|
|
|
|
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },
|
2020-02-23 15:46:00 +00:00
|
|
|
{ AREQ_ZDO_IEEE_ADDR_RSP, &Z_ReceiveIEEEAddr },
|
2020-03-01 10:25:59 +00:00
|
|
|
{ AREQ_ZDO_BIND_RSP, &Z_BindRsp },
|
2019-10-06 11:40:58 +01:00
|
|
|
};
|
|
|
|
|
2019-09-29 14:38:26 +01:00
|
|
|
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
|
|
|
|
// Default message handler for new messages
|
|
|
|
if (zigbee.init_phase) {
|
|
|
|
// if still during initialization phase, ignore any unexpected message
|
|
|
|
return -1; // ignore message
|
|
|
|
} else {
|
2019-10-06 11:40:58 +01:00
|
|
|
for (uint32_t i = 0; i < sizeof(Z_DispatchTable)/sizeof(Z_Dispatcher); i++) {
|
|
|
|
if (Z_ReceiveMatchPrefix(buf, Z_DispatchTable[i].match)) {
|
|
|
|
(*Z_DispatchTable[i].func)(res, buf);
|
|
|
|
}
|
2019-09-29 14:38:26 +01:00
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-17 23:02:01 +00:00
|
|
|
int32_t Z_Load_Devices(uint8_t value) {
|
|
|
|
// try to hidrate from known devices
|
|
|
|
loadZigbeeDevices();
|
|
|
|
return 0; // continue
|
|
|
|
}
|
|
|
|
|
2020-03-14 13:17:30 +00:00
|
|
|
int32_t Z_Query_Bulbs(uint8_t value) {
|
|
|
|
// Scan all devices and send deferred requests to know the state of bulbs
|
|
|
|
uint32_t wait_ms = 1000; // start with 1.0 s delay
|
|
|
|
const uint32_t inter_message_ms = 100; // wait 100ms between messages
|
|
|
|
for (uint32_t i = 0; i < zigbee_devices.devicesSize(); i++) {
|
|
|
|
const Z_Device &device = zigbee_devices.devicesAt(i);
|
|
|
|
|
|
|
|
if (0 <= device.bulbtype) {
|
|
|
|
uint16_t cluster;
|
2020-03-17 17:46:05 +00:00
|
|
|
uint8_t endpoint = zigbee_devices.findFirstEndpoint(device.shortaddr);
|
2020-03-14 13:17:30 +00:00
|
|
|
|
|
|
|
cluster = 0x0006;
|
|
|
|
if (endpoint) { // send only if we know the endpoint
|
|
|
|
zigbee_devices.setTimer(device.shortaddr, 0 /* groupaddr */, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
|
|
|
|
wait_ms += inter_message_ms;
|
|
|
|
}
|
|
|
|
|
|
|
|
cluster = 0x0008;
|
|
|
|
if (endpoint) { // send only if we know the endpoint
|
|
|
|
zigbee_devices.setTimer(device.shortaddr, 0 /* groupaddr */, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
|
|
|
|
wait_ms += inter_message_ms;
|
|
|
|
}
|
|
|
|
|
|
|
|
cluster = 0x0300;
|
|
|
|
if (endpoint) { // send only if we know the endpoint
|
|
|
|
zigbee_devices.setTimer(device.shortaddr, 0 /* groupaddr */, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
|
|
|
|
wait_ms += inter_message_ms;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0; // continue
|
|
|
|
}
|
|
|
|
|
2019-09-29 14:38:26 +01:00
|
|
|
int32_t Z_State_Ready(uint8_t value) {
|
|
|
|
zigbee.init_phase = false; // initialization phase complete
|
|
|
|
return 0; // continue
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // USE_ZIGBEE
|