Zigbee ``ZbEmulation`` to selectively exclude some devices from Hue/Alexa emulation (#20552)

This commit is contained in:
s-hadinger 2024-01-21 11:42:04 +01:00 committed by GitHub
parent 76a7ab5131
commit de91133414
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 108 additions and 9 deletions

View File

@ -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

View File

@ -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"

View File

@ -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;

View File

@ -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;
}

View File

@ -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());

View File

@ -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 += "\"";

View File

@ -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);
}
}