From d4a9ed41c9f18da6e6d3ecc833dcc3ba9402b4e1 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Thu, 26 Mar 2020 19:34:59 +0100 Subject: [PATCH] Add support for unreachable (unplugged) Zigbee devices in Philips Hue emulation and Alexa --- tasmota/CHANGELOG.md | 1 + tasmota/xdrv_23_zigbee_2_devices.ino | 43 ++++++++++++++------ tasmota/xdrv_23_zigbee_3_hue.ino | 28 ++++++++----- tasmota/xdrv_23_zigbee_5_converters.ino | 18 ++++----- tasmota/xdrv_23_zigbee_6_commands.ino | 15 +++++++ tasmota/xdrv_23_zigbee_8_parsers.ino | 54 ++++++++++++++----------- 6 files changed, 102 insertions(+), 57 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 12f581ce8..0977c85c7 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -6,6 +6,7 @@ - Change GPIO initialization solving possible Relay toggle on (OTA) restart - Fix Zigbee sending wrong Sat value with Hue emulation - Add command ``ZbRestore`` to restore device configuration dumped with ``ZbStatus 2`` +- Add support for unreachable (unplugged) Zigbee devices in Philips Hue emulation and Alexa ## Released diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index e4c6f6df0..d8cb1d5bc 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -46,7 +46,7 @@ typedef struct Z_Device { 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 power; // power state (boolean) + 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 @@ -66,12 +66,15 @@ typedef enum Z_Def_Category { Z_CAT_NONE = 0, // no category, it will happen anyways Z_CAT_READ_ATTR, // Attribute reporting, either READ_ATTRIBUTE or REPORT_ATTRIBUTE, we coalesce all attributes reported if we can Z_CAT_VIRTUAL_ATTR, // Creation of a virtual attribute, typically after a time-out. Ex: Aqara presence sensor + Z_CAT_REACHABILITY, // timer set to measure reachability of device, i.e. if we don't get an answer after 1s, it is marked as unreachable (for Alexa) Z_CAT_READ_0006, // Read 0x0006 cluster Z_CAT_READ_0008, // Read 0x0008 cluster Z_CAT_READ_0102, // Read 0x0300 cluster Z_CAT_READ_0300, // Read 0x0300 cluster } Z_Def_Category; +const uint32_t Z_CAT_REACHABILITY_TIMEOUT = 1000; // 1000 ms or 1s + typedef struct Z_Deferred { // below are per device timers, used for example to query the new state of the device uint32_t timer; // millis() when to fire the timer, 0 if no timer @@ -124,6 +127,7 @@ public: void setFriendlyName(uint16_t shortaddr, const char * str); const char * getFriendlyName(uint16_t shortaddr) const; const char * getModelId(uint16_t shortaddr) const; + void setReachable(uint16_t shortaddr, bool reachable); // get next sequence number for (increment at each all) uint8_t getNextSeqNumber(uint16_t shortaddr); @@ -137,15 +141,17 @@ public: void setHueBulbtype(uint16_t shortaddr, int8_t bulbtype); int8_t getHueBulbtype(uint16_t shortaddr) const ; void updateHueState(uint16_t shortaddr, - const uint8_t *power, const uint8_t *colormode, + const bool *power, const uint8_t *colormode, const uint8_t *dimmer, const uint8_t *sat, const uint16_t *ct, const uint16_t *hue, - const uint16_t *x, const uint16_t *y); + const uint16_t *x, const uint16_t *y, + const bool *reachable); bool getHueState(uint16_t shortaddr, - uint8_t *power, uint8_t *colormode, + bool *power, uint8_t *colormode, uint8_t *dimmer, uint8_t *sat, uint16_t *ct, uint16_t *hue, - uint16_t *x, uint16_t *y) const ; + uint16_t *x, uint16_t *y, + bool *reachable) const ; // Timers void resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category); @@ -262,7 +268,7 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { 0, // seqNumber // Hue support -1, // no Hue support - 0, // power + 0x80, // power off + reachable 0, // colormode 0, // dimmer 0, // sat @@ -583,6 +589,12 @@ const char * Z_Devices::getModelId(uint16_t shortaddr) const { return nullptr; } +void Z_Devices::setReachable(uint16_t shortaddr, bool reachable) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + bitWrite(device.power, 7, reachable); +} + // get the next sequance number for the device, or use the global seq number if device is unknown uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) { int32_t short_found = findShortAddr(shortaddr); @@ -616,12 +628,13 @@ int8_t Z_Devices::getHueBulbtype(uint16_t shortaddr) const { // Hue support void Z_Devices::updateHueState(uint16_t shortaddr, - const uint8_t *power, const uint8_t *colormode, + const bool *power, const uint8_t *colormode, const uint8_t *dimmer, const uint8_t *sat, const uint16_t *ct, const uint16_t *hue, - const uint16_t *x, const uint16_t *y) { + const uint16_t *x, const uint16_t *y, + const bool *reachable) { Z_Device &device = getShortAddr(shortaddr); - if (power) { device.power = *power; } + if (power) { bitWrite(device.power, 0, *power); } if (colormode){ device.colormode = *colormode; } if (dimmer) { device.dimmer = *dimmer; } if (sat) { device.sat = *sat; } @@ -629,18 +642,20 @@ void Z_Devices::updateHueState(uint16_t shortaddr, if (hue) { device.hue = *hue; } if (x) { device.x = *x; } if (y) { device.y = *y; } + if (reachable){ bitWrite(device.power, 7, *reachable); } } // return true if ok bool Z_Devices::getHueState(uint16_t shortaddr, - uint8_t *power, uint8_t *colormode, + bool *power, uint8_t *colormode, uint8_t *dimmer, uint8_t *sat, uint16_t *ct, uint16_t *hue, - uint16_t *x, uint16_t *y) const { + uint16_t *x, uint16_t *y, + bool *reachable) const { int32_t found = findShortAddr(shortaddr); if (found >= 0) { const Z_Device &device = *(_devices[found]); - if (power) { *power = device.power; } + if (power) { *power = bitRead(device.power, 0); } if (colormode){ *colormode = device.colormode; } if (dimmer) { *dimmer = device.dimmer; } if (sat) { *sat = device.sat; } @@ -648,6 +663,7 @@ bool Z_Devices::getHueState(uint16_t shortaddr, if (hue) { *hue = device.hue; } if (x) { *x = device.x; } if (y) { *y = device.y; } + if (reachable){ *reachable = bitRead(device.power, 7); } return true; } else { return false; @@ -951,7 +967,8 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const { dev[F(D_JSON_ZIGBEE_LIGHT)] = device.bulbtype; // sign extend, 0xFF changed as -1 if (0 <= device.bulbtype) { // bulbtype is defined - dev[F("Power")] = device.power; + dev[F("Power")] = bitRead(device.power, 0); + dev[F("Reachable")] = bitRead(device.power, 7); if (1 <= device.bulbtype) { dev[F("Dimmer")] = device.dimmer; } diff --git a/tasmota/xdrv_23_zigbee_3_hue.ino b/tasmota/xdrv_23_zigbee_3_hue.ino index d1bc5b731..ffbec109c 100644 --- a/tasmota/xdrv_23_zigbee_3_hue.ino +++ b/tasmota/xdrv_23_zigbee_3_hue.ino @@ -24,13 +24,19 @@ // idx: index in the list of zigbee_devices void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, String *response) { - uint8_t power, colormode, bri, sat; + static const char HUE_LIGHTS_STATUS_JSON1_SUFFIX_ZIGBEE[] PROGMEM = + "%s\"alert\":\"none\"," + "\"effect\":\"none\"," + "\"reachable\":%s}"; + + bool power, reachable; + uint8_t colormode, bri, sat; uint16_t ct, hue; uint16_t x, y; String light_status = ""; uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above - zigbee_devices.getHueState(shortaddr, &power, &colormode, &bri, &sat, &ct, &hue, &x, &y); + zigbee_devices.getHueState(shortaddr, &power, &colormode, &bri, &sat, &ct, &hue, &x, &y, &reachable); if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254 if (bri < 1) bri = 1; @@ -40,7 +46,7 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri const size_t buf_size = 256; char * buf = (char*) malloc(buf_size); // temp buffer for strings, avoid stack - snprintf_P(buf, buf_size, PSTR("{\"on\":%s,"), (power & 1) ? "true" : "false"); + snprintf_P(buf, buf_size, PSTR("{\"on\":%s,"), power ? "true" : "false"); // Brightness for all devices with PWM if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) { // force dimmer for 1st gen Echo snprintf_P(buf, buf_size, PSTR("%s\"bri\":%d,"), buf, bri); @@ -61,7 +67,7 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) { // white temp snprintf_P(buf, buf_size, PSTR("%s\"ct\":%d,"), buf, ct > 0 ? ct : 284); } - snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON1_SUFFIX, buf); + snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON1_SUFFIX_ZIGBEE, buf, reachable ? "true" : "false"); *response += buf; free(buf); @@ -123,9 +129,9 @@ void ZigbeeHueGroups(String * lights) { // Send commands // Power On/Off -void ZigbeeHuePower(uint16_t shortaddr, uint8_t power) { - zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0006, power, ""); - zigbee_devices.updateHueState(shortaddr, &power, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); +void ZigbeeHuePower(uint16_t shortaddr, bool power) { + zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0006, power ? 1 : 0, ""); + zigbee_devices.updateHueState(shortaddr, &power, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); } // Dimmer @@ -134,7 +140,7 @@ void ZigbeeHueDimmer(uint16_t shortaddr, uint8_t dimmer) { char param[8]; snprintf_P(param, sizeof(param), PSTR("%02X0A00"), dimmer); zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0008, 0x04, param); - zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, &dimmer, nullptr, nullptr, nullptr, nullptr, nullptr); + zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, &dimmer, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); } // CT @@ -145,7 +151,7 @@ void ZigbeeHueCT(uint16_t shortaddr, uint16_t ct) { snprintf_P(param, sizeof(param), PSTR("%02X%02X0A00"), ct & 0xFF, ct >> 8); uint8_t colormode = 2; // "ct" zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0300, 0x0A, param); - zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, &ct, nullptr, nullptr, nullptr); + zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, &ct, nullptr, nullptr, nullptr, nullptr); } // XY @@ -156,7 +162,7 @@ void ZigbeeHueXY(uint16_t shortaddr, uint16_t x, uint16_t y) { snprintf_P(param, sizeof(param), PSTR("%02X%02X%02X%02X0A00"), x & 0xFF, x >> 8, y & 0xFF, y >> 8); uint8_t colormode = 1; // "xy" zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0300, 0x07, param); - zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, nullptr, nullptr, &x, &y); + zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, nullptr, nullptr, &x, &y, nullptr); } // HueSat @@ -167,7 +173,7 @@ void ZigbeeHueHS(uint16_t shortaddr, uint16_t hue, uint8_t sat) { snprintf_P(param, sizeof(param), PSTR("%02X%02X0000"), hue8, sat); uint8_t colormode = 0; // "hs" zigbeeZCLSendStr(shortaddr, 0, 0, true, 0x0300, 0x06, param); - zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, &sat, nullptr, &hue, nullptr, nullptr); + zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, &sat, nullptr, &hue, nullptr, nullptr, nullptr); } void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) { diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index dfe544cbd..edd5ab682 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -1215,38 +1215,38 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { // see if we need to update the Hue bulb status if ((cluster == 0x0006) && ((attribute == 0x0000) || (attribute == 0x8000))) { - uint8_t power = value; + bool power = value; zigbee_devices.updateHueState(shortaddr, &power, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr); + nullptr, nullptr, nullptr, nullptr, nullptr); } else if ((cluster == 0x0008) && (attribute == 0x0000)) { uint8_t dimmer = value; zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, &dimmer, nullptr, - nullptr, nullptr, nullptr, nullptr); + nullptr, nullptr, nullptr, nullptr, nullptr); } else if ((cluster == 0x0300) && (attribute == 0x0000)) { uint16_t hue8 = value; uint16_t hue = changeUIntScale(hue8, 0, 254, 0, 360); // change range from 0..254 to 0..360 zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr, - nullptr, &hue, nullptr, nullptr); + nullptr, &hue, nullptr, nullptr, nullptr); } else if ((cluster == 0x0300) && (attribute == 0x0001)) { uint8_t sat = value; zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, &sat, - nullptr, nullptr, nullptr, nullptr); + nullptr, nullptr, nullptr, nullptr, nullptr); } else if ((cluster == 0x0300) && (attribute == 0x0003)) { uint16_t x = value; zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, &x, nullptr); + nullptr, nullptr, &x, nullptr, nullptr); } else if ((cluster == 0x0300) && (attribute == 0x0004)) { uint16_t y = value; zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, &y); + nullptr, nullptr, nullptr, &y, nullptr), nullptr; } else if ((cluster == 0x0300) && (attribute == 0x0007)) { uint16_t ct = value; zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr, - &ct, nullptr, nullptr, nullptr); + &ct, nullptr, nullptr, nullptr, nullptr); } else if ((cluster == 0x0300) && (attribute == 0x0008)) { uint8_t colormode = value; zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr); + nullptr, nullptr, nullptr, nullptr, nullptr); } // Iterate on filter diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index 4788ddf67..818f8afbe 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -173,6 +173,14 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus } } + +// This callback is registered after a an attribute read command was made to a light, and fires if we don't get any response after 1000 ms +int32_t Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) { + if (shortaddr) { + zigbee_devices.setReachable(shortaddr, false); // mark device as reachable + } +} + // set a timer to read back the value in the future void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint) { uint32_t wait_ms = 0; @@ -192,6 +200,9 @@ void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus } if (wait_ms) { zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback); + if (shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group + zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable); + } } } @@ -300,6 +311,10 @@ void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin } if ((endpoint) || (groupaddr)) { // send only if we know the endpoint zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, z_cat, 0 /* value */, &Z_ReadAttrCallback); + if (shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group + zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable); + } + } } } diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index b10c4be05..5e05d6c82 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -308,6 +308,7 @@ int32_t Z_DataConfirm(int32_t res, const class SBuffer &buf) { // // Handle Receive End Device Announce incoming message +// This message is also received when a previously paired device is powered up // Send back Active Ep Req message // int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { @@ -328,6 +329,9 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { (capabilities & 0x08) ? "true" : "false", (capabilities & 0x40) ? "true" : "false" ); + // query the state of the bulb (for Alexa) + uint32_t wait_ms = 2000; // wait for 2s + Z_Query_Bulb(nwkAddr, wait_ms); MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); @@ -513,6 +517,10 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { // Add linkquality json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality; + // since we just receveived data from the device, it is reachable + zigbee_devices.resetTimersForDevice(srcaddr, 0 /* groupaddr */, Z_CAT_REACHABILITY); // remove any reachability timer already there + zigbee_devices.setReachable(srcaddr, true); // mark device as reachable + if (defer_attributes) { // Prepare for publish if (zigbee_devices.jsonIsConflict(srcaddr, json)) { @@ -591,38 +599,36 @@ int32_t Z_Load_Devices(uint8_t value) { return 0; // continue } +// +// 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_NONE, 0 /* value */, &Z_ReadAttrCallback); + wait_ms += inter_message_ms; + zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0008, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback); + wait_ms += inter_message_ms; + zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0300, endpoint, Z_CAT_NONE, 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); + } + } +} + // // Send messages to query the state of each Hue emulated light // int32_t Z_Query_Bulbs(uint8_t value) { // Scan all devices and send deferred requests to know the state of bulbs uint32_t wait_ms = 1000; // start with 1.0 s delay - const uint32_t inter_message_ms = 100; // wait 100ms between messages for (uint32_t i = 0; i < zigbee_devices.devicesSize(); i++) { const Z_Device &device = zigbee_devices.devicesAt(i); - - if (0 <= device.bulbtype) { - uint16_t cluster; - uint8_t endpoint = zigbee_devices.findFirstEndpoint(device.shortaddr); - - cluster = 0x0006; - if (endpoint) { // send only if we know the endpoint - zigbee_devices.setTimer(device.shortaddr, 0 /* groupaddr */, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback); - wait_ms += inter_message_ms; - } - - cluster = 0x0008; - if (endpoint) { // send only if we know the endpoint - zigbee_devices.setTimer(device.shortaddr, 0 /* groupaddr */, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback); - wait_ms += inter_message_ms; - } - - cluster = 0x0300; - if (endpoint) { // send only if we know the endpoint - zigbee_devices.setTimer(device.shortaddr, 0 /* groupaddr */, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback); - wait_ms += inter_message_ms; - } - } + Z_Query_Bulb(device.shortaddr, wait_ms); } return 0; // continue }