From de91133414cf5f600d8ea1023c16b8fc0ccf8ba6 Mon Sep 17 00:00:00 2001 From: s-hadinger <49731213+s-hadinger@users.noreply.github.com> Date: Sun, 21 Jan 2024 11:42:04 +0100 Subject: [PATCH] Zigbee ``ZbEmulation`` to selectively exclude some devices from Hue/Alexa emulation (#20552) --- CHANGELOG.md | 1 + tasmota/include/i18n.h | 1 + .../xdrv_23_zigbee_2_devices.ino | 43 ++++++++++++++++- .../xdrv_23_zigbee_2a_devices_impl.ino | 16 ++++++- .../xdrv_23_zigbee_4b_data.ino | 7 ++- .../xdrv_23_zigbee_6_5_hue.ino | 2 +- .../xdrv_23_zigbee_A_impl.ino | 47 +++++++++++++++++-- 7 files changed, 108 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c8a74048..d755c2682 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file. - Berry solidification of strings longer than 255 bytes (#20529) - Berry syntax coloring for Notepad++ by FransO (#20541) - Berry/Zigbee web hook per device for customized status display (#20542) +- Zigbee ``ZbEmulation`` to selectively exclude some devices from Hue/Alexa emulation ### Breaking Changed diff --git a/tasmota/include/i18n.h b/tasmota/include/i18n.h index b7c8a21a0..1437473fa 100644 --- a/tasmota/include/i18n.h +++ b/tasmota/include/i18n.h @@ -700,6 +700,7 @@ #define D_CMND_ZIGBEE_SCAN "Scan" #define D_JSON_ZIGBEE_SCAN "ZbScan" #define D_CMND_ZIGBEE_CIE "CIE" +#define D_CMND_ZIGBEE_EMULATION "Emulation" #define D_CMND_ZIGBEE_ENROLL "Enroll" #define D_CMND_ZIGBEE_LOAD "Load" #define D_CMND_ZIGBEE_LOADDUMP "LoadDump" diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino index 548b4fe9b..5d9b315dd 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino @@ -109,6 +109,19 @@ Output: 00:00:00.231 >>>: v16=65535 out=63000 <> hex=0xFF */ +/*********************************************************************************************\ + * + * Flag to no advertize for Hue (Alexa) or Matter + * +\*********************************************************************************************/ + +enum class Z_no_advertize : uint8_t { + Z_no_ad_default = 0X00, // advertize on all + Z_no_ad_hue = 0x01, // no-advertize on Hue + Z_no_ad_matter = 0x02, // no-advertize on Matter + Z_no_ad_all = 0xFF, // no-advertize on all +}; + enum class Z_Data_Type : uint8_t { Z_Unknown = 0x00, Z_Light = 1, // Lights 1-5 channels @@ -961,7 +974,9 @@ public: uint32_t batt_last_probed; // Time when the device was last probed for batteyr values uint8_t lqi; // lqi from last message, 0xFF means unknown uint8_t batt_percent; // battery percentage (0..100), 0xFF means unknwon - uint16_t reserved_for_alignment; + Z_no_advertize no_advertize; // no advertize for Hue or Matter + uint8_t reserved_for_alignment; + // Debounce informmation when receiving commands // If we receive the same ZCL transaction number from the same device and the same endpoint within 300ms // then discard the second packet @@ -989,7 +1004,8 @@ public: batt_last_probed(0), lqi(0xFF), batt_percent(0xFF), - reserved_for_alignment(0xFFFF), + no_advertize(Z_no_advertize::Z_no_ad_default), + reserved_for_alignment(0xFF), debounce_endpoint(0), debounce_transact(0) { }; @@ -1008,6 +1024,28 @@ public: inline bool validLastSeen(void) const { return 0x0 != last_seen; } inline bool validBattLastSeen(void) const { return (0x0 != batt_last_seen) && (batt_last_seen < 0xFFFFFFF0); } + inline bool isAdvertizeNone(void) const { return (no_advertize == Z_no_advertize::Z_no_ad_all); } // never advertize in bridge mode + inline bool isAdvertizeHue(void) const { return !((uint8_t)no_advertize & (uint8_t)Z_no_advertize::Z_no_ad_hue) ; } // advertize to Alexa/Hue (if a bulb) + inline bool isAdvertizeMatter(void) const { return !((uint8_t)no_advertize & (uint8_t)Z_no_advertize::Z_no_ad_matter) ; } // advertize to Matter (type has to be defined in Matter) + inline bool isAdvertizeAll(void) const { return (no_advertize == Z_no_advertize::Z_no_ad_default) ; } // advertize to all possible (default) + + inline void setAdvertizeNone(void) { no_advertize = Z_no_advertize::Z_no_ad_all; } + inline void setAdvertizeAll(void) { no_advertize = Z_no_advertize::Z_no_ad_default; } + inline void setAdvertizeHue(bool adv) { + if (adv) { + no_advertize = (Z_no_advertize)((uint8_t)no_advertize & ~(uint8_t)Z_no_advertize::Z_no_ad_hue); + } else { + no_advertize = (Z_no_advertize)((uint8_t)no_advertize | (uint8_t)Z_no_advertize::Z_no_ad_hue); + } + } + inline void setAdvertizeMatter(bool adv) { + if (adv) { + no_advertize = (Z_no_advertize)((uint8_t)no_advertize & ~(uint8_t)Z_no_advertize::Z_no_ad_matter); + } else { + no_advertize = (Z_no_advertize)((uint8_t)no_advertize | (uint8_t)Z_no_advertize::Z_no_ad_matter); + } + } + inline void setReachable(bool _reachable) { reachable = _reachable; } inline bool getReachable(void) const { return reachable; } inline bool getPower(uint8_t ep = 0) const; @@ -1053,6 +1091,7 @@ public: void jsonAddModelManuf(Z_attribute_list & attr_list) const; void jsonAddEndpoints(Z_attribute_list & attr_list) const; void jsonAddConfig(Z_attribute_list & attr_list) const; + void jsonAddEmulation(Z_attribute_list & attr_list) const; void jsonAddDataAttributes(Z_attribute_list & attr_list) const; void jsonAddDeviceAttributes(Z_attribute_list & attr_list) const; void jsonDumpSingleDevice(Z_attribute_list & attr_list, uint32_t dump_mode, bool add_name) const; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino index a8cfdbbc6..549af03fc 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino @@ -814,6 +814,11 @@ void Z_Device::jsonAddConfig(Z_attribute_list & attr_list) const { } attr_list.addAttributePMEM(PSTR("Config")).setStrRaw(arr_data.toString().c_str()); } +// Add "HueEmulation":false, "Matter":false +void Z_Device::jsonAddEmulation(Z_attribute_list & attr_list) const { + if (!isAdvertizeHue()) { attr_list.addAttributePMEM(PSTR("HueEmulation")).setBool(false); } + if (!isAdvertizeMatter()) { attr_list.addAttributePMEM(PSTR("Matter")).setBool(false); } +} // Add All data attributes void Z_Device::jsonAddDataAttributes(Z_attribute_list & attr_list) const { // show internal data - mostly last known values @@ -824,7 +829,7 @@ void Z_Device::jsonAddDataAttributes(Z_attribute_list & attr_list) const { } } } -// Add "BatteryPercentage", "LastSeen", "LastSeenEpoch", "LinkQuality" +// Add "BatteryPercentage", "LastSeen", "LastSeenEpoch", "LinkQuality", "HueEmulation" (opt) and "Matter" (opt) void Z_Device::jsonAddDeviceAttributes(Z_attribute_list & attr_list) const { attr_list.addAttributePMEM(PSTR("Reachable")).setBool(getReachable()); if (validBatteryPercent()) { attr_list.addAttributePMEM(PSTR("BatteryPercentage")).setUInt(batt_percent); } @@ -879,6 +884,7 @@ void Z_Device::jsonDumpSingleDevice(Z_attribute_list & attr_list, uint32_t dump_ jsonAddModelManuf(attr_list); jsonAddEndpoints(attr_list); jsonAddConfig(attr_list); + jsonAddEmulation(attr_list); } if (dump_mode >= 3) { jsonAddDataAttributes(attr_list); @@ -928,7 +934,7 @@ String Z_Devices::dumpDevice(uint32_t dump_mode, const Z_Device & device) const // 0 : Ok // <0 : Error // -// Ex: {"Device":"0x5ADF","Name":"IKEA_Light","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"]} +// Ex: {"Device":"0x5ADF","Name":"IKEA_Light","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":["0x01","0xF2"],"HueEmulation":false} int32_t Z_Devices::deviceRestore(JsonParserObject json) { // params @@ -1007,6 +1013,12 @@ int32_t Z_Devices::deviceRestore(JsonParserObject json) { } } + // read "HueEmulation" and "Matter" + bool hue_emulation = json.getBool(PSTR("HueEmulation"), false); + bool matter = json.getBool(PSTR("Matter"), false); + device.setAdvertizeHue(hue_emulation); + device.setAdvertizeMatter(matter); + return 0; } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4b_data.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4b_data.ino index 62f2d7557..d50c3e623 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4b_data.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4b_data.ino @@ -50,6 +50,9 @@ int32_t hydrateDeviceWideData(class Z_Device & device, const SBuffer & buf, size if (segment_len >= 10) { device.batt_last_seen = buf.get32(start+7); } + if (segment_len >= 11) { + device.no_advertize = (Z_no_advertize)buf.get8(start+11); + } return segment_len + 1; } @@ -121,12 +124,14 @@ SBuffer hibernateDeviceData(const struct Z_Device & device) { buf.add16(device.shortaddr); // device wide data - buf.add8(10); // 10 bytes + buf.add8(11); // 10 bytes buf.add32(device.last_seen); buf.add8(device.lqi); buf.add8(device.batt_percent); // now storing batt_last_seen buf.add32(device.batt_last_seen); + // now storing no_advertize + buf.add8((uint8_t)device.no_advertize); for (const auto & data_elt : device.data) { size_t item_len = data_elt.DataTypeToLength(data_elt.getType()); diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_5_hue.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_5_hue.ino index 58baf9dce..1d60c1111 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_5_hue.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_5_hue.ino @@ -144,7 +144,7 @@ void ZigbeeCheckHue(String & response, bool * appending) { uint8_t ep = device.endpoints[i]; if (i > 0 && ep == 0) { break; } int8_t bulbtype = device.getHueBulbtype(ep); - if (bulbtype >= 0) { + if (bulbtype >= 0 && device.isAdvertizeHue()) { // this bulb is advertized if (*appending) { response += ","; } response += "\""; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino index 4f96e003c..c0dfc2306 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino @@ -42,6 +42,7 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_OCCUPANCY "|" D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" D_CMND_ZIGBEE_MAP "|" D_CMND_ZIGBEE_LEAVE "|" D_CMND_ZIGBEE_CONFIG "|" D_CMND_ZIGBEE_DATA "|" D_CMND_ZIGBEE_SCAN "|" D_CMND_ZIGBEE_ENROLL "|" D_CMND_ZIGBEE_CIE "|" + D_CMND_ZIGBEE_EMULATION "|" D_CMND_ZIGBEE_LOAD "|" D_CMND_ZIGBEE_UNLOAD "|" D_CMND_ZIGBEE_LOADDUMP #ifdef ZIGBEE_DOC "|" D_CMND_ZIGBEE_ATTRDUMP @@ -67,7 +68,8 @@ void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZbLight, &CmndZbOccupancy, &CmndZbRestore, &CmndZbBindState, &CmndZbMap, &CmndZbLeave, &CmndZbConfig, &CmndZbData, &CmndZbScan, - &CmndZbenroll, &CmndZbcie, + &CmndZbenroll, &CmndZbCIE, + &CmndZbEmulation, &CmndZbLoad, &CmndZbUnload, &CmndZbLoadDump, #ifdef ZIGBEE_DOC &CmndZbAttrDump, @@ -1518,7 +1520,7 @@ void CmndZbenroll(void) { } } -void CmndZbcie(void) { +void CmndZbCIE(void) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } if ((XdrvMailbox.data_len) && (ArgC() > 1)) { // Process parameter entry @@ -1536,6 +1538,38 @@ void CmndZbcie(void) { } } +void CmndZbEmulation(void) { + if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } + + if ((XdrvMailbox.data_len) && (ArgC() > 1)) { // Process parameter entry + char argument[XdrvMailbox.data_len]; + Z_Device & device = zigbee_devices.parseDeviceFromName(ArgV(argument, 1), nullptr, nullptr, XdrvMailbox.payload); + char * arg_v = ArgV(argument, 2); + int emulation = atoi(arg_v); + if (strlen(arg_v) == 0) { emulation = 255; } + + if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; } + + if (emulation == 0) { + device.setAdvertizeNone(); + } else if (emulation == 255) { + device.setAdvertizeAll(); + } else if (emulation == 1) { + device.setAdvertizeHue(true); + } else if (emulation == 2) { + device.setAdvertizeMatter(true); + } else if (emulation == -1) { + device.setAdvertizeHue(false); + } else if (emulation == -2) { + device.setAdvertizeMatter(false); + } + + ResponseCmndDone(); + } else { + ResponseCmndError(); + } +} + // Restore a device configuration previously exported via `ZbStatus2`` // Format: // Either the entire `ZbStatus3` export, or an array or just the device configuration. @@ -1755,20 +1789,27 @@ void ZigbeePermitJoinUpdate(void) { // // Command `ZbStatus` // +// Options: +// 0. `ZbStatus0` - Show information about coordinator `{"ZbStatus0":{"Device":"0x0000","IEEEAddr":"0x00124B0026B684E4","TotalDevices":19}}` +// 1.a. `ZbStatus` - Show all devices shortaddr and names `{"ZbStatus1":[{"Device":"0x868E","Name":"Room"}...]}` +// 1.b. `ZbStatus Room` - Show single device shortaddr and name `{"ZbStatus1":[{"Device":"0x868E","Name":"Room"}]}` +// 2. `ZbStatus2 Room` - Show detailed information of device `{"ZbStatus2":[{"Device":"0x868E","Name":"Room","IEEEAddr":"0x90FD9FFFFE03B051","ModelId":"TRADFRI bulb E27 WS opal 980lm","Manufacturer":"IKEA of Sweden","Endpoints":[1,242],"Config":["L01.2","O01"]}]}` void CmndZbStatus(void) { if (ZigbeeSerial) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } String dump; - if (0 == XdrvMailbox.index) { + if (0 == XdrvMailbox.index) { // Case 0 dump = zigbee_devices.dumpCoordinator(); } else { Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload); if (XdrvMailbox.data_len > 0) { if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; } + // case 1.b and 2. dump = zigbee_devices.dumpDevice(XdrvMailbox.index, device); } else { if (XdrvMailbox.index >= 2) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; } + // case 1.a dump = zigbee_devices.dumpDevice(XdrvMailbox.index, device_unk); } }