Merge pull request #8572 from s-hadinger/zigbee_report

Add Zigbee options to ``ZbSend`` to write and report attributes
This commit is contained in:
Theo Arends 2020-05-30 08:18:23 +02:00 committed by GitHub
commit abd60ea164
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1025 additions and 709 deletions

View File

@ -11,6 +11,7 @@
- Add Three Phase Export Active Energy to SDM630 driver - Add Three Phase Export Active Energy to SDM630 driver
- Add wildcard pattern ``?`` for JSON matching in rules - Add wildcard pattern ``?`` for JSON matching in rules
- Add support for unique MQTTClient (and inherited fallback topic) by full Mac address using ``mqttclient DVES_%12X`` (#8300) - Add support for unique MQTTClient (and inherited fallback topic) by full Mac address using ``mqttclient DVES_%12X`` (#8300)
- Add Zigbee options to ``ZbSend`` to write and report attributes
### 8.3.1.1 20200518 ### 8.3.1.1 20200518

View File

@ -513,10 +513,15 @@
#define D_CMND_ZIGBEE_FORGET "Forget" #define D_CMND_ZIGBEE_FORGET "Forget"
#define D_CMND_ZIGBEE_SAVE "Save" #define D_CMND_ZIGBEE_SAVE "Save"
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality" #define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
#define D_CMND_ZIGBEE_CLUSTER "Cluster"
#define D_CMND_ZIGBEE_ENDPOINT "Endpoint" #define D_CMND_ZIGBEE_ENDPOINT "Endpoint"
#define D_CMND_ZIGBEE_GROUP "Group" #define D_CMND_ZIGBEE_GROUP "Group"
#define D_CMND_ZIGBEE_MANUF "Manuf"
#define D_CMND_ZIGBEE_DEVICE "Device"
#define D_CMND_ZIGBEE_READ "Read" #define D_CMND_ZIGBEE_READ "Read"
#define D_CMND_ZIGBEE_SEND "Send" #define D_CMND_ZIGBEE_SEND "Send"
#define D_CMND_ZIGBEE_WRITE "Write"
#define D_CMND_ZIGBEE_REPORT "Report"
#define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent" #define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent"
#define D_JSON_ZIGBEE_RECEIVED "ZbReceived" #define D_JSON_ZIGBEE_RECEIVED "ZbReceived"
#define D_CMND_ZIGBEE_BIND "Bind" #define D_CMND_ZIGBEE_BIND "Bind"

View File

@ -458,6 +458,21 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
rule_param = String(SunMinutes(1)); rule_param = String(SunMinutes(1));
} }
#endif // USE_TIMERS and USE_SUNRISE #endif // USE_TIMERS and USE_SUNRISE
// #ifdef USE_ZIGBEE
// if (rule_param.startsWith(F("%ZBDEVICE%"))) {
// snprintf_P(stemp, sizeof(stemp), PSTR("0x%04X"), Z_GetLastDevice());
// rule_param = String(stemp);
// }
// if (rule_param.startsWith(F("%ZBGROUP%"))) {
// rule_param = String(Z_GetLastGroup());
// }
// if (rule_param.startsWith(F("%ZBCLUSTER%"))) {
// rule_param = String(Z_GetLastCluster());
// }
// if (rule_param.startsWith(F("%ZBENDPOINT%"))) {
// rule_param = String(Z_GetLastEndpoint());
// }
// #endif
rule_param.toUpperCase(); rule_param.toUpperCase();
strlcpy(rule_svalue, rule_param.c_str(), sizeof(rule_svalue)); strlcpy(rule_svalue, rule_param.c_str(), sizeof(rule_svalue));
@ -701,6 +716,13 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved)
RulesVarReplace(commands, F("%SUNRISE%"), String(SunMinutes(0))); RulesVarReplace(commands, F("%SUNRISE%"), String(SunMinutes(0)));
RulesVarReplace(commands, F("%SUNSET%"), String(SunMinutes(1))); RulesVarReplace(commands, F("%SUNSET%"), String(SunMinutes(1)));
#endif // USE_TIMERS and USE_SUNRISE #endif // USE_TIMERS and USE_SUNRISE
#ifdef USE_ZIGBEE
snprintf_P(stemp, sizeof(stemp), PSTR("0x%04X"), Z_GetLastDevice());
RulesVarReplace(commands, F("%ZBDEVICE%"), String(stemp));
RulesVarReplace(commands, F("%ZBGROUP%"), String(Z_GetLastGroup()));
RulesVarReplace(commands, F("%ZBCLUSTER%"), String(Z_GetLastCluster()));
RulesVarReplace(commands, F("%ZBENDPOINT%"), String(Z_GetLastEndpoint()));
#endif
char command[commands.length() +1]; char command[commands.length() +1];
strlcpy(command, commands.c_str(), sizeof(command)); strlcpy(command, commands.c_str(), sizeof(command));
@ -1261,6 +1283,16 @@ bool findNextVariableValue(char * &pVarname, float &value)
} else if (sVarName.equals(F("SUNSET"))) { } else if (sVarName.equals(F("SUNSET"))) {
value = SunMinutes(1); value = SunMinutes(1);
#endif #endif
// #ifdef USE_ZIGBEE
// // } else if (sVarName.equals(F("ZBDEVICE"))) {
// // value = Z_GetLastDevice();
// } else if (sVarName.equals(F("ZBGROUP"))) {
// value = Z_GetLastGroup();
// } else if (sVarName.equals(F("ZBCLUSTER"))) {
// value = Z_GetLastCluster();
// } else if (sVarName.equals(F("ZBENDPOINT"))) {
// value = Z_GetLastEndpoint();
// #endif
} else { } else {
succeed = false; succeed = false;
} }

