Merge pull request #6824 from s-hadinger/zigbee_1_0_RC1

Zigbee command support, considered as v1.0 for full Zigbee support
This commit is contained in:
Theo Arends 2019-11-03 13:22:25 +01:00 committed by GitHub
commit 58959349fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 324 additions and 141 deletions

View File

@ -4,6 +4,7 @@
* Add support for Honeywell I2C HIH series Humidity and Temperetaure sensor (#6808)
* Fix wrong Dimmer behavior introduced with #6799 when SetOption37 < 128
* Change add DS18x20 support in Tasmota-IR
* Add Zigbee command support, considered as v1.0 for full Zigbee support
*
* 7.0.0.1 20191027
* Remove references to versions before 6.0

View File

@ -456,6 +456,7 @@
#define D_JSON_TUYA_MCU_RECEIVED "TuyaReceived"
// Commands xdrv_23_zigbee.ino
#define D_ZIGBEE_NOT_STARTED "Zigbee not started (yet)"
#define D_CMND_ZIGBEE_PERMITJOIN "ZigbeePermitJoin"
#define D_CMND_ZIGBEE_STATUS "ZigbeeStatus"
#define D_CMND_ZIGBEE_RESET "ZigbeeReset"
@ -468,12 +469,12 @@
#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"
#define D_CMND_ZIGBEE_SEND "ZigbeeSend"
#define D_JSON_ZIGBEE_ZCL_SENT "ZigbeeZCLSent"
// Commands xdrv_25_A4988_Stepper.ino
#ifdef USE_A4988_STEPPER

View File

@ -68,7 +68,7 @@ public:
void updateLastSeen(uint16_t shortaddr);
// Dump json
String dump(uint8_t dump_mode) const;
String dump(uint32_t dump_mode, int32_t device_num = 0) const;
private:
std::vector<Z_Device> _devices = {};
@ -349,13 +349,21 @@ void Z_Devices::updateLastSeen(uint16_t shortaddr) {
// Dump the internal memory of Zigbee devices
// Mode = 1: simple dump of devices addresses and names
// Mode = 2: Mode 1 + also dump the endpoints, profiles and clusters
String Z_Devices::dump(uint8_t dump_mode) const {
String Z_Devices::dump(uint32_t dump_mode, int32_t device_num) const {
DynamicJsonBuffer jsonBuffer;
JsonArray& json = jsonBuffer.createArray();
JsonArray& devices = json;
//JsonArray& devices = json.createNestedArray(F("ZigbeeDevices"));
for (std::vector<Z_Device>::const_iterator it = _devices.begin(); it != _devices.end(); ++it) {
// if device_num == 0, then we show all devices.
// When no payload, the default is -99. In this case change it to 0.
if (device_num < 0) { device_num = 0; }
uint32_t device_current = 1;
for (std::vector<Z_Device>::const_iterator it = _devices.begin(); it != _devices.end(); ++it, ++device_current) {
// ignore non-current device, if specified device is non-zero
if ((device_num > 0) && (device_num != device_current)) { continue; }
const Z_Device& device = *it;
uint16_t shortaddr = device.shortaddr;
char hex[20];
@ -369,7 +377,7 @@ String Z_Devices::dump(uint8_t dump_mode) const {
dev[F(D_JSON_ZIGBEE_NAME)] = device.friendlyName;
}
if (1 == dump_mode) {
if (2 <= dump_mode) {
Uint64toHex(device.longaddr, hex, 64);
dev[F("IEEEAddr")] = hex;
if (device.modelId.length() > 0) {
@ -381,7 +389,7 @@ String Z_Devices::dump(uint8_t dump_mode) const {
}
// If dump_mode == 2, dump a lot more details
if (2 == dump_mode) {
if (3 <= dump_mode) {
JsonObject& dev_endpoints = dev.createNestedObject(F("Endpoints"));
for (std::vector<uint32_t>::const_iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) {
uint32_t ep_profile = *ite;

View File

@ -209,12 +209,19 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
}
}
break;
// Note: uint40, uint48, uint56, uint64 are not used in ZCL, so they are not implemented (yet)
case 0x24: // int40
case 0x25: // int48
case 0x26: // int56
case 0x27: // int64
i += attrtype - 0x1F; // 5 - 8;
// Note: uint40, uint48, uint56, uint64 are stored as Hex
case 0x24: // uint40
case 0x25: // uint48
case 0x26: // uint56
case 0x27: // uint64
{
uint8_t len = attrtype - 0x1F; // 5 - 8
// print as HEX
char hex[2*len+1];
ToHex_P(buf.buf(i), len, hex, sizeof(hex));
json[attrid_str] = hex;
i += len;
}
break;
case 0x28: // uint8
{
@ -243,12 +250,19 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
}
}
break;
// Note: int40, int48, int56, int64 are not used in ZCL, so they are not implemented (yet)
// Note: int40, int48, int56, int64 are not stored as Hex
case 0x2C: // int40
case 0x2D: // int48
case 0x2E: // int56
case 0x2F: // int64
i += attrtype - 0x27; // 5 - 8;
{
uint8_t len = attrtype - 0x27; // 5 - 8
// print as HEX
char hex[2*len+1];
ToHex_P(buf.buf(i), len, hex, sizeof(hex));
json[attrid_str] = hex;
i += len;
}
break;
case 0x41: // octet string, 1 byte len
@ -452,10 +466,6 @@ typedef struct Z_AttributeConverter {
// list of post-processing directives
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
{ 0x0000, 0x0000, "ZCLVersion", &Z_Copy },
{ 0x0000, 0x0001, "AppVersion", &Z_Copy },
{ 0x0000, 0x0002, "StackVersion", &Z_Copy },
@ -466,51 +476,36 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0000, 0x0007, "PowerSource", &Z_Copy },
{ 0x0000, 0x4000, "SWBuildID", &Z_Copy },
{ 0x0000, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
{ 0x0000, 0xFF01, nullptr, &Z_AqaraSensor }, // Occupancy (map8)
// 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 },
// Power Configuration cluster
{ 0x0001, 0x0000, "MainsVoltage", &Z_Copy },
{ 0x0001, 0x0001, "MainsFrequency", &Z_Copy },
{ 0x0001, 0x0020, "BatteryVoltage", &Z_Copy },
{ 0x0001, 0x0021, "BatteryPercentageRemaining",&Z_Copy },
// Device Temperature Configuration cluster
{ 0x0002, 0x0000, "CurrentTemperature", &Z_Copy },
{ 0x0002, 0x0001, "MinTempExperienced", &Z_Copy },
{ 0x0002, 0x0002, "MaxTempExperienced", &Z_Copy },
{ 0x0002, 0x0003, "OverTempTotalDwell", &Z_Copy },
// 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 },
// { 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
@ -602,11 +597,12 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 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 },
// 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 },
// Poll Control cluster
{ 0x0020, 0x0000, "CheckinInterval", &Z_Copy },
{ 0x0020, 0x0001, "LongPollInterval", &Z_Copy },
@ -651,24 +647,39 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0102, 0x0018, "IntermediateSetpointsLift",&Z_Copy },
{ 0x0102, 0x0019, "IntermediateSetpointsTilt",&Z_Copy },
// 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 },
// Color Control cluster
{ 0x0300, 0x0000, "CurrentHue", &Z_Copy },
{ 0x0300, 0x0001, "CurrentSaturation", &Z_Copy },
{ 0x0300, 0x0002, "RemainingTime", &Z_Copy },
{ 0x0300, 0x0003, "CurrentX", &Z_Copy },
{ 0x0300, 0x0004, "CurrentY", &Z_Copy },
{ 0x0300, 0x0005, "DriftCompensation", &Z_Copy },
{ 0x0300, 0x0006, "CompensationText", &Z_Copy },
{ 0x0300, 0x0007, "ColorTemperatureMireds",&Z_Copy },
{ 0x0300, 0x0008, "ColorMode", &Z_Copy },
{ 0x0300, 0x0010, "NumberOfPrimaries", &Z_Copy },
{ 0x0300, 0x0011, "Primary1X", &Z_Copy },
{ 0x0300, 0x0012, "Primary1Y", &Z_Copy },
{ 0x0300, 0x0013, "Primary1Intensity", &Z_Copy },
{ 0x0300, 0x0015, "Primary2X", &Z_Copy },
{ 0x0300, 0x0016, "Primary2Y", &Z_Copy },
{ 0x0300, 0x0017, "Primary2Intensity", &Z_Copy },
{ 0x0300, 0x0019, "Primary3X", &Z_Copy },
{ 0x0300, 0x001A, "Primary3Y", &Z_Copy },
{ 0x0300, 0x001B, "Primary3Intensity", &Z_Copy },
{ 0x0300, 0x0030, "WhitePointX", &Z_Copy },
{ 0x0300, 0x0031, "WhitePointY", &Z_Copy },
{ 0x0300, 0x0032, "ColorPointRX", &Z_Copy },
{ 0x0300, 0x0033, "ColorPointRY", &Z_Copy },
{ 0x0300, 0x0034, "ColorPointRIntensity", &Z_Copy },
{ 0x0300, 0x0036, "ColorPointGX", &Z_Copy },
{ 0x0300, 0x0037, "ColorPointGY", &Z_Copy },
{ 0x0300, 0x0038, "ColorPointGIntensity", &Z_Copy },
{ 0x0300, 0x003A, "ColorPointBX", &Z_Copy },
{ 0x0300, 0x003B, "ColorPointBY", &Z_Copy },
{ 0x0300, 0x003C, "ColorPointBIntensity", &Z_Copy },
// Illuminance Measurement cluster
{ 0x0400, 0x0000, D_JSON_ILLUMINANCE, &Z_Copy }, // Illuminance (in Lux)
{ 0x0400, 0x0001, "MinMeasuredValue", &Z_Copy }, //
{ 0x0400, 0x0002, "MaxMeasuredValue", &Z_Copy }, //
@ -676,16 +687,19 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0400, 0x0004, "LightSensorType", &Z_Copy }, //
{ 0x0400, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Illuminance Level Sensing cluster
{ 0x0401, 0x0000, "LevelStatus", &Z_Copy }, // Illuminance (in Lux)
{ 0x0401, 0x0001, "LightSensorType", &Z_Copy }, // LightSensorType
{ 0x0401, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Temperature Measurement cluster
{ 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
// Pressure Measurement cluster
{ 0x0403, 0x0000, D_JSON_PRESSURE_UNIT, &Z_AddPressureUnit }, // Pressure Unit
{ 0x0403, 0x0000, D_JSON_PRESSURE, &Z_Copy }, // Pressure
{ 0x0403, 0x0001, "MinMeasuredValue", &Z_Copy }, //
@ -698,24 +712,42 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0403, 0x0014, "Scale", &Z_Copy }, //
{ 0x0403, 0xFFFF, nullptr, &Z_Remove }, // Remove all other Pressure values
// Flow Measurement cluster
{ 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
// Relative Humidity Measurement cluster
{ 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
// Occupancy Sensing cluster
{ 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
{ 0x0000, 0xFF01, nullptr, &Z_AqaraSensor }, // Occupancy (map8)
// 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 },
// Diagnostics cluster
{ 0x0B05, 0x0000, "NumberOfResets", &Z_Copy },
{ 0x0B05, 0x0001, "PersistentMemoryWrites",&Z_Copy },
{ 0x0B05, 0x011C, "LastMessageLQI", &Z_Copy },
{ 0x0B05, 0x011D, "LastMessageRSSI", &Z_Copy },
};

View File

@ -31,11 +31,15 @@ const Z_CommandConverter Z_Commands[] = {
{ "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
{ "DimmerStop", "0008!03" }, // Stop any Dimmer animation
{ "ResetAlarm", "0009!00/xxyyyy" }, // Reset alarm (alarm code + cluster identifier)
{ "ResetAllAlarms","0009!01" }, // Reset all alarms
{ "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)
{ "Shutter", "0102!xx" },
{ "ShutterOpen", "0102!00"},
{ "ShutterClose", "0102!01"},
{ "ShutterStop", "0102!02"},
@ -43,6 +47,18 @@ const Z_CommandConverter Z_Commands[] = {
{ "ShutterTilt", "0102!08xx"}, // Tilt percentage
};
const __FlashStringHelper* zigbeeFindCommand(const char *command) {
char parm_uc[16]; // used to convert JSON keys to uppercase
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
const Z_CommandConverter *conv = &Z_Commands[i];
if (0 == strcasecmp_P(command, conv->tasmota_cmd)) {
return (const __FlashStringHelper*) conv->zcl_cmd;
}
}
return nullptr;
}
inline bool isXYZ(char c) {
return (c >= 'x') && (c <= 'z');
}
@ -54,8 +70,7 @@ inline char hexDigit(uint32_t h) {
}
// 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) {
String zigbeeCmdAddParams(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
@ -89,4 +104,26 @@ String SendZCLCommand_P(const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_t
return String(zcl_cmd);
}
const char kZ_Alias[] PROGMEM = "OFF|" D_OFF "|" D_FALSE "|" D_STOP "|" "OPEN" "|" // 0
"ON|" D_ON "|" D_TRUE "|" D_START "|" "CLOSE" "|" // 1
"TOGGLE|" D_TOGGLE "|" // 2
"ALL" ; // 255
const uint8_t kZ_Numbers[] PROGMEM = { 0,0,0,0,0,
1,1,1,1,1,
2,2,
255 };
uint32_t ZigbeeAliasOrNumber(const char *state_text) {
char command[16];
int state_number = GetCommandCode(command, sizeof(command), state_text, kZ_Alias);
if (state_number >= 0) {
// found an alias, get its value
return pgm_read_byte(kZ_Numbers + state_number);
} else {
// no alias found, convert it as number
return strtoul(state_text, nullptr, 0);
}
}
#endif // USE_ZIGBEE

View File

@ -325,7 +325,6 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
XdrvRulesProcess();
uint8_t cluster = zigbee_devices.findClusterEndpointIn(nwkAddr, 0x0000);
//Serial.printf(">>> Endpoint is 0x%02X for cluster 0x%04X\n", cluster, 0x0000);
if (cluster) {
Z_SendAFInfoRequest(nwkAddr, cluster, 0x0000, 0x01); // TODO, do we need tarnsacId counter?
}

View File

@ -37,11 +37,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_READ;
"|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND
"|" D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ ;
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
&CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeZCLSend,
&CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend,
&CmndZigbeeProbe, &CmndZigbeeRead };
int32_t ZigbeeProcessInput(class SBuffer &buf) {
@ -246,7 +246,7 @@ void ZigbeeInit(void)
* Commands
\*********************************************************************************************/
uint32_t strToUInt(const JsonVariant &val) {
uint32_t strToUInt(const JsonVariant val) {
// if the string starts with 0x, it is considered Hex, otherwise it is an int
if (val.is<unsigned int>()) {
return val.as<unsigned int>();
@ -258,7 +258,9 @@ uint32_t strToUInt(const JsonVariant &val) {
return 0; // couldn't parse anything
}
const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM = "2112000F0100"; // Z_SREQ | Z_SYS, SYS_OSAL_NV_DELETE, 0x0F00 id, 0x0001 len
const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM =
{ Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x01 /* STARTOPT_CLEAR_CONFIG */};
//"2605030101"; // Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 len, 0x01 STARTOPT_CLEAR_CONFIG
// Do a factory reset of the CC2530
void CmndZigbeeReset(void) {
if (ZigbeeSerial) {
@ -276,7 +278,7 @@ void CmndZigbeeReset(void) {
void CmndZigbeeStatus(void) {
if (ZigbeeSerial) {
String dump = zigbee_devices.dump(XdrvMailbox.payload);
String dump = zigbee_devices.dump(XdrvMailbox.index, XdrvMailbox.payload);
Response_P(PSTR("{\"%s%d\":%s}"), XdrvMailbox.command, XdrvMailbox.payload, dump.c_str());
}
}
@ -385,37 +387,15 @@ uint32_t parseHex(const char **data, size_t max_len = 8) {
return ret;
}
void CmndZigbeeZCLSend(void) {
char parm_uc[12]; // used to convert JSON keys to uppercase
// ZigbeeZCLSend { "dst":"0x1234", "endpoint":"0x01", "cmd":"AABBCC" }
char dataBufUc[XdrvMailbox.data_len];
UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc);
if (strlen(dataBufUc) < 8) { ResponseCmndChar(D_JSON_INVALID_JSON); return; }
void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject(dataBufUc);
if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; }
// params
uint16_t dstAddr = 0xFFFF; // 0xFFFF is braodcast, so considered invalid
uint16_t clusterId = 0x0000; // 0x0000 is a valid default value
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
uint16_t cluster = 0x0000; // 0x0000 is a valid default value
uint8_t cmd = ZCL_READ_ATTRIBUTES; // default command is READ_ATTRIBUTES
bool clusterSpecific = false;
const char* data = ""; // empty string is valid
UpperCase_P(parm_uc, PSTR("device"));
if (json.containsKey(parm_uc)) { dstAddr = strToUInt(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR("endpoint"));
if (json.containsKey(parm_uc)) { endpoint = strToUInt(json[parm_uc]); }
UpperCase_P(parm_uc, PSTR("cmd"));
if (json.containsKey(parm_uc)) { data = json[parm_uc].as<const char*>(); }
// Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC"
// where AA is the cluster number, BBBB the command number, CCCC... the payload
// First delimiter is '_' for a global command, or '!' for a cluster specific commanc
clusterId = parseHex(&data, 4);
cluster = parseHex(&data, 4);
// delimiter
if (('_' == *data) || ('!' == *data)) {
@ -442,24 +422,149 @@ void CmndZigbeeZCLSend(void) {
if (0 == endpoint) {
// endpoint is not specified, let's try to find it from shortAddr
endpoint = zigbee_devices.findClusterEndpointIn(dstAddr, clusterId);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CmndZigbeeZCLSend: guessing endpoint 0x%02X"), endpoint);
endpoint = zigbee_devices.findClusterEndpointIn(dstAddr, cluster);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeSend: guessing endpoint 0x%02X"), endpoint);
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CmndZigbeeZCLSend: dstAddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"),
dstAddr, clusterId, endpoint, cmd, data);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeSend: dstAddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"),
dstAddr, cluster, endpoint, cmd, data);
if (0 == endpoint) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("CmndZigbeeZCLSend: unspecified endpoint"));
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeSend: unspecified endpoint"));
return;
}
// everything is good, we can send the command
ZigbeeZCLSend(dstAddr, clusterId, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len());
ZigbeeZCLSend(dstAddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len());
ResponseCmndDone();
}
// Get an JSON attribute, with case insensitive key search
JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) {
// key can be in PROGMEM
if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
return *(JsonVariant*)nullptr;
}
for (auto kv : json) {
const char *key = kv.key;
JsonVariant &value = kv.value;
if (0 == strcasecmp_P(key, needle)) {
return value;
}
}
// if not found
return *(JsonVariant*)nullptr;
}
void CmndZigbeeSend(void) {
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"0xFF"} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":null} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":false} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":true} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"true"} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"ShutterClose":null} }
// ZigbeeSend { "devicse":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} }
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data);
if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; }
// params
static char delim[] = ", "; // delimiters for parameters
uint16_t device = 0xFFFF; // 0xFFFF is broadcast, so considered valid
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
String cmd_str = ""; // the actual low-level command, either specified or computed
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("device"));
if (nullptr != &val_device) { device = strToUInt(val_device); }
const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("endpoint"));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
const JsonVariant val_cmd = getCaseInsensitive(json, PSTR("Send"));
if (nullptr != &val_cmd) {
// probe the type of the argument
// If JSON object, it's high level commands
// If String, it's a low level command
if (val_cmd.is<JsonObject>()) {
// we have a high-level command
JsonObject &cmd_obj = val_cmd.as<JsonObject&>();
int32_t cmd_size = cmd_obj.size();
if (cmd_size > 1) {
Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size);
return;
} else if (1 == cmd_size) {
// We have exactly 1 command, parse it
JsonObject::iterator it = cmd_obj.begin(); // just get the first key/value
String key = it->key;
JsonVariant& value = it->value;
uint32_t x = 0, y = 0, z = 0;
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str());
if (tasmota_cmd) {
cmd_str = tasmota_cmd;
} else {
Response_P(PSTR("Unrecognized zigbee command: %s"), key.c_str());
return;
}
// parse the JSON value, depending on its type fill in x,y,z
if (value.is<bool>()) {
x = value.as<bool>() ? 1 : 0;
} else if (value.is<unsigned int>()) {
x = value.as<unsigned int>();
} else {
// if non-bool or non-int, trying char*
const char *s_const = value.as<const char*>();
if (s_const != nullptr) {
char s[strlen(s_const)+1];
strcpy(s, s_const);
if ((nullptr != s) && (0x00 != *s)) { // ignore any null or empty string, could represent 'null' json value
char *sval = strtok(s, delim);
if (sval) {
x = ZigbeeAliasOrNumber(sval);
sval = strtok(nullptr, delim);
if (sval) {
y = ZigbeeAliasOrNumber(sval);
sval = strtok(nullptr, delim);
if (sval) {
z = ZigbeeAliasOrNumber(sval);
}
}
}
}
}
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeSend: command_template = %s"), cmd_str.c_str());
cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z); // fill in parameters
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeSend: command_final = %s"), cmd_str.c_str());
} else {
// we have zero command, pass through until last error for missing command
}
} else if (val_cmd.is<char*>()) {
// low-level command
cmd_str = val_cmd.as<String>();
} else {
// we have an unsupported command type, just ignore it and fallback to missing command
}
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"),
device, endpoint, cmd_str.c_str());
zigbeeZCLSendStr(device, endpoint, cmd_str.c_str());
} else {
Response_P(PSTR("Missing zigbee 'Send'"));
return;
}
}
// Probe a specific device to get its endpoints and supported clusters
void CmndZigbeeProbe(void) {
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
char dataBufUc[XdrvMailbox.data_len];
UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc);
@ -476,34 +581,33 @@ void CmndZigbeeProbe(void) {
// 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; }
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
DynamicJsonBuffer jsonBuf;
JsonObject &json = jsonBuf.parseObject(dataBufUc);
JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data);
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
uint16_t device = 0xFFFF; // 0xFFFF is braodcast, so considered valid
uint16_t cluster = 0x0000; // default to general cluster
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
size_t attrs_len = 0;
uint8_t* attrs = nullptr; // empty string is valid
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;
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) { device = strToUInt(val_device); }
const JsonVariant val_cluster = getCaseInsensitive(json, PSTR("Cluster"));
if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); }
const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint"));
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
const JsonVariant &val_attr = getCaseInsensitive(json, PSTR("Read"));
if (nullptr != &val_attr) {
if (val_attr.is<JsonArray>()) {
JsonArray& attr_arr = val_attr;
attrs_len = attr_arr.size() * 2;
attrs = new uint8_t[attrs_len];
@ -517,13 +621,13 @@ void CmndZigbeeRead(void) {
} else {
attrs_len = 2;
attrs = new uint8_t[attrs_len];
uint16_t val = strToUInt(attr_data);
uint16_t val = strToUInt(val_attr);
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 */);
ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */);
if (attrs) { delete[] attrs; }
}
@ -531,6 +635,7 @@ void CmndZigbeeRead(void) {
// Allow or Deny pairing of new Zigbee devices
void CmndZigbeePermitJoin(void)
{
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
uint32_t payload = XdrvMailbox.payload;
if (payload < 0) { payload = 0; }
if ((99 != payload) && (payload > 1)) { payload = 1; }