Merge pull request #7832 from s-hadinger/zigbee_32

Add Zigbee features and improvements
This commit is contained in:
Theo Arends 2020-03-01 13:49:43 +01:00 committed by GitHub
commit 46fe19bcd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 550 additions and 280 deletions

View File

@ -5,6 +5,7 @@
- Change default my_user_config.h driver and sensor support removing most sensors and adding most drivers
- Add support for Jarolift rollers by Keeloq algorithm
- Change IRremoteESP8266 library updated to v2.7.4
- Add Zigbee features and improvements
### 8.1.0.9 20200220

View File

@ -470,10 +470,10 @@
// Commands xdrv_23_zigbee.ino
#define D_PRFX_ZB "Zb"
#define D_PRFX_ZIGBEE "Zigbee"
#define D_ZIGBEE_NOT_STARTED "Zigbee not started (yet)"
#define D_CMND_ZIGBEE_PERMITJOIN "PermitJoin"
#define D_CMND_ZIGBEE_STATUS "Status"
#define D_JSON_ZIGBEE_Status "Status"
#define D_CMND_ZIGBEE_RESET "Reset"
#define D_JSON_ZIGBEE_CC2530 "CC2530"
#define D_CMND_ZIGBEEZNPRECEIVE "ZNPReceive" // only for debug
@ -486,19 +486,27 @@
#define D_JSON_ZIGBEE_DEVICE "Device"
#define D_JSON_ZIGBEE_NAME "Name"
#define D_CMND_ZIGBEE_NAME "Name"
#define D_CMND_ZIGBEE_MODELID "ModelId"
#define D_JSON_ZIGBEE_MODELID "ModelId"
#define D_CMND_ZIGBEE_PROBE "Probe"
#define D_CMND_ZIGBEE_FORGET "Forget"
#define D_CMND_ZIGBEE_SAVE "Save"
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
#define D_CMND_ZIGBEE_ENDPOINT "Endpoint"
#define D_CMND_ZIGBEE_GROUP "Group"
#define D_CMND_ZIGBEE_READ "Read"
#define D_CMND_ZIGBEE_SEND "Send"
#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"
#define D_JSON_ZIGBEE_BIND "ZbBind"
#define D_CMND_ZIGBEE_PING "Ping"
#define D_JSON_ZIGBEE_PING "ZbPing"
#define D_JSON_ZIGBEE_IEEE "IEEEAddr"
#define D_JSON_ZIGBEE_RESPONSE "ZbResponse"
#define D_JSON_ZIGBEE_CMD "Command"
#define D_JSON_ZIGBEE_STATUS "Status"
#define D_JSON_ZIGBEE_STATUS_MSG "StatusMessage"
// Commands xdrv_25_A4988_Stepper.ino
#define D_CMND_MOTOR "MOTOR"

View File

