mirror of https://github.com/arendst/Tasmota.git
Merge pull request #7780 from s-hadinger/zigbee_commands_ping
Add Zigbee enhanced commands decoding, added ``ZbPing``
This commit is contained in:
commit
c3f5296d32
|
@ -6,6 +6,7 @@
|
|||
- Add initial support for Sensors AHT10 and AHT15 by Martin Wagner (#7596)
|
||||
- Add support for Wemos Motor Shield V1 by Denis Sborets (#7764)
|
||||
- Fix Zigbee auto-increment transaction number (#7757)
|
||||
- Add Zigbee enhanced commands decoding, added ``ZbPing``
|
||||
|
||||
### 8.1.0.8 20200212
|
||||
|
||||
|
|
|
@ -496,6 +496,8 @@
|
|||
#define D_JSON_ZIGBEE_RECEIVED "ZbReceived"
|
||||
#define D_JSON_ZIGBEE_RECEIVED_LEGACY "ZigbeeReceived"
|
||||
#define D_CMND_ZIGBEE_BIND "Bind"
|
||||
#define D_CMND_ZIGBEE_PING "Ping"
|
||||
#define D_JSON_ZIGBEE_PING "ZbPing"
|
||||
|
||||
// Commands xdrv_25_A4988_Stepper.ino
|
||||
#define D_CMND_MOTOR "MOTOR"
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
#ifndef ZIGBEERECEIVED
|
||||
#define ZIGBEERECEIVED 1
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
|
@ -725,18 +729,22 @@ 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();
|
||||
|
|
|
@ -486,18 +486,8 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
|
|||
|
||||
|
||||
// Parse non-normalized attributes
|
||||
// The key is "s_" followed by 16 bits clusterId, "_" followed by 8 bits command id
|
||||
void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
|
||||
uint32_t i = offset;
|
||||
uint32_t len = _payload.len();
|
||||
|
||||
char attrid_str[12];
|
||||
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X!%02X"), _cluster_id, _cmd_id);
|
||||
|
||||
char hex_char[_payload.len()*2+2];
|
||||
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
|
||||
|
||||
json[attrid_str] = hex_char;
|
||||
convertClusterSpecific(json, _cluster_id, _cmd_id, _frame_control.b.direction, _payload);
|
||||
}
|
||||
|
||||
// return value:
|
||||
|
|
|
@ -19,34 +19,81 @@
|
|||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
//typedef int32_t (*Z_AttrConverter)(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param);
|
||||
typedef struct Z_CommandConverter {
|
||||
const char * tasmota_cmd;
|
||||
const char * zcl_cmd;
|
||||
uint16_t cluster;
|
||||
uint8_t cmd; // normally 8 bits, 0xFF means it's a parameter
|
||||
uint8_t direction; // direction of the command. 0x01 client->server, 0x02 server->client, 0x03 both
|
||||
const char * param;
|
||||
} Z_CommandConverter;
|
||||
|
||||
typedef struct Z_XYZ_Var { // Holds values for vairables X, Y and Z
|
||||
uint32_t x = 0;
|
||||
uint32_t y = 0;
|
||||
uint32_t z = 0;
|
||||
uint8_t x_type = 0; // 0 = no value, 1 = 1 bytes, 2 = 2 bytes
|
||||
uint8_t y_type = 0;
|
||||
uint8_t z_type = 0;
|
||||
} Z_XYZ_Var;
|
||||
|
||||
// list of post-processing directives
|
||||
const Z_CommandConverter Z_Commands[] = {
|
||||
{ "Power", "0006!xx" }, // 0=Off, 1=On, 2=Toggle
|
||||
{ "Dimmer", "0008!04/xx0A00" }, // Move to Level with On/Off, xx=0..254 (255 is invalid)
|
||||
{ "Dimmer+", "0008!06/001902" }, // Step up by 10%, 0.2 secs
|
||||
{ "Dimmer-", "0008!06/011902" }, // Step down by 10%, 0.2 secs
|
||||
{ "DimmerStop", "0008!03" }, // Stop any Dimmer animation
|
||||
{ "ResetAlarm", "0009!00/xxyyyy" }, // Reset alarm (alarm code + cluster identifier)
|
||||
{ "ResetAllAlarms","0009!01" }, // Reset all alarms
|
||||
{ "Hue", "0300!00/xx000A00" }, // Move to Hue, shortest time, 1s
|
||||
{ "Sat", "0300!03/xx0A00" }, // Move to Sat
|
||||
{ "HueSat", "0300!06/xxyy0A00" }, // Hue, Sat
|
||||
{ "Color", "0300!07/xxxxyyyy0A00" }, // x, y (uint16)
|
||||
{ "CT", "0300!0A/xxxx0A00" }, // Color Temperature Mireds (uint16)
|
||||
{ "Shutter", "0102!xx" },
|
||||
{ "ShutterOpen", "0102!00" },
|
||||
{ "ShutterClose", "0102!01" },
|
||||
{ "ShutterStop", "0102!02" },
|
||||
{ "ShutterLift", "0102!05xx" }, // Lift percentage, 0%=open, 100%=closed
|
||||
{ "ShutterTilt", "0102!08xx" }, // Tilt percentage
|
||||
// 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
|
||||
{ "GetGroup", 0x0004, 0x02, 0x01, "01xxxx" }, // Get one group membership
|
||||
{ "GetAllGroups", 0x0004, 0x02, 0x01, "00" }, // Get all groups membership
|
||||
{ "RemoveGroup", 0x0004, 0x03, 0x01, "xxxx" }, // Remove one group
|
||||
{ "RemoveAllGroups",0x0004, 0x04, 0x01, "" }, // Remove all groups
|
||||
// Light & Shutter commands
|
||||
{ "Power", 0x0006, 0xFF, 0x01, "" }, // 0=Off, 1=On, 2=Toggle
|
||||
{ "Dimmer", 0x0008, 0x04, 0x01, "xx0A00" }, // Move to Level with On/Off, xx=0..254 (255 is invalid)
|
||||
{ "Dimmer+", 0x0008, 0x06, 0x01, "001902" }, // Step up by 10%, 0.2 secs
|
||||
{ "Dimmer-", 0x0008, 0x06, 0x01, "011902" }, // Step down by 10%, 0.2 secs
|
||||
{ "DimmerStop", 0x0008, 0x03, 0x01, "" }, // Stop any Dimmer animation
|
||||
{ "ResetAlarm", 0x0009, 0x00, 0x01, "xxyyyy" }, // Reset alarm (alarm code + cluster identifier)
|
||||
{ "ResetAllAlarms", 0x0009, 0x01, 0x01, "" }, // Reset all alarms
|
||||
{ "Hue", 0x0300, 0x00, 0x01, "xx000A00" }, // Move to Hue, shortest time, 1s
|
||||
{ "Sat", 0x0300, 0x03, 0x01, "xx0A00" }, // Move to Sat
|
||||
{ "HueSat", 0x0300, 0x06, 0x01, "xxyy0A00" }, // Hue, Sat
|
||||
{ "Color", 0x0300, 0x07, 0x01, "xxxxyyyy0A00" }, // x, y (uint16)
|
||||
{ "CT", 0x0300, 0x0A, 0x01, "xxxx0A00" }, // Color Temperature Mireds (uint16)
|
||||
{ "ShutterOpen", 0x0102, 0x00, 0x01, "" },
|
||||
{ "ShutterClose", 0x0102, 0x01, 0x01, "" },
|
||||
{ "ShutterStop", 0x0102, 0x02, 0x01, "" },
|
||||
{ "ShutterLift", 0x0102, 0x05, 0x01, "xx" }, // Lift percentage, 0%=open, 100%=closed
|
||||
{ "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
|
||||
// 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" },
|
||||
{ "DimmerStep", 0x0008, 0x02, 0x01, "xx190A00" },
|
||||
{ "DimmerMove", 0x0008, 0x05, 0x01, "xx0A" },
|
||||
{ "Dimmer+", 0x0008, 0x06, 0x01, "00" },
|
||||
{ "Dimmer-", 0x0008, 0x06, 0x01, "01" },
|
||||
{ "DimmerStop", 0x0008, 0x07, 0x01, "" },
|
||||
{ "HueMove", 0x0300, 0x01, 0x01, "xx19" },
|
||||
{ "HueStep", 0x0300, 0x02, 0x01, "xx190A00" },
|
||||
{ "SatMove", 0x0300, 0x04, 0x01, "xx19" },
|
||||
{ "SatStep", 0x0300, 0x05, 0x01, "xx190A" },
|
||||
{ "ColorMove", 0x0300, 0x08, 0x01, "xxxxyyyy" },
|
||||
{ "ColorStep", 0x0300, 0x09, 0x01, "xxxxyyyy0A00" },
|
||||
// Tradfri
|
||||
{ "ArrowClick", 0x0005, 0x07, 0x01, "xx" }, // xx == 0x01 = left, 0x00 = right
|
||||
{ "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
|
||||
// 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
|
||||
};
|
||||
|
||||
|
||||
#define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian
|
||||
|
||||
// Below are the attributes we wand to read from each cluster
|
||||
|
@ -55,6 +102,7 @@ const uint8_t CLUSTER_0008[] = { ZLE(0x0000) }; // CurrentLevel
|
|||
const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; // AlarmCount
|
||||
const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007) }; // Hue, Sat, X, Y, CT
|
||||
|
||||
// This callback is registered after a cluster specific command and sends a read command for the same cluster
|
||||
int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
|
||||
size_t attrs_len = 0;
|
||||
const uint8_t* attrs = nullptr;
|
||||
|
@ -82,7 +130,6 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoi
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// set a timer to read back the value in the future
|
||||
void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint) {
|
||||
uint32_t wait_ms = 0;
|
||||
|
@ -105,22 +152,182 @@ void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoi
|
|||
}
|
||||
}
|
||||
|
||||
const __FlashStringHelper* zigbeeFindCommand(const char *command) {
|
||||
char parm_uc[16]; // used to convert JSON keys to uppercase
|
||||
// returns true if char is 'x', 'y' or 'z'
|
||||
inline bool isXYZ(char c) {
|
||||
return (c >= 'x') && (c <= 'z');
|
||||
}
|
||||
|
||||
// returns the Hex value of a digit [0-9A-Fa-f]
|
||||
// return: 0x00-0x0F
|
||||
// or -1 if cannot be parsed
|
||||
inline int8_t hexValue(char c) {
|
||||
if ((c >= '0') && (c <= '9')) {
|
||||
return c - '0';
|
||||
}
|
||||
if ((c >= 'A') && (c <= 'F')) {
|
||||
return 10 + c - 'A';
|
||||
}
|
||||
if ((c >= 'a') && (c <= 'f')) {
|
||||
return 10 + c - 'a';
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse a Big Endian suite of max_len digits, or stops when a non-hex digit is found
|
||||
uint32_t parseHex_P(const char **data, size_t max_len = 8) {
|
||||
uint32_t ret = 0;
|
||||
for (uint32_t i = 0; i < max_len; i++) {
|
||||
int8_t v = hexValue(pgm_read_byte(*data));
|
||||
if (v < 0) { break; } // non hex digit, we stop parsing
|
||||
ret = (ret << 4) | v;
|
||||
*data += 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Parse a model like "xxyy00"
|
||||
// and fill x, y and z values
|
||||
// Little Endian encoding
|
||||
// On exit, xyz is updated, and x_type, y_type, z_type contain the number of bytes read for each
|
||||
void parseXYZ(const char *model, const SBuffer &payload, struct Z_XYZ_Var *xyz) {
|
||||
const char *p = model; // pointer to the model character
|
||||
uint32_t v = 0; // index in the payload bytes buffer
|
||||
char c = pgm_read_byte(p); // cur char
|
||||
while (c) {
|
||||
char c1 = pgm_read_byte(p+1); // next char
|
||||
if (!c1) { break; } // unexpected end of model
|
||||
if (isXYZ(c) && (c == c1) && (v < payload.len())) { // if char is [x-z] and followed by same char
|
||||
uint8_t val = payload.get8(v);
|
||||
switch (c) {
|
||||
case 'x':
|
||||
xyz->x = xyz->x | (val << (xyz->x_type * 8));
|
||||
xyz->x_type++;
|
||||
break;
|
||||
case 'y':
|
||||
xyz->y = xyz->y | (val << (xyz->y_type * 8));
|
||||
xyz->y_type++;
|
||||
break;
|
||||
case 'z':
|
||||
xyz->z = xyz->z | (val << (xyz->z_type * 8));
|
||||
xyz->z_type++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
p += 2;
|
||||
v++;
|
||||
c = pgm_read_byte(p);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
|
||||
// Parse a cluster specific command, and try to convert into human readable
|
||||
void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, bool direction, const SBuffer &payload) {
|
||||
size_t hex_char_len = payload.len()*2+2;
|
||||
char *hex_char = (char*) malloc(hex_char_len);
|
||||
if (!hex_char) { return; }
|
||||
ToHex_P((unsigned char*)payload.getBuffer(), payload.len(), hex_char, hex_char_len);
|
||||
|
||||
const __FlashStringHelper* command_name = nullptr;
|
||||
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) {
|
||||
// cluster match
|
||||
if ((0xFF == conv->cmd) || (cmd == conv->cmd)) {
|
||||
// cmd match
|
||||
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)
|
||||
// - payload matches conv->param until 'x', 'y' or 'z'
|
||||
const char * p = conv->param;
|
||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR(">>>++1 param = %s"), p);
|
||||
bool match = true;
|
||||
for (uint8_t i = 0; i < payload.len(); i++) {
|
||||
const char c1 = pgm_read_byte(p);
|
||||
const char c2 = pgm_read_byte(p+1);
|
||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR(">>>++2 c1 = %c, c2 = %c"), c1, c2);
|
||||
if ((0x00 == c1) || isXYZ(c1)) {
|
||||
break;
|
||||
}
|
||||
const char * p2 = p;
|
||||
uint32_t nextbyte = parseHex_P(&p2, 2);
|
||||
//AddLog_P2(LOG_LEVEL_INFO, PSTR(">>>++3 parseHex_P = %02X"), nextbyte);
|
||||
if (nextbyte != payload.get8(i)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
p += 2;
|
||||
}
|
||||
if (match) {
|
||||
command_name = (const __FlashStringHelper*) conv->tasmota_cmd;
|
||||
parseXYZ(conv->param, payload, &xyz);
|
||||
if (0xFF == conv->cmd) {
|
||||
// shift all values
|
||||
xyz.z = xyz.y;
|
||||
xyz.z_type = xyz.y_type;
|
||||
xyz.y = xyz.x;
|
||||
xyz.y_type = xyz.x_type;
|
||||
xyz.x = cmd;
|
||||
xyz.x_type = 1; // 1 byte
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// always report attribute in raw format
|
||||
// Format: "0001!06": "00" = "<cluster>!<cmd>": "<payload>" for commands to devices
|
||||
// Format: "0004<00": "00" = "<cluster><<cmd>": "<payload>" for commands to devices
|
||||
char attrid_str[12];
|
||||
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X%c%02X"), cluster, direction ? '<' : '!', cmd);
|
||||
json[attrid_str] = hex_char;
|
||||
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
|
||||
} 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
|
||||
// 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)) {
|
||||
return (const __FlashStringHelper*) conv->zcl_cmd;
|
||||
*cluster = conv->cluster;
|
||||
*cmd = conv->cmd;
|
||||
return (const __FlashStringHelper*) conv->param;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline bool isXYZ(char c) {
|
||||
return (c >= 'x') && (c <= 'z');
|
||||
}
|
||||
|
||||
// take the lower 4 bits and turn it to an hex char
|
||||
inline char hexDigit(uint32_t h) {
|
||||
uint32_t nybble = h & 0x0F;
|
||||
|
|
|
@ -33,6 +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_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
|
||||
|
|
|
@ -176,15 +176,24 @@ int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// 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) };
|
||||
|
||||
uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ,
|
||||
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
||||
|
||||
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
|
||||
|
||||
// uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ,
|
||||
// Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
||||
|
||||
//ZigbeeZNPSend(NodeDescReq, sizeof(NodeDescReq)); Not sure this is useful
|
||||
}
|
||||
|
||||
|
@ -335,6 +344,40 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
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);
|
||||
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();
|
||||
// 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());
|
||||
} else {
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"}}"), nwkAddr);
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -484,6 +527,7 @@ ZBM(AREQ_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND) // 45CA
|
|||
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
|
||||
|
||||
const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
|
||||
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
|
||||
|
@ -493,6 +537,7 @@ const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
|
|||
{ AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc },
|
||||
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },
|
||||
{ AREQ_ZDO_SIMPLEDESCRSP, &Z_ReceiveSimpleDesc },
|
||||
{ AREQ_ZDO_IEEE_ADDR_RSP, &Z_ReceiveIEEEAddr },
|
||||
};
|
||||
|
||||
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
|
||||
|
|
|
@ -33,19 +33,22 @@ 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_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_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND "|"
|
||||
D_CMND_ZIGBEE_PING ;
|
||||
|
||||
void (* const ZigbeeCommand[])(void) PROGMEM = {
|
||||
&CmndZbZNPSend, &CmndZbPermitJoin,
|
||||
&CmndZbStatus, &CmndZbReset, &CmndZbSend,
|
||||
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive,
|
||||
&CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind
|
||||
&CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind,
|
||||
&CmndZbPing,
|
||||
};
|
||||
|
||||
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
||||
|
@ -367,62 +370,17 @@ void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8
|
|||
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
||||
}
|
||||
|
||||
inline int8_t hexValue(char c) {
|
||||
if ((c >= '0') && (c <= '9')) {
|
||||
return c - '0';
|
||||
}
|
||||
if ((c >= 'A') && (c <= 'F')) {
|
||||
return 10 + c - 'A';
|
||||
}
|
||||
if ((c >= 'a') && (c <= 'f')) {
|
||||
return 10 + c - 'a';
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t parseHex(const char **data, size_t max_len = 8) {
|
||||
uint32_t ret = 0;
|
||||
for (uint32_t i = 0; i < max_len; i++) {
|
||||
int8_t v = hexValue(**data);
|
||||
if (v < 0) { break; } // non hex digit, we stop parsing
|
||||
ret = (ret << 4) | v;
|
||||
*data += 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
|
||||
|
||||
uint16_t cluster = 0x0000; // 0x0000 is a valid default value
|
||||
uint8_t cmd = ZCL_READ_ATTRIBUTES; // default command is READ_ATTRIBUTES
|
||||
bool clusterSpecific = false;
|
||||
// Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC"
|
||||
// where AA is the cluster number, BBBB the command number, CCCC... the payload
|
||||
// First delimiter is '_' for a global command, or '!' for a cluster specific commanc
|
||||
cluster = parseHex(&data, 4);
|
||||
|
||||
// delimiter
|
||||
if (('_' == *data) || ('!' == *data)) {
|
||||
if ('!' == *data) { clusterSpecific = true; }
|
||||
data++;
|
||||
} else {
|
||||
ResponseCmndChar("Wrong delimiter for payload");
|
||||
return;
|
||||
}
|
||||
// parse cmd number
|
||||
cmd = parseHex(&data, 2);
|
||||
|
||||
// move to end of payload
|
||||
// delimiter is optional
|
||||
if ('/' == *data) { data++; } // skip delimiter
|
||||
|
||||
size_t size = strlen(data);
|
||||
void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, bool clusterSpecific,
|
||||
uint16_t cluster, uint8_t cmd, const char *param) {
|
||||
size_t size = param ? strlen(param) : 0;
|
||||
SBuffer buf((size+2)/2); // actual bytes buffer for data
|
||||
|
||||
while (*data) {
|
||||
uint8_t code = parseHex(&data, 2);
|
||||
if (param) {
|
||||
while (*param) {
|
||||
uint8_t code = parseHex_P(¶m, 2);
|
||||
buf.add8(code);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 == endpoint) {
|
||||
// endpoint is not specified, let's try to find it from shortAddr
|
||||
|
@ -430,7 +388,7 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
|
|||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
|
||||
}
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: dstAddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"),
|
||||
dstAddr, cluster, endpoint, cmd, data);
|
||||
dstAddr, cluster, endpoint, cmd, param);
|
||||
|
||||
if (0 == endpoint) {
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint"));
|
||||
|
@ -467,6 +425,9 @@ void CmndZbSend(void) {
|
|||
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
|
||||
// Command elements
|
||||
uint16_t cluster = 0;
|
||||
uint8_t cmd = 0;
|
||||
String cmd_str = ""; // the actual low-level command, either specified or computed
|
||||
|
||||
// parse JSON
|
||||
|
@ -497,8 +458,9 @@ void CmndZbSend(void) {
|
|||
String key = it->key;
|
||||
JsonVariant& value = it->value;
|
||||
uint32_t x = 0, y = 0, z = 0;
|
||||
uint16_t cmd_var;
|
||||
|
||||
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str());
|
||||
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str(), &cluster, &cmd_var);
|
||||
if (tasmota_cmd) {
|
||||
cmd_str = tasmota_cmd;
|
||||
} else {
|
||||
|
@ -534,9 +496,16 @@ void CmndZbSend(void) {
|
|||
}
|
||||
}
|
||||
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str());
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str());
|
||||
if (0xFF == cmd_var) { // if command number is a variable, replace it with x
|
||||
cmd = x;
|
||||
x = y; // and shift other variables
|
||||
y = z;
|
||||
} else {
|
||||
cmd = cmd_var; // or simply copy the cmd number
|
||||
}
|
||||
cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z); // fill in parameters
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str());
|
||||
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str());
|
||||
} else {
|
||||
// we have zero command, pass through until last error for missing command
|
||||
}
|
||||
|
@ -547,9 +516,9 @@ void CmndZbSend(void) {
|
|||
// we have an unsupported command type, just ignore it and fallback to missing command
|
||||
}
|
||||
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"),
|
||||
device, endpoint, cmd_str.c_str());
|
||||
zigbeeZCLSendStr(device, endpoint, cmd_str.c_str());
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%04X!%02X/%s\"}"),
|
||||
device, endpoint, cluster, cmd, cmd_str.c_str());
|
||||
zigbeeZCLSendStr(device, endpoint, true, cluster, cmd, cmd_str.c_str());
|
||||
} else {
|
||||
Response_P(PSTR("Missing zigbee 'Send'"));
|
||||
return;
|
||||
|
@ -615,16 +584,28 @@ void CmndZbBind(void) {
|
|||
|
||||
// Probe a specific device to get its endpoints and supported clusters
|
||||
void CmndZbProbe(void) {
|
||||
CmndZbProbeOrPing(true);
|
||||
}
|
||||
|
||||
void CmndZbProbeOrPing(boolean probe) {
|
||||
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
||||
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
|
||||
if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; }
|
||||
if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; }
|
||||
|
||||
// everything is good, we can send the command
|
||||
Z_SendIEEEAddrReq(shortaddr);
|
||||
if (probe) {
|
||||
Z_SendActiveEpReq(shortaddr);
|
||||
}
|
||||
ResponseCmndDone();
|
||||
}
|
||||
|
||||
// Ping a device, actually a simplified version of ZbProbe
|
||||
void CmndZbPing(void) {
|
||||
CmndZbProbeOrPing(false);
|
||||
}
|
||||
|
||||
// Specify, read or erase a Friendly Name
|
||||
void CmndZbName(void) {
|
||||
// Syntax is:
|
||||
|
@ -729,6 +710,11 @@ void CmndZbRead(void) {
|
|||
}
|
||||
}
|
||||
|
||||
if (0 == endpoint) { // try to compute the endpoint
|
||||
endpoint = zigbee_devices.findClusterEndpointIn(device, cluster);
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
|
||||
}
|
||||
|
||||
if ((0 != endpoint) && (attrs_len > 0)) {
|
||||
ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device));
|
||||
ResponseCmndDone();
|
||||
|
|
Loading…
Reference in New Issue