diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index b2badf70f..479f295aa 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -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 diff --git a/sonoff/i18n.h b/sonoff/i18n.h index 96f4db54e..e97cd1fee 100644 --- a/sonoff/i18n.h +++ b/sonoff/i18n.h @@ -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 diff --git a/sonoff/xdrv_23_zigbee_3_devices.ino b/sonoff/xdrv_23_zigbee_3_devices.ino index 3b0989027..3596ef78e 100644 --- a/sonoff/xdrv_23_zigbee_3_devices.ino +++ b/sonoff/xdrv_23_zigbee_3_devices.ino @@ -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) { diff --git a/sonoff/xdrv_23_zigbee_5_converters.ino b/sonoff/xdrv_23_zigbee_5_converters.ino index 8aa11ad01..76df95aeb 100644 --- a/sonoff/xdrv_23_zigbee_5_converters.ino +++ b/sonoff/xdrv_23_zigbee_5_converters.ino @@ -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()); - 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()); - 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); } - } } } diff --git a/sonoff/xdrv_23_zigbee_6_commands.ino b/sonoff/xdrv_23_zigbee_6_commands.ino new file mode 100644 index 000000000..017d2cd98 --- /dev/null +++ b/sonoff/xdrv_23_zigbee_6_commands.ino @@ -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 . +*/ + +#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 diff --git a/sonoff/xdrv_23_zigbee_7_statemachine.ino b/sonoff/xdrv_23_zigbee_7_statemachine.ino index 7bd3a4129..844086bce 100644 --- a/sonoff/xdrv_23_zigbee_7_statemachine.ino +++ b/sonoff/xdrv_23_zigbee_7_statemachine.ino @@ -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") diff --git a/sonoff/xdrv_23_zigbee_8_parsers.ino b/sonoff/xdrv_23_zigbee_8_parsers.ino index 4050e45ae..495ce61ab 100644 --- a/sonoff/xdrv_23_zigbee_8_parsers.ino +++ b/sonoff/xdrv_23_zigbee_8_parsers.ino @@ -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; } diff --git a/sonoff/xdrv_23_zigbee_9_impl.ino b/sonoff/xdrv_23_zigbee_9_impl.ino index 32d1e8885..07fec54f4 100644 --- a/sonoff/xdrv_23_zigbee_9_impl.ino +++ b/sonoff/xdrv_23_zigbee_9_impl.ino @@ -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& 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) {