From 85a5e8cf5d865c45a7c475a217724d5bc5d1a013 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sat, 22 Aug 2020 18:40:44 +0200 Subject: [PATCH] Add Zigbee web gui widget for Temp/Humidity/Pressure sensors --- tasmota/CHANGELOG.md | 1 + tasmota/xdrv_23_zigbee_0_constants.ino | 2 - tasmota/xdrv_23_zigbee_2_devices.ino | 367 +++++++++--------------- tasmota/xdrv_23_zigbee_3_hue.ino | 37 ++- tasmota/xdrv_23_zigbee_5__constants.ino | 2 +- tasmota/xdrv_23_zigbee_5_converters.ino | 62 ++-- tasmota/xdrv_23_zigbee_A_impl.ino | 77 +++-- 7 files changed, 239 insertions(+), 309 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 3280a80cd..a94fa3d3c 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -8,6 +8,7 @@ - Add command ``SetOption108 0/1`` to enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1) - Add better config corruption recovery (#9046) - Add virtual CT for 4 channels lights, emulating a 5th channel - Add support for DYP ME007 ultrasonic distance sensor by Janusz Kostorz (#9113) +- Add Zigbee web gui widget for Temp/Humidity/Pressure sensors ### 8.4.0.1 20200730 diff --git a/tasmota/xdrv_23_zigbee_0_constants.ino b/tasmota/xdrv_23_zigbee_0_constants.ino index 1d797528c..857279f48 100644 --- a/tasmota/xdrv_23_zigbee_0_constants.ino +++ b/tasmota/xdrv_23_zigbee_0_constants.ino @@ -1121,8 +1121,6 @@ enum ZCL_Global_Commands { ZCL_DISCOVER_ATTRIBUTES_RESPONSE = 0x0d }; -#define ZF(s) static const char ZS_ ## s[] PROGMEM = #s; -#define Z(s) ZS_ ## s #define Z_(s) Zo_ ## s // ZDP Enumeration, see Zigbee spec 2.4.5 diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index c513d533f..9bdd512d3 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -50,7 +50,9 @@ uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; } const size_t endpoints_max = 8; // we limit to 8 endpoints -typedef struct Z_Device { +class Z_Device { +public: + uint64_t longaddr; // 0x00 means unspecified char * manufacturerId; char * modelId; @@ -82,9 +84,69 @@ typedef struct Z_Device { 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 lqi; // lqi from last message, 0xFF means unknown uint8_t batterypercent; // battery percentage (0..100), 0xFF means unknwon -} Z_Device; + // sensor data + int16_t temperature; // temperature in 1/10th of Celsius, 0x8000 if unknown + uint16_t pressure; // air pressure in hPa, 0xFFFF if unknown + uint8_t humidity; // humidity in percent, 0..100, 0xFF if unknown + + // Constructor with all defaults + Z_Device(uint16_t _shortaddr, uint64_t _longaddr = 0x00): + longaddr(_longaddr), + manufacturerId(nullptr), + modelId(nullptr), + friendlyName(nullptr), + endpoints{ 0, 0, 0, 0, 0, 0, 0, 0 }, + json_buffer(nullptr), + json(nullptr), + shortaddr(_shortaddr), + seqNumber(0), + // Hue support + zb_profile(0xFF), // no profile + power(0x02), // 0x80 = reachable, 0x01 = power on, 0x02 = power unknown + colormode(0xFF), + dimmer(0xFF), + sat(0xFF), + ct(0xFFFF), + hue(0xFFFF), + x(0xFFFF), + y(0xFFFF), + lqi(0xFF), + batterypercent(0xFF), + temperature(-0x8000), + pressure(0xFFFF), + humidity(0xFF) + { }; + + inline bool valid(void) const { return BAD_SHORTADDR != shortaddr; } // is the device known, valid and found? + + inline bool validLongaddr(void) const { return 0x0000 != longaddr; } + inline bool validManufacturerId(void) const { return nullptr != manufacturerId; } + inline bool validModelId(void) const { return nullptr != modelId; } + inline bool validFriendlyName(void) const { return nullptr != friendlyName; } + + inline bool validPower(void) const { return 0x00 == (power & 0x02); } + inline bool validColormode(void) const { return 0xFF != colormode; } + inline bool validDimmer(void) const { return 0xFF != dimmer; } + inline bool validSat(void) const { return 0xFF != sat; } + inline bool validCT(void) const { return 0xFFFF != ct; } + inline bool validHue(void) const { return 0xFFFF != hue; } + inline bool validX(void) const { return 0xFFFF != x; } + inline bool validY(void) const { return 0xFFFF != y; } + + inline bool validLqi(void) const { return 0xFF != lqi; } + inline bool validBatteryPercent(void) const { return 0xFF != batterypercent; } + + inline bool validTemperature(void) const { return -0x8000 != temperature; } + inline bool validPressure(void) const { return 0xFFFF != pressure; } + inline bool validHumidity(void) const { return 0xFF != humidity; } + + inline void setReachable(bool reachable) { bitWrite(power, 7, reachable); } + inline bool getReachable(void) const { return bitRead(power, 7); } + inline void setPower(bool power_on) { bitWrite(power, 0, power_on); bitWrite(power, 1, false); } + inline bool getPower(void) const { return bitRead(power, 0); } +}; /*********************************************************************************************\ * Structures for deferred callbacks @@ -136,11 +198,17 @@ public: // - 0x0000 = not found // - BAD_SHORTADDR = bad parameter // - 0x = the device's short address - uint16_t isKnownShortAddr(uint16_t shortaddr) const; uint16_t isKnownLongAddr(uint64_t longaddr) const; uint16_t isKnownIndex(uint32_t index) const; uint16_t isKnownFriendlyName(const char * name) const; + + const Z_Device & findShortAddr(uint16_t shortaddr) const; + Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist + const Z_Device & getShortAddrConst(uint16_t shortaddr) const ; // find Device from shortAddr, creates it if does not exist + Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist + int32_t findLongAddr(uint64_t longaddr) const; + int32_t findFriendlyName(const char * name) const; uint64_t getDeviceLongAddr(uint16_t shortaddr) const; uint8_t findFirstEndpoint(uint16_t shortaddr) const; @@ -157,12 +225,19 @@ public: void setManufId(uint16_t shortaddr, const char * str); void setModelId(uint16_t shortaddr, const char * str); void setFriendlyName(uint16_t shortaddr, const char * str); - const char * getFriendlyName(uint16_t shortaddr) const; - const char * getModelId(uint16_t shortaddr) const; - const char * getManufacturerId(uint16_t shortaddr) const; + inline const char * getFriendlyName(uint16_t shortaddr) const { + return findShortAddr(shortaddr).friendlyName; + } + inline const char * getModelId(uint16_t shortaddr) const { + return findShortAddr(shortaddr).modelId; + } + inline const char * getManufacturerId(uint16_t shortaddr) const{ + return findShortAddr(shortaddr).manufacturerId; + } + void setReachable(uint16_t shortaddr, bool reachable); void setLQI(uint16_t shortaddr, uint8_t lqi); - uint8_t getLQI(uint16_t shortaddr) const; + // uint8_t getLQI(uint16_t shortaddr) const; void setBatteryPercent(uint16_t shortaddr, uint8_t bp); uint8_t getBatteryPercent(uint16_t shortaddr) const; @@ -183,18 +258,6 @@ public: 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, - const uint16_t *ct, const uint16_t *hue, - const uint16_t *x, const uint16_t *y, - const bool *reachable); - bool getHueState(uint16_t shortaddr, - bool *power, uint8_t *colormode, - uint8_t *dimmer, uint8_t *sat, - uint16_t *ct, uint16_t *hue, - uint16_t *x, uint16_t *y, - bool *reachable) const ; // Timers void resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category); @@ -234,20 +297,14 @@ private: uint32_t _saveTimer = 0; uint8_t _seqNumber = 0; // global seqNumber if device is unknown + // Following device is used represent the unknown device, with all defaults + // Any find() function will not return Null, instead it will return this instance + const Z_Device device_unk = Z_Device(BAD_SHORTADDR); + template < typename T> static bool findInVector(const std::vector & vecOfElements, const T & element); - template < typename T> - static int32_t findEndpointInVector(const std::vector & vecOfElements, uint8_t element); - - Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist - const Z_Device & getShortAddrConst(uint16_t shortaddr) const ; // find Device from shortAddr, creates it if does not exist - Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist - - int32_t findShortAddr(uint16_t shortaddr) const; - int32_t findLongAddr(uint64_t longaddr) const; - int32_t findFriendlyName(const char * name) const; - + int32_t findShortAddrIdx(uint16_t shortaddr) const; // Create a new entry in the devices list - must be called if it is sure it does not already exist Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0); void freeDeviceEntry(Z_Device *device); @@ -283,47 +340,13 @@ bool Z_Devices::findInVector(const std::vector & vecOfElements, const T & e } } -template < typename T> -int32_t Z_Devices::findEndpointInVector(const std::vector & vecOfElements, uint8_t element) { - // Find given element in vector - - int32_t found = 0; - for (auto &elem : vecOfElements) { - if (elem == element) { return found; } - found++; - } - - return -1; -} - // // Create a new Z_Device entry in _devices. Only to be called if you are sure that no // entry with same shortaddr or longaddr exists. // Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { - if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return *(Z_Device*) nullptr; } // it is not legal to create this entry - //Z_Device* device_alloc = (Z_Device*) malloc(sizeof(Z_Device)); - Z_Device* device_alloc = new Z_Device{ - longaddr, - nullptr, // ManufId - nullptr, // DeviceId - nullptr, // FriendlyName - { 0, 0, 0, 0, 0, 0, 0, 0 }, // endpoints - nullptr, nullptr, - shortaddr, - 0, // seqNumber - // Hue support - 0xFF, // no Hue support - 0x80, // power off + reachable - 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 - }; + if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return (Z_Device&) device_unk; } // it is not legal to create this entry + Z_Device * device_alloc = new Z_Device(shortaddr, longaddr); device_alloc->json_buffer = new DynamicJsonBuffer(16); _devices.push_back(device_alloc); @@ -346,7 +369,7 @@ void Z_Devices::freeDeviceEntry(Z_Device *device) { // Out: // index in _devices of entry, -1 if not found // -int32_t Z_Devices::findShortAddr(uint16_t shortaddr) const { +int32_t Z_Devices::findShortAddrIdx(uint16_t shortaddr) const { if (BAD_SHORTADDR == shortaddr) { return -1; } // does not make sense to look for BAD_SHORTADDR shortaddr (broadcast) int32_t found = 0; for (auto &elem : _devices) { @@ -355,6 +378,12 @@ int32_t Z_Devices::findShortAddr(uint16_t shortaddr) const { } return -1; } +const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const { + for (auto &elem : _devices) { + if (elem->shortaddr == shortaddr) { return *elem; } + } + return device_unk; +} // // Scan all devices to find a corresponding longaddr // Looks info device.longaddr entry @@ -395,16 +424,6 @@ int32_t Z_Devices::findFriendlyName(const char * name) const { return -1; } -// Probe if device is already known but don't create any entry -uint16_t Z_Devices::isKnownShortAddr(uint16_t shortaddr) const { - int32_t found = findShortAddr(shortaddr); - if (found >= 0) { - return shortaddr; - } else { - return BAD_SHORTADDR; // unknown - } -} - uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const { int32_t found = findLongAddr(longaddr); if (found >= 0) { @@ -436,16 +455,15 @@ uint16_t Z_Devices::isKnownFriendlyName(const char * name) const { } uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const { - const Z_Device &device = getShortAddrConst(shortaddr); - return (&device != nullptr) ? device.longaddr : 0; + return getShortAddrConst(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00 } // // We have a seen a shortaddr on the network, get the corresponding device object // Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) { - if (BAD_SHORTADDR == shortaddr) { return *(Z_Device*) nullptr; } // this is not legal - int32_t found = findShortAddr(shortaddr); + if (BAD_SHORTADDR == shortaddr) { return (Z_Device&) device_unk; } // this is not legal + int32_t found = findShortAddrIdx(shortaddr); if (found >= 0) { return *(_devices[found]); } @@ -454,17 +472,16 @@ Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) { } // Same version but Const const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const { - if (BAD_SHORTADDR == shortaddr) { return *(Z_Device*) nullptr; } // this is not legal - int32_t found = findShortAddr(shortaddr); + int32_t found = findShortAddrIdx(shortaddr); if (found >= 0) { return *(_devices[found]); } - return *((Z_Device*)nullptr); + return device_unk; } // find the Device object by its longaddr (unique key if not null) Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) { - if (!longaddr) { return *(Z_Device*) nullptr; } + if (!longaddr) { return (Z_Device&) device_unk; } int32_t found = findLongAddr(longaddr); if (found > 0) { return *(_devices[found]); @@ -474,7 +491,7 @@ Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) { // Remove device from list, return true if it was known, false if it was not recorded bool Z_Devices::removeDevice(uint16_t shortaddr) { - int32_t found = findShortAddr(shortaddr); + int32_t found = findShortAddrIdx(shortaddr); if (found >= 0) { freeDeviceEntry(_devices.at(found)); _devices.erase(_devices.begin() + found); @@ -490,7 +507,7 @@ bool Z_Devices::removeDevice(uint16_t shortaddr) { // shortaddr // longaddr (both can't be null at the same time) void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) { - int32_t s_found = findShortAddr(shortaddr); // is there already a shortaddr entry + int32_t s_found = findShortAddrIdx(shortaddr); // is there already a shortaddr entry int32_t l_found = findLongAddr(longaddr); // is there already a longaddr entry if ((s_found >= 0) && (l_found >= 0)) { // both shortaddr and longaddr are already registered @@ -525,8 +542,6 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) { // void Z_Devices::clearEndpoints(uint16_t shortaddr) { Z_Device &device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found - for (uint32_t i = 0; i < endpoints_max; i++) { device.endpoints[i] = 0; // no dirty here because it doesn't make sense to store it, does it? @@ -539,7 +554,6 @@ void Z_Devices::clearEndpoints(uint16_t shortaddr) { void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) { if (0x00 == endpoint) { return; } Z_Device &device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found for (uint32_t i = 0; i < endpoints_max; i++) { if (endpoint == device.endpoints[i]) { @@ -558,7 +572,7 @@ void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) { // uint32_t Z_Devices::countEndpoints(uint16_t shortaddr) const { uint32_t count_ep = 0; - int32_t found = findShortAddr(shortaddr); + int32_t found = findShortAddrIdx(shortaddr); if (found < 0) return 0; // avoid creating an entry if the device was never seen const Z_Device &device = devicesAt(found); @@ -574,11 +588,7 @@ uint32_t Z_Devices::countEndpoints(uint16_t shortaddr) const { uint8_t Z_Devices::findFirstEndpoint(uint16_t shortaddr) const { // When in router of end-device mode, the coordinator was not probed, in this case always talk to endpoint 1 if (0x0000 == shortaddr) { return 1; } - int32_t found = findShortAddr(shortaddr); - if (found < 0) return 0; // avoid creating an entry if the device was never seen - const Z_Device &device = devicesAt(found); - - return device.endpoints[0]; // returns 0x00 if no endpoint + return findShortAddr(shortaddr).endpoints[0]; // returns 0x00 if no endpoint } void Z_Devices::setStringAttribute(char*& attr, const char * str) { @@ -613,94 +623,34 @@ void Z_Devices::setStringAttribute(char*& attr, const char * str) { // - Any actual change in ManufId (i.e. setting a different value) triggers a `dirty()` and saving to Flash // void Z_Devices::setManufId(uint16_t shortaddr, const char * str) { - Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found - - setStringAttribute(device.manufacturerId, str); + setStringAttribute(getShortAddr(shortaddr).manufacturerId, str); } void Z_Devices::setModelId(uint16_t shortaddr, const char * str) { - Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found - - setStringAttribute(device.modelId, str); + setStringAttribute(getShortAddr(shortaddr).modelId, str); } void Z_Devices::setFriendlyName(uint16_t shortaddr, const char * str) { - Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found - - setStringAttribute(device.friendlyName, str); + setStringAttribute(getShortAddr(shortaddr).friendlyName, str); } -const char * Z_Devices::getFriendlyName(uint16_t shortaddr) const { - int32_t found = findShortAddr(shortaddr); - if (found >= 0) { - const Z_Device & device = devicesAt(found); - return device.friendlyName; - } - return nullptr; -} - -const char * Z_Devices::getModelId(uint16_t shortaddr) const { - int32_t found = findShortAddr(shortaddr); - if (found >= 0) { - const Z_Device & device = devicesAt(found); - return device.modelId; - } - return nullptr; -} - -const char * Z_Devices::getManufacturerId(uint16_t shortaddr) const { - int32_t found = findShortAddr(shortaddr); - if (found >= 0) { - const Z_Device & device = devicesAt(found); - return device.manufacturerId; - } - 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); + getShortAddr(shortaddr).setReachable(reachable); } void Z_Devices::setLQI(uint16_t shortaddr, uint8_t lqi) { if (shortaddr == localShortAddr) { return; } - Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found - device.linkquality = lqi; -} - -uint8_t Z_Devices::getLQI(uint16_t shortaddr) const { - if (shortaddr == localShortAddr) { return 0xFF; } - int32_t found = findShortAddr(shortaddr); - if (found >= 0) { - const Z_Device & device = devicesAt(found); - return device.linkquality; - } - return 0xFF; + getShortAddr(shortaddr).lqi = lqi; } void Z_Devices::setBatteryPercent(uint16_t shortaddr, uint8_t bp) { - Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found - device.batterypercent = bp; -} - -uint8_t Z_Devices::getBatteryPercent(uint16_t shortaddr) const { - int32_t found = findShortAddr(shortaddr); - if (found >= 0) { - const Z_Device & device = devicesAt(found); - return device.batterypercent; - } - return 0xFF; + getShortAddr(shortaddr).batterypercent = bp; } // 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); + int32_t short_found = findShortAddrIdx(shortaddr); if (short_found >= 0) { Z_Device &device = getShortAddr(shortaddr); device.seqNumber += 1; @@ -732,7 +682,7 @@ void Z_Devices::updateZbProfile(uint16_t shortaddr) { { 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 (!device.validPower()) { device.setPower(false); } if (1 <= channels) { if (0xFF == device.dimmer) { device.dimmer = 0; } } @@ -754,12 +704,7 @@ void Z_Devices::updateZbProfile(uint16_t shortaddr) { // 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]->zb_profile; - } else { - return 0xFF; // Hue not activated - } + return findShortAddr(shortaddr).zb_profile; } // Hue support @@ -793,7 +738,7 @@ void Z_Devices::hideHueBulb(uint16_t shortaddr, bool hidden) { } // 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); + int32_t found = findShortAddrIdx(shortaddr); if (found >= 0) { uint8_t zb_profile = _devices[found]->zb_profile; if (0x00 == (zb_profile & 0xF0)) { @@ -804,50 +749,6 @@ bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const { return true; // Fallback - Device is considered as hidden } -// Hue support -void Z_Devices::updateHueState(uint16_t shortaddr, - 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 bool *reachable) { - Z_Device &device = getShortAddr(shortaddr); - if (power) { bitWrite(device.power, 0, *power); } - if (colormode){ device.colormode = *colormode; } - if (dimmer) { device.dimmer = *dimmer; } - if (sat) { device.sat = *sat; } - if (ct) { device.ct = *ct; } - 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, - bool *power, uint8_t *colormode, - uint8_t *dimmer, uint8_t *sat, - uint16_t *ct, uint16_t *hue, - 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 = bitRead(device.power, 0); } - if (colormode){ *colormode = device.colormode; } - if (dimmer) { *dimmer = device.dimmer; } - if (sat) { *sat = device.sat; } - if (ct) { *ct = device.ct; } - 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; - } -} - // Deferred actions // Parse for a specific category, of all deferred for a device if category == 0xFF void Z_Devices::resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category) { @@ -907,8 +808,6 @@ void Z_Devices::runTimer(void) { // Clear the JSON buffer for coalesced and deferred attributes void Z_Devices::jsonClear(uint16_t shortaddr) { Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found - device.json = nullptr; device.json_buffer->clear(); } @@ -961,7 +860,6 @@ void CopyJsonObject(JsonObject &to, const JsonObject &from) { // false - new attributes can be safely added bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) { Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return false; } // don't crash if not found if (&values == nullptr) { return false; } if (nullptr == device.json) { @@ -1005,7 +903,6 @@ bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) { void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) { Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found if (&values == nullptr) { return; } if (nullptr == device.json) { @@ -1026,16 +923,14 @@ void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) { } const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) { - Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return nullptr; } // don't crash if not found - return device.json; + return getShortAddr(shortaddr).json; } void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { Z_Device & device = getShortAddr(shortaddr); - if (&device == nullptr) { return; } // don't crash if not found + if (!device.valid()) { return; } // safeguard JsonObject & json = *device.json; - if (&json == nullptr) { return; } // abort if nothing in buffer + if (&json == nullptr) { return; } // abort if nothing in buffer const char * fname = zigbee_devices.getFriendlyName(shortaddr); bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? @@ -1111,7 +1006,7 @@ uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_know // expect a short address shortaddr = strtoull(dataBuf, nullptr, 0); if (short_must_be_known) { - shortaddr = zigbee_devices.isKnownShortAddr(shortaddr); + shortaddr = zigbee_devices.findShortAddr(shortaddr).shortaddr; // if not found, it reverts to the unknown_device with address BAD_SHORTADDR } // else we don't check if it's already registered to force unregistered devices } else { @@ -1133,7 +1028,7 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const { JsonObject& json = jsonBuffer.createObject(); char hex[8]; - int32_t found = findShortAddr(shortaddr); + int32_t found = findShortAddrIdx(shortaddr); if (found >= 0) { const Z_Device & device = devicesAt(found); const char * fname = getFriendlyName(shortaddr); @@ -1153,15 +1048,15 @@ 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)] = 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; } + dev[F("Reachable")] = device.getReachable(); // TODO TODO + if (device.validPower()) { dev[F("Power")] = device.getPower(); } + if (device.validDimmer()) { dev[F("Dimmer")] = device.dimmer; } + if (device.validColormode()) { dev[F("Colormode")] = device.colormode; } + if (device.validCT()) { dev[F("CT")] = device.ct; } + if (device.validSat()) { dev[F("Sat")] = device.sat; } + if (device.validHue()) { dev[F("Hue")] = device.hue; } + if (device.validX()) { dev[F("X")] = device.x; } + if (device.validY()) { dev[F("Y")] = device.y; } } String payload = ""; diff --git a/tasmota/xdrv_23_zigbee_3_hue.ino b/tasmota/xdrv_23_zigbee_3_hue.ino index c1349d4fd..2accf849c 100644 --- a/tasmota/xdrv_23_zigbee_3_hue.ino +++ b/tasmota/xdrv_23_zigbee_3_hue.ino @@ -36,7 +36,17 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri 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, &reachable); + const Z_Device & device = zigbee_devices.findShortAddr(shortaddr); + // TODO TODO check also validity + bri = device.dimmer; + power = device.getPower(); + colormode = device.colormode; + sat = device.sat; + ct = device.ct; + hue = device.hue; + x = device.x; + y = device.y; + reachable = device.getReachable(); if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254 if (bri < 1) bri = 1; @@ -78,9 +88,10 @@ void HueLightStatus2Zigbee(uint16_t shortaddr, String *response) const size_t buf_size = 300; char * buf = (char*) malloc(buf_size); - const char * friendlyName = zigbee_devices.getFriendlyName(shortaddr); - const char * modelId = zigbee_devices.getModelId(shortaddr); - const char * manufacturerId = zigbee_devices.getManufacturerId(shortaddr); + 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); @@ -137,7 +148,7 @@ void ZigbeeHueGroups(String * lights) { // Power On/Off void ZigbeeHuePower(uint16_t shortaddr, bool power) { zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0006, power ? 1 : 0, ""); - zigbee_devices.updateHueState(shortaddr, &power, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + zigbee_devices.getShortAddr(shortaddr).setPower(power); } // Dimmer @@ -146,7 +157,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, 0, 0x0008, 0x04, param); - zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, &dimmer, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + zigbee_devices.getShortAddr(shortaddr).dimmer = dimmer; } // CT @@ -157,7 +168,9 @@ 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, 0, 0x0300, 0x0A, param); - zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, &ct, nullptr, nullptr, nullptr, nullptr); + Z_Device & device = zigbee_devices.getShortAddr(shortaddr); + device.colormode = colormode; + device.ct = ct; } // XY @@ -168,7 +181,10 @@ 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, 0, 0x0300, 0x07, param); - zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, nullptr, nullptr, &x, &y, nullptr); + Z_Device & device = zigbee_devices.getShortAddr(shortaddr); + device.colormode = colormode; + device.x = x; + device.y = y; } // HueSat @@ -179,7 +195,10 @@ 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, 0, 0x0300, 0x06, param); - zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, &sat, nullptr, &hue, nullptr, nullptr, nullptr); + Z_Device device = zigbee_devices.getShortAddr(shortaddr); + device.colormode = colormode; + device.sat = sat; + device.hue = hue; } void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) { diff --git a/tasmota/xdrv_23_zigbee_5__constants.ino b/tasmota/xdrv_23_zigbee_5__constants.ino index 42aa3237a..01ac1834a 100644 --- a/tasmota/xdrv_23_zigbee_5__constants.ino +++ b/tasmota/xdrv_23_zigbee_5__constants.ino @@ -445,7 +445,7 @@ const char Z_strings[] PROGMEM = "AddScene" "\x00" "xxyyyyzz" "\x00" "StoreScene" "\x00" - "\x00"; + ; enum Z_offsets { diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 7ab7a6be2..bde944132 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -626,6 +626,7 @@ public: void parseResponse(void); void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0); void postProcessAttributes(uint16_t shortaddr, JsonObject& json); + void updateInternalAttributes(uint16_t shortaddr, JsonObject& json); inline void setGroupId(uint16_t groupid) { _groupaddr = groupid; @@ -1224,7 +1225,7 @@ int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clu // Aqara Cube int32_t Z_AqaraCubeFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { - const char * modelId_c = zigbee_devices.getModelId(shortaddr); // null if unknown + const char * modelId_c = zigbee_devices.findShortAddr(shortaddr).modelId; // null if unknown String modelId((char*) modelId_c); if (modelId.startsWith(F("lumi.sensor_cube"))) { // only for Aqara cube @@ -1487,6 +1488,24 @@ int32_t Z_ApplyConverter(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObje return 1; // Fix GCC 10.1 warning } +// Scan all the final attributes and update any internal representation like sensors +void ZCLFrame::updateInternalAttributes(uint16_t shortaddr, JsonObject& json) { + Z_Device & device = zigbee_devices.getShortAddr(shortaddr); + for (auto kv : json) { + String key_string = kv.key; + const char * key = key_string.c_str(); + JsonVariant& value = kv.value; + + if (key_string.equalsIgnoreCase(F("Temperature"))) { + device.temperature = value.as() * 10 + 0.5f; + } else if (key_string.equalsIgnoreCase(F("Humidity"))) { + device.humidity = value.as() + 0.5f; + } else if (key_string.equalsIgnoreCase(F("Pressure"))) { + device.pressure = value.as() + 0.5f; + } + } +} + void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { // source endpoint uint8_t src_ep = _srcendpoint; @@ -1509,40 +1528,26 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { suffix = strtoul(delimiter2+1, nullptr, 10); } + Z_Device & device = zigbee_devices.getShortAddr(shortaddr); + uint16_t val16 = value; // call converter from JSonVariant to int only once // see if we need to update the Hue bulb status if ((cluster == 0x0006) && ((attribute == 0x0000) || (attribute == 0x8000))) { bool power = value; - zigbee_devices.updateHueState(shortaddr, &power, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr); + device.setPower(power); } else if ((cluster == 0x0008) && (attribute == 0x0000)) { - uint8_t dimmer = value; - zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, &dimmer, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr); + device.dimmer = val16; } 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); + device.hue = changeUIntScale(val16, 0, 254, 0, 360); // change range from 0..254 to 0..360 } else if ((cluster == 0x0300) && (attribute == 0x0001)) { - uint8_t sat = value; - zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, &sat, - nullptr, nullptr, nullptr, nullptr, nullptr); + device.sat = val16; } else if ((cluster == 0x0300) && (attribute == 0x0003)) { - uint16_t x = value; - zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, &x, nullptr, nullptr); + device.x = val16; } else if ((cluster == 0x0300) && (attribute == 0x0004)) { - uint16_t y = value; - zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, &y, nullptr), nullptr; + device.y = val16; } else if ((cluster == 0x0300) && (attribute == 0x0007)) { - uint16_t ct = value; - zigbee_devices.updateHueState(shortaddr, nullptr, nullptr, nullptr, nullptr, - &ct, nullptr, nullptr, nullptr, nullptr); + device.ct = val16; } else if ((cluster == 0x0300) && (attribute == 0x0008)) { - uint8_t colormode = value; - zigbee_devices.updateHueState(shortaddr, nullptr, &colormode, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr); + device.colormode = val16; } // Iterate on filter @@ -1557,11 +1562,6 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { ((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) { String new_name_str = (const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset)); if (suffix > 1) { new_name_str += suffix; } // append suffix number - // else if (Settings.flag4.zb_index_ep) { - // if (zigbee_devices.countEndpoints(shortaddr) > 0) { - // new_name_str += _srcendpoint; - // } - // } // apply the transformation int32_t drop = Z_ApplyConverter(this, shortaddr, json, key, value, new_name_str, conv_cluster, conv_attribute, conv_multiplier, conv_cb); if (drop) { @@ -1572,6 +1572,8 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { } } } + + updateInternalAttributes(shortaddr, json); } #endif // USE_ZIGBEE diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 80e26ec19..e9a9722f4 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -1328,46 +1328,61 @@ void ZigbeeShow(bool json) const uint8_t px_lqi = (strlen(D_LQI) + 4) * 10; // LQI 254 = 70px WSContentSend_P(PSTR("{t}")); // Terminate current two column table and open new table -// WSContentSend_P(PSTR("{t}")); // Insert multi column table - -// WSContentSend_PD(PSTR("{s}Device 0x1234" D_BATT " 100%%" D_LQI " 254{e}")); -// WSContentSend_PD(PSTR("{s}Device 0x1234" D_BATT " 100%%" D_LQI " 254{e}")); -// WSContentSend_PD(PSTR("{s}Device 0x1234" D_BATT " 100%%" D_LQI " 254{e}"), px_batt, px_lqi); - - char sdevice[33]; - char sbatt[20]; - char slqi[20]; for (uint32_t i = 0; i < zigbee_num; i++) { - uint16_t shortaddr = zigbee_devices.devicesAt(i).shortaddr; - char *name = (char*)zigbee_devices.getFriendlyName(shortaddr); - if (nullptr == name) { - snprintf_P(sdevice, sizeof(sdevice), PSTR(D_DEVICE " 0x%04X"), shortaddr); - name = sdevice; + const Z_Device &device = zigbee_devices.devicesAt(i); + uint16_t shortaddr = device.shortaddr; + { // exxplicit scope to free up stack allocated strings + char *name = (char*) device.friendlyName; + char sdevice[33]; + if (nullptr == name) { + snprintf_P(sdevice, sizeof(sdevice), PSTR(D_DEVICE " 0x%04X"), shortaddr); + name = sdevice; + } + + char slqi[8]; + snprintf_P(slqi, sizeof(slqi), PSTR("-")); + if (device.validLqi()) { + snprintf_P(slqi, sizeof(slqi), PSTR("%d"), device.lqi); + } + + char sbatt[20]; + snprintf_P(sbatt, sizeof(sbatt), PSTR(" ")); + if (device.validBatteryPercent()) { + snprintf_P(sbatt, sizeof(sbatt), PSTR(D_BATT " %d%%"), device.batterypercent); + } + + if (!i) { // First row needs style info + WSContentSend_PD(PSTR("%s%s" D_LQI " %s{e}"), + name, px_batt, sbatt, px_lqi, slqi); + } else { // Following rows don't need style info so reducing ajax package + WSContentSend_PD(PSTR("%s%s" D_LQI " %s{e}"), name, sbatt, slqi); + } } - snprintf_P(slqi, sizeof(slqi), PSTR("-")); - uint8_t lqi = zigbee_devices.getLQI(shortaddr); - if (0xFF != lqi) { - snprintf_P(slqi, sizeof(slqi), PSTR("%d"), lqi); - } + // Sensor + bool temperature_ok = device.validTemperature(); + bool humidity_ok = device.validHumidity(); + bool pressure_ok = device.validPressure(); - snprintf_P(sbatt, sizeof(sbatt), PSTR(" ")); - uint8_t bp = zigbee_devices.getBatteryPercent(shortaddr); - if (0xFF != bp) { - snprintf_P(sbatt, sizeof(sbatt), PSTR(D_BATT " %d%%"), bp); - } - - if (!i) { // First row needs style info - WSContentSend_PD(PSTR("{s}%s%s" D_LQI " %s{e}"), - name, px_batt, sbatt, px_lqi, slqi); - } else { // Following rows don't need style info so reducing ajax package - WSContentSend_PD(PSTR("{s}%s{m}%s" D_LQI " %s{e}"), name, sbatt, slqi); + if (temperature_ok || humidity_ok || pressure_ok) { + WSContentSend_P(PSTR("| ")); + if (temperature_ok) { + char buf[12]; + dtostrf(device.temperature / 10.0f, 3, 1, buf); + WSContentSend_PD(PSTR("  ☀️%s°C"), buf); + } + if (humidity_ok) { + WSContentSend_P(PSTR("  💧%d%%"), device.humidity); + } + if (pressure_ok) { + WSContentSend_P(PSTR("  ⛅ %d hPa"), device.pressure); + } + WSContentSend_P(PSTR("{e}")); } } WSContentSend_P(PSTR("{t}")); // Terminate current multi column table and open new table -// WSContentSend_P(PSTR("{e}")); // Terminate multi column table #endif } }