View File

@ -26,6 +26,24 @@
#endif #endif
const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds
/*********************************************************************************************\
* Structures for Rules variables related to the last received message
\*********************************************************************************************/
typedef struct Z_LastMessageVars {
uint16_t device; // device short address
uint16_t groupaddr; // group address
uint16_t cluster; // cluster id
uint8_t endpoint; // source endpoint
} Z_LastMessageVars;
Z_LastMessageVars gZbLastMessage;
uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; }
uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
/*********************************************************************************************\ /*********************************************************************************************\
* Structures for device configuration * Structures for device configuration
\*********************************************************************************************/ \*********************************************************************************************/
@ -256,7 +274,7 @@ int32_t Z_Devices::findEndpointInVector(const std::vector<T> & vecOfElements, u
// entry with same shortaddr or longaddr exists. // entry with same shortaddr or longaddr exists.
// //
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
if (!shortaddr && !longaddr) { return *(Z_Device*) nullptr; } // it is not legal to create an enrty with both short/long addr null if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return *(Z_Device*) nullptr; } // it is not legal to create this entry
//Z_Device* device_alloc = (Z_Device*) malloc(sizeof(Z_Device)); //Z_Device* device_alloc = (Z_Device*) malloc(sizeof(Z_Device));
Z_Device* device_alloc = new Z_Device{ Z_Device* device_alloc = new Z_Device{
longaddr, longaddr,
@ -340,7 +358,7 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
if (name_len) { if (name_len) {
for (auto &elem : _devices) { for (auto &elem : _devices) {
if (elem->friendlyName) { if (elem->friendlyName) {
if (strcmp(elem->friendlyName, name) == 0) { return found; } if (strcasecmp(elem->friendlyName, name) == 0) { return found; }
} }
found++; found++;
} }
@ -860,21 +878,21 @@ const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
Z_Device & device = getShortAddr(shortaddr); Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found if (&device == nullptr) { return; } // don't crash if not found
JsonObject * json = device.json; JsonObject & json = *device.json;
if (json == nullptr) { return; } // abort if nothing in buffer if (&json == nullptr) { return; } // abort if nothing in buffer
const char * fname = zigbee_devices.getFriendlyName(shortaddr); const char * fname = zigbee_devices.getFriendlyName(shortaddr);
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
// Remove redundant "Name" or "Device" // save parameters is global variables to be used by Rules
if (use_fname) { gZbLastMessage.device = shortaddr; // %zbdevice%
json->remove(F(D_JSON_ZIGBEE_NAME)); gZbLastMessage.groupaddr = json[F(D_CMND_ZIGBEE_GROUP)]; // %zbgroup%
} else { gZbLastMessage.cluster = json[F(D_CMND_ZIGBEE_CLUSTER)]; // %zbcluster%
json->remove(F(D_JSON_ZIGBEE_DEVICE)); gZbLastMessage.endpoint = json[F(D_CMND_ZIGBEE_ENDPOINT)]; // %zbendpoint%
}
// dump json in string
String msg = ""; String msg = "";
json->printTo(msg); json.printTo(msg);
zigbee_devices.jsonClear(shortaddr); zigbee_devices.jsonClear(shortaddr);
if (use_fname) { if (use_fname) {
@ -889,7 +907,7 @@ void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
} else { } else {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
} }
XdrvRulesProcess(); XdrvRulesProcess(); // apply rules
} }
void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) { void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) {
@ -923,7 +941,7 @@ uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_know
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 99)) { if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 99)) {
shortaddr = zigbee_devices.isKnownIndex(XdrvMailbox.payload - 1); shortaddr = zigbee_devices.isKnownIndex(XdrvMailbox.payload - 1);
} }
} else if ((dataBuf[0] == '0') && (dataBuf[1] == 'x')) { } else if ((dataBuf[0] == '0') && ((dataBuf[1] == 'x') || (dataBuf[1] == 'X'))) {
// starts with 0x // starts with 0x
if (strlen(dataBuf) < 18) { if (strlen(dataBuf) < 18) {
// expect a short address // expect a short address

File diff suppressed because it is too large Load Diff

View File

@ -106,10 +106,11 @@ enum Zigbee_StateMachine_Instruction_Set {
// Labels used in the State Machine -- internal only // Labels used in the State Machine -- internal only
const uint8_t ZIGBEE_LABEL_INIT_COORD = 10; // Start ZNP as coordinator const uint8_t ZIGBEE_LABEL_INIT_COORD = 10; // Start ZNP as coordinator
const uint8_t ZIGBEE_LABEL_START_COORD = 11; // Start ZNP as coordinator const uint8_t ZIGBEE_LABEL_START_COORD = 11; // Start ZNP as coordinator
const uint8_t ZIGBEE_LABEL_INIT_ROUTER = 12; // Start ZNP as router const uint8_t ZIGBEE_LABEL_INIT_ROUTER = 12; // Init ZNP as router
const uint8_t ZIGBEE_LABEL_START_ROUTER = 13; // Start ZNP as router const uint8_t ZIGBEE_LABEL_START_ROUTER = 13; // Start ZNP as router
const uint8_t ZIGBEE_LABEL_INIT_DEVICE = 14; // Start ZNP as end-device const uint8_t ZIGBEE_LABEL_INIT_DEVICE = 14; // Init ZNP as end-device
// const uint8_t ZIGBEE_LABEL_START_DEVICE = 15; // Start ZNP as end-device - same as ZIGBEE_LABEL_START_ROUTER const uint8_t ZIGBEE_LABEL_START_DEVICE = 15; // Start ZNP as end-device
const uint8_t ZIGBEE_LABEL_START_ROUTER_DEVICE = 16; // Start common to router and device
const uint8_t ZIGBEE_LABEL_FACT_RESET_ROUTER_DEVICE_POST = 19; // common post configuration for router and device const uint8_t ZIGBEE_LABEL_FACT_RESET_ROUTER_DEVICE_POST = 19; // common post configuration for router and device
const uint8_t ZIGBEE_LABEL_READY = 20; // goto label 20 for main loop const uint8_t ZIGBEE_LABEL_READY = 20; // goto label 20 for main loop
const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21; // main loop const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21; // main loop
@ -400,7 +401,7 @@ void Z_UpdateConfig(uint8_t zb_channel, uint16_t zb_pan_id, uint64_t zb_ext_pani
const char kCheckingDeviceConfiguration[] PROGMEM = D_LOG_ZIGBEE "checking device configuration"; const char kCheckingDeviceConfiguration[] PROGMEM = D_LOG_ZIGBEE "checking device configuration";
const char kConfiguredCoord[] PROGMEM = "Configured, starting coordinator"; const char kConfiguredCoord[] PROGMEM = "Configured, starting coordinator";
const char kConfiguredRouter[] PROGMEM = "Configured, starting router"; const char kConfiguredRouter[] PROGMEM = "Configured, starting router";
const char kConfiguredDevice[] PROGMEM = "Configured, starting end-device"; const char kConfiguredDevice[] PROGMEM = "Configured, starting device";
const char kStarted[] PROGMEM = "Started"; const char kStarted[] PROGMEM = "Started";
const char kZigbeeStarted[] PROGMEM = D_LOG_ZIGBEE "Zigbee started"; const char kZigbeeStarted[] PROGMEM = D_LOG_ZIGBEE "Zigbee started";
const char kResetting[] PROGMEM = "Resetting configuration"; const char kResetting[] PROGMEM = "Resetting configuration";
@ -426,7 +427,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_WAIT_RECV_FUNC(2000, ZBR_VERSION, &Z_ReceiveCheckVersion) // Check if version is valid ZI_WAIT_RECV_FUNC(2000, ZBR_VERSION, &Z_ReceiveCheckVersion) // Check if version is valid
// Dispatching whether coordinator, router or end-device // Dispatching whether coordinator, router or end-device
ZI_CALL(&Z_SwitchDeviceType, 0) // goto ZIGBEE_LABEL_START_ROUTER, ZIGBEE_LABEL_START_DEVICE or continue if coordinator ZI_CALL(&Z_SwitchDeviceType, 0) // goto ZIGBEE_LABEL_INIT_ROUTER, ZIGBEE_LABEL_INIT_DEVICE or continue if coordinator
// ====================================================================== // ======================================================================
// Start as Zigbee Coordinator // Start as Zigbee Coordinator
@ -537,8 +538,9 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_SEND(ZBS_LOGTYPE) // check the logical type ZI_SEND(ZBS_LOGTYPE) // check the logical type
ZI_WAIT_RECV(1000, ZBS_LOGTYPE_ROUTER) // it should be coordinator ZI_WAIT_RECV(1000, ZBS_LOGTYPE_ROUTER) // it should be coordinator
ZI_LABEL(ZIGBEE_LABEL_START_ROUTER) // Init as a router // ZI_LABEL(ZIGBEE_LABEL_START_ROUTER) // Init as a router
ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, kConfiguredRouter) ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, kConfiguredRouter)
ZI_LABEL(ZIGBEE_LABEL_START_ROUTER_DEVICE)
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT) ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
ZI_SEND(ZBS_AF_REGISTER_ALL) // Z_AF register for endpoint 01, profile 0x0104 Home Automation ZI_SEND(ZBS_AF_REGISTER_ALL) // Z_AF register for endpoint 01, profile 0x0104 Home Automation
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER) ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
@ -570,7 +572,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured
ZI_WAIT_RECV(1000, ZBR_WNV_OK) ZI_WAIT_RECV(1000, ZBR_WNV_OK)
ZI_GOTO(ZIGBEE_LABEL_START_ROUTER) ZI_GOTO(ZIGBEE_LABEL_START_ROUTER_DEVICE)
// ====================================================================== // ======================================================================
// Start as Zigbee Device // Start as Zigbee Device
@ -583,7 +585,8 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_SEND(ZBS_LOGTYPE) // check the logical type ZI_SEND(ZBS_LOGTYPE) // check the logical type
ZI_WAIT_RECV(1000, ZBS_LOGTYPE_DEVICE) // it should be coordinator ZI_WAIT_RECV(1000, ZBS_LOGTYPE_DEVICE) // it should be coordinator
ZI_GOTO(ZIGBEE_LABEL_START_ROUTER) ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, kConfiguredDevice)
ZI_GOTO(ZIGBEE_LABEL_START_ROUTER_DEVICE)
ZI_LABEL(ZIGBEE_LABEL_FACT_RESET_DEVICE) // Factory reset for router ZI_LABEL(ZIGBEE_LABEL_FACT_RESET_DEVICE) // Factory reset for router
ZI_MQTT_STATE(ZIGBEE_STATUS_RESET_CONF, kResetting) ZI_MQTT_STATE(ZIGBEE_STATUS_RESET_CONF, kResetting)

