diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index bd4024bf7..6e1544a41 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -22,7 +22,7 @@ #include #ifndef ZIGBEE_SAVE_DELAY_SECONDS -#define ZIGBEE_SAVE_DELAY_SECONDS 2; // wait for 2s before saving Zigbee info +#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info #endif const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds @@ -63,14 +63,25 @@ typedef struct Z_Device { uint16_t shortaddr; // unique key if not null, or unspecified if null uint8_t seqNumber; // Light information for Hue integration integration, last known values - int8_t bulbtype; // number of channel for the bulb: 0-5, or 0xFF if no Hue integration + uint8_t zb_profile; // profile of the device + // high 4 bits is device type: + // 0x0. = bulb + // 0x1. = switch + // 0x2. = motion sensor + // 0x3. = other alarms + // 0xE. = reserved for extension + // 0xF. = unknown + // For Bulb (0x0.) + // 0x0N = number of channel for the bulb: 0-5 + // 0x08 = the device is hidden from Alexa + // other status uint8_t power; // power state (boolean), MSB (0x80) stands for reachable - uint8_t colormode; // 0x00: Hue/Sat, 0x01: XY, 0x02: CT - uint8_t dimmer; // last Dimmer value: 0-254 - uint8_t sat; // last Sat: 0..254 - uint16_t ct; // last CT: 153-500 - uint16_t hue; // last Hue: 0..359 - uint16_t x, y; // last color [x,y] + uint8_t colormode; // 0x00: Hue/Sat, 0x01: XY, 0x02: CT | 0xFF not set, default 0x01 + uint8_t dimmer; // last Dimmer value: 0-254 | 0xFF not set, default 0x00 + uint8_t sat; // last Sat: 0..254 | 0xFF not set, default 0x00 + uint16_t ct; // last CT: 153-500 | 0xFFFF not set, default 200 + uint16_t hue; // last Hue: 0..359 | 0xFFFF not set, default 0 + uint16_t x, y; // last color [x,y] | 0xFFFF not set, default 0 uint8_t linkquality; // lqi from last message, 0xFF means unknown uint8_t batterypercent; // battery percentage (0..100), 0xFF means unknwon } Z_Device; @@ -163,9 +174,15 @@ public: String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const; int32_t deviceRestore(const JsonObject &json); + // General Zigbee device profile support + void setZbProfile(uint16_t shortaddr, uint8_t zb_profile); + uint8_t getZbProfile(uint16_t shortaddr) const ; + // Hue support void setHueBulbtype(uint16_t shortaddr, int8_t bulbtype); int8_t getHueBulbtype(uint16_t shortaddr) const ; + void hideHueBulb(uint16_t shortaddr, bool hidden); + bool isHueBulbHidden(uint16_t shortaddr) const ; void updateHueState(uint16_t shortaddr, const bool *power, const uint8_t *colormode, const uint8_t *dimmer, const uint8_t *sat, @@ -236,6 +253,8 @@ private: void freeDeviceEntry(Z_Device *device); void setStringAttribute(char*& attr, const char * str); + + void updateZbProfile(uint16_t shortaddr); }; /*********************************************************************************************\ @@ -294,14 +313,14 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { shortaddr, 0, // seqNumber // Hue support - -1, // no Hue support + 0xFF, // no Hue support 0x80, // power off + reachable - 0, // colormode - 0, // dimmer - 0, // sat - 200, // ct - 0, // hue - 0, 0, // x, y + 0xFF, // colormode + 0xFF, // dimmer + 0xFF, // sat + 0xFFFF, // ct + 0xFFFF, // hue + 0xFFFF, 0xFFFF, // x, y 0xFF, // lqi, 0xFF = unknown 0xFF // battery percentage x 2, 0xFF means unknown }; @@ -691,24 +710,99 @@ uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) { } } - -// Hue support -void Z_Devices::setHueBulbtype(uint16_t shortaddr, int8_t bulbtype) { +// General Zigbee device profile support +void Z_Devices::setZbProfile(uint16_t shortaddr, uint8_t zb_profile) { Z_Device &device = getShortAddr(shortaddr); - if (bulbtype != device.bulbtype) { - device.bulbtype = bulbtype; + if (zb_profile != device.zb_profile) { + device.zb_profile = zb_profile; + updateZbProfile(shortaddr); dirty(); } } -int8_t Z_Devices::getHueBulbtype(uint16_t shortaddr) const { + +// Do all the required action when a profile is changed +void Z_Devices::updateZbProfile(uint16_t shortaddr) { + Z_Device &device = getShortAddr(shortaddr); + uint8_t zb_profile = device.zb_profile; + if (0xFF == zb_profile) { return; } + + switch (zb_profile & 0xF0) { + case 0x00: // bulb profile + { + uint32_t channels = zb_profile & 0x07; + // depending on the bulb type, the default parameters from unknown to credible defaults + if (0xFF == device.power) { device.power = 0; } + if (1 <= channels) { + if (0xFF == device.dimmer) { device.dimmer = 0; } + } + if (3 <= channels) { + if (0xFF == device.sat) { device.sat = 0; } + if (0xFFFF == device.hue) { device.hue = 0; } + if (0xFFFF == device.x) { device.x = 0; } + if (0xFFFF == device.y) { device.y = 0; } + if (0xFF == device.colormode) { device.colormode = 0; } // HueSat mode + } + if ((2 == channels) || (5 == channels)) { + if (0xFFFF == device.ct) { device.ct = 200; } + if (0xFF == device.colormode) { device.colormode = 2; } // CT mode + } + } + break; + } +} + +// Returns the device profile or 0xFF if the device or profile is unknown +uint8_t Z_Devices::getZbProfile(uint16_t shortaddr) const { int32_t found = findShortAddr(shortaddr); if (found >= 0) { - return _devices[found]->bulbtype; + return _devices[found]->zb_profile; } else { - return -1; // Hue not activated + return 0xFF; // Hue not activated } } +// Hue support +void Z_Devices::setHueBulbtype(uint16_t shortaddr, int8_t bulbtype) { + uint8_t zb_profile = (0 > bulbtype) ? 0xFF : (bulbtype & 0x07); + setZbProfile(shortaddr, zb_profile); +} + +int8_t Z_Devices::getHueBulbtype(uint16_t shortaddr) const { + uint8_t zb_profile = getZbProfile(shortaddr); + if (0x00 == (zb_profile & 0xF0)) { + return (zb_profile & 0x07); + } else { + // not a bulb + return -1; + } +} + +void Z_Devices::hideHueBulb(uint16_t shortaddr, bool hidden) { + uint8_t hue_hidden_flag = hidden ? 0x08 : 0x00; + + Z_Device &device = getShortAddr(shortaddr); + if (0x00 == (device.zb_profile & 0xF0)) { + // bulb type + // set bit 3 accordingly + if (hue_hidden_flag != (device.zb_profile & 0x08)) { + device.zb_profile = (device.zb_profile & 0xF7) | hue_hidden_flag; + dirty(); + } + } +} +// true if device is not knwon or not a bulb - it wouldn't make sense to publish a non-bulb +bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + uint8_t zb_profile = _devices[found]->zb_profile; + if (0x00 == (zb_profile & 0xF0)) { + // bulb type + return (zb_profile & 0x08) ? true : false; + } + } + return true; // Fallback - Device is considered as hidden +} + // Hue support void Z_Devices::updateHueState(uint16_t shortaddr, const bool *power, const uint8_t *colormode, @@ -1056,27 +1150,17 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const { } // expose the last known status of the bulb, for Hue integration - dev[F(D_JSON_ZIGBEE_LIGHT)] = device.bulbtype; // sign extend, 0xFF changed as -1 - if (0 <= device.bulbtype) { - // bulbtype is defined - dev[F("Power")] = bitRead(device.power, 0); - dev[F("Reachable")] = bitRead(device.power, 7); - if (1 <= device.bulbtype) { - dev[F("Dimmer")] = device.dimmer; - } - if (2 <= device.bulbtype) { - dev[F("Colormode")] = device.colormode; - } - if ((2 == device.bulbtype) || (5 == device.bulbtype)) { - dev[F("CT")] = device.ct; - } - if (3 <= device.bulbtype) { - dev[F("Sat")] = device.sat; - dev[F("Hue")] = device.hue; - dev[F("X")] = device.x; - dev[F("Y")] = device.y; - } - } + dev[F(D_JSON_ZIGBEE_LIGHT)] = getHueBulbtype(shortaddr); // sign extend, 0xFF changed as -1 + // dump all known values + dev[F("Reachable")] = bitRead(device.power, 7); // TODO TODO + if (0xFF != device.power) { dev[F("Power")] = bitRead(device.power, 0); } + if (0xFF != device.dimmer) { dev[F("Dimmer")] = device.dimmer; } + if (0xFF != device.colormode) { dev[F("Colormode")] = device.colormode; } + if (0xFFFF != device.ct) { dev[F("CT")] = device.ct; } + if (0xFF != device.sat) { dev[F("Sat")] = device.sat; } + if (0xFFFF != device.hue) { dev[F("Hue")] = device.hue; } + if (0xFFFF != device.x) { dev[F("X")] = device.x; } + if (0xFFFF != device.y) { dev[F("Y")] = device.y; } } String payload = ""; @@ -1119,8 +1203,9 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const { if (device.modelId) { dev[F(D_JSON_MODEL D_JSON_ID)] = device.modelId; } - if (device.bulbtype >= 0) { - dev[F(D_JSON_ZIGBEE_LIGHT)] = device.bulbtype; // sign extend, 0xFF changed as -1 + int8_t bulbtype = getHueBulbtype(shortaddr); + if (bulbtype >= 0) { + dev[F(D_JSON_ZIGBEE_LIGHT)] = bulbtype; // sign extend, 0xFF changed as -1 } if (device.manufacturerId) { dev[F("Manufacturer")] = device.manufacturerId; diff --git a/tasmota/xdrv_23_zigbee_3_hue.ino b/tasmota/xdrv_23_zigbee_3_hue.ino index e4e8a8b63..c1349d4fd 100644 --- a/tasmota/xdrv_23_zigbee_3_hue.ino +++ b/tasmota/xdrv_23_zigbee_3_hue.ino @@ -103,10 +103,10 @@ void ZigbeeHueStatus(String * response, uint16_t shortaddr) { void ZigbeeCheckHue(String * response, bool &appending) { uint32_t zigbee_num = zigbee_devices.devicesSize(); for (uint32_t i = 0; i < zigbee_num; i++) { - int8_t bulbtype = zigbee_devices.devicesAt(i).bulbtype; + uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr; + int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr); if (bulbtype >= 0) { - uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr; // this bulb is advertized if (appending) { *response += ","; } *response += "\""; @@ -122,7 +122,8 @@ void ZigbeeCheckHue(String * response, bool &appending) { void ZigbeeHueGroups(String * lights) { uint32_t zigbee_num = zigbee_devices.devicesSize(); for (uint32_t i = 0; i < zigbee_num; i++) { - int8_t bulbtype = zigbee_devices.devicesAt(i).bulbtype; + uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr; + int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr); if (bulbtype >= 0) { *lights += ",\""; diff --git a/tasmota/xdrv_23_zigbee_4_persistence.ino b/tasmota/xdrv_23_zigbee_4_persistence.ino index 88126d2fc..1a1fdc38c 100644 --- a/tasmota/xdrv_23_zigbee_4_persistence.ino +++ b/tasmota/xdrv_23_zigbee_4_persistence.ino @@ -46,7 +46,7 @@ // str - FriendlyName (null terminated C string, 32 chars max) // reserved for extensions // -- V2 -- -// int8_t - bulbtype +// int8_t - zigbee profile of the device // Memory footprint #ifdef ESP8266 @@ -126,8 +126,8 @@ class SBuffer hibernateDevice(const struct Z_Device &device) { } buf.add8(0x00); // end of string marker - // Hue Bulbtype - buf.add8(device.bulbtype); + // Zigbee Profile + buf.add8(device.zb_profile); // update overall length buf.set8(0, buf.len()); @@ -225,7 +225,7 @@ void hydrateDevices(const SBuffer &buf) { // Hue bulbtype - if present if (d < dev_record_len) { - zigbee_devices.setHueBulbtype(shortaddr, buf_d.get8(d)); + zigbee_devices.setZbProfile(shortaddr, buf_d.get8(d)); d++; } diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 62fa7a832..421c3ae0a 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -99,7 +99,7 @@ typedef struct Z_AttributeConverter { uint8_t cluster_short; uint16_t attribute; const char * name; - int16_t multiplier; // multiplier for numerical value, (if > 0 multiply by x, if <0 device by x) + int8_t multiplier; // multiplier for numerical value, (if > 0 multiply by x, if <0 device by x) uint8_t cb; // callback func from Z_ConvOperators // Z_AttrConverter func; } Z_AttributeConverter; @@ -142,10 +142,12 @@ enum Z_ConvOperators { }; ZF(ZCLVersion) ZF(AppVersion) ZF(StackVersion) ZF(HWVersion) ZF(Manufacturer) ZF(ModelId) -ZF(DateCode) ZF(PowerSource) ZF(SWBuildID) ZF(Power) ZF(SwitchType) ZF(Dimmer) +ZF(GenericDeviceClass) ZF(GenericDeviceType) ZF(ProductCode) ZF(ProductURL) +ZF(DateCode) ZF(PowerSource) ZF(SWBuildID) ZF(Power) ZF(SwitchType) ZF(Dimmer) ZF(DimmerOptions) +ZF(DimmerRemainingTime) ZF(OnOffTransitionTime) ZF(StartUpOnOff) ZF(MainsVoltage) ZF(MainsFrequency) ZF(BatteryVoltage) ZF(BatteryPercentage) ZF(CurrentTemperature) ZF(MinTempExperienced) ZF(MaxTempExperienced) ZF(OverTempTotalDwell) -ZF(IdentifyTime) +ZF(IdentifyTime) ZF(GroupNameSupport) ZF(SceneCount) ZF(CurrentScene) ZF(CurrentGroup) ZF(SceneValid) ZF(AlarmCount) ZF(Time) ZF(TimeStatus) ZF(TimeZone) ZF(DstStart) ZF(DstEnd) ZF(DstShift) ZF(StandardTime) ZF(LocalTime) ZF(LastSetTime) ZF(ValidUntilTime) ZF(TimeEpoch) @@ -243,6 +245,10 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { { Zstring, Cx0000, 0x0005, Z(ModelId), 1, Z_ModelKeep }, // record Model { Zstring, Cx0000, 0x0006, Z(DateCode), 1, Z_Nop }, { Zenum8, Cx0000, 0x0007, Z(PowerSource), 1, Z_Nop }, + { Zenum8, Cx0000, 0x0008, Z(GenericDeviceClass), 1, Z_Nop }, + { Zenum8, Cx0000, 0x0009, Z(GenericDeviceType), 1, Z_Nop }, + { Zoctstr, Cx0000, 0x000A, Z(ProductCode), 1, Z_Nop }, + { Zstring, Cx0000, 0x000B, Z(ProductURL), 1, Z_Nop }, { Zstring, Cx0000, 0x4000, Z(SWBuildID), 1, Z_Nop }, // { Zunk, Cx0000, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary @@ -263,6 +269,9 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { // Identify cluster { Zuint16, Cx0003, 0x0000, Z(IdentifyTime), 1, Z_Nop }, + // Groups cluster + { Zmap8, Cx0004, 0x0000, Z(GroupNameSupport), 1, Z_Nop }, + // Scenes cluster { Zuint8, Cx0005, 0x0000, Z(SceneCount), 1, Z_Nop }, { Zuint8, Cx0005, 0x0001, Z(CurrentScene), 1, Z_Nop }, @@ -272,6 +281,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { // On/off cluster { Zbool, Cx0006, 0x0000, Z(Power), 1, Z_Nop }, + { Zenum8, Cx0006, 0x4003, Z(StartUpOnOff), 1, Z_Nop }, { Zbool, Cx0006, 0x8000, Z(Power), 1, Z_Nop }, // See 7280 // On/Off Switch Configuration cluster @@ -279,12 +289,13 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { // Level Control cluster { Zuint8, Cx0008, 0x0000, Z(Dimmer), 1, Z_Nop }, - // { Zuint16, Cx0008, 0x0001, Z(RemainingTime", 1, Z_Nop }, - // { Zuint16, Cx0008, 0x0010, Z(OnOffTransitionTime", 1, Z_Nop }, - // { Zuint8, Cx0008, 0x0011, Z(OnLevel", 1, Z_Nop }, - // { Zuint16, Cx0008, 0x0012, Z(OnTransitionTime", 1, Z_Nop }, - // { Zuint16, Cx0008, 0x0013, Z(OffTransitionTime", 1, Z_Nop }, - // { Zuint16, Cx0008, 0x0014, Z(DefaultMoveRate", 1, Z_Nop }, + { Zmap8, Cx0008, 0x000F, Z(DimmerOptions), 1, Z_Nop }, + { Zuint16, Cx0008, 0x0001, Z(DimmerRemainingTime), 1, Z_Nop }, + { Zuint16, Cx0008, 0x0010, Z(OnOffTransitionTime), 1, Z_Nop }, + // { Zuint8, Cx0008, 0x0011, Z(OnLevel), 1, Z_Nop }, + // { Zuint16, Cx0008, 0x0012, Z(OnTransitionTime), 1, Z_Nop }, + // { Zuint16, Cx0008, 0x0013, Z(OffTransitionTime), 1, Z_Nop }, + // { Zuint16, Cx0008, 0x0014, Z(DefaultMoveRate), 1, Z_Nop }, // Alarms cluster { Zuint16, Cx0009, 0x0000, Z(AlarmCount), 1, Z_Nop }, @@ -615,7 +626,7 @@ typedef union ZCLHeaderFrameControl_t { // If not found: // - returns nullptr const __FlashStringHelper* zigbeeFindAttributeByName(const char *command, - uint16_t *cluster, uint16_t *attribute, int16_t *multiplier, + uint16_t *cluster, uint16_t *attribute, int8_t *multiplier, uint8_t *cb) { for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) { const Z_AttributeConverter *converter = &Z_PostProcess[i]; @@ -623,7 +634,7 @@ const __FlashStringHelper* zigbeeFindAttributeByName(const char *command, if (0 == strcasecmp_P(command, converter->name)) { if (cluster) { *cluster = CxToCluster(pgm_read_byte(&converter->cluster_short)); } if (attribute) { *attribute = pgm_read_word(&converter->attribute); } - if (multiplier) { *multiplier = pgm_read_word(&converter->multiplier); } + if (multiplier) { *multiplier = pgm_read_byte(&converter->multiplier); } if (cb) { *cb = pgm_read_byte(&converter->cb); } return (const __FlashStringHelper*) converter->name; } @@ -1363,7 +1374,6 @@ int32_t Z_AqaraCubeFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObjec // presentValue = x + 128 = 180ยบ flip to side x on top // presentValue = x + 256 = push/slide cube while side x is on top // presentValue = x + 512 = double tap while side x is on top - return 0; } @@ -1487,7 +1497,7 @@ int32_t Z_AqaraSensorFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObj // apply the transformation from the converter int32_t Z_ApplyConverter(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, - uint16_t cluster, uint16_t attr, int16_t multiplier, uint8_t cb) { + uint16_t cluster, uint16_t attr, int8_t multiplier, uint8_t cb) { // apply multiplier if needed if (1 == multiplier) { // copy unchanged json[new_name] = value; @@ -1596,7 +1606,7 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { const Z_AttributeConverter *converter = &Z_PostProcess[i]; uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short)); uint16_t conv_attribute = pgm_read_word(&converter->attribute); - int16_t conv_multiplier = pgm_read_word(&converter->multiplier); + int8_t conv_multiplier = pgm_read_byte(&converter->multiplier); uint8_t conv_cb = pgm_read_byte(&converter->cb); // callback id if ((conv_cluster == cluster) && diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 62451566c..adcf985bd 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -190,7 +190,7 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, // multiplier == 1: ignore // multiplier > 0: divide by the multiplier // multiplier < 0: multiply by the -multiplier (positive) -void ZbApplyMultiplier(double &val_d, int16_t multiplier) { +void ZbApplyMultiplier(double &val_d, int8_t multiplier) { if ((0 != multiplier) && (1 != multiplier)) { if (multiplier > 0) { // inverse of decoding val_d = val_d / multiplier; @@ -217,7 +217,7 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t uint16_t attr_id = 0xFFFF; uint16_t cluster_id = 0xFFFF; uint8_t type_id = Znodata; - int16_t multiplier = 1; // multiplier to adjust the key value + int8_t multiplier = 1; // multiplier to adjust the key value double val_d = 0; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss const char* val_str = ""; // variant as string @@ -245,7 +245,7 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t uint16_t local_attr_id = pgm_read_word(&converter->attribute); uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short)); uint8_t local_type_id = pgm_read_byte(&converter->type); - int16_t local_multiplier = pgm_read_word(&converter->multiplier); + int8_t local_multiplier = pgm_read_byte(&converter->multiplier); // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id); if (delimiter) {