@ -169,43 +169,6 @@ enum Z_configuration {
ZNP_HAS_CONFIGURED = 0xF00
};
// enum Z_nvItemIds {
// SCENE_TABLE = 145,
// MIN_FREE_NWK_ADDR = 146,
// MAX_FREE_NWK_ADDR = 147,
// MIN_FREE_GRP_ID = 148,
// MAX_FREE_GRP_ID = 149,
// MIN_GRP_IDS = 150,
// MAX_GRP_IDS = 151,
// OTA_BLOCK_REQ_DELAY = 152,
// SAPI_ENDPOINT = 161,
// SAS_SHORT_ADDR = 177,
// SAS_EXT_PANID = 178,
// SAS_PANID = 179,
// SAS_CHANNEL_MASK = 180,
// SAS_PROTOCOL_VER = 181,
// SAS_STACK_PROFILE = 182,
// SAS_STARTUP_CTRL = 183,
// SAS_TC_ADDR = 193,
// SAS_TC_MASTER_KEY = 194,
// SAS_NWK_KEY = 195,
// SAS_USE_INSEC_JOIN = 196,
// SAS_PRECFG_LINK_KEY = 197,
// SAS_NWK_KEY_SEQ_NUM = 198,
// SAS_NWK_KEY_TYPE = 199,
// SAS_NWK_MGR_ADDR = 200,
// SAS_CURR_TC_MASTER_KEY = 209,
// SAS_CURR_NWK_KEY = 210,
// SAS_CURR_PRECFG_LINK_KEY = 211,
// TCLK_TABLE_START = 257,
// TCLK_TABLE_END = 511,
// APS_LINK_KEY_DATA_START = 513,
// APS_LINK_KEY_DATA_END = 767,
// DUPLICATE_BINDING_TABLE = 768,
// DUPLICATE_DEVICE_LIST = 769,
// DUPLICATE_DEVICE_LIST_KA_TIMEOUT = 770,
//};
//
enum Z_Status {
Z_Success = 0x00,
@ -261,16 +224,14 @@ enum Z_Device_Ids {
// 0x0403 IAS Warning Device
};
// enum class AddrMode : uint8_t {
// NotPresent = 0,
// Group = 1,
// ShortAddress = 2,
// IEEEAddress = 3,
// Broadcast = 0xFF
// };
//
//
//
enum Z_AddrMode : uint8_t {
Z_Addr_NotPresent = 0,
Z_Addr_Group = 1,
Z_Addr_ShortAddress = 2,
Z_Addr_IEEEAddress = 3,
Z_Addr_Broadcast = 0xFF
};
// Commands in the AF subsystem
enum AfCommand : uint8_t {
AF_REGISTER = 0x00,
@ -286,7 +247,7 @@ enum AfCommand : uint8_t {
AF_INCOMING_MSG = 0x81,
AF_INCOMING_MSG_EXT = 0x82
};
//
// Commands in the ZDO subsystem
enum : uint8_t {
ZDO_NWK_ADDR_REQ = 0x00,
@ -371,7 +332,7 @@ enum ZdoStates {
ZDO_DEV_ZB_COORD = 0x09, // Started as a a Zigbee Coordinator
ZDO_DEV_NWK_ORPHAN = 0x0A, // Device has lost information about its parent.
};
//
// Commands in the UTIL subsystem
enum Z_Util {
Z_UTIL_GET_DEVICE_INFO = 0x00,
@ -427,4 +388,57 @@ enum ZCL_Global_Commands {
const uint16_t Z_ProfileIds[] PROGMEM = { 0x0104, 0x0109, 0xA10E, 0xC05E };
const char Z_ProfileNames[] PROGMEM = "ZigBee Home Automation|ZigBee Smart Energy|ZigBee Green Power|ZigBee Light Link";
typedef struct Z_StatusLine {
uint32_t status; // no need to use uint8_t since it uses 32 bits anyways
const char * status_msg;
} Z_StatusLine;
const Z_StatusLine Z_Status[] PROGMEM = {
0x00, "SUCCESS",
0x01, "FAILURE",
0x7E, "NOT_AUTHORIZED",
0x7F, "RESERVED_FIELD_NOT_ZERO",
0x80, "MALFORMED_COMMAND",
0x81, "UNSUP_CLUSTER_COMMAND",
0x82, "UNSUP_GENERAL_COMMAND",
0x83, "UNSUP_MANUF_CLUSTER_COMMAND",
0x84, "UNSUP_MANUF_GENERAL_COMMAND",
0x85, "INVALID_FIELD",
0x86, "UNSUPPORTED_ATTRIBUTE",
0x87, "INVALID_VALUE",
0x88, "READ_ONLY",
0x89, "INSUFFICIENT_SPACE",
0x8A, "DUPLICATE_EXISTS",
0x8B, "NOT_FOUND",
0x8C, "UNREPORTABLE_ATTRIBUTE",
0x8D, "INVALID_DATA_TYPE",
0x8E, "INVALID_SELECTOR",
0x8F, "WRITE_ONLY",
0x90, "INCONSISTENT_STARTUP_STATE",
0x91, "DEFINED_OUT_OF_BAND",
0x92, "INCONSISTENT",
0x93, "ACTION_DENIED",
0x94, "TIMEOUT",
0x95, "ABORT",
0x96, "INVALID_IMAGE",
0x97, "WAIT_FOR_DATA",
0x98, "NO_IMAGE_AVAILABLE",
0x99, "REQUIRE_MORE_IMAGE",
0x9A, "NOTIFICATION_PENDING",
0xC0, "HARDWARE_FAILURE",
0xC1, "SOFTWARE_FAILURE",
0xC2, "CALIBRATION_ERROR",
0xC3, "UNSUPPORTED_CLUSTER",
};
const __FlashStringHelper* getZigbeeStatusMessage(uint8_t status) {
for (uint32_t i = 0; i < sizeof(Z_Status) / sizeof(Z_Status[0]); i++) {
const Z_StatusLine *statl = &Z_Status[i];
if (statl->status == status) {
return (const __FlashStringHelper*) statl->status_msg;
}
}
return nullptr;
}
#endif // USE_ZIGBEE

View File

@ -19,10 +19,6 @@
#ifdef USE_ZIGBEE
#ifndef ZIGBEERECEIVED
#define ZIGBEERECEIVED 1
#endif
#include <vector>
#include <map>
@ -97,7 +93,8 @@ public:
void setManufId(uint16_t shortaddr, const char * str);
void setModelId(uint16_t shortaddr, const char * str);
void setFriendlyName(uint16_t shortaddr, const char * str);
const String * getFriendlyName(uint16_t) const;
const String * getFriendlyName(uint16_t shortaddr) const;
const String * getModelId(uint16_t shortaddr) const;
// device just seen on the network, update the lastSeen field
void updateLastSeen(uint16_t shortaddr);
@ -537,6 +534,17 @@ const String * Z_Devices::getFriendlyName(uint16_t shortaddr) const {
return nullptr;
}
const String * Z_Devices::getModelId(uint16_t shortaddr) const {
int32_t found = findShortAddr(shortaddr);
if (found >= 0) {
const Z_Device & device = devicesAt(found);
if (device.modelId.length() > 0) {
return &device.modelId;
}
}
return nullptr;
}
// device just seen on the network, update the lastSeen field
void Z_Devices::updateLastSeen(uint16_t shortaddr) {
Z_Device & device = getShortAddr(shortaddr);
@ -656,13 +664,31 @@ bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) {
return false; // if no previous value, no conflict
}
// compare groups
uint16_t group1 = 0;
uint16_t group2 = 0;
if (device.json->containsKey(D_CMND_ZIGBEE_GROUP)) {
group1 = device.json->get<unsigned int>(D_CMND_ZIGBEE_GROUP);
}
if (values.containsKey(D_CMND_ZIGBEE_GROUP)) {
group2 = values.get<unsigned int>(D_CMND_ZIGBEE_GROUP);
}
if (group1 != group2) {
return true; // if group addresses differ, then conflict
}
// parse all other parameters
for (auto kv : values) {
String key_string = kv.key;
if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_ENDPOINT))) {
// attribute "Endpoint"
if (kv.value.as<unsigned int>() != device.json->get<unsigned int>(kv.key)) {
return true;
if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_GROUP))) {
// ignore group, it was handled already
} else if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_ENDPOINT))) {
// attribute "Endpoint" or "Group"
if (device.json->containsKey(kv.key)) {
if (kv.value.as<unsigned int>() != device.json->get<unsigned int>(kv.key)) {
return true;
}
}
} else if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { // exception = ignore duplicates for LinkQuality
if (device.json->containsKey(kv.key)) {
@ -734,25 +760,11 @@ void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str());
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
#if ZIGBEERECEIVED
// DEPRECATED TODO
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str());
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
#endif
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
#if ZIGBEERECEIVED
// DEPRECATED TODO
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
#endif
}
// MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
// XdrvRulesProcess();
}
void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) {

View File

@ -112,6 +112,7 @@ public:
static void generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len);
void parseRawAttributes(JsonObject& json, uint8_t offset = 0);
void parseReadAttributes(JsonObject& json, uint8_t offset = 0);
void parseResponse(void);
void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0);
void postProcessAttributes(uint16_t shortaddr, JsonObject& json);
@ -484,6 +485,51 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
}
}
// ZCL_DEFAULT_RESPONSE
void ZCLFrame::parseResponse(void) {
if (_payload.len() < 2) { return; } // wrong format
uint8_t cmd = _payload.get8(0);
uint8_t status = _payload.get8(1);
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
// "Device"
char s[12];
snprintf_P(s, sizeof(s), PSTR("0x%04X"), _srcaddr);
json[F(D_JSON_ZIGBEE_DEVICE)] = s;
// "Name"
const String * friendlyName = zigbee_devices.getFriendlyName(_srcaddr);
if (friendlyName) {
json[F(D_JSON_ZIGBEE_NAME)] = *friendlyName;
}
// "Command"
snprintf_P(s, sizeof(s), PSTR("%04X!%02X"), _cluster_id, cmd);
json[F(D_JSON_ZIGBEE_CMD)] = s;
// "Status"
json[F(D_JSON_ZIGBEE_STATUS)] = status;
// "StatusMessage"
const __FlashStringHelper* statm = getZigbeeStatusMessage(status);
if (statm) {
json[F(D_JSON_ZIGBEE_STATUS_MSG)] = statm;
}
// Add Endpoint
json[F(D_CMND_ZIGBEE_ENDPOINT)] = _srcendpoint;
// Add Group if non-zero
if (_group_id) {
json[F(D_CMND_ZIGBEE_GROUP)] = _group_id;
}
// Add linkquality
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = _linkquality;
String msg("");
msg.reserve(100);
json.printTo(msg);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RESPONSE "\":%s}"), msg.c_str());
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
}
// Parse non-normalized attributes
void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
@ -519,7 +565,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// Power Configuration cluster
{ 0x0001, 0x0000, "MainsVoltage", &Z_Copy },
{ 0x0001, 0x0001, "MainsFrequency", &Z_Copy },
{ 0x0001, 0x0020, "BatteryVoltage", &Z_Copy },
{ 0x0001, 0x0020, "BatteryVoltage", &Z_FloatDiv10 },
{ 0x0001, 0x0021, "BatteryPercentageRemaining",&Z_Copy },
// Device Temperature Configuration cluster
@ -564,78 +610,112 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x000B, 0x0000, "QualityMeasure", &Z_Copy },
{ 0x000B, 0x0000, "NumberOfDevices", &Z_Copy },
// Analog Input cluster
{ 0x000C, 0x0004, "ActiveText", &Z_Copy },
{ 0x000C, 0x001C, "Description", &Z_Copy },
{ 0x000C, 0x002E, "InactiveText", &Z_Copy },
{ 0x000C, 0x0041, "MaxPresentValue", &Z_Copy },
{ 0x000C, 0x0045, "MinPresentValue", &Z_Copy },
{ 0x000C, 0x0051, "OutOfService", &Z_Copy },
{ 0x000C, 0x0004, "AnalogInActiveText", &Z_Copy },
{ 0x000C, 0x001C, "AnalogInDescription", &Z_Copy },
{ 0x000C, 0x002E, "AnalogInInactiveText", &Z_Copy },
{ 0x000C, 0x0041, "AnalogInMaxValue", &Z_Copy },
{ 0x000C, 0x0045, "AnalogInMinValue", &Z_Copy },
{ 0x000C, 0x0051, "AnalogInOutOfService", &Z_Copy },
{ 0x000C, 0x0055, "AqaraRotate", &Z_Copy },
{ 0x000C, 0x0057, "PriorityArray", &Z_Copy },
{ 0x000C, 0x0067, "Reliability", &Z_Copy },
{ 0x000C, 0x0068, "RelinquishDefault", &Z_Copy },
{ 0x000C, 0x006A, "Resolution", &Z_Copy },
{ 0x000C, 0x006F, "StatusFlags", &Z_Copy },
{ 0x000C, 0x0075, "EngineeringUnits", &Z_Copy },
{ 0x000C, 0x0100, "ApplicationType", &Z_Copy },
{ 0x000C, 0x0057, "AnalogInPriorityArray",&Z_Copy },
{ 0x000C, 0x0067, "AnalogInReliability", &Z_Copy },
{ 0x000C, 0x0068, "AnalogInRelinquishDefault",&Z_Copy },
{ 0x000C, 0x006A, "AnalogInResolution", &Z_Copy },
{ 0x000C, 0x006F, "AnalogInStatusFlags", &Z_Copy },
{ 0x000C, 0x0075, "AnalogInEngineeringUnits",&Z_Copy },
{ 0x000C, 0x0100, "AnalogInApplicationType",&Z_Copy },
{ 0x000C, 0xFF05, "Aqara_FF05", &Z_Copy },
// Analog Output cluster
{ 0x000D, 0x001C, "AnalogOutDescription", &Z_Copy },
{ 0x000D, 0x0041, "AnalogOutMaxValue", &Z_Copy },
{ 0x000D, 0x0045, "AnalogOutMinValue", &Z_Copy },
{ 0x000D, 0x0051, "AnalogOutOutOfService",&Z_Copy },
{ 0x000D, 0x0055, "AnalogOutValue", &Z_Copy },
{ 0x000D, 0x0057, "AnalogOutPriorityArray",&Z_Copy },
{ 0x000D, 0x0067, "AnalogOutReliability", &Z_Copy },
{ 0x000D, 0x0068, "AnalogOutRelinquishDefault",&Z_Copy },
{ 0x000D, 0x006A, "AnalogOutResolution", &Z_Copy },
{ 0x000D, 0x006F, "AnalogOutStatusFlags", &Z_Copy },
{ 0x000D, 0x0075, "AnalogOutEngineeringUnits",&Z_Copy },
{ 0x000D, 0x0100, "AnalogOutApplicationType",&Z_Copy },
// Analog Value cluster
{ 0x000E, 0x001C, "AnalogDescription", &Z_Copy },
{ 0x000E, 0x0051, "AnalogOutOfService", &Z_Copy },
{ 0x000E, 0x0055, "AnalogValue", &Z_Copy },
{ 0x000E, 0x0057, "AnalogPriorityArray", &Z_Copy },
{ 0x000E, 0x0067, "AnalogReliability", &Z_Copy },
{ 0x000E, 0x0068, "AnalogRelinquishDefault",&Z_Copy },
{ 0x000E, 0x006F, "AnalogStatusFlags", &Z_Copy },
{ 0x000E, 0x0075, "AnalogEngineeringUnits",&Z_Copy },
{ 0x000E, 0x0100, "AnalogApplicationType",&Z_Copy },
// Binary Input cluster
{ 0x000F, 0x0004, "BinaryInActiveText", &Z_Copy },
{ 0x000F, 0x001C, "BinaryInDescription", &Z_Copy },
{ 0x000F, 0x002E, "BinaryInInactiveText",&Z_Copy },
{ 0x000F, 0x0051, "BinaryInOutOfService",&Z_Copy },
{ 0x000F, 0x0054, "BinaryInPolarity", &Z_Copy },
{ 0x000F, 0x0055, "BinaryInValue", &Z_Copy },
{ 0x000F, 0x0057, "BinaryInPriorityArray",&Z_Copy },
{ 0x000F, 0x0067, "BinaryInReliability", &Z_Copy },
{ 0x000F, 0x006F, "BinaryInStatusFlags", &Z_Copy },
{ 0x000F, 0x0100, "BinaryInApplicationType",&Z_Copy },
// Binary Output cluster
{ 0x0010, 0x0004, "ActiveText", &Z_Copy },
{ 0x0010, 0x001C, "Description", &Z_Copy },
{ 0x0010, 0x002E, "InactiveText", &Z_Copy },
{ 0x0010, 0x0042, "MinimumOffTime", &Z_Copy },
{ 0x0010, 0x0043, "MinimumOnTime", &Z_Copy },
{ 0x0010, 0x0051, "OutOfService", &Z_Copy },
{ 0x0010, 0x0054, "Polarity", &Z_Copy },
{ 0x0010, 0x0055, "PresentValue", &Z_Copy },
{ 0x0010, 0x0057, "PriorityArray", &Z_Copy },
{ 0x0010, 0x0067, "Reliability", &Z_Copy },
{ 0x0010, 0x0068, "RelinquishDefault", &Z_Copy },
{ 0x0010, 0x006F, "StatusFlags", &Z_Copy },
{ 0x0010, 0x0100, "ApplicationType", &Z_Copy },
{ 0x0010, 0x0004, "BinaryOutActiveText", &Z_Copy },
{ 0x0010, 0x001C, "BinaryOutDescription", &Z_Copy },
{ 0x0010, 0x002E, "BinaryOutInactiveText",&Z_Copy },
{ 0x0010, 0x0042, "BinaryOutMinimumOffTime",&Z_Copy },
{ 0x0010, 0x0043, "BinaryOutMinimumOnTime",&Z_Copy },
{ 0x0010, 0x0051, "BinaryOutOutOfService",&Z_Copy },
{ 0x0010, 0x0054, "BinaryOutPolarity", &Z_Copy },
{ 0x0010, 0x0055, "BinaryOutValue", &Z_Copy },
{ 0x0010, 0x0057, "BinaryOutPriorityArray",&Z_Copy },
{ 0x0010, 0x0067, "BinaryOutReliability", &Z_Copy },
{ 0x0010, 0x0068, "BinaryOutRelinquishDefault",&Z_Copy },
{ 0x0010, 0x006F, "BinaryOutStatusFlags", &Z_Copy },
{ 0x0010, 0x0100, "BinaryOutApplicationType",&Z_Copy },
// Binary Value cluster
{ 0x0011, 0x0004, "ActiveText", &Z_Copy },
{ 0x0011, 0x001C, "Description", &Z_Copy },
{ 0x0011, 0x002E, "InactiveText", &Z_Copy },
{ 0x0011, 0x0042, "MinimumOffTime", &Z_Copy },
{ 0x0011, 0x0043, "MinimumOnTime", &Z_Copy },
{ 0x0011, 0x0051, "OutOfService", &Z_Copy },
{ 0x0011, 0x0055, "PresentValue", &Z_Copy },
{ 0x0011, 0x0057, "PriorityArray", &Z_Copy },
{ 0x0011, 0x0067, "Reliability", &Z_Copy },
{ 0x0011, 0x0068, "RelinquishDefault", &Z_Copy },
{ 0x0011, 0x006F, "StatusFlags", &Z_Copy },
{ 0x0011, 0x0100, "ApplicationType", &Z_Copy },
{ 0x0011, 0x0004, "BinaryActiveText", &Z_Copy },
{ 0x0011, 0x001C, "BinaryDescription", &Z_Copy },
{ 0x0011, 0x002E, "BinaryInactiveText", &Z_Copy },
{ 0x0011, 0x0042, "BinaryMinimumOffTime", &Z_Copy },
{ 0x0011, 0x0043, "BinaryMinimumOnTime", &Z_Copy },
{ 0x0011, 0x0051, "BinaryOutOfService", &Z_Copy },
{ 0x0011, 0x0055, "BinaryValue", &Z_Copy },
{ 0x0011, 0x0057, "BinaryPriorityArray", &Z_Copy },
{ 0x0011, 0x0067, "BinaryReliability", &Z_Copy },
{ 0x0011, 0x0068, "BinaryRelinquishDefault",&Z_Copy },
{ 0x0011, 0x006F, "BinaryStatusFlags", &Z_Copy },
{ 0x0011, 0x0100, "BinaryApplicationType",&Z_Copy },
// Multistate Input cluster
{ 0x0012, 0x000E, "StateText", &Z_Copy },
{ 0x0012, 0x001C, "Description", &Z_Copy },
{ 0x0012, 0x004A, "NumberOfStates", &Z_Copy },
{ 0x0012, 0x0051, "OutOfService", &Z_Copy },
{ 0x0012, 0x0055, "PresentValue", &Z_AqaraCube },
{ 0x0012, 0x0067, "Reliability", &Z_Copy },
{ 0x0012, 0x006F, "StatusFlags", &Z_Copy },
{ 0x0012, 0x0100, "ApplicationType", &Z_Copy },
{ 0x0012, 0x000E, "MultiInStateText", &Z_Copy },
{ 0x0012, 0x001C, "MultiInDescription", &Z_Copy },
{ 0x0012, 0x004A, "MultiInNumberOfStates",&Z_Copy },
{ 0x0012, 0x0051, "MultiInOutOfService", &Z_Copy },
{ 0x0012, 0x0055, "MultiInValue", &Z_AqaraCube },
{ 0x0012, 0x0067, "MultiInReliability", &Z_Copy },
{ 0x0012, 0x006F, "MultiInStatusFlags", &Z_Copy },
{ 0x0012, 0x0100, "MultiInApplicationType",&Z_Copy },
// Multistate output
{ 0x0013, 0x000E, "StateText", &Z_Copy },
{ 0x0013, 0x001C, "Description", &Z_Copy },
{ 0x0013, 0x004A, "NumberOfStates", &Z_Copy },
{ 0x0013, 0x0051, "OutOfService", &Z_Copy },
{ 0x0013, 0x0055, "PresentValue", &Z_Copy },
{ 0x0013, 0x0057, "PriorityArray", &Z_Copy },
{ 0x0013, 0x0067, "Reliability", &Z_Copy },
{ 0x0013, 0x0068, "RelinquishDefault", &Z_Copy },
{ 0x0013, 0x006F, "StatusFlags", &Z_Copy },
{ 0x0013, 0x0100, "ApplicationType", &Z_Copy },
{ 0x0013, 0x000E, "MultiOutStateText", &Z_Copy },
{ 0x0013, 0x001C, "MultiOutDescription", &Z_Copy },
{ 0x0013, 0x004A, "MultiOutNumberOfStates",&Z_Copy },
{ 0x0013, 0x0051, "MultiOutOutOfService", &Z_Copy },
{ 0x0013, 0x0055, "MultiOutValue", &Z_Copy },
{ 0x0013, 0x0057, "MultiOutPriorityArray",&Z_Copy },
{ 0x0013, 0x0067, "MultiOutReliability", &Z_Copy },
{ 0x0013, 0x0068, "MultiOutRelinquishDefault",&Z_Copy },
{ 0x0013, 0x006F, "MultiOutStatusFlags", &Z_Copy },
{ 0x0013, 0x0100, "MultiOutApplicationType",&Z_Copy },
// Multistate Value cluster
{ 0x0014, 0x000E, "StateText", &Z_Copy },
{ 0x0014, 0x001C, "Description", &Z_Copy },
{ 0x0014, 0x004A, "NumberOfStates", &Z_Copy },
{ 0x0014, 0x0051, "OutOfService", &Z_Copy },
{ 0x0014, 0x0055, "PresentValue", &Z_Copy },
{ 0x0014, 0x0067, "Reliability", &Z_Copy },
{ 0x0014, 0x0068, "RelinquishDefault", &Z_Copy },
{ 0x0014, 0x006F, "StatusFlags", &Z_Copy },
{ 0x0014, 0x0100, "ApplicationType", &Z_Copy },
{ 0x0014, 0x000E, "MultiStateText", &Z_Copy },
{ 0x0014, 0x001C, "MultiDescription", &Z_Copy },
{ 0x0014, 0x004A, "MultiNumberOfStates", &Z_Copy },
{ 0x0014, 0x0051, "MultiOutOfService", &Z_Copy },
{ 0x0014, 0x0055, "MultiValue", &Z_Copy },
{ 0x0014, 0x0067, "MultiReliability", &Z_Copy },
{ 0x0014, 0x0068, "MultiRelinquishDefault",&Z_Copy },
{ 0x0014, 0x006F, "MultiStatusFlags", &Z_Copy },
{ 0x0014, 0x0100, "MultiApplicationType", &Z_Copy },
// Power Profile cluster
{ 0x001A, 0x0000, "TotalProfileNum", &Z_Copy },
{ 0x001A, 0x0001, "MultipleScheduling", &Z_Copy },
@ -682,12 +762,12 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0102, 0x0009, "CurrentPositionTiltPercentage",&Z_Copy },
{ 0x0102, 0x0010, "InstalledOpenLimitLift",&Z_Copy },
{ 0x0102, 0x0011, "InstalledClosedLimitLift",&Z_Copy },
{ 0x0102, 0x0012, "InstalledOpenLimitTilt", &Z_Copy },
{ 0x0102, 0x0013, "InstalledClosedLimitTilt", &Z_Copy },
{ 0x0102, 0x0014, "VelocityLift",&Z_Copy },
{ 0x0102, 0x0012, "InstalledOpenLimitTilt",&Z_Copy },
{ 0x0102, 0x0013, "InstalledClosedLimitTilt",&Z_Copy },
{ 0x0102, 0x0014, "VelocityLift", &Z_Copy },
{ 0x0102, 0x0015, "AccelerationTimeLift",&Z_Copy },
{ 0x0102, 0x0016, "DecelerationTimeLift", &Z_Copy },
{ 0x0102, 0x0017, "Mode",&Z_Copy },
{ 0x0102, 0x0016, "DecelerationTimeLift", &Z_Copy },
{ 0x0102, 0x0017, "Mode", &Z_Copy },
{ 0x0102, 0x0018, "IntermediateSetpointsLift",&Z_Copy },
{ 0x0102, 0x0019, "IntermediateSetpointsTilt",&Z_Copy },
@ -725,49 +805,49 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// Illuminance Measurement cluster
{ 0x0400, 0x0000, D_JSON_ILLUMINANCE, &Z_Copy }, // Illuminance (in Lux)
{ 0x0400, 0x0001, "MinMeasuredValue", &Z_Copy }, //
{ 0x0400, 0x0002, "MaxMeasuredValue", &Z_Copy }, //
{ 0x0400, 0x0003, "Tolerance", &Z_Copy }, //
{ 0x0400, 0x0004, "LightSensorType", &Z_Copy }, //
{ 0x0400, 0x0001, "IlluminanceMinMeasuredValue", &Z_Copy }, //
{ 0x0400, 0x0002, "IlluminanceMaxMeasuredValue", &Z_Copy }, //
{ 0x0400, 0x0003, "IlluminanceTolerance", &Z_Copy }, //
{ 0x0400, 0x0004, "IlluminanceLightSensorType", &Z_Copy }, //
{ 0x0400, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Illuminance Level Sensing cluster
{ 0x0401, 0x0000, "LevelStatus", &Z_Copy }, // Illuminance (in Lux)
{ 0x0401, 0x0001, "LightSensorType", &Z_Copy }, // LightSensorType
{ 0x0401, 0x0000, "IlluminanceLevelStatus", &Z_Copy }, // Illuminance (in Lux)
{ 0x0401, 0x0001, "IlluminanceLightSensorType", &Z_Copy }, // LightSensorType
{ 0x0401, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Temperature Measurement cluster
{ 0x0402, 0x0000, D_JSON_TEMPERATURE, &Z_FloatDiv100 }, // Temperature
{ 0x0402, 0x0001, "MinMeasuredValue", &Z_FloatDiv100 }, //
{ 0x0402, 0x0002, "MaxMeasuredValue", &Z_FloatDiv100 }, //
{ 0x0402, 0x0003, "Tolerance", &Z_FloatDiv100 }, //
{ 0x0402, 0x0001, "TemperatureMinMeasuredValue", &Z_FloatDiv100 }, //
{ 0x0402, 0x0002, "TemperatureMaxMeasuredValue", &Z_FloatDiv100 }, //
{ 0x0402, 0x0003, "TemperatureTolerance", &Z_FloatDiv100 }, //
{ 0x0402, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Pressure Measurement cluster
{ 0x0403, 0x0000, D_JSON_PRESSURE_UNIT, &Z_AddPressureUnit }, // Pressure Unit
{ 0x0403, 0x0000, D_JSON_PRESSURE, &Z_Copy }, // Pressure
{ 0x0403, 0x0001, "MinMeasuredValue", &Z_Copy }, //
{ 0x0403, 0x0002, "MaxMeasuredValue", &Z_Copy }, //
{ 0x0403, 0x0003, "Tolerance", &Z_Copy }, //
{ 0x0403, 0x0010, "ScaledValue", &Z_Copy }, //
{ 0x0403, 0x0011, "MinScaledValue", &Z_Copy }, //
{ 0x0403, 0x0012, "MaxScaledValue", &Z_Copy }, //
{ 0x0403, 0x0013, "ScaledTolerance", &Z_Copy }, //
{ 0x0403, 0x0014, "Scale", &Z_Copy }, //
{ 0x0403, 0x0001, "PressureMinMeasuredValue", &Z_Copy }, //
{ 0x0403, 0x0002, "PressureMaxMeasuredValue", &Z_Copy }, //
{ 0x0403, 0x0003, "PressureTolerance", &Z_Copy }, //
{ 0x0403, 0x0010, "PressureScaledValue", &Z_Copy }, //
{ 0x0403, 0x0011, "PressureMinScaledValue", &Z_Copy }, //
{ 0x0403, 0x0012, "PressureMaxScaledValue", &Z_Copy }, //
{ 0x0403, 0x0013, "PressureScaledTolerance", &Z_Copy }, //
{ 0x0403, 0x0014, "PressureScale", &Z_Copy }, //
{ 0x0403, 0xFFFF, nullptr, &Z_Remove }, // Remove all other Pressure values
// Flow Measurement cluster
{ 0x0404, 0x0000, D_JSON_FLOWRATE, &Z_FloatDiv10 }, // Flow (in m3/h)
{ 0x0404, 0x0001, "MinMeasuredValue", &Z_Copy }, //
{ 0x0404, 0x0002, "MaxMeasuredValue", &Z_Copy }, //
{ 0x0404, 0x0003, "Tolerance", &Z_Copy }, //
{ 0x0404, 0x0001, "FlowMinMeasuredValue", &Z_Copy }, //
{ 0x0404, 0x0002, "FlowMaxMeasuredValue", &Z_Copy }, //
{ 0x0404, 0x0003, "FlowTolerance", &Z_Copy }, //
{ 0x0404, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Relative Humidity Measurement cluster
{ 0x0405, 0x0000, D_JSON_HUMIDITY, &Z_FloatDiv100 }, // Humidity
{ 0x0405, 0x0001, "MinMeasuredValue", &Z_Copy }, //
{ 0x0405, 0x0002, "MaxMeasuredValue", &Z_Copy }, //
{ 0x0405, 0x0003, "Tolerance", &Z_Copy }, //
{ 0x0405, 0x0001, "HumidityMinMeasuredValue", &Z_Copy }, //
{ 0x0405, 0x0002, "HumidityMaxMeasuredValue", &Z_Copy }, //
{ 0x0405, 0x0003, "HumidityTolerance", &Z_Copy }, //
{ 0x0405, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Occupancy Sensing cluster
@ -837,6 +917,11 @@ int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject&
json[new_name] = ((float)value) / 10.0f;
return 1; // remove original key
}
// Convert int to float and divide by 10
int32_t Z_FloatDiv2(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = ((float)value) / 2.0f;
return 1; // remove original key
}
// Publish a message for `"Occupancy":0` when the timer expired
int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
@ -947,9 +1032,6 @@ int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObje
int32_t Angle_X = 0.5f + atanf(X/sqrtf(z*z+y*y)) * f_180pi;
int32_t Angle_Y = 0.5f + atanf(Y/sqrtf(x*x+z*z)) * f_180pi;
int32_t Angle_Z = 0.5f + atanf(Z/sqrtf(x*x+y*y)) * f_180pi;
// int32_t Angle_X = 0.5f + atanf(X/sqrtf(Z*Z+Y*Y)) * f_180pi;
// int32_t Angle_Y = 0.5f + atanf(Y/sqrtf(X*X+Z*Z)) * f_180pi;
// int32_t Angle_Z = 0.5f + atanf(Z/sqrtf(X*X+Y*Y)) * f_180pi;
JsonArray& angles = json.createNestedArray(F("AqaraAngles"));
angles.add(Angle_X);
angles.add(Angle_Y);
@ -968,6 +1050,7 @@ int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject&
char tmp[] = "tmp"; // for obscure reasons, it must be converted from const char* to char*, otherwise ArduinoJson gets confused
JsonVariant sub_value;
const String * modelId = zigbee_devices.getModelId(shortaddr); // null if unknown
while (len - i >= 2) {
uint8_t attrid = buf2.get8(i++);
@ -975,25 +1058,43 @@ int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject&
i += parseSingleAttribute(json, tmp, buf2, i, len);
float val = json[tmp];
json.remove(tmp);
bool translated = false; // were we able to translate to a known format?
if (0x01 == attrid) {
json[F(D_JSON_VOLTAGE)] = val / 1000.0f;
json[F("Battery")] = toPercentageCR2032(val);
} else if (0 == zcl->getManufCode()) {
// onla Aqara Temp/Humidity has manuf_code of zero. If non-zero we skip the parameters
if (0x64 == attrid) {
json[F(D_JSON_TEMPERATURE)] = val / 100.0f;
} else if (0x65 == attrid) {
json[F(D_JSON_HUMIDITY)] = val / 100.0f;
} else if (0x66 == attrid) {
json[F(D_JSON_PRESSURE)] = val / 100.0f;
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
} else if (0x01 == attrid) {
json[F(D_JSON_VOLTAGE)] = val / 1000.0f;
json[F("Battery")] = toPercentageCR2032(val);
} else if ((nullptr != modelId) && (0 == zcl->getManufCode())) {
translated = true;
if (modelId->startsWith(F("lumi.sensor_ht")) ||
modelId->startsWith(F("lumi.weather"))) { // Temp sensor
// Filter according to prefix of model name
// onla Aqara Temp/Humidity has manuf_code of zero. If non-zero we skip the parameters
if (0x64 == attrid) {
json[F(D_JSON_TEMPERATURE)] = val / 100.0f;
} else if (0x65 == attrid) {
json[F(D_JSON_HUMIDITY)] = val / 100.0f;
} else if (0x66 == attrid) {
json[F(D_JSON_PRESSURE)] = val / 100.0f;
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
}
} else if (modelId->startsWith(F("lumi.sensor_smoke"))) { // gas leak
if (0x64 == attrid) {
json[F("SmokeDensity")] = val;
}
} else if (modelId->startsWith(F("lumi.sensor_natgas"))) { // gas leak
if (0x64 == attrid) {
json[F("GasDensity")] = val;
}
} else {
translated = false; // we didn't find a match
}
// } else if (0x115F == zcl->getManufCode()) { // Aqara Motion Sensor, still unknown field
}
if (!translated) {
if (attrid >= 100) { // payload is always above 0x64 or 100
char attr_name[12];
snprintf_P(attr_name, sizeof(attr_name), PSTR("Xiaomi_%02X"), attrid);
json[attr_name] = val;
}
} else if (0x115F == zcl->getManufCode()) {
// Aqara Motion Sensor, still unknown field
json[F("AqaraUnknown")] = val;
}
}
return 1; // remove original key

View File

@ -36,8 +36,14 @@ typedef struct Z_XYZ_Var { // Holds values for vairables X, Y and Z
uint8_t z_type = 0;
} Z_XYZ_Var;
// list of post-processing directives
const Z_CommandConverter Z_Commands[] = {
// Cluster specific commands
// Note: the table is both for sending commands, but also displaying received commands
// - tasmota_cmd: the human-readable name of the command as entered or displayed, use '|' to split into multiple commands when displayed
// - cluster: cluster number of the command
// - cmd: the command number, of 0xFF if it's actually a variable to be assigned from 'xx'
// - direction: the direction of the command (bit field). 0x01=from client to server (coord to device), 0x02= from server to client (response), 0x80=needs specific decoding
// - param: the paylod template, x/y/z are substituted with arguments, little endian. For command display, payload must match until x/y/z character or until the end of the paylod. '??' means ignore.
const Z_CommandConverter Z_Commands[] PROGMEM = {
// Group adress commands
{ "AddGroup", 0x0004, 0x00, 0x01, "xxxx00" }, // Add group id, group name is not supported
{ "ViewGroup", 0x0004, 0x01, 0x01, "xxxx" }, // Ask for the group name
@ -65,7 +71,7 @@ const Z_CommandConverter Z_Commands[] = {
{ "ShutterTilt", 0x0102, 0x08, 0x01, "xx" }, // Tilt percentage
{ "Shutter", 0x0102, 0xFF, 0x01, "" },
// Blitzwolf PIR
{ "Occupancy", 0xEF00, 0x01, 0x01, "xx"}, // Specific decoder for Blitzwolf PIR, empty name means special treatment
{ "Occupancy", 0xEF00, 0x01, 0x82, ""}, // Specific decoder for Blitzwolf PIR, empty name means special treatment
// Decoders only - normally not used to send, and names may be masked by previous definitions
{ "Dimmer", 0x0008, 0x00, 0x01, "xx" },
{ "DimmerMove", 0x0008, 0x01, 0x01, "xx0A" },
@ -85,15 +91,14 @@ const Z_CommandConverter Z_Commands[] = {
{ "ArrowHold", 0x0005, 0x08, 0x01, "xx" }, // xx == 0x01 = left, 0x00 = right
{ "ArrowRelease", 0x0005, 0x09, 0x01, "" },
// IAS - Intruder Alarm System + leak/fire detection
{ "ZoneStatusChange",0x0500, 0x00, 0x02, "xxxxyyzz" }, // xxxx = zone status, yy = extended status, zz = zone id, Delay is ignored
{ "ZoneStatusChange",0x0500, 0x00, 0x82, "xxxxyyzz" }, // xxxx = zone status, yy = extended status, zz = zone id, Delay is ignored
// responses for Group cluster commands
{ "AddGroupResp", 0x0004, 0x00, 0x02, "xxyyyy" }, // xx = status, yy = group id
{ "ViewGroupResp", 0x0004, 0x01, 0x02, "xxyyyy" }, // xx = status, yy = group id, name ignored
{ "GetGroupResp", 0x0004, 0x02, 0x02, "xxyyzzzz" }, // xx = capacity, yy = count, zzzz = first group id, following groups ignored
{ "RemoveGroup", 0x0004, 0x03, 0x02, "xxyyyy" }, // xx = status, yy = group id
{ "AddGroup", 0x0004, 0x00, 0x82, "xxyyyy" }, // xx = status, yy = group id
{ "ViewGroup", 0x0004, 0x01, 0x82, "xxyyyy" }, // xx = status, yy = group id, name ignored
{ "GetGroup", 0x0004, 0x02, 0x82, "xxyyzzzz" }, // xx = capacity, yy = count, zzzz = first group id, following groups ignored
{ "RemoveGroup", 0x0004, 0x03, 0x82, "xxyyyy" }, // xx = status, yy = group id
};
#define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian
// Below are the attributes we wand to read from each cluster
@ -235,16 +240,20 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
ToHex_P((unsigned char*)payload.getBuffer(), payload.len(), hex_char, hex_char_len);
const __FlashStringHelper* command_name = nullptr;
uint8_t conv_direction;
Z_XYZ_Var xyz;
//AddLog_P2(LOG_LEVEL_INFO, PSTR(">>> len = %d - %02X%02X%02X"), payload.len(), payload.get8(0), payload.get8(1), payload.get8(2));
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
const Z_CommandConverter *conv = &Z_Commands[i];
if (conv->cluster == cluster) {
uint16_t conv_cluster = pgm_read_word(&conv->cluster);
if (conv_cluster == cluster) {
// cluster match
if ((0xFF == conv->cmd) || (cmd == conv->cmd)) {
uint8_t conv_cmd = pgm_read_byte(&conv->cmd);
conv_direction = pgm_read_byte(&conv->direction);
if ((0xFF == conv_cmd) || (cmd == conv_cmd)) {
// cmd match
if ((direction && (conv->direction & 0x02)) || (!direction && (conv->direction & 0x01))) {
if ((direction && (conv_direction & 0x02)) || (!direction && (conv_direction & 0x01))) {
// check if we have a match for params too
// Match if:
// - payload exactly matches conv->param (conv->param may be longer)
@ -271,7 +280,7 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
if (match) {
command_name = (const __FlashStringHelper*) conv->tasmota_cmd;
parseXYZ(conv->param, payload, &xyz);
if (0xFF == conv->cmd) {
if (0xFF == conv_cmd) {
// shift all values
xyz.z = xyz.y;
xyz.z_type = xyz.y_type;
@ -296,31 +305,63 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
free(hex_char);
if (command_name) {
if (0 == xyz.x_type) {
json[command_name] = true; // no parameter
} else if (0 == xyz.y_type) {
json[command_name] = xyz.x; // 1 parameter
// Now try to transform into a human readable format
// if (direction & 0x80) then specific transform
if (conv_direction & 0x80) {
// TODO need to create a specific command
// IAS
String command_name2 = String(command_name);
if ((cluster == 0x0500) && (cmd == 0x00)) {
// "ZoneStatusChange"
json[command_name] = xyz.x;
json[command_name2 + "Ext"] = xyz.y;
json[command_name2 + "Zone"] = xyz.z;
} else if ((cluster == 0x0004) && ((cmd == 0x00) || (cmd == 0x01) || (cmd == 0x03))) {
// AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
json[command_name] = xyz.y;
json[command_name2 + "Status"] = xyz.x;
json[command_name2 + "StatusMsg"] = getZigbeeStatusMessage(xyz.x);
} else if ((cluster == 0x0004) && (cmd == 0x02)) {
// GetGroupResp
json[command_name2 + "Capacity"] = xyz.x;
json[command_name2 + "Count"] = xyz.y;
JsonArray &arr = json.createNestedArray(command_name);
for (uint32_t i = 0; i < xyz.y; i++) {
arr.add(payload.get16(2 + 2*i));
}
//arr.add(xyz.z);
}
} else {
// multiple answers, create an array
JsonArray &arr = json.createNestedArray(command_name);
arr.add(xyz.x);
arr.add(xyz.y);
if (xyz.z_type) {
arr.add(xyz.z);
if (0 == xyz.x_type) {
json[command_name] = true; // no parameter
} else if (0 == xyz.y_type) {
json[command_name] = xyz.x; // 1 parameter
} else {
// multiple answers, create an array
JsonArray &arr = json.createNestedArray(command_name);
arr.add(xyz.x);
arr.add(xyz.y);
if (xyz.z_type) {
arr.add(xyz.z);
}
}
}
}
}
// Find the command details by command name
// Only take commands outgoing, i.e. direction == 0
// If not found:
// - returns nullptr
const __FlashStringHelper* zigbeeFindCommand(const char *command, uint16_t *cluster, uint16_t *cmd) {
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
const Z_CommandConverter *conv = &Z_Commands[i];
if (0 == strcasecmp_P(command, conv->tasmota_cmd)) {
*cluster = conv->cluster;
*cmd = conv->cmd;
uint8_t conv_direction = pgm_read_byte(&conv->direction);
uint8_t conv_cmd = pgm_read_byte(&conv->cmd);
uint16_t conv_cluster = pgm_read_word(&conv->cluster);
if ((conv_direction & 0x01) && (0 == strcasecmp_P(command, conv->tasmota_cmd))) {
*cluster = conv_cluster;
*cmd = conv_cmd;
return (const __FlashStringHelper*) conv->param;
}
}

View File

@ -33,7 +33,7 @@ const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; // Node descriptor
const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints descriptor
const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters)
const uint8_t ZIGBEE_STATUS_DEVICE_INDICATION = 34; // Device announces its address
const uint8_t ZIGBEE_STATUS_DEVICE_IEEE = 35; // Request of device address
//const uint8_t ZIGBEE_STATUS_DEVICE_IEEE = 35; // Request of device address
const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
@ -49,9 +49,6 @@ typedef union Zigbee_Instruction {
uint16_t d16; // 16 bits data
} i;
const void *p; // pointer
// const void *m; // for type checking only, message
// const ZB_Func f;
// const ZB_RecvMsgFunc fr;
} Zigbee_Instruction;
//
// Zigbee_Instruction z1 = { .i = {1,2,3}};

View File

@ -39,7 +39,7 @@ int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) {
char hex[20];
Uint64toHex(long_adr, hex, 64);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
"\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\""
",\"DeviceType\":%d,\"DeviceState\":%d"
",\"NumAssocDevices\":%d"),
ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state,
@ -355,21 +355,24 @@ int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) {
zigbee_devices.updateDevice(nwkAddr, ieeeAddr);
char hex[20];
Uint64toHex(ieeeAddr, hex, 64);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
"}}"),
ZIGBEE_STATUS_DEVICE_IEEE, hex, nwkAddr
);
// Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
// "\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
// "}}"),
// ZIGBEE_STATUS_DEVICE_IEEE, hex, nwkAddr
// );
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
// MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
// XdrvRulesProcess();
// Ping response
const String * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
if (friendlyName) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), nwkAddr, friendlyName->c_str());
",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\""
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), nwkAddr, hex, friendlyName->c_str());
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"}}"), nwkAddr);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\""
"}}"), nwkAddr, hex);
}
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
@ -378,6 +381,27 @@ int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) {
return -1;
}
int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) {
Z_ShortAddress nwkAddr = buf.get16(2);
uint8_t status = buf.get8(4);
const String * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
if (friendlyName) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""
",\"" D_JSON_ZIGBEE_Status "\":%d"
"}}"), nwkAddr, friendlyName->c_str(), status);
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_Status "\":%d"
"}}"), nwkAddr, status);
}
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
return -1;
}
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
Z_ShortAddress srcAddr = buf.get16(2);
Z_ShortAddress nwkAddr = buf.get16(4);
@ -480,36 +504,45 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
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);
} 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 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);
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);
} 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);
zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes);
} else {
// Publish immediately
zigbee_devices.jsonPublishNow(srcaddr, json);
}
zigbee_devices.jsonAppend(srcaddr, json);
zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes);
} else {
// Publish immediately
zigbee_devices.jsonPublishNow(srcaddr, json);
}
return -1;
}
@ -527,6 +560,7 @@ 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
ZBM(AREQ_ZDO_IEEE_ADDR_RSP, Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP) // 4581
ZBM(AREQ_ZDO_BIND_RSP, Z_AREQ | Z_ZDO, ZDO_BIND_RSP) // 45A1
const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
@ -537,6 +571,7 @@ const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },
{ AREQ_ZDO_SIMPLEDESCRSP, &Z_ReceiveSimpleDesc },
{ AREQ_ZDO_IEEE_ADDR_RSP, &Z_ReceiveIEEEAddr },
{ AREQ_ZDO_BIND_RSP, &Z_BindRsp },
};
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {

View File

@ -33,22 +33,16 @@ 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_BIND "|"
D_CMND_ZIGBEE_PING ;
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_BIND "|"
D_CMND_ZIGBEE_PING ;
D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|"
D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID
;
void (* const ZigbeeCommand[])(void) PROGMEM = {
&CmndZbZNPSend, &CmndZbPermitJoin,
&CmndZbStatus, &CmndZbReset, &CmndZbSend,
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive,
&CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind,
&CmndZbPing,
&CmndZbForget, &CmndZbSave, &CmndZbName,
&CmndZbBind, &CmndZbPing, &CmndZbModelId,
};
int32_t ZigbeeProcessInput(class SBuffer &buf) {
@ -547,35 +541,75 @@ void CmndZbBind(void) {
// 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 srcDevice = 0xFFFF; // 0xFFFF is broadcast, so considered invalid
uint16_t dstDevice = 0xFFFF; // 0xFFFF is broadcast, so considered invalid
uint64_t dstLongAddr = 0;
uint8_t endpoint = 0x00; // 0x00 is invalid for the src endpoint
uint8_t toendpoint = 0x00; // 0x00 is invalid for the dst endpoint
uint16_t toGroup = 0x0000; // group address
uint16_t cluster = 0; // 0xFFFF is invalid
uint32_t group = 0xFFFFFFFF; // 16 bits values, otherwise 0xFFFFFFFF is unspecified
// Information about source device: "Device", "Endpoint", "Cluster"
// - the source endpoint must have a known IEEE address
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (0xFFFF == device) { ResponseCmndChar("Invalid parameter"); return; }
srcDevice = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (0xFFFF == srcDevice) { ResponseCmndChar("Invalid parameter"); return; }
}
if ((nullptr == &val_device) || (0x000 == device)) { ResponseCmndChar("Unknown device"); return; }
if ((nullptr == &val_device) || (0x0000 == srcDevice)) { ResponseCmndChar("Unknown source device"); return; }
// check if IEEE address is known
uint64_t srcLongAddr = zigbee_devices.getDeviceLongAddr(srcDevice);
if (0 == srcLongAddr) { ResponseCmndChar("Unknown source IEEE address"); return; }
// look for source endpoint
const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint"));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
// look for source cluster
const JsonVariant &val_cluster = getCaseInsensitive(json, PSTR("Cluster"));
if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); }
// TODO compute endpoint from cluster
// Either Device address
// In this case the following parameters are mandatory
// - "ToDevice" and the device must have a known IEEE address
// - "ToEndpoint"
const JsonVariant &dst_device = getCaseInsensitive(json, PSTR("ToDevice"));
if (nullptr != &dst_device) {
dstDevice = zigbee_devices.parseDeviceParam(dst_device.as<char*>());
if (0xFFFF == dstDevice) { ResponseCmndChar("Invalid parameter"); return; }
if (0x0000 == dstDevice) {
dstLongAddr = localIEEEAddr;
} else {
dstLongAddr = zigbee_devices.getDeviceLongAddr(dstDevice);
}
if (0 == dstLongAddr) { ResponseCmndChar("Unknown dest IEEE address"); return; }
const JsonVariant &val_toendpoint = getCaseInsensitive(json, PSTR("ToEndpoint"));
if (nullptr != &val_toendpoint) { toendpoint = strToUInt(val_endpoint); } else { toendpoint = endpoint; }
}
// Or Group Address - we don't need a dstEndpoint in this case
const JsonVariant &to_group = getCaseInsensitive(json, PSTR("ToGroup"));
if (nullptr != &to_group) { toGroup = strToUInt(to_group); }
// make sure we don't have conflicting parameters
if (toGroup && dstLongAddr) { ResponseCmndChar("Cannot have both \"ToDevice\" and \"ToGroup\""); return; }
if (!toGroup && !dstLongAddr) { ResponseCmndChar("Missing \"ToDevice\" or \"ToGroup\""); return; }
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.add16(srcDevice);
buf.add64(srcLongAddr);
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
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);
}
ZigbeeZNPSend(buf.getBuffer(), buf.len());
@ -635,6 +669,35 @@ void CmndZbName(void) {
}
}
// Specify, read or erase a ModelId, only for debug purposes
void CmndZbModelId(void) {
// Syntax is:
// ZigbeeName <device_id>,<friendlyname> - assign a friendly name
// ZigbeeName <device_id> - display the current friendly name
// ZigbeeName <device_id>, - remove friendly name
//
// Where <device_id> can be: short_addr, long_addr, device_index, friendly_name
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
// check if parameters contain a comma ','
char *p;
char *str = strtok_r(XdrvMailbox.data, ", ", &p);
// parse first part, <device_id>
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered
if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; }
if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; }
if (p == nullptr) {
const String * modelId = zigbee_devices.getModelId(shortaddr);
Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_MODELID "\":\"%s\"}}"), shortaddr, modelId ? modelId->c_str() : "");
} else {
zigbee_devices.setModelId(shortaddr, p);
Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_MODELID "\":\"%s\"}}"), shortaddr, p);
}
}
// Remove an old Zigbee device from the list of known devices, use ZigbeeStatus to know all registered devices
void CmndZbForget(void) {
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
@ -775,7 +838,6 @@ bool Xdrv23(uint8_t function)
case FUNC_LOOP:
if (ZigbeeSerial) { ZigbeeInput(); }
if (zigbee.state_machine) {
//ZigbeeStateMachine();
ZigbeeStateMachine_Run();
}
break;
@ -784,7 +846,6 @@ bool Xdrv23(uint8_t function)
break;
case FUNC_COMMAND:
result = DecodeCommand(kZbCommands, ZigbeeCommand);
result = result || DecodeCommand(kZigbeeCommands, ZigbeeCommand); // deprecated
break;
}
}