diff --git a/CHANGELOG.md b/CHANGELOG.md index c54e6ae96..156ca4f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ## [12.1.1.4] ### Added - Support for Shelly Plus 2PM using template ``{"NAME":"Shelly Plus 2PM PCB v0.1.9","GPIO":[320,0,0,0,32,192,0,0,225,224,0,0,0,0,193,0,0,0,0,0,0,608,640,3458,0,0,0,0,0,9472,0,4736,0,0,0,0],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,10000,10000,3350"}`` +- Zigbee Alexa/Hue emulation, support multiple switches on separate endpoints + ### Changed ### Fixed diff --git a/tasmota/tasmota_xdrv_driver/xdrv_20_hue.ino b/tasmota/tasmota_xdrv_driver/xdrv_20_hue.ino index 650b40e34..734b0c0dd 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_20_hue.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_20_hue.ino @@ -415,12 +415,14 @@ const char HueConfigResponse_JSON[] PROGMEM = "\x3D\xA7\xB3\xAC\x6B\x3D\x87\x99\ /********************************************************************************************/ -String GetHueDeviceId(uint16_t id) +String GetHueDeviceId(uint16_t id, uint8_t ep = 0) { char s[32]; String deviceid = WiFi.macAddress(); deviceid.toLowerCase(); - snprintf(s, sizeof(s), "%s:%02x:11-%02x", deviceid.c_str(), (id >> 8) & 0xFF, id & 0xFF); + if (0x11 == ep) { ep = 0xFE; } // avoid collision with 0x11 which is used as default for `0` + if (0 == ep) { ep = 0x11; } // if ep is zero, revert to original value + snprintf(s, sizeof(s), "%s:%02x:%02X-%02x", deviceid.c_str(), (id >> 8) & 0xFF, ep, id & 0xFF); return String(s); // 5c:cf:7f:13:9f:3d:00:11-01 } @@ -989,7 +991,7 @@ void HueLights(String *path_req) uint16_t shortaddr; device = DecodeLightIdZigbee(device_id, &shortaddr); // device is endpoint when in Zigbee mode if (shortaddr) { - code = ZigbeeHandleHue(shortaddr, device_id, device, response); + code = ZigbeeHandleHue(shortaddr, device, device_id, device, response); goto exit; } #endif // USE_ZIGBEE @@ -1022,7 +1024,7 @@ void HueLights(String *path_req) uint16_t shortaddr; device = DecodeLightIdZigbee(device_id, &shortaddr); if (shortaddr) { - code = ZigbeeHueStatus(&response, shortaddr); + code = ZigbeeHueStatus(&response, shortaddr, device); goto exit; } #endif // USE_ZIGBEE 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 1bc7d04df..548b4fe9b 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino @@ -1010,7 +1010,7 @@ public: inline void setReachable(bool _reachable) { reachable = _reachable; } inline bool getReachable(void) const { return reachable; } - inline bool getPower(uint8_t ep =0) const; + inline bool getPower(uint8_t ep = 0) const; inline bool isRouter(void) const { return is_router; } inline bool isCoordinator(void) const { return 0x0000 == shortaddr; } @@ -1066,8 +1066,8 @@ public: void setPower(bool power_on, uint8_t ep = 0); // If light, returns the number of channels, or 0xFF if unknown - int8_t getLightChannels(void) const { - const Z_Data_Light & light = data.find(0); + int8_t getLightChannels(uint8_t ep = 0) const { + const Z_Data_Light & light = data.find(ep); if (&light != &z_data_unk) { return light.getConfig(); } else { @@ -1075,7 +1075,16 @@ public: } } - void setLightChannels(int8_t channels); + int8_t getHueBulbtype(uint8_t ep = 0) const { + int8_t light_profile = getLightChannels(ep); + if (0x00 == (light_profile & 0xF0)) { + return (light_profile & 0x07); + } else { + // not a bulb + return -1; + } + } + void setLightChannels(int8_t channels, uint8_t ep); static void setStringAttribute(char*& attr, const char * str); }; @@ -1097,9 +1106,10 @@ typedef enum Z_Def_Category { Z_CAT_PERMIT_JOIN, // timer to signal the end of the PermitJoin period // Below will clear based on device + cluster pair. Z_CLEAR_DEVICE_CLUSTER, - Z_CAT_READ_CLUSTER, + // none for now // Below will clear based on device + cluster + endpoint Z_CLEAR_DEVICE_CLUSTER_ENDPOINT, + Z_CAT_READ_CLUSTER, Z_CAT_EP_DESC, // read endpoint descriptor to gather clusters Z_CAT_BIND, // send auto-binding to coordinator Z_CAT_CONFIG_ATTR, // send a config attribute reporting request @@ -1183,10 +1193,10 @@ public: int32_t deviceRestore(JsonParserObject json); // Hue support - int8_t getHueBulbtype(uint16_t shortaddr) const ; + int8_t getHueBulbtype(uint16_t shortaddr, uint8_t ep = 0) const ; void hideHueBulb(uint16_t shortaddr, bool hidden); bool isHueBulbHidden(uint16_t shortaddr) const ; - Z_Data_Light & getLight(uint16_t shortaddr); + Z_Data_Light & getLight(uint16_t shortaddr, uint8_t ep = 0); // device is reachable void deviceWasReached(uint16_t shortaddr); 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 4489b8a63..8d5a71fb5 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 @@ -413,38 +413,47 @@ uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) { } // returns: dirty flag, did we change the value of the object -void Z_Device::setLightChannels(int8_t channels) { - if (channels >= 0) { +void Z_Device::setLightChannels(int8_t channels, uint8_t ep) { + if (channels >= 0) { + if (ep) { // if ep is not zero, the endpoint must exist + bool found = false; + for (uint32_t i = 0; i < endpoints_max; i++) { + if (ep == endpoints[i]) { found = true; break; } + } + if (!found) { + AddLog(LOG_LEVEL_INFO, D_LOG_ZIGBEE "cannot set light type to unknown ep=%i", ep); + return; + } + } else { + // if ep == 0, use first endpoint, or zero if no endpoint is known + ep = endpoints[0]; + } // retrieve of create light object - Z_Data_Light & light = data.get(0); + Z_Data_Light & light = data.get(ep); if (channels != light.getConfig()) { light.setConfig(channels); zigbee_devices.dirty(); } - Z_Data_OnOff & onoff = data.get(0); + Z_Data_OnOff & onoff = data.get(ep); (void)onoff; } else { // remove light / onoff object if any for (auto & data_elt : data) { if ((data_elt.getType() == Z_Data_Type::Z_Light) || (data_elt.getType() == Z_Data_Type::Z_OnOff)) { - // remove light object - data.remove(&data_elt); - zigbee_devices.dirty(); + if (ep == 0 || data_elt.getEndpoint() == ep) { // if remove ep==0 then remove all definitions + // remove light object + data.remove(&data_elt); + zigbee_devices.dirty(); + } } } } } -int8_t Z_Devices::getHueBulbtype(uint16_t shortaddr) const { +int8_t Z_Devices::getHueBulbtype(uint16_t shortaddr, uint8_t ep) const { const Z_Device &device = findShortAddr(shortaddr); - int8_t light_profile = device.getLightChannels(); - if (0x00 == (light_profile & 0xF0)) { - return (light_profile & 0x07); - } else { - // not a bulb - return -1; - } + return device.getHueBulbtype(ep); } void Z_Devices::hideHueBulb(uint16_t shortaddr, bool hidden) { @@ -1001,8 +1010,8 @@ int32_t Z_Devices::deviceRestore(JsonParserObject json) { return 0; } -Z_Data_Light & Z_Devices::getLight(uint16_t shortaddr) { - return getShortAddr(shortaddr).data.get(); +Z_Data_Light & Z_Devices::getLight(uint16_t shortaddr, uint8_t ep) { + return getShortAddr(shortaddr).data.get(ep); } bool Z_Devices::isTuyaProtocol(uint16_t shortaddr, uint8_t ep) const { 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 795556c66..58baf9dce 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 @@ -23,7 +23,7 @@ // Add global functions for Hue Emulation // idx: index in the list of zigbee_devices -void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, String *response) { +void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t ep, uint8_t local_light_subtype, String *response) { static const char HUE_LIGHTS_STATUS_JSON1_SUFFIX_ZIGBEE[] PROGMEM = "%s\"alert\":\"none\"," "\"effect\":\"none\"," @@ -41,7 +41,7 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above const Z_Device & device = zigbee_devices.findShortAddr(shortaddr); - const Z_Data_Light & light = device.data.find(); + const Z_Data_Light & light = device.data.find(ep); if (&light != &z_data_unk) { bri = light.getDimmer(); colormode = light.getColorMode(); @@ -51,7 +51,7 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri x = light.getX(); y = light.getY(); } - power = device.getPower(); + power = device.getPower(ep); reachable = device.getReachable(); if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254 @@ -89,29 +89,45 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri free(buf); } -void HueLightStatus2Zigbee(uint16_t shortaddr, String *response) +void HueLightStatus2Zigbee(uint16_t shortaddr, uint8_t ep, String *response) { const Z_Device & device = zigbee_devices.findShortAddr(shortaddr); const char * friendlyName = device.friendlyName; const char * modelId = device.modelId; const char * manufacturerId = device.manufacturerId; - char shortaddrname[8]; - snprintf_P(shortaddrname, sizeof(shortaddrname), PSTR("0x%04X"), shortaddr); - char* buf = HueLightStatus2Generic((friendlyName) ? friendlyName : shortaddrname, + char name[32+4]; + const char * local_friendfly_name = device.ep_names.getEPName(ep != 0 ? ep : device.endpoints[0]); // try endpoint, if `0` then found nothing + if (local_friendfly_name != nullptr) { + snprintf_P(name, sizeof(name), PSTR("%s"), local_friendfly_name); + } else if (friendlyName != nullptr) { + if (ep == 0 || ep == device.endpoints[0]) { // default endpoint, no suffix + snprintf_P(name, sizeof(name), PSTR("%s"), friendlyName); + } else { // no endpoint specific name so add suffix + snprintf_P(name, sizeof(name), PSTR("%s-%i"), friendlyName, ep); + } + } else { + if (ep == 0 || ep == device.endpoints[0]) { // default endpoint, no suffix + snprintf_P(name, sizeof(name), PSTR("0x%04X"), shortaddr); + } else { + snprintf_P(name, sizeof(name), PSTR("0x%04X-%i"), shortaddr, ep); + } + } + + char* buf = HueLightStatus2Generic(name, (modelId) ? modelId : PSTR("Unknown"), (manufacturerId) ? manufacturerId : PSTR("Tasmota"), - GetHueDeviceId(shortaddr).c_str()); + GetHueDeviceId(shortaddr, ep).c_str()); *response += buf; free(buf); } -int32_t ZigbeeHueStatus(String * response, uint16_t shortaddr) { - int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr); +int32_t ZigbeeHueStatus(String * response, uint16_t shortaddr, uint8_t ep) { + int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr, ep); if (bulbtype >= 0) { // respond only if eligible *response += F("{\"state\":"); - HueLightStatus1Zigbee(shortaddr, zigbee_devices.getHueBulbtype(shortaddr), response); - HueLightStatus2Zigbee(shortaddr, response); + HueLightStatus1Zigbee(shortaddr, ep, zigbee_devices.getHueBulbtype(shortaddr, ep), response); + HueLightStatus2Zigbee(shortaddr, ep, response); return 200; } else { return -3; @@ -120,40 +136,51 @@ int32_t 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++) { - uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr; - int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr); - - if (bulbtype >= 0) { - // this bulb is advertized - if (*appending) { response += ","; } - response += "\""; - response += EncodeLightIdZigbee(0, shortaddr); - response += F("\":{\"state\":"); - HueLightStatus1Zigbee(shortaddr, bulbtype, &response); // TODO - HueLightStatus2Zigbee(shortaddr, &response); - *appending = true; + for (uint32_t idx = 0; idx < zigbee_num; idx++) { + const Z_Device & device = zigbee_devices.devicesAt(idx); + uint16_t shortaddr = device.shortaddr; + + for (uint32_t i = 0; i < endpoints_max; i++) { + uint8_t ep = device.endpoints[i]; + if (i > 0 && ep == 0) { break; } + int8_t bulbtype = device.getHueBulbtype(ep); + if (bulbtype >= 0) { + // this bulb is advertized + if (*appending) { response += ","; } + response += "\""; + response += EncodeLightIdZigbee((i == 0) ? 0 : ep, shortaddr); + response += F("\":{\"state\":"); + HueLightStatus1Zigbee(shortaddr, ep, bulbtype, &response); + HueLightStatus2Zigbee(shortaddr, (i == 0) ? 0 : ep, &response); // if first endpoint ,announce as `0` + *appending = true; + } } } } void ZigbeeHueGroups(String * lights) { uint32_t zigbee_num = zigbee_devices.devicesSize(); - for (uint32_t i = 0; i < zigbee_num; i++) { - uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr; - int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr); + for (uint32_t idx = 0; idx < zigbee_num; idx++) { + const Z_Device & device = zigbee_devices.devicesAt(idx); + uint16_t shortaddr = device.shortaddr; - if (bulbtype >= 0) { - *lights += ",\""; - *lights += EncodeLightIdZigbee(0, shortaddr); - *lights += "\""; + for (uint32_t i = 0; i < endpoints_max; i++) { + uint8_t ep = device.endpoints[i]; + if (i > 0 && ep == 0) { break; } // no more endpoints + int8_t bulbtype = device.getHueBulbtype(ep); + if (bulbtype >= 0) { + *lights += ",\""; + *lights += EncodeLightIdZigbee((i == 0) ? 0 : ep, shortaddr); // if first endpont, announce as `0` + *lights += "\""; + } } } } -void ZigbeeSendHue(uint16_t shortaddr, uint16_t cluster, uint8_t cmd, const SBuffer & s) { +void ZigbeeSendHue(uint16_t shortaddr, uint8_t ep, uint16_t cluster, uint8_t cmd, const SBuffer & s) { ZCLFrame zcl(s.len()); zcl.shortaddr = shortaddr; + zcl.dstendpoint = ep; // if set to `0`, we will use the first endpoint zcl.cluster = cluster; zcl.cmd = cmd; zcl.clusterSpecific = true; @@ -165,69 +192,69 @@ void ZigbeeSendHue(uint16_t shortaddr, uint16_t cluster, uint8_t cmd, const SBuf // Send commands // Power On/Off -void ZigbeeHuePower(uint16_t shortaddr, bool power) { +void ZigbeeHuePower(uint16_t shortaddr, uint8_t ep, bool power) { SBuffer s(0); - ZigbeeSendHue(shortaddr, 0x0006, power ? 1 : 0, s); - zigbee_devices.getShortAddr(shortaddr).setPower(power, 0); + ZigbeeSendHue(shortaddr, ep, 0x0006, power ? 1 : 0, s); + zigbee_devices.getShortAddr(shortaddr).setPower(power, ep); } // Dimmer -void ZigbeeHueDimmer(uint16_t shortaddr, uint8_t dimmer) { +void ZigbeeHueDimmer(uint16_t shortaddr, uint8_t ep, uint8_t dimmer) { if (dimmer > 0xFE) { dimmer = 0xFE; } SBuffer s(4); s.add8(dimmer); s.add16(0x000A); // transition time = 1s - ZigbeeSendHue(shortaddr, 0x0008, 0x04, s); - zigbee_devices.getLight(shortaddr).setDimmer(dimmer); + ZigbeeSendHue(shortaddr, ep, 0x0008, 0x04, s); + zigbee_devices.getLight(shortaddr, ep).setDimmer(dimmer); } // CT -void ZigbeeHueCT(uint16_t shortaddr, uint16_t ct) { +void ZigbeeHueCT(uint16_t shortaddr, uint8_t ep, uint16_t ct) { if (ct > 0xFEFF) { ct = 0xFEFF; } SBuffer s(4); s.add16(ct); s.add16(0x000A); // transition time = 1s - ZigbeeSendHue(shortaddr, 0x0300, 0x0A, s); - Z_Data_Light & light = zigbee_devices.getLight(shortaddr); + ZigbeeSendHue(shortaddr, ep, 0x0300, 0x0A, s); + Z_Data_Light & light = zigbee_devices.getLight(shortaddr, ep); light.setColorMode(2); // "ct" light.setCT(ct); } // XY -void ZigbeeHueXY(uint16_t shortaddr, uint16_t x, uint16_t y) { +void ZigbeeHueXY(uint16_t shortaddr, uint8_t ep, uint16_t x, uint16_t y) { if (x > 0xFEFF) { x = 0xFEFF; } if (y > 0xFEFF) { y = 0xFEFF; } SBuffer s(8); s.add16(x); s.add16(y); s.add16(0x000A); // transition time = 1s - ZigbeeSendHue(shortaddr, 0x0300, 0x07, s); - Z_Data_Light & light = zigbee_devices.getLight(shortaddr); + ZigbeeSendHue(shortaddr, ep, 0x0300, 0x07, s); + Z_Data_Light & light = zigbee_devices.getLight(shortaddr, ep); light.setColorMode(1); // "xy" light.setX(x); light.setY(y); } // HueSat -void ZigbeeHueHS(uint16_t shortaddr, uint16_t hue, uint8_t sat) { +void ZigbeeHueHS(uint16_t shortaddr, uint8_t ep, uint16_t hue, uint8_t sat) { uint8_t hue8 = changeUIntScale(hue, 0, 360, 0, 254); if (sat > 0xFE) { sat = 0xFE; } SBuffer s(4); s.add8(hue8); s.add8(sat); s.add16(0); - ZigbeeSendHue(shortaddr, 0x0300, 0x06, s); - Z_Data_Light & light = zigbee_devices.getLight(shortaddr); + ZigbeeSendHue(shortaddr, ep, 0x0300, 0x06, s); + Z_Data_Light & light = zigbee_devices.getLight(shortaddr, ep); light.setColorMode(0); // "hs" light.setSat(sat); light.setHue(hue); } -int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, uint8_t endpoint, String &response) { +int32_t ZigbeeHandleHue(uint16_t shortaddr, uint8_t ep, uint32_t device_id, uint8_t endpoint, String &response) { uint8_t bri, sat; uint16_t ct, hue; - int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr); + int8_t bulbtype = zigbee_devices.getHueBulbtype(shortaddr, ep); if (bulbtype < 0) { // respond only if eligible response = F("{}"); return 200; @@ -259,9 +286,9 @@ int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, uint8_t endpoint device_id, on ? PSTR("true") : PSTR("false")); if (on) { - ZigbeeHuePower(shortaddr, 0x01); + ZigbeeHuePower(shortaddr, ep, 0x01); } else { - ZigbeeHuePower(shortaddr, 0x00); + ZigbeeHuePower(shortaddr, ep, 0x00); } response += buf; resp = true; @@ -280,7 +307,7 @@ int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, uint8_t endpoint if (LST_SINGLE <= bulbtype) { // extend bri value if set to max if (254 <= bri) { bri = 255; } - ZigbeeHueDimmer(shortaddr, bri); + ZigbeeHueDimmer(shortaddr, ep, bri); } resp = true; } @@ -304,7 +331,7 @@ int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, uint8_t endpoint resp = true; uint16_t xi = x * 65536.0f; uint16_t yi = y * 65536.0f; - ZigbeeHueXY(shortaddr, xi, yi); + ZigbeeHueXY(shortaddr, ep, xi, yi); } bool huesat_changed = false; @@ -342,7 +369,7 @@ int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, uint8_t endpoint huesat_changed = true; } if (huesat_changed) { - ZigbeeHueHS(shortaddr, hue, sat); + ZigbeeHueHS(shortaddr, ep, hue, sat); } resp = true; } @@ -358,7 +385,7 @@ int32_t ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, uint8_t endpoint device_id, PSTR("ct"), ct); response += buf; if ((LST_COLDWARM == bulbtype) || (LST_RGBW <= bulbtype)) { - ZigbeeHueCT(shortaddr, ct); + ZigbeeHueCT(shortaddr, ep, ct); } resp = true; } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino index 148f3d47f..db21e801f 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino @@ -972,7 +972,7 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const SBuffer &buf) { ); // query the state of the bulb (for Alexa) uint32_t wait_ms = 2000; // wait for 2s - Z_Query_Bulb(nwkAddr, wait_ms); + Z_Query_Bulb(nwkAddr, 0xFF, wait_ms); // 0xFF means iterate on all endpoints MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); // Continue the discovery process and auto-binding only if the device was unknown or if PermitJoin is ongoing @@ -2118,24 +2118,39 @@ int32_t Z_Load_Data(uint8_t value) { return 0; // continue } +static void Z_Query_Bulb_inner(uint16_t shortaddr, uint8_t ep, uint32_t &wait_ms) { + const uint32_t inter_message_ms = 100; // wait 100ms between messages + if (ep == 0) { ep = zigbee_devices.findFirstEndpoint(shortaddr); } + if (ep) { + if (0 <= zigbee_devices.getHueBulbtype(shortaddr, ep)) { + zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0006, ep, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback); + wait_ms += inter_message_ms; + zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0008, ep, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback); + wait_ms += inter_message_ms; + zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0300, ep, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback); + wait_ms += inter_message_ms; + zigbee_devices.setTimer(shortaddr, 0, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, 0, ep, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable); + wait_ms += 1000; // wait 1 second between devices + } + } +} + // // Query the state of a bulb (light) if its type allows it // -void Z_Query_Bulb(uint16_t shortaddr, uint32_t &wait_ms) { - const uint32_t inter_message_ms = 100; // wait 100ms between messages - - if (0 <= zigbee_devices.getHueBulbtype(shortaddr)) { - uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr); - - if (endpoint) { // send only if we know the endpoint - zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0006, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback); - wait_ms += inter_message_ms; - zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0008, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback); - wait_ms += inter_message_ms; - zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0300, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback); - wait_ms += inter_message_ms; - zigbee_devices.setTimer(shortaddr, 0, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, 0, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable); - wait_ms += 1000; // wait 1 second between devices +// ep==0 is default endpoint +// ep==0xFF iterates on all endpoints +void Z_Query_Bulb(uint16_t shortaddr, uint8_t ep, uint32_t &wait_ms) { + if (ep != 0xFF) { + Z_Query_Bulb_inner(shortaddr, ep, wait_ms); // try a single endpoint + } else { + // iterate on all endpoints + const Z_Device & device = zigbee_devices.findShortAddr(shortaddr); + if (!device.valid()) { return; } + for (uint32_t i = 0; i < endpoints_max; i++) { + ep = device.endpoints[i]; + if (ep == 0) { break; } + Z_Query_Bulb_inner(shortaddr, ep, wait_ms); // try a single endpoint } } } @@ -2173,7 +2188,7 @@ int32_t Z_Query_Bulbs(uint8_t value) { uint32_t wait_ms = 1000; // start with 1.0 s delay for (uint32_t i = 0; i < zigbee_devices.devicesSize(); i++) { const Z_Device &device = zigbee_devices.devicesAt(i); - Z_Query_Bulb(device.shortaddr, wait_ms); + Z_Query_Bulb(device.shortaddr, 0xFF, wait_ms); // 0xFF means all endpoints } } return 0; // continue 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 3052d3aa5..f3ab5af77 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino @@ -1218,7 +1218,7 @@ void CmndZbModelId(void) { // Specify, read or erase a Light type for Hue/Alexa integration void CmndZbLight(void) { // Syntax is: - // ZbLight , - assign a bulb type 0-5 + // ZbLight , - assign a bulb type 0-5, or -1 to remove // ZbLight - display the current bulb type and status // // Where can be: short_addr, long_addr, device_index, friendly_name @@ -1226,18 +1226,20 @@ void CmndZbLight(void) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } // check if parameters contain a comma ',' - char *p; - strtok_r(XdrvMailbox.data, ", ", &p); + char *p = XdrvMailbox.data; + char *device_id = strsep(&p, ","); // zigbee identifier + char *bulbtype_str = strsep(&p, ","); // friendly name + int32_t ep = (p != nullptr) ? strtol(p, nullptr, 10) : 0; // get endpoint number, or `0` if none // parse first part, - Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered + Z_Device & device = zigbee_devices.parseDeviceFromName(device_id, nullptr, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered if (!device.valid()) { ResponseCmndChar_P(PSTR(D_ZIGBEE_UNKNOWN_DEVICE)); return; } - if (p) { - int8_t bulbtype = strtol(p, nullptr, 10); + if (bulbtype_str != nullptr) { + int8_t bulbtype = strtol(bulbtype_str, nullptr, 10); if (bulbtype > 5) { bulbtype = 5; } if (bulbtype < -1) { bulbtype = -1; } - device.setLightChannels(bulbtype); + device.setLightChannels(bulbtype, ep); // assign by default to first endpoint, or 0 if no endpoint known } Z_attribute_list attr_list; device.jsonLightState(attr_list); @@ -1271,7 +1273,7 @@ void CmndZbOccupancy(void) { // check if parameters contain a comma ',' char *p; - strtok_r(XdrvMailbox.data, ", ", &p); + strtok_r(XdrvMailbox.data, ",", &p); // parse first part, Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, nullptr, nullptr, XdrvMailbox.payload); // in case of short_addr, it must be already registered