mirror of https://github.com/arendst/Tasmota.git
Add Zigbee add options to ``ZbSend`` ``Config`` and ``ReadCondig``
This allows to configure the attribute reporting of devices
This commit is contained in:
parent
5e064c1531
commit
bf1d76e28f
|
@ -5,6 +5,7 @@
|
|||
- Fix ESP32 PWM range
|
||||
- Add Zigbee better support for IKEA Motion Sensor
|
||||
- Add ESP32 Analog input support for GPIO32 to GPIO39
|
||||
- Add Zigbee add options to ``ZbSend`` ``Config`` and ``ReadCondig``
|
||||
|
||||
### 8.4.0 20200730
|
||||
|
||||
|
|
|
@ -541,6 +541,8 @@
|
|||
#define D_CMND_ZIGBEE_SEND "Send"
|
||||
#define D_CMND_ZIGBEE_WRITE "Write"
|
||||
#define D_CMND_ZIGBEE_REPORT "Report"
|
||||
#define D_CMND_ZIGBEE_READ_CONFIG "ReadConfig"
|
||||
#define D_CMND_ZIGBEE_CONFIG "Config"
|
||||
#define D_CMND_ZIGBEE_RESPONSE "Response"
|
||||
#define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent"
|
||||
#define D_JSON_ZIGBEE_RECEIVED "ZbReceived"
|
||||
|
|
|
@ -79,6 +79,16 @@ uint8_t Z_getDatatypeLen(uint8_t t) {
|
|||
}
|
||||
}
|
||||
|
||||
// is the type a discrete type, cf. section 2.6.2 of ZCL spec
|
||||
bool Z_isDiscreteDataType(uint8_t t) {
|
||||
if ( ((t >= 0x20) && (t <= 0x2F)) || // uint8 - int64
|
||||
((t >= 0x38) && (t <= 0x3A)) || // semi - double
|
||||
((t >= 0xE0) && (t <= 0xE2)) ) { // ToD - UTC
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// return value:
|
||||
// 0 = keep initial value
|
||||
|
@ -102,7 +112,7 @@ enum Cx_cluster_short {
|
|||
Cx0008, Cx0009, Cx000A, Cx000B, Cx000C, Cx000D, Cx000E, Cx000F,
|
||||
Cx0010, Cx0011, Cx0012, Cx0013, Cx0014, Cx001A, Cx0020, Cx0100,
|
||||
Cx0101, Cx0102, Cx0300, Cx0400, Cx0401, Cx0402, Cx0403, Cx0404,
|
||||
Cx0405, Cx0406, Cx0B01, Cx0B05,
|
||||
Cx0405, Cx0406, Cx0500, Cx0B01, Cx0B05,
|
||||
};
|
||||
|
||||
const uint16_t Cx_cluster[] PROGMEM = {
|
||||
|
@ -110,7 +120,7 @@ const uint16_t Cx_cluster[] PROGMEM = {
|
|||
0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x001A, 0x0020, 0x0100,
|
||||
0x0101, 0x0102, 0x0300, 0x0400, 0x0401, 0x0402, 0x0403, 0x0404,
|
||||
0x0405, 0x0406, 0x0B01, 0x0B05,
|
||||
0x0405, 0x0406, 0x0500, 0x0B01, 0x0B05,
|
||||
};
|
||||
|
||||
uint16_t CxToCluster(uint8_t cx) {
|
||||
|
@ -216,6 +226,8 @@ ZF(Humidity) ZF(HumidityMinMeasuredValue) ZF(HumidityMaxMeasuredValue) ZF(Humidi
|
|||
|
||||
ZF(Occupancy) ZF(OccupancySensorType)
|
||||
|
||||
ZF(ZoneState) ZF(ZoneType) ZF(ZoneStatus)
|
||||
|
||||
ZF(CompanyName) ZF(MeterTypeID) ZF(DataQualityID) ZF(CustomerName) ZF(Model) ZF(PartNumber)
|
||||
ZF(SoftwareRevision) ZF(POD) ZF(AvailablePower) ZF(PowerThreshold) ZF(ProductRevision) ZF(UtilityName)
|
||||
|
||||
|
@ -554,6 +566,11 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
|||
{ Zenum8, Cx0406, 0x0001, Z(OccupancySensorType), 1, Z_Nop }, // OccupancySensorType
|
||||
{ Zunk, Cx0406, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values
|
||||
|
||||
// IAS Cluster (Intruder Alarm System)
|
||||
{ Zenum8, Cx0500, 0x0000, Z(ZoneState), 1, Z_Nop }, // Occupancy (map8)
|
||||
{ Zenum16, Cx0500, 0x0001, Z(ZoneType), 1, Z_Nop }, // Occupancy (map8)
|
||||
{ Zmap16, Cx0500, 0x0002, Z(ZoneStatus), 1, Z_Nop }, // Occupancy (map8)
|
||||
|
||||
// Meter Identification cluster
|
||||
{ Zstring, Cx0B01, 0x0000, Z(CompanyName), 1, Z_Nop },
|
||||
{ Zuint16, Cx0B01, 0x0001, Z(MeterTypeID), 1, Z_Nop },
|
||||
|
@ -589,6 +606,27 @@ typedef union ZCLHeaderFrameControl_t {
|
|||
} ZCLHeaderFrameControl_t;
|
||||
|
||||
|
||||
|
||||
// Find the attribute details by attribute name
|
||||
// If not found:
|
||||
// - returns nullptr
|
||||
const __FlashStringHelper* zigbeeFindAttributeByName(const char *command,
|
||||
uint16_t *cluster, uint16_t *attribute, int16_t *multiplier,
|
||||
uint8_t *cb) {
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
|
||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||
if (nullptr == converter->name) { continue; } // avoid strcasecmp_P() from crashing
|
||||
if (0 == strcasecmp_P(command, converter->name)) {
|
||||
if (cluster) { *cluster = CxToCluster(pgm_read_byte(&converter->cluster_short)); }
|
||||
if (attribute) { *attribute = pgm_read_word(&converter->attribute); }
|
||||
if (multiplier) { *multiplier = pgm_read_word(&converter->multiplier); }
|
||||
if (cb) { *cb = pgm_read_byte(&converter->cb); }
|
||||
return (const __FlashStringHelper*) converter->name;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class ZCLFrame {
|
||||
public:
|
||||
|
||||
|
@ -660,6 +698,8 @@ public:
|
|||
void parseReportAttributes(JsonObject& json, uint8_t offset = 0);
|
||||
void parseReadAttributes(JsonObject& json, uint8_t offset = 0);
|
||||
void parseReadAttributesResponse(JsonObject& json, uint8_t offset = 0);
|
||||
void parseReadConfigAttributes(JsonObject& json, uint8_t offset = 0);
|
||||
void parseConfigAttributes(JsonObject& json, uint8_t offset = 0);
|
||||
void parseResponse(void);
|
||||
void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0);
|
||||
void postProcessAttributes(uint16_t shortaddr, JsonObject& json);
|
||||
|
@ -732,32 +772,13 @@ uint8_t toPercentageCR2032(uint32_t voltage) {
|
|||
//
|
||||
// Appends the attribute value to Write or to Report
|
||||
// Adds to buf:
|
||||
// - 2 bytes: attribute identigier
|
||||
// - 1 byte: attribute type
|
||||
// - n bytes: value (typically between 1 and 4 bytes, or bigger for strings)
|
||||
// returns number of bytes of attribute, or <0 if error
|
||||
// status: shall we insert a status OK (0x00) as required by ReadResponse
|
||||
int32_t encodeSingleAttribute(class SBuffer &buf, const JsonVariant &val, float val_f, uint16_t attr, uint8_t attrtype, bool status = false) {
|
||||
int32_t encodeSingleAttribute(class SBuffer &buf, double val_d, const char *val_str, uint8_t attrtype) {
|
||||
uint32_t len = Z_getDatatypeLen(attrtype); // pre-compute lenght, overloaded for variable length attributes
|
||||
uint32_t u32;
|
||||
int32_t i32;
|
||||
float f32;
|
||||
|
||||
if (&val) {
|
||||
u32 = val.as<uint32_t>();
|
||||
i32 = val.as<int32_t>();
|
||||
f32 = val.as<float>();
|
||||
} else {
|
||||
u32 = val_f;
|
||||
i32 = val_f;
|
||||
f32 = val_f;
|
||||
}
|
||||
|
||||
buf.add16(attr); // prepend with attribute identifier
|
||||
if (status) {
|
||||
buf.add8(Z_SUCCESS); // status OK = 0x00
|
||||
}
|
||||
buf.add8(attrtype); // prepend with attribute type
|
||||
uint32_t u32 = val_d;
|
||||
int32_t i32 = val_d;
|
||||
float f32 = val_d;
|
||||
|
||||
switch (attrtype) {
|
||||
// unsigned 8
|
||||
|
@ -802,7 +823,6 @@ int32_t encodeSingleAttribute(class SBuffer &buf, const JsonVariant &val, float
|
|||
case Zstring:
|
||||
case Zstring16:
|
||||
{
|
||||
const char * val_str = (&val) ? val.as<const char*>() : ""; // avoid crash if &val is null
|
||||
if (nullptr == val_str) { return -2; }
|
||||
size_t val_len = strlen(val_str);
|
||||
if (val_len > 32) { val_len = 32; }
|
||||
|
@ -819,18 +839,29 @@ int32_t encodeSingleAttribute(class SBuffer &buf, const JsonVariant &val, float
|
|||
break;
|
||||
|
||||
default:
|
||||
// remove the attribute type we just added
|
||||
buf.setLen(buf.len() - (status ? 4 : 3));
|
||||
return -1;
|
||||
}
|
||||
return len + (status ? 4 : 3);
|
||||
return len;
|
||||
}
|
||||
|
||||
//
|
||||
// parse a single attribute
|
||||
//
|
||||
// Input:
|
||||
// json: json Object where to add the attribute
|
||||
// attrid_str: the key for the attribute
|
||||
// buf: the buffer to read from
|
||||
// offset: location in the buffer to read from
|
||||
// attrtype: type of attribute (byte) or -1 to read from the stream as first byte
|
||||
// Output:
|
||||
// return: the length in bytes of the attribute
|
||||
uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer &buf,
|
||||
uint32_t offset, uint32_t buflen) {
|
||||
uint32_t offset, int32_t attrtype = -1) {
|
||||
|
||||
uint32_t i = offset;
|
||||
uint32_t attrtype = buf.get8(i++);
|
||||
if (attrtype < 0) {
|
||||
attrtype = buf.get8(i++);
|
||||
}
|
||||
|
||||
// fallback - enter a null value
|
||||
json[attrid_str] = (char*) nullptr;
|
||||
|
@ -1061,7 +1092,7 @@ void ZCLFrame::parseReportAttributes(JsonObject& json, uint8_t offset) {
|
|||
_payload.set8(i, 0x41); // change type from 0x42 to 0x41
|
||||
}
|
||||
}
|
||||
i += parseSingleAttribute(json, key, _payload, i, len);
|
||||
i += parseSingleAttribute(json, key, _payload, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1075,7 +1106,7 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
|
|||
|
||||
JsonArray &attr_list = json.createNestedArray(F("Read"));
|
||||
JsonObject &attr_names = json.createNestedObject(F("ReadNames"));
|
||||
while (len - i >= 2) {
|
||||
while (len >= 2 + i) {
|
||||
uint16_t attrid = _payload.get16(i);
|
||||
attr_list.add(attrid);
|
||||
|
||||
|
@ -1094,6 +1125,79 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
|
|||
}
|
||||
}
|
||||
|
||||
// ZCL_CONFIGURE_REPORTING_RESPONSE
|
||||
void ZCLFrame::parseConfigAttributes(JsonObject& json, uint8_t offset) {
|
||||
uint32_t i = offset;
|
||||
uint32_t len = _payload.len();
|
||||
|
||||
JsonObject &config_rsp = json.createNestedObject(F("ConfigResponse"));
|
||||
uint8_t status = _payload.get8(i);
|
||||
config_rsp[F("Status")] = status;
|
||||
config_rsp[F("StatusMsg")] = getZigbeeStatusMessage(status);
|
||||
}
|
||||
|
||||
// ZCL_READ_REPORTING_CONFIGURATION_RESPONSE
|
||||
void ZCLFrame::parseReadConfigAttributes(JsonObject& json, uint8_t offset) {
|
||||
uint32_t i = offset;
|
||||
uint32_t len = _payload.len();
|
||||
|
||||
// json[F(D_CMND_ZIGBEE_CLUSTER)] = _cluster_id; // TODO is it necessary?
|
||||
|
||||
JsonObject &attr_names = json.createNestedObject(F("ReadConfig"));
|
||||
while (len >= i + 4) {
|
||||
uint8_t status = _payload.get8(i);
|
||||
uint8_t direction = _payload.get8(i+1);
|
||||
uint16_t attrid = _payload.get16(i+2);
|
||||
char attr_hex[12];
|
||||
snprintf_P(attr_hex, sizeof(attr_hex), "%04X/%04X", _cluster_id, attrid);
|
||||
JsonObject &attr_details = attr_names.createNestedObject(attr_hex);
|
||||
|
||||
if (direction) {
|
||||
attr_details[F("DirectionReceived")] = true;
|
||||
}
|
||||
|
||||
// find the attribute name
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
|
||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||
uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
|
||||
uint16_t conv_attribute = pgm_read_word(&converter->attribute);
|
||||
|
||||
if ((conv_cluster == _cluster_id) && (conv_attribute == attrid)) {
|
||||
attr_details[(const __FlashStringHelper*) converter->name] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
i += 4;
|
||||
if (0 != status) {
|
||||
attr_details[F("Status")] = status;
|
||||
attr_details[F("StatusMsg")] = getZigbeeStatusMessage(status);
|
||||
} else {
|
||||
// no error, decode data
|
||||
if (direction) {
|
||||
// only Timeout period is present
|
||||
uint16_t attr_timeout = _payload.get16(i);
|
||||
i += 2;
|
||||
attr_details[F("TimeoutPeriod")] = (0xFFFF == attr_timeout) ? -1 : attr_timeout;
|
||||
} else {
|
||||
// direction == 0, we have a data type
|
||||
uint8_t attr_type = _payload.get8(i);
|
||||
bool attr_discrete = Z_isDiscreteDataType(attr_type);
|
||||
uint16_t attr_min_interval = _payload.get16(i+1);
|
||||
uint16_t attr_max_interval = _payload.get16(i+3);
|
||||
i += 5;
|
||||
attr_details[F("MinInterval")] = (0xFFFF == attr_min_interval) ? -1 : attr_min_interval;
|
||||
attr_details[F("MaxInterval")] = (0xFFFF == attr_max_interval) ? -1 : attr_max_interval;
|
||||
if (!attr_discrete) {
|
||||
// decode Reportable Change
|
||||
char attr_name[20];
|
||||
strcpy_P(attr_name, PSTR("ReportableChange"));
|
||||
i += parseSingleAttribute(attr_details, attr_name, _payload, i, attr_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ZCL_READ_ATTRIBUTES_RESPONSE
|
||||
void ZCLFrame::parseReadAttributesResponse(JsonObject& json, uint8_t offset) {
|
||||
uint32_t i = offset;
|
||||
|
@ -1108,7 +1212,7 @@ void ZCLFrame::parseReadAttributesResponse(JsonObject& json, uint8_t offset) {
|
|||
char key[16];
|
||||
generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
|
||||
|
||||
i += parseSingleAttribute(json, key, _payload, i, len);
|
||||
i += parseSingleAttribute(json, key, _payload, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1321,10 +1425,10 @@ int32_t Z_AqaraSensorFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObj
|
|||
const char * modelId_c = zigbee_devices.getModelId(shortaddr); // null if unknown
|
||||
String modelId((char*) modelId_c);
|
||||
|
||||
while (len - i >= 2) {
|
||||
while (len >= 2 + i) {
|
||||
uint8_t attrid = buf2.get8(i++);
|
||||
|
||||
i += parseSingleAttribute(json, tmp, buf2, i, len);
|
||||
i += parseSingleAttribute(json, tmp, buf2, i);
|
||||
float val = json[tmp];
|
||||
json.remove(tmp);
|
||||
bool translated = false; // were we able to translate to a known format?
|
||||
|
@ -1378,7 +1482,7 @@ int32_t Z_AqaraSensorFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObj
|
|||
|
||||
// apply the transformation from the converter
|
||||
int32_t Z_ApplyConverter(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name,
|
||||
uint16_t cluster, uint16_t attr, int16_t multiplier, uint16_t cb) {
|
||||
uint16_t cluster, uint16_t attr, int16_t multiplier, uint8_t cb) {
|
||||
// apply multiplier if needed
|
||||
if (1 == multiplier) { // copy unchanged
|
||||
json[new_name] = value;
|
||||
|
@ -1483,12 +1587,12 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
|
|||
}
|
||||
|
||||
// Iterate on filter
|
||||
for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) {
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
|
||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||
uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
|
||||
uint16_t conv_attribute = pgm_read_word(&converter->attribute);
|
||||
int16_t conv_multiplier = pgm_read_word(&converter->multiplier);
|
||||
uint16_t conv_cb = pgm_read_word(&converter->cb); // callback id
|
||||
uint8_t conv_cb = pgm_read_byte(&converter->cb); // callback id
|
||||
|
||||
if ((conv_cluster == cluster) &&
|
||||
((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) {
|
||||
|
|
|
@ -426,8 +426,12 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
|
|||
if ((cluster == 0x0500) && (cmd == 0x00)) {
|
||||
// "ZoneStatusChange"
|
||||
json[command_name] = xyz.x;
|
||||
json[command_name2 + F("Ext")] = xyz.y;
|
||||
json[command_name2 + F("Zone")] = xyz.z;
|
||||
if (0 != xyz.y) {
|
||||
json[command_name2 + F("Ext")] = xyz.y;
|
||||
}
|
||||
if ((0 != xyz.z) && (0xFF != xyz.z)) {
|
||||
json[command_name2 + F("Zone")] = xyz.z;
|
||||
}
|
||||
} else if ((cluster == 0x0004) && ((cmd == 0x00) || (cmd == 0x01) || (cmd == 0x03))) {
|
||||
// AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
|
||||
json[command_name] = xyz.y;
|
||||
|
@ -525,6 +529,7 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
|
|||
// If not found:
|
||||
// - returns nullptr
|
||||
const __FlashStringHelper* zigbeeFindCommand(const char *command, uint16_t *cluster, uint16_t *cmd) {
|
||||
if (nullptr == command) { return nullptr; }
|
||||
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
|
||||
const Z_CommandConverter *conv = &Z_Commands[i];
|
||||
uint8_t conv_direction = pgm_read_byte(&conv->direction);
|
||||
|
|
|
@ -1028,6 +1028,10 @@ void Z_IncomingMessage(ZCLFrame &zcl_received) {
|
|||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) {
|
||||
zcl_received.parseReadAttributes(json);
|
||||
// never defer read_attributes, so the auto-responder can send response back on a per cluster basis
|
||||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_REPORTING_CONFIGURATION_RESPONSE == zcl_received.getCmdId())) {
|
||||
zcl_received.parseReadConfigAttributes(json);
|
||||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_CONFIGURE_REPORTING_RESPONSE == zcl_received.getCmdId())) {
|
||||
zcl_received.parseConfigAttributes(json);
|
||||
} else if (zcl_received.isClusterSpecificCommand()) {
|
||||
zcl_received.parseClusterSpecificCommand(json);
|
||||
}
|
||||
|
|
|
@ -185,7 +185,22 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint,
|
|||
}
|
||||
}
|
||||
|
||||
// Parse "Report", "Write" or "Response" attribute
|
||||
// Special encoding for multiplier:
|
||||
// multiplier == 0: ignore
|
||||
// multiplier == 1: ignore
|
||||
// multiplier > 0: divide by the multiplier
|
||||
// multiplier < 0: multiply by the -multiplier (positive)
|
||||
void ZbApplyMultiplier(double &val_d, int16_t multiplier) {
|
||||
if ((0 != multiplier) && (1 != multiplier)) {
|
||||
if (multiplier > 0) { // inverse of decoding
|
||||
val_d = val_d / multiplier;
|
||||
} else {
|
||||
val_d = val_d * (-multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse "Report", "Write", "Response" or "Condig" attribute
|
||||
// Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01)
|
||||
void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint32_t operation) {
|
||||
SBuffer buf(200); // buffer to store the binary output of attibutes
|
||||
|
@ -203,7 +218,8 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
|
|||
uint16_t cluster_id = 0xFFFF;
|
||||
uint8_t type_id = Znodata;
|
||||
int16_t multiplier = 1; // multiplier to adjust the key value
|
||||
float val_f = 0.0f; // alternative value if multiplier is used
|
||||
double val_d = 0; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss
|
||||
const char* val_str = ""; // variant as string
|
||||
|
||||
// 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)
|
||||
|
@ -268,22 +284,88 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
|
|||
ResponseCmndChar_P(PSTR("No more than one cluster id per command"));
|
||||
return;
|
||||
}
|
||||
// apply multiplier if needed
|
||||
bool use_val = true;
|
||||
if ((0 != multiplier) && (1 != multiplier)) {
|
||||
val_f = value;
|
||||
if (multiplier > 0) { // inverse of decoding
|
||||
val_f = val_f / multiplier;
|
||||
} else {
|
||||
val_f = val_f * (-multiplier);
|
||||
|
||||
// ////////////////////////////////////////////////////////////////////////////////
|
||||
// Split encoding depending on message
|
||||
if (operation != ZCL_CONFIGURE_REPORTING) {
|
||||
// apply multiplier if needed
|
||||
val_d = value.as<double>();
|
||||
val_str = value.as<const char*>();
|
||||
ZbApplyMultiplier(val_d, multiplier);
|
||||
|
||||
// push the value in the buffer
|
||||
buf.add16(attr_id); // prepend with attribute identifier
|
||||
if (operation == ZCL_READ_ATTRIBUTES_RESPONSE) {
|
||||
buf.add8(Z_SUCCESS); // status OK = 0x00
|
||||
}
|
||||
buf.add8(type_id); // prepend with attribute type
|
||||
int32_t res = encodeSingleAttribute(buf, val_d, val_str, type_id);
|
||||
if (res < 0) {
|
||||
// remove the attribute type we just added
|
||||
// buf.setLen(buf.len() - (operation == ZCL_READ_ATTRIBUTES_RESPONSE ? 4 : 3));
|
||||
Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, type_id);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
// ////////////////////////////////////////////////////////////////////////////////
|
||||
// ZCL_CONFIGURE_REPORTING
|
||||
if (!value.is<JsonObject>()) {
|
||||
ResponseCmndChar_P(PSTR("Config requires JSON objects"));
|
||||
return;
|
||||
}
|
||||
JsonObject &attr_config = value.as<JsonObject>();
|
||||
bool attr_direction = false;
|
||||
|
||||
const JsonVariant &val_attr_direction = GetCaseInsensitive(attr_config, PSTR("DirectionReceived"));
|
||||
if (nullptr != &val_attr_direction) {
|
||||
uint32_t dir = strToUInt(val_attr_direction);
|
||||
if (dir) {
|
||||
attr_direction = true;
|
||||
}
|
||||
}
|
||||
|
||||
// read MinInterval and MaxInterval, default to 0xFFFF if not specified
|
||||
uint16_t attr_min_interval = 0xFFFF;
|
||||
uint16_t attr_max_interval = 0xFFFF;
|
||||
const JsonVariant &val_attr_min = GetCaseInsensitive(attr_config, PSTR("MinInterval"));
|
||||
if (nullptr != &val_attr_min) { attr_min_interval = strToUInt(val_attr_min); }
|
||||
const JsonVariant &val_attr_max = GetCaseInsensitive(attr_config, PSTR("MaxInterval"));
|
||||
if (nullptr != &val_attr_max) { attr_max_interval = strToUInt(val_attr_max); }
|
||||
|
||||
// read ReportableChange
|
||||
const JsonVariant &val_attr_rc = GetCaseInsensitive(attr_config, PSTR("ReportableChange"));
|
||||
if (nullptr != &val_attr_rc) {
|
||||
val_d = val_attr_rc.as<double>();
|
||||
val_str = val_attr_rc.as<const char*>();
|
||||
ZbApplyMultiplier(val_d, multiplier);
|
||||
}
|
||||
|
||||
// read TimeoutPeriod
|
||||
uint16_t attr_timeout = 0x0000;
|
||||
const JsonVariant &val_attr_timeout = GetCaseInsensitive(attr_config, PSTR("TimeoutPeriod"));
|
||||
if (nullptr != &val_attr_timeout) { attr_timeout = strToUInt(val_attr_timeout); }
|
||||
|
||||
bool attr_discrete = Z_isDiscreteDataType(type_id);
|
||||
|
||||
// all fields are gathered, output the butes into the buffer, ZCL 2.5.7.1
|
||||
// common bytes
|
||||
buf.add8(attr_direction ? 0x01 : 0x00);
|
||||
buf.add16(attr_id);
|
||||
if (attr_direction) {
|
||||
buf.add16(attr_timeout);
|
||||
} else {
|
||||
buf.add8(type_id);
|
||||
buf.add16(attr_min_interval);
|
||||
buf.add16(attr_max_interval);
|
||||
if (!attr_discrete) {
|
||||
int32_t res = encodeSingleAttribute(buf, val_d, val_str, type_id);
|
||||
if (res < 0) {
|
||||
Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, type_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
use_val = false;
|
||||
}
|
||||
// push the value in the buffer
|
||||
int32_t res = encodeSingleAttribute(buf, use_val ? value : *(const JsonVariant*)nullptr, val_f, attr_id, type_id, operation == ZCL_READ_ATTRIBUTES_RESPONSE); // force status if Reponse
|
||||
if (res < 0) {
|
||||
Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, type_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -428,39 +510,50 @@ void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr,
|
|||
|
||||
|
||||
// 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) {
|
||||
void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint32_t operation) {
|
||||
// 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}}
|
||||
|
||||
// ZbSend {"Device":"0xF289","ReadConig":{"Power":true}}
|
||||
// ZbSend {"Device":"0xF289","Cluster":6,"Endpoint":3,"ReadConfig":0}
|
||||
|
||||
// params
|
||||
size_t attrs_len = 0;
|
||||
uint8_t* attrs = nullptr; // empty string is valid
|
||||
size_t attr_item_len = 2; // how many bytes per attribute, standard for "Read"
|
||||
size_t attr_item_offset = 0; // how many bytes do we offset to store attribute
|
||||
if (ZCL_READ_REPORTING_CONFIGURATION == operation) {
|
||||
attr_item_len = 3;
|
||||
attr_item_offset = 1;
|
||||
}
|
||||
|
||||
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];
|
||||
attrs_len = attr_arr.size() * attr_item_len;
|
||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||
|
||||
uint32_t i = 0;
|
||||
for (auto value : attr_arr) {
|
||||
uint16_t val = strToUInt(value);
|
||||
i += attr_item_offset;
|
||||
attrs[i++] = val & 0xFF;
|
||||
attrs[i++] = val >> 8;
|
||||
i += attr_item_len - 2 - attr_item_offset; // normally 0
|
||||
}
|
||||
} 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];
|
||||
attrs_len = attr_obj.size() * attr_item_len;
|
||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||
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
|
||||
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
|
||||
|
@ -475,15 +568,21 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
|
|||
// match name
|
||||
// check if there is a conflict with cluster
|
||||
// TODO
|
||||
if (!value && attr_item_offset) {
|
||||
// If value is false (non-default) then set direction to 1 (for ReadConfig)
|
||||
attrs[actual_attr_len] = 0x01;
|
||||
}
|
||||
actual_attr_len += attr_item_offset;
|
||||
attrs[actual_attr_len++] = local_attr_id & 0xFF;
|
||||
attrs[actual_attr_len++] = local_attr_id >> 8;
|
||||
actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0
|
||||
found = true;
|
||||
// check cluster
|
||||
if (0xFFFF == cluster) {
|
||||
cluster = local_cluster_id;
|
||||
} else if (cluster != local_cluster_id) {
|
||||
ResponseCmndChar_P(PSTR("No more than one cluster id per command"));
|
||||
if (attrs) { delete[] attrs; }
|
||||
if (attrs) { free(attrs); }
|
||||
return;
|
||||
}
|
||||
break; // found, exit loop
|
||||
|
@ -496,20 +595,20 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
|
|||
|
||||
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;
|
||||
attrs_len = attr_item_len;
|
||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||
attrs[0 + attr_item_offset] = val & 0xFF; // little endian
|
||||
attrs[1 + attr_item_offset] = 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));
|
||||
ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, operation, 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; }
|
||||
if (attrs) { free(attrs); }
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -594,9 +693,12 @@ void CmndZbSend(void) {
|
|||
const JsonVariant &val_write = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_WRITE));
|
||||
const JsonVariant &val_publish = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_REPORT));
|
||||
const JsonVariant &val_response = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_RESPONSE));
|
||||
uint32_t multi_cmd = (nullptr != &val_cmd) + (nullptr != &val_read) + (nullptr != &val_write) + (nullptr != &val_publish)+ (nullptr != &val_response);
|
||||
const JsonVariant &val_read_config = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_READ_CONFIG));
|
||||
const JsonVariant &val_config = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CONFIG));
|
||||
uint32_t multi_cmd = (nullptr != &val_cmd) + (nullptr != &val_read) + (nullptr != &val_write) + (nullptr != &val_publish)
|
||||
+ (nullptr != &val_response) + (nullptr != &val_read_config) + (nullptr != &val_config);
|
||||
if (multi_cmd > 1) {
|
||||
ResponseCmndChar_P(PSTR("Can only have one of: 'Send', 'Read', 'Write', 'Report' or 'Reponse'"));
|
||||
ResponseCmndChar_P(PSTR("Can only have one of: 'Send', 'Read', 'Write', 'Report', 'Reponse', 'ReadConfig' or 'Config'"));
|
||||
return;
|
||||
}
|
||||
// from here we have one and only one command
|
||||
|
@ -608,7 +710,7 @@ void CmndZbSend(void) {
|
|||
} else if (nullptr != &val_read) {
|
||||
// "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...]
|
||||
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
|
||||
ZbSendRead(val_read, device, groupaddr, cluster, endpoint, manuf);
|
||||
ZbSendRead(val_read, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_ATTRIBUTES);
|
||||
} else if (nullptr != &val_write) {
|
||||
// only KSON object
|
||||
if (!val_write.is<JsonObject>()) {
|
||||
|
@ -618,7 +720,7 @@ void CmndZbSend(void) {
|
|||
// "Write":{...attributes...}
|
||||
ZbSendReportWrite(val_write, device, groupaddr, cluster, endpoint, manuf, ZCL_WRITE_ATTRIBUTES);
|
||||
} else if (nullptr != &val_publish) {
|
||||
// "Report":{...attributes...}
|
||||
// "Publish":{...attributes...}
|
||||
// only KSON object
|
||||
if (!val_publish.is<JsonObject>()) {
|
||||
ResponseCmndChar_P(PSTR("Missing parameters"));
|
||||
|
@ -633,6 +735,18 @@ void CmndZbSend(void) {
|
|||
return;
|
||||
}
|
||||
ZbSendReportWrite(val_response, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_ATTRIBUTES_RESPONSE);
|
||||
} else if (nullptr != &val_read_config) {
|
||||
// "ReadConfg":{...attributes...}, "ReadConfg":attribute or "ReadConfg":[...attributes...]
|
||||
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
|
||||
ZbSendRead(val_read_config, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_REPORTING_CONFIGURATION);
|
||||
} else if (nullptr != &val_config) {
|
||||
// "Config":{...attributes...}
|
||||
// only JSON object
|
||||
if (!val_config.is<JsonObject>()) {
|
||||
ResponseCmndChar_P(PSTR("Missing parameters"));
|
||||
return;
|
||||
}
|
||||
ZbSendReportWrite(val_config, device, groupaddr, cluster, endpoint, manuf, ZCL_CONFIGURE_REPORTING);
|
||||
} else {
|
||||
Response_P(PSTR("Missing zigbee 'Send', 'Write', 'Report' or 'Response'"));
|
||||
return;
|
||||
|
@ -675,18 +789,36 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
|
|||
// look for source endpoint
|
||||
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_ENDPOINT));
|
||||
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
|
||||
else { endpoint = zigbee_devices.findFirstEndpoint(srcDevice); }
|
||||
// look for source 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); // first convert as number
|
||||
if (0 == cluster) {
|
||||
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Or Group Address - we don't need a dstEndpoint in this case
|
||||
const JsonVariant &to_group = GetCaseInsensitive(json, PSTR("ToGroup"));
|
||||
if (nullptr != &to_group) { toGroup = strToUInt(to_group); }
|
||||
|
||||
// Either Device address
|
||||
// In this case the following parameters are mandatory
|
||||
// - "ToDevice" and the device must have a known IEEE address
|
||||
// - "ToEndpoint"
|
||||
const JsonVariant &dst_device = GetCaseInsensitive(json, PSTR("ToDevice"));
|
||||
if (nullptr != &dst_device) {
|
||||
dstDevice = zigbee_devices.parseDeviceParam(dst_device.as<char*>());
|
||||
if (BAD_SHORTADDR == dstDevice) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
|
||||
|
||||
// If no target is specified, we default to coordinator 0x0000
|
||||
if ((nullptr == &to_group) && (nullptr == &dst_device)) {
|
||||
dstDevice = 0x0000;
|
||||
}
|
||||
|
||||
if ((nullptr != &dst_device) || (BAD_SHORTADDR != dstDevice)) {
|
||||
if (BAD_SHORTADDR == dstDevice) {
|
||||
dstDevice = zigbee_devices.parseDeviceParam(dst_device.as<char*>());
|
||||
if (BAD_SHORTADDR == dstDevice) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
|
||||
}
|
||||
if (0x0000 == dstDevice) {
|
||||
dstLongAddr = localIEEEAddr;
|
||||
} else {
|
||||
|
@ -695,13 +827,10 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
|
|||
if (0 == dstLongAddr) { ResponseCmndChar_P(PSTR("Unknown dest IEEE address")); return; }
|
||||
|
||||
const JsonVariant &val_toendpoint = GetCaseInsensitive(json, PSTR("ToEndpoint"));
|
||||
if (nullptr != &val_toendpoint) { toendpoint = strToUInt(val_toendpoint); } else { toendpoint = endpoint; }
|
||||
if (nullptr != &val_toendpoint) { toendpoint = strToUInt(val_toendpoint); }
|
||||
else { toendpoint = 0x01; } // default to endpoint 1
|
||||
}
|
||||
|
||||
// Or Group Address - we don't need a dstEndpoint in this case
|
||||
const JsonVariant &to_group = GetCaseInsensitive(json, PSTR("ToGroup"));
|
||||
if (nullptr != &to_group) { toGroup = strToUInt(to_group); }
|
||||
|
||||
// make sure we don't have conflicting parameters
|
||||
if (&to_group && dstLongAddr) { ResponseCmndChar_P(PSTR("Cannot have both \"ToDevice\" and \"ToGroup\"")); return; }
|
||||
if (!&to_group && !dstLongAddr) { ResponseCmndChar_P(PSTR("Missing \"ToDevice\" or \"ToGroup\"")); return; }
|
||||
|
|
Loading…
Reference in New Issue