mirror of
https://github.com/arendst/Tasmota.git
synced 2025-04-24 23:07:17 +00:00
Merge pull request #8572 from s-hadinger/zigbee_report
Add Zigbee options to ``ZbSend`` to write and report attributes
This commit is contained in:
commit
abd60ea164
@ -11,6 +11,7 @@
|
||||
- Add Three Phase Export Active Energy to SDM630 driver
|
||||
- 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 Zigbee options to ``ZbSend`` to write and report attributes
|
||||
|
||||
### 8.3.1.1 20200518
|
||||
|
||||
|
@ -513,10 +513,15 @@
|
||||
#define D_CMND_ZIGBEE_FORGET "Forget"
|
||||
#define D_CMND_ZIGBEE_SAVE "Save"
|
||||
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
|
||||
#define D_CMND_ZIGBEE_CLUSTER "Cluster"
|
||||
#define D_CMND_ZIGBEE_ENDPOINT "Endpoint"
|
||||
#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_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_RECEIVED "ZbReceived"
|
||||
#define D_CMND_ZIGBEE_BIND "Bind"
|
||||
|
@ -458,6 +458,21 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
|
||||
rule_param = String(SunMinutes(1));
|
||||
}
|
||||
#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();
|
||||
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("%SUNSET%"), String(SunMinutes(1)));
|
||||
#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];
|
||||
strlcpy(command, commands.c_str(), sizeof(command));
|
||||
@ -1261,6 +1283,16 @@ bool findNextVariableValue(char * &pVarname, float &value)
|
||||
} else if (sVarName.equals(F("SUNSET"))) {
|
||||
value = SunMinutes(1);
|
||||
#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 {
|
||||
succeed = false;
|
||||
}
|
||||
|
@ -26,6 +26,24 @@
|
||||
#endif
|
||||
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
|
||||
\*********************************************************************************************/
|
||||
@ -256,7 +274,7 @@ int32_t Z_Devices::findEndpointInVector(const std::vector<T> & vecOfElements, u
|
||||
// entry with same shortaddr or longaddr exists.
|
||||
//
|
||||
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 = new Z_Device{
|
||||
longaddr,
|
||||
@ -340,7 +358,7 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
|
||||
if (name_len) {
|
||||
for (auto &elem : _devices) {
|
||||
if (elem->friendlyName) {
|
||||
if (strcmp(elem->friendlyName, name) == 0) { return found; }
|
||||
if (strcasecmp(elem->friendlyName, name) == 0) { return found; }
|
||||
}
|
||||
found++;
|
||||
}
|
||||
@ -860,21 +878,21 @@ const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
|
||||
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
|
||||
Z_Device & device = getShortAddr(shortaddr);
|
||||
if (&device == nullptr) { return; } // don't crash if not found
|
||||
JsonObject * json = device.json;
|
||||
if (json == nullptr) { return; } // abort if nothing in buffer
|
||||
JsonObject & json = *device.json;
|
||||
if (&json == nullptr) { return; } // abort if nothing in buffer
|
||||
|
||||
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
|
||||
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
||||
|
||||
// Remove redundant "Name" or "Device"
|
||||
if (use_fname) {
|
||||
json->remove(F(D_JSON_ZIGBEE_NAME));
|
||||
} else {
|
||||
json->remove(F(D_JSON_ZIGBEE_DEVICE));
|
||||
}
|
||||
// save parameters is global variables to be used by Rules
|
||||
gZbLastMessage.device = shortaddr; // %zbdevice%
|
||||
gZbLastMessage.groupaddr = json[F(D_CMND_ZIGBEE_GROUP)]; // %zbgroup%
|
||||
gZbLastMessage.cluster = json[F(D_CMND_ZIGBEE_CLUSTER)]; // %zbcluster%
|
||||
gZbLastMessage.endpoint = json[F(D_CMND_ZIGBEE_ENDPOINT)]; // %zbendpoint%
|
||||
|
||||
// dump json in string
|
||||
String msg = "";
|
||||
json->printTo(msg);
|
||||
json.printTo(msg);
|
||||
zigbee_devices.jsonClear(shortaddr);
|
||||
|
||||
if (use_fname) {
|
||||
@ -889,7 +907,7 @@ void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
|
||||
} else {
|
||||
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) {
|
||||
@ -923,7 +941,7 @@ uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_know
|
||||
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 99)) {
|
||||
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
|
||||
if (strlen(dataBuf) < 18) {
|
||||
// expect a short address
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -106,10 +106,11 @@ enum Zigbee_StateMachine_Instruction_Set {
|
||||
// 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_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_INIT_DEVICE = 14; // Start 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_INIT_DEVICE = 14; // Init ZNP as end-device
|
||||
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_READY = 20; // goto label 20 for 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 kConfiguredCoord[] PROGMEM = "Configured, starting coordinator";
|
||||
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 kZigbeeStarted[] PROGMEM = D_LOG_ZIGBEE "Zigbee started";
|
||||
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
|
||||
|
||||
// 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
|
||||
@ -537,8 +538,9 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
|
||||
ZI_SEND(ZBS_LOGTYPE) // check the logical type
|
||||
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_LABEL(ZIGBEE_LABEL_START_ROUTER_DEVICE)
|
||||
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
||||
ZI_SEND(ZBS_AF_REGISTER_ALL) // Z_AF register for endpoint 01, profile 0x0104 Home Automation
|
||||
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_WAIT_RECV(1000, ZBR_WNV_OK)
|
||||
|
||||
ZI_GOTO(ZIGBEE_LABEL_START_ROUTER)
|
||||
ZI_GOTO(ZIGBEE_LABEL_START_ROUTER_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_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_MQTT_STATE(ZIGBEE_STATUS_RESET_CONF, kResetting)
|
||||
|
@ -653,13 +653,16 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
|
||||
JsonObject& json = jsonBuffer.createObject();
|
||||
|
||||
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) {
|
||||
zcl_received.parseResponse();
|
||||
zcl_received.parseResponse(); // Zigbee general "Degault Response", publish ZbResponse message
|
||||
} else {
|
||||
// Build the ZbReceive json
|
||||
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
|
||||
} 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);
|
||||
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
|
||||
} else if (zcl_received.isClusterSpecificCommand()) {
|
||||
|
@ -32,7 +32,7 @@ TasmotaSerial *ZigbeeSerial = nullptr;
|
||||
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_PROBE "|" D_CMND_ZIGBEEZNPRECEIVE "|"
|
||||
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_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 = {
|
||||
&CmndZbZNPSend, &CmndZbPermitJoin,
|
||||
&CmndZbStatus, &CmndZbReset, &CmndZbSend,
|
||||
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive,
|
||||
&CmndZbProbe, &CmndZbZNPReceive,
|
||||
&CmndZbForget, &CmndZbSave, &CmndZbName,
|
||||
&CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId,
|
||||
&CmndZbLight, &CmndZbRestore, &CmndZbBindState,
|
||||
@ -393,9 +393,301 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse "Report" or "Write" attribute
|
||||
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
|
||||
|
||||
const JsonObject &attrs = val_pubwrite.as<const JsonObject&>();
|
||||
// iterate on keys
|
||||
for (JsonObject::const_iterator it=attrs.begin(); it!=attrs.end(); ++it) {
|
||||
const char *key = it->key;
|
||||
const JsonVariant &value = it->value;
|
||||
|
||||
uint16_t attr_id = 0xFFFF;
|
||||
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;
|
||||
String cmd_str = ""; // the actual low-level command, either specified or computed
|
||||
const char *cmd_s; // pointer to payload string
|
||||
bool clusterSpecific = true;
|
||||
|
||||
static char delim[] = ", "; // delimiters for parameters
|
||||
// probe the type of the argument
|
||||
// If JSON object, it's high level commands
|
||||
// If String, it's a low level command
|
||||
if (val_cmd.is<JsonObject>()) {
|
||||
// we have a high-level command
|
||||
const JsonObject &cmd_obj = val_cmd.as<const JsonObject&>();
|
||||
int32_t cmd_size = cmd_obj.size();
|
||||
if (cmd_size > 1) {
|
||||
Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size);
|
||||
return;
|
||||
} else if (1 == cmd_size) {
|
||||
// We have exactly 1 command, parse it
|
||||
JsonObject::const_iterator it = cmd_obj.begin(); // just get the first key/value
|
||||
String key = it->key;
|
||||
const JsonVariant& value = it->value;
|
||||
uint32_t x = 0, y = 0, z = 0;
|
||||
uint16_t cmd_var;
|
||||
|
||||
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str(), &cluster, &cmd_var);
|
||||
if (tasmota_cmd) {
|
||||
cmd_str = tasmota_cmd;
|
||||
} else {
|
||||
Response_P(PSTR("Unrecognized zigbee command: %s"), key.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// parse the JSON value, depending on its type fill in x,y,z
|
||||
if (value.is<bool>()) {
|
||||
x = value.as<bool>() ? 1 : 0;
|
||||
} else if (value.is<unsigned int>()) {
|
||||
x = value.as<unsigned int>();
|
||||
} else {
|
||||
// if non-bool or non-int, trying char*
|
||||
const char *s_const = value.as<const char*>();
|
||||
if (s_const != nullptr) {
|
||||
char s[strlen(s_const)+1];
|
||||
strcpy(s, s_const);
|
||||
if ((nullptr != s) && (0x00 != *s)) { // ignore any null or empty string, could represent 'null' json value
|
||||
char *sval = strtok(s, delim);
|
||||
if (sval) {
|
||||
x = ZigbeeAliasOrNumber(sval);
|
||||
sval = strtok(nullptr, delim);
|
||||
if (sval) {
|
||||
y = ZigbeeAliasOrNumber(sval);
|
||||
sval = strtok(nullptr, delim);
|
||||
if (sval) {
|
||||
z = ZigbeeAliasOrNumber(sval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//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());
|
||||
cmd_s = cmd_str.c_str();
|
||||
} else {
|
||||
// we have zero command, pass through until last error for missing command
|
||||
}
|
||||
} else if (val_cmd.is<const char*>()) {
|
||||
// low-level command
|
||||
cmd_str = val_cmd.as<String>();
|
||||
// Now parse the string to extract cluster, command, and payload
|
||||
// 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 command
|
||||
const char * data = cmd_str.c_str();
|
||||
cluster = parseHex(&data, 4);
|
||||
|
||||
// delimiter
|
||||
if (('_' == *data) || ('!' == *data)) {
|
||||
if ('_' == *data) { clusterSpecific = false; }
|
||||
data++;
|
||||
} else {
|
||||
ResponseCmndChar_P(PSTR("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
|
||||
|
||||
cmd_s = data;
|
||||
} else {
|
||||
// we have an unsupported command type, just ignore it and fallback to missing command
|
||||
}
|
||||
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLSend device: 0x%04X, group: 0x%04X, endpoint:%d, cluster:0x%04X, cmd:0x%02X, send:\"%s\""),
|
||||
device, groupaddr, endpoint, cluster, cmd, cmd_s);
|
||||
zigbeeZCLSendStr(device, groupaddr, endpoint, clusterSpecific, manuf, cluster, cmd, cmd_s);
|
||||
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 {
|
||||
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"} }
|
||||
@ -405,7 +697,7 @@ void CmndZbSend(void) {
|
||||
// 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":{"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; }
|
||||
@ -414,26 +706,21 @@ void CmndZbSend(void) {
|
||||
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||
|
||||
// params
|
||||
static char delim[] = ", "; // delimiters for parameters
|
||||
uint16_t device = BAD_SHORTADDR; // 0x0000 is local, so considered invalid
|
||||
uint16_t groupaddr = 0x0000; // group address
|
||||
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
|
||||
uint16_t manuf = 0x0000; // Manuf Id in ZCL frame
|
||||
// Command elements
|
||||
uint16_t cluster = 0;
|
||||
uint8_t cmd = 0;
|
||||
String cmd_str = ""; // the actual low-level command, either specified or computed
|
||||
const char *cmd_s; // pointer to payload string
|
||||
bool clusterSpecific = true;
|
||||
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 JSON
|
||||
const JsonVariant &val_device = GetCaseInsensitive(json, PSTR("Device"));
|
||||
|
||||
// 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("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
|
||||
@ -442,116 +729,54 @@ void CmndZbSend(void) {
|
||||
}
|
||||
}
|
||||
|
||||
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR("Endpoint"));
|
||||
// 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("Manuf"));
|
||||
const JsonVariant &val_manuf = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_MANUF));
|
||||
if (nullptr != &val_manuf) { manuf = strToUInt(val_manuf); }
|
||||
const JsonVariant &val_cmd = GetCaseInsensitive(json, PSTR("Send"));
|
||||
|
||||
// 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) {
|
||||
// probe the type of the argument
|
||||
// If JSON object, it's high level commands
|
||||
// If String, it's a low level command
|
||||
if (val_cmd.is<JsonObject>()) {
|
||||
// we have a high-level command
|
||||
const JsonObject &cmd_obj = val_cmd.as<const JsonObject&>();
|
||||
int32_t cmd_size = cmd_obj.size();
|
||||
if (cmd_size > 1) {
|
||||
Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size);
|
||||
return;
|
||||
} else if (1 == cmd_size) {
|
||||
// We have exactly 1 command, parse it
|
||||
JsonObject::const_iterator it = cmd_obj.begin(); // just get the first key/value
|
||||
String key = it->key;
|
||||
const JsonVariant& value = it->value;
|
||||
uint32_t x = 0, y = 0, z = 0;
|
||||
uint16_t cmd_var;
|
||||
|
||||
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str(), &cluster, &cmd_var);
|
||||
if (tasmota_cmd) {
|
||||
cmd_str = tasmota_cmd;
|
||||
} else {
|
||||
Response_P(PSTR("Unrecognized zigbee command: %s"), key.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// parse the JSON value, depending on its type fill in x,y,z
|
||||
if (value.is<bool>()) {
|
||||
x = value.as<bool>() ? 1 : 0;
|
||||
} else if (value.is<unsigned int>()) {
|
||||
x = value.as<unsigned int>();
|
||||
} else {
|
||||
// if non-bool or non-int, trying char*
|
||||
const char *s_const = value.as<const char*>();
|
||||
if (s_const != nullptr) {
|
||||
char s[strlen(s_const)+1];
|
||||
strcpy(s, s_const);
|
||||
if ((nullptr != s) && (0x00 != *s)) { // ignore any null or empty string, could represent 'null' json value
|
||||
char *sval = strtok(s, delim);
|
||||
if (sval) {
|
||||
x = ZigbeeAliasOrNumber(sval);
|
||||
sval = strtok(nullptr, delim);
|
||||
if (sval) {
|
||||
y = ZigbeeAliasOrNumber(sval);
|
||||
sval = strtok(nullptr, delim);
|
||||
if (sval) {
|
||||
z = ZigbeeAliasOrNumber(sval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//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());
|
||||
cmd_s = cmd_str.c_str();
|
||||
} else {
|
||||
// we have zero command, pass through until last error for missing command
|
||||
}
|
||||
} else if (val_cmd.is<const char*>()) {
|
||||
// low-level command
|
||||
cmd_str = val_cmd.as<String>();
|
||||
// Now parse the string to extract cluster, command, and payload
|
||||
// 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 command
|
||||
const char * data = cmd_str.c_str();
|
||||
cluster = parseHex(&data, 4);
|
||||
|
||||
// delimiter
|
||||
if (('_' == *data) || ('!' == *data)) {
|
||||
if ('_' == *data) { clusterSpecific = false; }
|
||||
data++;
|
||||
} else {
|
||||
ResponseCmndChar_P(PSTR("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
|
||||
|
||||
cmd_s = data;
|
||||
} else {
|
||||
// we have an unsupported command type, just ignore it and fallback to missing command
|
||||
// "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;
|
||||
}
|
||||
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLSend device: 0x%04X, group: 0x%04X, endpoint:%d, cluster:0x%04X, cmd:0x%02X, send:\"%s\""),
|
||||
device, groupaddr, endpoint, cluster, cmd, cmd_s);
|
||||
zigbeeZCLSendStr(device, groupaddr, endpoint, clusterSpecific, manuf, cluster, cmd, cmd_s);
|
||||
ResponseCmndDone();
|
||||
// "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'"));
|
||||
Response_P(PSTR("Missing zigbee 'Send', 'Write' or 'Report'"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -570,7 +795,6 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
|
||||
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||
|
||||
// params
|
||||
// static char delim[] = ", "; // delimiters for parameters
|
||||
uint16_t srcDevice = 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;
|
||||
@ -582,7 +806,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
|
||||
|
||||
// Information about source device: "Device", "Endpoint", "Cluster"
|
||||
// - 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) {
|
||||
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);
|
||||
if (0 == srcLongAddr) { ResponseCmndChar_P(PSTR("Unknown source IEEE address")); return; }
|
||||
// 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); }
|
||||
// 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); }
|
||||
|
||||
// Either Device address
|
||||
@ -885,90 +1109,6 @@ void CmndZbRestore(void) {
|
||||
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`
|
||||
// Allow or Deny pairing of new Zigbee devices
|
||||
|
Loading…
x
Reference in New Issue
Block a user