Merge pull request #6702 from s-hadinger/zigbee_read

Add ZigbeeRead command and many improvements (#6095)
This commit is contained in:
Theo Arends 2019-10-20 17:37:20 +02:00 committed by GitHub
commit 7ff3e22ae7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 484 additions and 127 deletions

View File

@ -3,6 +3,7 @@
* Add command SetOption65 0/1 to disable (1) fast power cycle detection fixing unwanted brownout trigger
* Add absolute PowerDelta using command PowerDelta 101..32000 where 101 = 101-100 = 1W, 202 = 202-100 = 102W (#5901)
* Add support for EX-Store WiFi Dimmer V4 (#5856)
* Add ZigbeeRead command and many improvements (#6095)
*
* 6.6.0.19 20191018
* Replace obsolete xsns_23_sdm120 with xnrg_08_sdm120 and consolidate define USE_SDM120

View File

@ -468,9 +468,14 @@
#define D_JSON_ZIGBEEZNPSENT "ZigbeeZNPSent"
#define D_JSON_ZIGBEEZCL_RECEIVED "ZigbeeZCLReceived"
#define D_JSON_ZIGBEEZCL_RAW_RECEIVED "ZigbeeZCLRawReceived"
#define D_JSON_ZIGBEE_DEVICE "Device"
#define D_JSON_ZIGBEE_NAME "Name"
#define D_CMND_ZIGBEE_ZCL_SEND "ZigbeeZCLSend"
#define D_JSON_ZIGBEE_ZCL_SENT "ZigbeeZCLSent"
#define D_CMND_ZIGBEE_PROBE "ZigbeeProbe"
#define D_CMND_ZIGBEE_RECEIVED "ZigbeeReceived"
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
#define D_CMND_ZIGBEE_READ "ZigbeeRead"
// Commands xdrv_25_A4988_Stepper.ino
#ifdef USE_A4988_STEPPER

View File

@ -363,10 +363,10 @@ String Z_Devices::dump(uint8_t dump_mode) const {
JsonObject& dev = devices.createNestedObject();
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
dev[F("ShortAddr")] = hex;
dev[F(D_JSON_ZIGBEE_DEVICE)] = hex;
if (device.friendlyName.length() > 0) {
dev[F("FriendlyName")] = device.friendlyName;
dev[F(D_JSON_ZIGBEE_NAME)] = device.friendlyName;
}
if (1 == dump_mode) {

View File

@ -59,7 +59,7 @@ public:
Response_P(PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{"
"\"groupid\":%d," "\"clusterid\":%d," "\"srcaddr\":\"0x%04X\","
"\"srcendpoint\":%d," "\"dstendpoint\":%d," "\"wasbroadcast\":%d,"
"\"linkquality\":%d," "\"securityuse\":%d," "\"seqnumber\":%d,"
"\"" D_CMND_ZIGBEE_LINKQUALITY "\":%d," "\"securityuse\":%d," "\"seqnumber\":%d,"
"\"timestamp\":%d,"
"\"fc\":\"0x%02X\",\"manuf\":\"0x%04X\",\"transact\":%d,"
"\"cmdid\":\"0x%02X\",\"payload\":\"%s\""),
@ -390,8 +390,8 @@ void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
i += 2;
char key[16];
snprintf_P(key, sizeof(key), PSTR("%04X_%02X_%04X"),
_cluster_id, _cmd_id, attrid);
snprintf_P(key, sizeof(key), PSTR("%04X/%04X"),
_cluster_id, attrid);
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
@ -415,8 +415,8 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
if (0 == status) {
char key[16];
snprintf_P(key, sizeof(key), PSTR("%04X_%02X_%04X"),
_cluster_id, _cmd_id, attrid);
snprintf_P(key, sizeof(key), PSTR("%04X/%04X"),
_cluster_id, attrid);
i += parseSingleAttribute(json, key, _payload, i, len);
}
@ -442,106 +442,327 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
// return value:
// 0 = keep initial value
// 1 = remove initial value
typedef int32_t (*Z_AttrConverter)(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param);
typedef int32_t (*Z_AttrConverter)(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper* new_name);
typedef struct Z_AttributeConverter {
const char * filter;
uint16_t cluster;
uint16_t attribute;
const char * name;
Z_AttrConverter func;
void * param;
} Z_AttributeConverter;
const float Z_100 PROGMEM = 100.0f;
const float Z_10 PROGMEM = 10.0f;
// list of post-processing directives
const Z_AttributeConverter Z_PostProcess[] = {
{ "0000_0?_0004", nullptr, &Z_ManufKeep, nullptr }, // record Manufacturer
{ "0000_0?_0005", nullptr, &Z_ModelKeep, nullptr }, // record Model
const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// { 0x0000, 0x0004, "Manufacturer", &Z_ManufKeep }, // record Manufacturer
// { 0x0000, 0x0005, D_JSON_MODEL D_JSON_ID, &Z_ModelKeep }, // record Model
// { 0x0405, 0x0000, D_JSON_HUMIDITY, &Z_FloatDiv100 }, // Humidity
{ "0000_0?_0000", "ZCLVersion", &Z_Copy, nullptr },
{ "0000_0?_0001", "AppVersion", &Z_Copy, nullptr },
{ "0000_0?_0002", "StackVersion", &Z_Copy, nullptr },
{ "0000_0?_0003", "HWVersion", &Z_Copy, nullptr },
{ "0000_0?_0004", "Manufacturer", &Z_Copy, nullptr },
{ "0000_0?_0005", D_JSON_MODEL D_JSON_ID, &Z_Copy, nullptr },
{ "0000_0?_0006", "DateCode", &Z_Copy, nullptr },
{ "0000_0?_0007", "PowerSource", &Z_Copy, nullptr },
{ "0000_0?_4000", "SWBuildID", &Z_Copy, nullptr },
{ "0000_0?_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
{ 0x0000, 0x0000, "ZCLVersion", &Z_Copy },
{ 0x0000, 0x0001, "AppVersion", &Z_Copy },
{ 0x0000, 0x0002, "StackVersion", &Z_Copy },
{ 0x0000, 0x0003, "HWVersion", &Z_Copy },
{ 0x0000, 0x0004, "Manufacturer", &Z_ManufKeep }, // record Manufacturer
{ 0x0000, 0x0005, D_JSON_MODEL D_JSON_ID, &Z_ModelKeep }, // record Model
{ 0x0000, 0x0006, "DateCode", &Z_Copy },
{ 0x0000, 0x0007, "PowerSource", &Z_Copy },
{ 0x0000, 0x4000, "SWBuildID", &Z_Copy },
{ 0x0000, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
{ "0400_0A_0000", D_JSON_ILLUMINANCE, &Z_Copy, nullptr }, // Illuminance (in Lux)
{ "0400_0A_0004", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType
{ "0400_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
// Color Control cluster
{ 0x0003, 0x0000, "CurrentHue", &Z_Copy },
{ 0x0003, 0x0001, "CurrentSaturation", &Z_Copy },
{ 0x0003, 0x0002, "RemainingTime", &Z_Copy },
{ 0x0003, 0x0003, "CurrentX", &Z_Copy },
{ 0x0003, 0x0004, "CurrentY", &Z_Copy },
{ 0x0003, 0x0005, "DriftCompensation", &Z_Copy },
{ 0x0003, 0x0006, "CompensationText", &Z_Copy },
{ 0x0003, 0x0007, "ColorTemperatureMireds",&Z_Copy },
{ 0x0003, 0x0008, "ColorMode", &Z_Copy },
{ 0x0003, 0x0010, "NumberOfPrimaries", &Z_Copy },
{ 0x0003, 0x0011, "Primary1X", &Z_Copy },
{ 0x0003, 0x0012, "Primary1Y", &Z_Copy },
{ 0x0003, 0x0013, "Primary1Intensity", &Z_Copy },
{ 0x0003, 0x0015, "Primary2X", &Z_Copy },
{ 0x0003, 0x0016, "Primary2Y", &Z_Copy },
{ 0x0003, 0x0017, "Primary2Intensity", &Z_Copy },
{ 0x0003, 0x0019, "Primary3X", &Z_Copy },
{ 0x0003, 0x001A, "Primary3Y", &Z_Copy },
{ 0x0003, 0x001B, "Primary3Intensity", &Z_Copy },
{ 0x0003, 0x0030, "WhitePointX", &Z_Copy },
{ 0x0003, 0x0031, "WhitePointY", &Z_Copy },
{ 0x0003, 0x0032, "ColorPointRX", &Z_Copy },
{ 0x0003, 0x0033, "ColorPointRY", &Z_Copy },
{ 0x0003, 0x0034, "ColorPointRIntensity", &Z_Copy },
{ 0x0003, 0x0036, "ColorPointGX", &Z_Copy },
{ 0x0003, 0x0037, "ColorPointGY", &Z_Copy },
{ 0x0003, 0x0038, "ColorPointGIntensity", &Z_Copy },
{ 0x0003, 0x003A, "ColorPointBX", &Z_Copy },
{ 0x0003, 0x003B, "ColorPointBY", &Z_Copy },
{ 0x0003, 0x003C, "ColorPointBIntensity", &Z_Copy },
{ "0401_0A_0000", "LevelStatus", &Z_Copy, nullptr }, // Illuminance (in Lux)
{ "0401_0A_0001", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType
{ "0401_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
// On/off cluster
{ 0x0006, 0x0000, "Power", &Z_Copy },
// On/Off Switch Configuration cluster
{ 0x0007, 0x0000, "SwitchType", &Z_Copy },
// Level Control cluster
{ 0x0008, 0x0000, "CurrentLevel", &Z_Copy },
{ 0x0008, 0x0001, "RemainingTime", &Z_Copy },
{ 0x0008, 0x0010, "OnOffTransitionTime", &Z_Copy },
{ 0x0008, 0x0011, "OnLevel", &Z_Copy },
{ 0x0008, 0x0012, "OnTransitionTime", &Z_Copy },
{ 0x0008, 0x0013, "OffTransitionTime", &Z_Copy },
{ 0x0008, 0x0014, "DefaultMoveRate", &Z_Copy },
// Alarms cluster
{ 0x0009, 0x0000, "AlarmCount", &Z_Copy },
// Time cluster
{ 0x000A, 0x0000, "Time", &Z_Copy },
{ 0x000A, 0x0001, "TimeStatus", &Z_Copy },
{ 0x000A, 0x0002, "TimeZone", &Z_Copy },
{ 0x000A, 0x0003, "DstStart", &Z_Copy },
{ 0x000A, 0x0004, "DstStart", &Z_Copy },
{ 0x000A, 0x0005, "DstShift", &Z_Copy },
{ 0x000A, 0x0006, "StandardTime", &Z_Copy },
{ 0x000A, 0x0007, "LocalTime", &Z_Copy },
{ 0x000A, 0x0008, "LastSetTime", &Z_Copy },
{ 0x000A, 0x0009, "ValidUntilTime", &Z_Copy },
// RSSI Location cluster
{ 0x000B, 0x0000, "LocationType", &Z_Copy },
{ 0x000B, 0x0000, "LocationMethod", &Z_Copy },
{ 0x000B, 0x0000, "LocationAge", &Z_Copy },
{ 0x000B, 0x0000, "QualityMeasure", &Z_Copy },
{ 0x000B, 0x0000, "NumberOfDevices", &Z_Copy },
// Analog Input cluster
{ 0x000C, 0x0004, "ActiveText", &Z_Copy },
{ 0x000C, 0x001C, "Description", &Z_Copy },
{ 0x000C, 0x002E, "InactiveText", &Z_Copy },
{ 0x000C, 0x0041, "MaxPresentValue", &Z_Copy },
{ 0x000C, 0x0045, "MinPresentValue", &Z_Copy },
{ 0x000C, 0x0051, "OutOfService", &Z_Copy },
{ 0x000C, 0x0055, "PresentValue", &Z_Copy },
{ 0x000C, 0x0057, "PriorityArray", &Z_Copy },
{ 0x000C, 0x0067, "Reliability", &Z_Copy },
{ 0x000C, 0x0068, "RelinquishDefault", &Z_Copy },
{ 0x000C, 0x006A, "Resolution", &Z_Copy },
{ 0x000C, 0x006F, "StatusFlags", &Z_Copy },
{ 0x000C, 0x0075, "EngineeringUnits", &Z_Copy },
{ 0x000C, 0x0100, "ApplicationType", &Z_Copy },
// Binary Output cluster
{ 0x0010, 0x0004, "ActiveText", &Z_Copy },
{ 0x0010, 0x001C, "Description", &Z_Copy },
{ 0x0010, 0x002E, "InactiveText", &Z_Copy },
{ 0x0010, 0x0042, "MinimumOffTime", &Z_Copy },
{ 0x0010, 0x0043, "MinimumOnTime", &Z_Copy },
{ 0x0010, 0x0051, "OutOfService", &Z_Copy },
{ 0x0010, 0x0054, "Polarity", &Z_Copy },
{ 0x0010, 0x0055, "PresentValue", &Z_Copy },
{ 0x0010, 0x0057, "PriorityArray", &Z_Copy },
{ 0x0010, 0x0067, "Reliability", &Z_Copy },
{ 0x0010, 0x0068, "RelinquishDefault", &Z_Copy },
{ 0x0010, 0x006F, "StatusFlags", &Z_Copy },
{ 0x0010, 0x0100, "ApplicationType", &Z_Copy },
// Binary Value cluster
{ 0x0011, 0x0004, "ActiveText", &Z_Copy },
{ 0x0011, 0x001C, "Description", &Z_Copy },
{ 0x0011, 0x002E, "InactiveText", &Z_Copy },
{ 0x0011, 0x0042, "MinimumOffTime", &Z_Copy },
{ 0x0011, 0x0043, "MinimumOnTime", &Z_Copy },
{ 0x0011, 0x0051, "OutOfService", &Z_Copy },
{ 0x0011, 0x0055, "PresentValue", &Z_Copy },
{ 0x0011, 0x0057, "PriorityArray", &Z_Copy },
{ 0x0011, 0x0067, "Reliability", &Z_Copy },
{ 0x0011, 0x0068, "RelinquishDefault", &Z_Copy },
{ 0x0011, 0x006F, "StatusFlags", &Z_Copy },
{ 0x0011, 0x0100, "ApplicationType", &Z_Copy },
// Multistate Input cluster
{ 0x0012, 0x000E, "StateText", &Z_Copy },
{ 0x0012, 0x001C, "Description", &Z_Copy },
{ 0x0012, 0x004A, "NumberOfStates", &Z_Copy },
{ 0x0012, 0x0051, "OutOfService", &Z_Copy },
{ 0x0012, 0x0055, "PresentValue", &Z_Copy },
{ 0x0012, 0x0067, "Reliability", &Z_Copy },
{ 0x0012, 0x006F, "StatusFlags", &Z_Copy },
{ 0x0012, 0x0100, "ApplicationType", &Z_Copy },
// Multistate output
{ 0x0013, 0x000E, "StateText", &Z_Copy },
{ 0x0013, 0x001C, "Description", &Z_Copy },
{ 0x0013, 0x004A, "NumberOfStates", &Z_Copy },
{ 0x0013, 0x0051, "OutOfService", &Z_Copy },
{ 0x0013, 0x0055, "PresentValue", &Z_Copy },
{ 0x0013, 0x0057, "PriorityArray", &Z_Copy },
{ 0x0013, 0x0067, "Reliability", &Z_Copy },
{ 0x0013, 0x0068, "RelinquishDefault", &Z_Copy },
{ 0x0013, 0x006F, "StatusFlags", &Z_Copy },
{ 0x0013, 0x0100, "ApplicationType", &Z_Copy },
// Multistate Value cluster
{ 0x0014, 0x000E, "StateText", &Z_Copy },
{ 0x0014, 0x001C, "Description", &Z_Copy },
{ 0x0014, 0x004A, "NumberOfStates", &Z_Copy },
{ 0x0014, 0x0051, "OutOfService", &Z_Copy },
{ 0x0014, 0x0055, "PresentValue", &Z_Copy },
{ 0x0014, 0x0067, "Reliability", &Z_Copy },
{ 0x0014, 0x0068, "RelinquishDefault", &Z_Copy },
{ 0x0014, 0x006F, "StatusFlags", &Z_Copy },
{ 0x0014, 0x0100, "ApplicationType", &Z_Copy },
// Diagnostics cluster
{ 0x0B05, 0x0000, "NumberOfResets", &Z_Copy },
{ 0x0B05, 0x0001, "PersistentMemoryWrites",&Z_Copy },
{ 0x0B05, 0x011C, "LastMessageLQI", &Z_Copy },
{ 0x0B05, 0x011D, "LastMessageRSSI", &Z_Copy },
// Poll Control cluster
{ 0x0020, 0x0000, "CheckinInterval", &Z_Copy },
{ 0x0020, 0x0001, "LongPollInterval", &Z_Copy },
{ 0x0020, 0x0002, "ShortPollInterval", &Z_Copy },
{ 0x0020, 0x0003, "FastPollTimeout", &Z_Copy },
{ 0x0020, 0x0004, "CheckinIntervalMin", &Z_Copy },
{ 0x0020, 0x0005, "LongPollIntervalMin", &Z_Copy },
{ 0x0020, 0x0006, "FastPollTimeoutMax", &Z_Copy },
// Shade Configuration cluster
{ 0x0100, 0x0000, "PhysicalClosedLimit", &Z_Copy },
{ 0x0100, 0x0001, "MotorStepSize", &Z_Copy },
{ 0x0100, 0x0002, "Status", &Z_Copy },
{ 0x0100, 0x0010, "ClosedLimit", &Z_Copy },
{ 0x0100, 0x0011, "Mode", &Z_Copy },
// Door Lock cluster
{ 0x0101, 0x0000, "LockState", &Z_Copy },
{ 0x0101, 0x0001, "LockType", &Z_Copy },
{ 0x0101, 0x0002, "ActuatorEnabled", &Z_Copy },
{ 0x0101, 0x0003, "DoorState", &Z_Copy },
{ 0x0101, 0x0004, "DoorOpenEvents", &Z_Copy },
{ 0x0101, 0x0005, "DoorClosedEvents", &Z_Copy },
{ 0x0101, 0x0006, "OpenPeriod", &Z_Copy },
// Window Covering cluster
{ 0x0102, 0x0000, "WindowCoveringType", &Z_Copy },
{ 0x0102, 0x0001, "PhysicalClosedLimitLift",&Z_Copy },
{ 0x0102, 0x0002, "PhysicalClosedLimitTilt",&Z_Copy },
{ 0x0102, 0x0003, "CurrentPositionLift", &Z_Copy },
{ 0x0102, 0x0004, "CurrentPositionTilt", &Z_Copy },
{ 0x0102, 0x0005, "NumberofActuationsLift",&Z_Copy },
{ 0x0102, 0x0006, "NumberofActuationsTilt",&Z_Copy },
{ 0x0102, 0x0007, "ConfigStatus", &Z_Copy },
{ 0x0102, 0x0008, "CurrentPositionLiftPercentage",&Z_Copy },
{ 0x0102, 0x0009, "CurrentPositionTiltPercentage",&Z_Copy },
{ 0x0102, 0x0010, "InstalledOpenLimitLift",&Z_Copy },
{ 0x0102, 0x0011, "InstalledClosedLimitLift",&Z_Copy },
{ 0x0102, 0x0012, "InstalledOpenLimitTilt", &Z_Copy },
{ 0x0102, 0x0013, "InstalledClosedLimitTilt", &Z_Copy },
{ 0x0102, 0x0014, "VelocityLift",&Z_Copy },
{ 0x0102, 0x0015, "AccelerationTimeLift",&Z_Copy },
{ 0x0102, 0x0016, "DecelerationTimeLift", &Z_Copy },
{ 0x0102, 0x0017, "Mode",&Z_Copy },
{ 0x0102, 0x0018, "IntermediateSetpointsLift",&Z_Copy },
{ 0x0102, 0x0019, "IntermediateSetpointsTilt",&Z_Copy },
{ "0402_0A_0000", D_JSON_TEMPERATURE, &Z_ConvFloatDivider, (void*) &Z_100 }, // Temperature
{ "0402_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
// Power Profile cluster
{ 0x001A, 0x0000, "TotalProfileNum", &Z_Copy },
{ 0x001A, 0x0001, "MultipleScheduling", &Z_Copy },
{ 0x001A, 0x0002, "EnergyFormatting", &Z_Copy },
{ 0x001A, 0x0003, "EnergyRemote", &Z_Copy },
{ 0x001A, 0x0004, "ScheduleMode", &Z_Copy },
// Meter Identification cluster
{ 0x0B01, 0x0000, "CompanyName", &Z_Copy },
{ 0x0B01, 0x0001, "MeterTypeID", &Z_Copy },
{ 0x0B01, 0x0004, "DataQualityID", &Z_Copy },
{ 0x0B01, 0x0005, "CustomerName", &Z_Copy },
{ 0x0B01, 0x0006, "Model", &Z_Copy },
{ 0x0B01, 0x0007, "PartNumber", &Z_Copy },
{ 0x0B01, 0x000A, "SoftwareRevision", &Z_Copy },
{ 0x0B01, 0x000C, "POD", &Z_Copy },
{ 0x0B01, 0x000D, "AvailablePower", &Z_Copy },
{ 0x0B01, 0x000E, "PowerThreshold", &Z_Copy },
{ "0403_0A_0000", D_JSON_PRESSURE_UNIT, &Z_Const_Keep, (void*) D_UNIT_PRESSURE}, // Pressure Unit
{ "0403_0A_0000", D_JSON_PRESSURE, &Z_Copy, nullptr }, // Pressure
{ "0403_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other Pressure values
{ 0x0400, 0x0000, D_JSON_ILLUMINANCE, &Z_Copy }, // Illuminance (in Lux)
{ 0x0400, 0x0001, "MinMeasuredValue", &Z_Copy }, //
{ 0x0400, 0x0002, "MaxMeasuredValue", &Z_Copy }, //
{ 0x0400, 0x0003, "Tolerance", &Z_Copy }, //
{ 0x0400, 0x0004, "LightSensorType", &Z_Copy }, //
{ 0x0400, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
{ "0404_0A_0000", D_JSON_FLOWRATE, &Z_ConvFloatDivider, (void*) &Z_10 }, // Flow (in m3/h)
{ "0404_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
{ 0x0401, 0x0000, "LevelStatus", &Z_Copy }, // Illuminance (in Lux)
{ 0x0401, 0x0001, "LightSensorType", &Z_Copy }, // LightSensorType
{ 0x0401, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
{ "0405_0A_0000", D_JSON_HUMIDITY, &Z_ConvFloatDivider, (void*) &Z_100 }, // Humidity
{ "0405_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
{ 0x0402, 0x0000, D_JSON_TEMPERATURE, &Z_FloatDiv100 }, // Temperature
{ 0x0402, 0x0001, "MinMeasuredValue", &Z_FloatDiv100 }, //
{ 0x0402, 0x0002, "MaxMeasuredValue", &Z_FloatDiv100 }, //
{ 0x0402, 0x0003, "Tolerance", &Z_FloatDiv100 }, //
{ 0x0402, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
{ "0406_0A_0000", "Occupancy", &Z_Copy, nullptr }, // Occupancy (map8)
{ "0406_0A_0001", "OccupancySensorType", &Z_Copy, nullptr }, // OccupancySensorType
{ "0406_0A_????", nullptr, &Z_Remove, nullptr }, // Remove all other values
{ 0x0403, 0x0000, D_JSON_PRESSURE_UNIT, &Z_AddPressureUnit }, // Pressure Unit
{ 0x0403, 0x0000, D_JSON_PRESSURE, &Z_Copy }, // Pressure
{ 0x0403, 0x0001, "MinMeasuredValue", &Z_Copy }, //
{ 0x0403, 0x0002, "MaxMeasuredValue", &Z_Copy }, //
{ 0x0403, 0x0003, "Tolerance", &Z_Copy }, //
{ 0x0403, 0x0010, "ScaledValue", &Z_Copy }, //
{ 0x0403, 0x0011, "MinScaledValue", &Z_Copy }, //
{ 0x0403, 0x0012, "MaxScaledValue", &Z_Copy }, //
{ 0x0403, 0x0013, "ScaledTolerance", &Z_Copy }, //
{ 0x0403, 0x0014, "Scale", &Z_Copy }, //
{ 0x0403, 0xFFFF, nullptr, &Z_Remove }, // Remove all other Pressure values
{ 0x0404, 0x0000, D_JSON_FLOWRATE, &Z_FloatDiv10 }, // Flow (in m3/h)
{ 0x0404, 0x0001, "MinMeasuredValue", &Z_Copy }, //
{ 0x0404, 0x0002, "MaxMeasuredValue", &Z_Copy }, //
{ 0x0404, 0x0003, "Tolerance", &Z_Copy }, //
{ 0x0404, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
{ 0x0405, 0x0000, D_JSON_HUMIDITY, &Z_FloatDiv100 }, // Humidity
{ 0x0405, 0x0001, "MinMeasuredValue", &Z_Copy }, //
{ 0x0405, 0x0002, "MaxMeasuredValue", &Z_Copy }, //
{ 0x0405, 0x0003, "Tolerance", &Z_Copy }, //
{ 0x0405, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
{ 0x0406, 0x0000, "Occupancy", &Z_Copy }, // Occupancy (map8)
{ 0x0406, 0x0001, "OccupancySensorType", &Z_Copy }, // OccupancySensorType
{ 0x0406, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
{ "0000_0A_FF01", nullptr, &Z_AqaraSensor, nullptr }, // Occupancy (map8)
// // 0x0b04 Electrical Measurement
// { "0B04_0100", "DCVoltage", &Z_Copy, nullptr }, //
// { "0B04_0001", "OccupancySensorType", &Z_Copy, nullptr }, //
// { "0B04_????", "", &Z_Remove, nullptr }, //
};
{ 0x0000, 0xFF01, nullptr, &Z_AqaraSensor }, // Occupancy (map8)
};
// ======================================================================
// Record Manuf
int32_t Z_ManufKeep(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
int32_t Z_ManufKeep(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
json[new_name] = value;
zigbee_devices.setManufId(shortaddr, value.as<const char*>());
return 0; // keep original key
return 1;
}
//
int32_t Z_ModelKeep(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
int32_t Z_ModelKeep(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
json[new_name] = value;
zigbee_devices.setModelId(shortaddr, value.as<const char*>());
return 0; // keep original key
return 1;
}
// ======================================================================
// Remove attribute
int32_t Z_Remove(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
int32_t Z_Remove(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
return 1; // remove original key
}
// Copy value as-is
int32_t Z_Copy(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
int32_t Z_Copy(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
json[new_name] = value;
return 1; // remove original key
}
// Copy value as-is
int32_t Z_Const_Keep(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
json[new_name] = (char*)param;
// Add pressure unit
int32_t Z_AddPressureUnit(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
json[new_name] = F(D_UNIT_PRESSURE);
return 0; // keep original key
}
// Convert int to float with divider
int32_t Z_ConvFloatDivider(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
float f_value = value;
float *divider = (float*) param;
json[new_name] = f_value / *divider;
// Convert int to float and divide by 100
int32_t Z_FloatDiv100(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
json[new_name] = ((float)value) / 100.0f;
return 1; // remove original key
}
// Convert int to float and divide by 10
int32_t Z_FloatDiv10(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
json[new_name] = ((float)value) / 10.0f;
return 1; // remove original key
}
int32_t Z_AqaraSensor(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) {
int32_t Z_AqaraSensor(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
String hex = value;
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint32_t i = 0;
@ -572,68 +793,46 @@ int32_t Z_AqaraSensor(uint16_t shortaddr, JsonObject& json, const char *name, Js
}
// ======================================================================
// #define ZCL_MODELID "A_0000_0005" // Cmd 0x0A - Cluster 0x0000, attribute 0x05
// #define ZCL_TEMPERATURE "A_0402_0000" // Cmd 0x0A - Cluster 0x0402, attribute 0x00
// #define ZCL_PRESSURE "A_0403_0000" // Cmd 0x0A - Cluster 0x0403, attribute 0x00
// #define ZCL_PRESSURE_SCALED "A_0403_0010" // Cmd 0x0A - Cluster 0x0403, attribute 0x10
// #define ZCL_PRESSURE_SCALE "A_0403_0014" // Cmd 0x0A - Cluster 0x0403, attribute 0x14
// #define ZCL_HUMIDITY "A_0405_0000" // Cmd 0x0A - Cluster 0x0403, attribute 0x00
// #define ZCL_LUMI_WEATHER "A_0000_FF01" // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
// Cluster Specific commands
#define ZCL_OO_OFF "s_0006_00" // Cluster 0x0006, cmd 0x00 - On/Off - Off
#define ZCL_OO_ON "s_0006_01" // Cluster 0x0006, cmd 0x01 - On/Off - On
#define ZCL_COLORTEMP_MOVE "s_0300_0A" // Cluster 0x0300, cmd 0x0A, Move to Color Temp
#define ZCL_LC_MOVE "s_0008_00" // Cluster 0x0008, cmd 0x00, Level Control Move to Level
#define ZCL_LC_MOVE_1 "s_0008_01" // Cluster 0x0008, cmd 0x01, Level Control Move
#define ZCL_LC_STEP "s_0008_02" // Cluster 0x0008, cmd 0x02, Level Control Step
#define ZCL_LC_STOP "s_0008_03" // Cluster 0x0008, cmd 0x03, Level Control Stop
#define ZCL_LC_MOVE_WOO "s_0008_04" // Cluster 0x0008, cmd 0x04, Level Control Move to Level, with On/Off
#define ZCL_LC_MOVE_1_WOO "s_0008_05" // Cluster 0x0008, cmd 0x05, Level Control Move, with On/Off
#define ZCL_LC_STEP_WOO "s_0008_06" // Cluster 0x0008, cmd 0x05, Level Control Step, with On/Off
#define ZCL_LC_STOP_WOO "s_0008_07" // Cluster 0x0008, cmd 0x07, Level Control Stop
// #define ZCL_OO_OFF "s_0006_00" // Cluster 0x0006, cmd 0x00 - On/Off - Off
// #define ZCL_OO_ON "s_0006_01" // Cluster 0x0006, cmd 0x01 - On/Off - On
// #define ZCL_COLORTEMP_MOVE "s_0300_0A" // Cluster 0x0300, cmd 0x0A, Move to Color Temp
// #define ZCL_LC_MOVE "s_0008_00" // Cluster 0x0008, cmd 0x00, Level Control Move to Level
// #define ZCL_LC_MOVE_1 "s_0008_01" // Cluster 0x0008, cmd 0x01, Level Control Move
// #define ZCL_LC_STEP "s_0008_02" // Cluster 0x0008, cmd 0x02, Level Control Step
// #define ZCL_LC_STOP "s_0008_03" // Cluster 0x0008, cmd 0x03, Level Control Stop
// #define ZCL_LC_MOVE_WOO "s_0008_04" // Cluster 0x0008, cmd 0x04, Level Control Move to Level, with On/Off
// #define ZCL_LC_MOVE_1_WOO "s_0008_05" // Cluster 0x0008, cmd 0x05, Level Control Move, with On/Off
// #define ZCL_LC_STEP_WOO "s_0008_06" // Cluster 0x0008, cmd 0x05, Level Control Step, with On/Off
// #define ZCL_LC_STOP_WOO "s_0008_07" // Cluster 0x0008, cmd 0x07, Level Control Stop
// inspired from https://github.com/torvalds/linux/blob/master/lib/glob.c
bool mini_glob_match(char const *pat, char const *str) {
for (;;) {
unsigned char c = *str++;
unsigned char d = *pat++;
switch (d) {
case '?': /* Wildcard: anything but nul */
if (c == '\0')
return false;
break;
case '\\':
d = *pat++;
/*FALLTHROUGH*/
default: /* Literal character */
if (c == d) {
if (d == '\0')
return true;
break;
}
return false; /* No point continuing */
}
}
}
void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
// iterate on json elements
for (auto kv : json) {
String key = kv.key;
String key_string = kv.key;
const char * key = key_string.c_str();
JsonVariant& value = kv.value;
// Check that format looks like "CCCC/AAAA"
char * delimiter = strchr(key, '/');
if (delimiter) {
uint16_t cluster = strtoul(key, &delimiter, 16);
uint16_t attribute = strtoul(delimiter+1, nullptr, 16);
// Iterate on filter
for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
// Iterate on filter
for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
uint16_t conv_cluster = pgm_read_word(&converter->cluster);
uint16_t conv_attribute = pgm_read_word(&converter->attribute);
if ((conv_cluster == cluster) &&
((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) {
int32_t drop = (*converter->func)(shortaddr, json, key, value, (const __FlashStringHelper*) converter->name);
if (drop) {
json.remove(key);
}
if (mini_glob_match(converter->filter, key.c_str())) {
int32_t drop = (*converter->func)(shortaddr, json, key.c_str(), value, converter->name, converter->param);
if (drop) {
json.remove(key);
}
}
}
}

View File

@ -0,0 +1,92 @@
/*
xdrv_23_zigbee_converters.ino - zigbee support for Sonoff-Tasmota
Copyright (C) 2019 Theo Arends and Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ZIGBEE
//typedef int32_t (*Z_AttrConverter)(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param);
typedef struct Z_CommandConverter {
const char * tasmota_cmd;
const char * zcl_cmd;
} Z_CommandConverter;
// list of post-processing directives
const Z_CommandConverter Z_Commands[] = {
{ "Power", "0006!xx" }, // 0=Off, 1=On, 2=Toggle
{ "Dimmer", "0008!04/xx0A00" }, // Move to Level with On/Off, xx=0..254 (255 is invalid)
{ "Dimmer+", "0008!06/001902" }, // Step up by 10%, 0.2 secs
{ "Dimmer-", "0008!06/011902" }, // Step down by 10%, 0.2 secs
{ "Hue", "0300!00/xx000A00" }, // Move to Hue, shortest time, 1s
{ "Sat", "0300!03/xx0A00" }, // Move to Sat
{ "HueSat", "0300!06/xxyy0A00" }, // Hue, Sat
{ "Color", "0300!07/xxxxyyyy0A00" }, // x, y (uint16)
{ "CT", "0300!0A/xxxx0A00" }, // Color Temperature Mireds (uint16)
{ "ShutterOpen", "0102!00"},
{ "ShutterClose", "0102!01"},
{ "ShutterStop", "0102!02"},
{ "ShutterLift", "0102!05xx"}, // Lift percentage, 0%=open, 100%=closed
{ "ShutterTilt", "0102!08xx"}, // Tilt percentage
};
inline bool isXYZ(char c) {
return (c >= 'x') && (c <= 'z');
}
// take the lower 4 bits and turn it to an hex char
inline char hexDigit(uint32_t h) {
uint32_t nybble = h & 0x0F;
return (nybble > 9) ? 'A' - 10 + nybble : '0' + nybble;
}
// replace all xx/yy/zz substrings with unsigned ints, and the corresponding len (8, 16 or 32 bits)
// zcl_cmd can be in PROGMEM
String SendZCLCommand_P(const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_t z) {
size_t len = strlen_P(zcl_cmd_P);
char zcl_cmd[len+1];
strcpy_P(zcl_cmd, zcl_cmd_P); // copy into RAM
char *p = zcl_cmd;
while (*p) {
if (isXYZ(*p) && (*p == *(p+1))) { // if char is [x-z] and followed by same char
uint8_t val;
switch (*p) {
case 'x':
val = x & 0xFF;
x = x >> 8;
break;
case 'y':
val = y & 0xFF;
y = y >> 8;
break;
case 'z':
val = z & 0xFF;
z = z >> 8;
break;
}
*p = hexDigit(val >> 4);
*(p+1) = hexDigit(val);
p++;
}
p++;
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SendZCLCommand_P: zcl_cmd = %s"), zcl_cmd);
return String(zcl_cmd);
}
#endif // USE_ZIGBEE

View File

@ -319,7 +319,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
ZI_ON_TIMEOUT_GOTO(ZIGBEE_LABEL_ABORT)
ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default)
ZI_WAIT(10000) // wait for 10 seconds for Tasmota to stabilize
ZI_WAIT(10500) // wait for 10 seconds for Tasmota to stabilize
ZI_ON_ERROR_GOTO(50)
//ZI_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting")

View File

@ -274,7 +274,7 @@ void Z_SendAFInfoRequest(uint16_t shortaddr, uint8_t endpoint, uint16_t clusteri
buf.add8(3 + 2*sizeof(uint16_t)); // Len = 0x07
buf.add8(0x00); // Frame Control Field
buf.add8(transacid); // Transaction Sequance Number
buf.add8(transacid); // Transaction Sequence Number
buf.add8(ZCL_READ_ATTRIBUTES); // 00 Command
buf.add16(0x0004); // 0400 ManufacturerName
buf.add16(0x0005); // 0500 ModelIdentifier
@ -385,7 +385,9 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
DynamicJsonBuffer jsonBuffer;
JsonObject& json_root = jsonBuffer.createObject();
JsonObject& json = json_root.createNestedObject(shortaddr);
JsonObject& json = json_root.createNestedObject(F(D_CMND_ZIGBEE_RECEIVED));
json[F(D_JSON_ZIGBEE_DEVICE)] = shortaddr;
// TODO add name field if it is known
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseRawAttributes(json);
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
@ -399,11 +401,13 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeZCLRawReceived: %s"), msg.c_str());
zcl_received.postProcessAttributes(srcaddr, json);
// Add linkquality
json[F("_" D_CMND_ZIGBEE_LINKQUALITY)] = linkquality; // prefix with underscore for metadata
msg = "";
json_root.printTo(msg);
Response_P(PSTR("%s"), msg.c_str());
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
return -1;
}

View File

@ -38,11 +38,11 @@ TasmotaSerial *ZigbeeSerial = nullptr;
const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN
"|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_ZCL_SEND
"|" D_CMND_ZIGBEE_PROBE;
"|" D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ;
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
&CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeZCLSend,
&CmndZigbeeProbe };
&CmndZigbeeProbe, &CmndZigbeeRead };
int32_t ZigbeeProcessInput(class SBuffer &buf) {
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
@ -338,7 +338,7 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
#endif
}
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, uint8_t transacId = 1) {
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp = true, uint8_t transacId = 1) {
SBuffer buf(25+len);
buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST); // 01
@ -351,10 +351,12 @@ void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8
buf.add8(0x1E); // 1E radius
buf.add8(3 + len);
buf.add8(0x10 | (clusterSpecific ? 0x01 : 0x00)); // Frame Control Field
buf.add8((disableDefResp ? 0x10 : 0x00) | (clusterSpecific ? 0x01 : 0x00)); // Frame Control Field
buf.add8(transacId); // Transaction Sequance Number
buf.add8(cmdId);
buf.addBuffer(msg, len); // add the payload
if (len > 0) {
buf.addBuffer(msg, len); // add the payload
}
ZigbeeZNPSend(buf.getBuffer(), buf.len());
}
@ -385,7 +387,7 @@ uint32_t parseHex(const char **data, size_t max_len = 8) {
void CmndZigbeeZCLSend(void) {
char parm_uc[12]; // used to convert JSON keys to uppercase
// ZigbeeZCLSend { "dst":"0x1234", "cluster":"0x0300", "endpoint":"0x01", "cmd":10, "data":"AABBCC" }
// ZigbeeZCLSend { "dst":"0x1234", "endpoint":"0x01", "cmd":"AABBCC" }
char dataBufUc[XdrvMailbox.data_len];
UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc);
@ -472,6 +474,60 @@ void CmndZigbeeProbe(void) {
ResponseCmndDone();
}
// Send an attribute read command to a device, specifying cluster and list of attributes
void CmndZigbeeRead(void) {
char parm_uc[12]; // used to convert JSON keys to uppercase
// ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5}
// ZigbeeRead {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Attr":"0x0005"}
// ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":[5,6,7,4]}
char dataBufUc[XdrvMailbox.data_len];
UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc);
if (strlen(dataBufUc) < 8) { ResponseCmndChar(D_JSON_INVALID_JSON); return; }
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject(dataBufUc);
if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; }
// params
uint16_t dstAddr = 0x0000; // default to local address
uint16_t cluster = 0x0000; // default to general clsuter
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
size_t attrs_len = 0;
uint8_t* attrs = nullptr; // empty string is valid
dstAddr = strToUInt(json[UpperCase_P(parm_uc, PSTR("device"))]);
endpoint = strToUInt(json[UpperCase_P(parm_uc, PSTR("endpoint"))]);
cluster = strToUInt(json[UpperCase_P(parm_uc, PSTR("cluster"))]);
UpperCase_P(parm_uc, PSTR("Attr"));
if (json.containsKey(parm_uc)) {
const JsonVariant& attr_data = json[parm_uc];
if (attr_data.is<JsonArray>()) {
JsonArray& attr_arr = attr_data;
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];
uint16_t val = strToUInt(attr_data);
attrs[0] = val & 0xFF; // little endian
attrs[1] = val >> 8;
}
}
ZigbeeZCLSend(dstAddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */);
if (attrs) { delete[] attrs; }
}
// Allow or Deny pairing of new Zigbee devices
void CmndZigbeePermitJoin(void)
{