View File

@ -653,13 +653,16 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
JsonObject& json = jsonBuffer.createObject(); JsonObject& json = jsonBuffer.createObject();
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) { if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseResponse(); zcl_received.parseResponse(); // Zigbee general "Degault Response", publish ZbResponse message
} else { } else {
// Build the ZbReceive json // Build the ZbReceive json
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) { if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseRawAttributes(json); zcl_received.parseRawAttributes(json); // Zigbee report attributes from sensors
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) { } else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseReadAttributesResponse(json);
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseReadAttributes(json); zcl_received.parseReadAttributes(json);
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if (zcl_received.isClusterSpecificCommand()) { } else if (zcl_received.isClusterSpecificCommand()) {

View File

@ -32,7 +32,7 @@ TasmotaSerial *ZigbeeSerial = nullptr;
const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" 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_PROBE "|" D_CMND_ZIGBEEZNPRECEIVE "|"
D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|"
D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|" D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|"
D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|"
@ -42,7 +42,7 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix
void (* const ZigbeeCommand[])(void) PROGMEM = { void (* const ZigbeeCommand[])(void) PROGMEM = {
&CmndZbZNPSend, &CmndZbPermitJoin, &CmndZbZNPSend, &CmndZbPermitJoin,
&CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbStatus, &CmndZbReset, &CmndZbSend,
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive, &CmndZbProbe, &CmndZbZNPReceive,
&CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbForget, &CmndZbSave, &CmndZbName,
&CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId, &CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId,
&CmndZbLight, &CmndZbRestore, &CmndZbBindState, &CmndZbLight, &CmndZbRestore, &CmndZbBindState,
@ -393,61 +393,107 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint,
} }
} }
// // Parse "Report" or "Write" attribute
// Command `ZbSend` void ZbSendReportWrite(const JsonVariant &val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, bool write) {
// SBuffer buf(200); // buffer to store the binary output of attibutes
void CmndZbSend(void) {
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"0xFF"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":null} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":false} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":true} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"true"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"ShutterClose":null} }
// ZbSend { "devicse":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} }
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
DynamicJsonBuffer jsonBuf;
const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params const JsonObject &attrs = val_pubwrite.as<const JsonObject&>();
static char delim[] = ", "; // delimiters for parameters // iterate on keys
uint16_t device = BAD_SHORTADDR; // 0x0000 is local, so considered invalid for (JsonObject::const_iterator it=attrs.begin(); it!=attrs.end(); ++it) {
uint16_t groupaddr = 0x0000; // group address const char *key = it->key;
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint const JsonVariant &value = it->value;
uint16_t manuf = 0x0000; // Manuf Id in ZCL frame
// Command elements uint16_t attr_id = 0xFFFF;
uint16_t cluster = 0; uint16_t cluster_id = 0xFFFF;
uint8_t type_id = Znodata;
// check if the name has the format "XXXX/YYYY" where XXXX is the cluster, YYYY the attribute id
// alternative "XXXX/YYYY%ZZ" where ZZ is the type (for unregistered attributes)
char * delimiter = strchr(key, '/');
char * delimiter2 = strchr(key, '%');
if (delimiter) {
cluster_id = strtoul(key, &delimiter, 16);
if (!delimiter2) {
attr_id = strtoul(delimiter+1, nullptr, 16);
} else {
attr_id = strtoul(delimiter+1, &delimiter2, 16);
type_id = strtoul(delimiter2+1, nullptr, 16);
}
}
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X"), cluster_id, attr_id);
// do we already know the type, i.e. attribute and cluster are also known
if (Znodata == type_id) {
// scan attributes to find by name, and retrieve type
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
bool match = false;
uint16_t local_attr_id = pgm_read_word(&converter->attribute);
uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
uint8_t local_type_id = pgm_read_byte(&converter->type);
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id);
if (delimiter) {
if ((cluster_id == local_cluster_id) && (attr_id == local_attr_id)) {
type_id = local_type_id;
break;
}
} else if (converter->name) {
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Comparing '%s' with '%s'"), attr_name, converter->name);
if (0 == strcasecmp_P(key, converter->name)) {
// match
cluster_id = local_cluster_id;
attr_id = local_attr_id;
type_id = local_type_id;
break;
}
}
}
}
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X, type_id = 0x%02X"), cluster_id, attr_id, type_id);
if ((0xFFFF == attr_id) || (0xFFFF == cluster_id)) {
Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute "), key);
return;
}
if (Znodata == type_id) {
Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute type for attribute "), key);
return;
}
if (0xFFFF == cluster) {
cluster = cluster_id; // set the cluster for this packet
} else if (cluster != cluster_id) {
ResponseCmndChar_P(PSTR("No more than one cluster id per command"));
return;
}
// push the value in the buffer
int32_t res = encodeSingleAttribute(buf, value, attr_id, type_id);
if (res < 0) {
Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, type_id);
return;
}
}
// did we have any attribute?
if (0 == buf.len()) {
ResponseCmndChar_P(PSTR("No attribute in list"));
return;
}
// all good, send the packet
ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, write ? ZCL_WRITE_ATTRIBUTES : ZCL_REPORT_ATTRIBUTES, false /* not cluster specific */, manuf, buf.getBuffer(), buf.len(), false /* noresponse */, zigbee_devices.getNextSeqNumber(device));
ResponseCmndDone();
}
// Parse the "Send" attribute and send the command
void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf) {
uint8_t cmd = 0; uint8_t cmd = 0;
String cmd_str = ""; // the actual low-level command, either specified or computed String cmd_str = ""; // the actual low-level command, either specified or computed
const char *cmd_s; // pointer to payload string const char *cmd_s; // pointer to payload string
bool clusterSpecific = true; bool clusterSpecific = true;
// parse JSON static char delim[] = ", "; // delimiters for parameters
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
}
if (BAD_SHORTADDR == device) { // if not found, check if we have a group
const JsonVariant &val_group = GetCaseInsensitive(json, PSTR("Group"));
if (nullptr != &val_group) {
groupaddr = strToUInt(val_group);
} else { // no device nor group
ResponseCmndChar_P(PSTR("Unknown device"));
return;
}
}
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR("Endpoint"));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
const JsonVariant &val_manuf = GetCaseInsensitive(json, PSTR("Manuf"));
if (nullptr != &val_manuf) { manuf = strToUInt(val_manuf); }
const JsonVariant &val_cmd = GetCaseInsensitive(json, PSTR("Send"));
if (nullptr != &val_cmd) {
// probe the type of the argument // probe the type of the argument
// If JSON object, it's high level commands // If JSON object, it's high level commands
// If String, it's a low level command // If String, it's a low level command
@ -550,8 +596,187 @@ void CmndZbSend(void) {
device, groupaddr, endpoint, cluster, cmd, cmd_s); device, groupaddr, endpoint, cluster, cmd, cmd_s);
zigbeeZCLSendStr(device, groupaddr, endpoint, clusterSpecific, manuf, cluster, cmd, cmd_s); zigbeeZCLSendStr(device, groupaddr, endpoint, clusterSpecific, manuf, cluster, cmd, cmd_s);
ResponseCmndDone(); ResponseCmndDone();
}
// Parse the "Send" attribute and send the command
void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf) {
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":5}
// ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"}
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]}
// ZbSend {"Device":"0xF289","Endpoint":3,"Read":{"ModelId":true}}
// ZbSend {"Device":"0xF289","Read":{"ModelId":true}}
// params
size_t attrs_len = 0;
uint8_t* attrs = nullptr; // empty string is valid
uint16_t val = strToUInt(val_attr);
if (val_attr.is<JsonArray>()) {
const JsonArray& attr_arr = val_attr.as<const JsonArray&>();
attrs_len = attr_arr.size() * 2;
attrs = new uint8_t[attrs_len];
uint32_t i = 0;
for (auto value : attr_arr) {
uint16_t val = strToUInt(value);
attrs[i++] = val & 0xFF;
attrs[i++] = val >> 8;
}
} else if (val_attr.is<JsonObject>()) {
const JsonObject& attr_obj = val_attr.as<const JsonObject&>();
attrs_len = attr_obj.size() * 2;
attrs = new uint8_t[attrs_len];
uint32_t actual_attr_len = 0;
// iterate on keys
for (JsonObject::const_iterator it=attr_obj.begin(); it!=attr_obj.end(); ++it) {
const char *key = it->key;
// const JsonVariant &value = it->value; // we don't need the value here, only keys are relevant
bool found = false;
// scan attributes to find by name, and retrieve type
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
bool match = false;
uint16_t local_attr_id = pgm_read_word(&converter->attribute);
uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
// uint8_t local_type_id = pgm_read_byte(&converter->type);
if ((converter->name) && (0 == strcasecmp_P(key, converter->name))) {
// match name
// check if there is a conflict with cluster
// TODO
attrs[actual_attr_len++] = local_attr_id & 0xFF;
attrs[actual_attr_len++] = local_attr_id >> 8;
found = true;
break; // found, exit loop
}
}
if (!found) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: Unknown attribute name (ignored): %s"), key);
}
}
attrs_len = actual_attr_len;
} else { } else {
Response_P(PSTR("Missing zigbee 'Send'")); attrs_len = 2;
attrs = new uint8_t[attrs_len];
attrs[0] = val & 0xFF; // little endian
attrs[1] = val >> 8;
}
if (attrs_len > 0) {
ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, manuf, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device));
ResponseCmndDone();
} else {
ResponseCmndChar_P(PSTR("Missing parameters"));
}
if (attrs) { delete[] attrs; }
}
//
// Command `ZbSend`
//
// Examples:
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"0006/0000":0}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"Power":0}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"AqaraRotate":0}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"AqaraRotate":12.5}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"006/0000%39":12.5}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"AnalogInApplicationType":1000000}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"TimeZone":-1000000}}
// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"Manufacturer":"Tasmota","ModelId":"Tasmota Z2T Router"}}
void CmndZbSend(void) {
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"0xFF"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":null} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":false} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":true} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"true"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"ShutterClose":null} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} }
// ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} }
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
DynamicJsonBuffer jsonBuf;
const JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params
uint16_t device = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
uint16_t groupaddr = 0x0000; // group address valid only if device == BAD_SHORTADDR
uint16_t cluster = 0xFFFF; // no default
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
uint16_t manuf = 0x0000; // Manuf Id in ZCL frame
// parse "Device" and "Group"
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_DEVICE));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
}
if (BAD_SHORTADDR == device) { // if not found, check if we have a group
const JsonVariant &val_group = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_GROUP));
if (nullptr != &val_group) {
groupaddr = strToUInt(val_group);
} else { // no device nor group
ResponseCmndChar_P(PSTR("Unknown device"));
return;
}
}
// read other parameters
const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CLUSTER));
if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); }
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_ENDPOINT));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
const JsonVariant &val_manuf = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_MANUF));
if (nullptr != &val_manuf) { manuf = strToUInt(val_manuf); }
// infer endpoint
if (BAD_SHORTADDR == device) {
endpoint = 0xFF; // endpoint not used for group addresses
} else if (0 == endpoint) {
endpoint = zigbee_devices.findFirstEndpoint(device);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: guessing endpoint %d"), endpoint);
}
const JsonVariant &val_cmd = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_SEND));
const JsonVariant &val_read = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_READ));
const JsonVariant &val_write = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_WRITE));
const JsonVariant &val_publish = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_REPORT));
uint32_t multi_cmd = (nullptr != &val_cmd) + (nullptr != &val_read) + (nullptr != &val_write) + (nullptr != &val_publish);
if (multi_cmd > 1) {
ResponseCmndChar_P(PSTR("Can only have one of: 'Send', 'Read', 'Write' or 'Report'"));
return;
}
if (nullptr != &val_cmd) {
// "Send":{...commands...}
ZbSendSend(val_cmd, device, groupaddr, cluster, endpoint, manuf);
} else if (nullptr != &val_read) {
// "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...]
ZbSendRead(val_read, device, groupaddr, cluster, endpoint, manuf);
} else if (nullptr != &val_write) {
if ((0 == endpoint) || (!val_write.is<JsonObject>())) {
ResponseCmndChar_P(PSTR("Missing parameters"));
return;
}
// "Write":{...attributes...}
ZbSendReportWrite(val_write, device, groupaddr, cluster, endpoint, manuf, true /* write */);
} else if (nullptr != &val_publish) {
if ((0 == endpoint) || (!val_publish.is<JsonObject>())) {
ResponseCmndChar_P(PSTR("Missing parameters"));
return;
}
// "Report":{...attributes...}
ZbSendReportWrite(val_publish, device, groupaddr, cluster, endpoint, manuf, false /* report */);
} else {
Response_P(PSTR("Missing zigbee 'Send', 'Write' or 'Report'"));
return; return;
} }
} }
@ -570,7 +795,6 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params // params
// static char delim[] = ", "; // delimiters for parameters
uint16_t srcDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid uint16_t srcDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
uint16_t dstDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid uint16_t dstDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
uint64_t dstLongAddr = 0; uint64_t dstLongAddr = 0;
@ -582,7 +806,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
// Information about source device: "Device", "Endpoint", "Cluster" // Information about source device: "Device", "Endpoint", "Cluster"
// - the source endpoint must have a known IEEE address // - the source endpoint must have a known IEEE address
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR("Device")); const JsonVariant &val_device = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_DEVICE));
if (nullptr != &val_device) { if (nullptr != &val_device) {
srcDevice = zigbee_devices.parseDeviceParam(val_device.as<char*>()); srcDevice = zigbee_devices.parseDeviceParam(val_device.as<char*>());
} }
@ -591,10 +815,10 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
uint64_t srcLongAddr = zigbee_devices.getDeviceLongAddr(srcDevice); uint64_t srcLongAddr = zigbee_devices.getDeviceLongAddr(srcDevice);
if (0 == srcLongAddr) { ResponseCmndChar_P(PSTR("Unknown source IEEE address")); return; } if (0 == srcLongAddr) { ResponseCmndChar_P(PSTR("Unknown source IEEE address")); return; }
// look for source endpoint // look for source endpoint
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR("Endpoint")); const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_ENDPOINT));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
// look for source cluster // look for source cluster
const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR("Cluster")); const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CLUSTER));
if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); } if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); }
// Either Device address // Either Device address
@ -885,90 +1109,6 @@ void CmndZbRestore(void) {
ResponseCmndDone(); ResponseCmndDone();
} }
//
// Command `ZbRead`
// Send an attribute read command to a device, specifying cluster and list of attributes
//
void CmndZbRead(void) {
// ZbRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5}
// ZbRead {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Attr":"0x0005"}
// ZbRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":[5,6,7,4]}
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data);
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params
uint16_t device = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
uint16_t groupaddr = 0x0000; // if 0x0000 ignore group adress
uint16_t cluster = 0x0000; // default to general cluster
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
uint16_t manuf = 0x0000; // Manuf Id in ZCL frame
size_t attrs_len = 0;
uint8_t* attrs = nullptr; // empty string is valid
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
}
if (BAD_SHORTADDR == device) { // if not found, check if we have a group
const JsonVariant &val_group = GetCaseInsensitive(json, PSTR("Group"));
if (nullptr != &val_group) {
groupaddr = strToUInt(val_group);
} else { // no device nor group
ResponseCmndChar_P(PSTR("Unknown device"));
return;
}
}
const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR("Cluster"));
if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); }
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR("Endpoint"));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
const JsonVariant &val_manuf = GetCaseInsensitive(json, PSTR("Manuf"));
if (nullptr != &val_manuf) { manuf = strToUInt(val_manuf); }
const JsonVariant &val_attr = GetCaseInsensitive(json, PSTR("Read"));
if (nullptr != &val_attr) {
uint16_t val = strToUInt(val_attr);
if (val_attr.is<JsonArray>()) {
const JsonArray& attr_arr = val_attr.as<const JsonArray&>();
attrs_len = attr_arr.size() * 2;
attrs = new uint8_t[attrs_len];
uint32_t i = 0;
for (auto value : attr_arr) {
uint16_t val = strToUInt(value);
attrs[i++] = val & 0xFF;
attrs[i++] = val >> 8;
}
} else {
attrs_len = 2;
attrs = new uint8_t[attrs_len];
attrs[0] = val & 0xFF; // little endian
attrs[1] = val >> 8;
}
}
if ((0 == endpoint) && (device)) { // try to compute the endpoint
endpoint = zigbee_devices.findFirstEndpoint(device);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbRead: guessing endpoint 0x%02X"), endpoint);
}
if (BAD_SHORTADDR == device) {
endpoint = 0xFF; // endpoint not used for group addresses
}
if ((0 != endpoint) && (attrs_len > 0)) {
ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, manuf, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device));
ResponseCmndDone();
} else {
ResponseCmndChar_P(PSTR("Missing parameters"));
}
if (attrs) { delete[] attrs; }
}
// //
// Command `ZbPermitJoin` // Command `ZbPermitJoin`
// Allow or Deny pairing of new Zigbee devices // Allow or Deny pairing of new Zigbee devices