diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 8c60b3c69..3d8254960 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -565,6 +565,7 @@ #define D_CMND_ZIGBEE_RESPONSE "Response" #define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent" #define D_JSON_ZIGBEE_RECEIVED "ZbReceived" +#define D_JSON_ZIGBEE_INFO "ZbInfo" #define D_CMND_ZIGBEE_BIND "Bind" #define D_JSON_ZIGBEE_BIND "ZbBind" #define D_CMND_ZIGBEE_UNBIND "Unbind" diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index db8f70428..8163c572d 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -73,7 +73,7 @@ public: inline uint8_t getEndpoint(void) const { return _endpoint; } - void toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const; + void toAttributes(Z_attribute_list & attr_list) const; // update internal structures after an attribut update // True if a configuration was changed @@ -215,7 +215,7 @@ public: 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 validHue(void) const { return 0xFFFF != hue; } + inline bool validHue(void) const { return 0xFF != hue; } inline bool validCT(void) const { return 0xFFFF != ct; } inline bool validX(void) const { return 0xFFFF != x; } inline bool validY(void) const { return 0xFFFF != y; } @@ -674,6 +674,18 @@ public: void setLastSeenNow(void); + // multiple function to dump part of the Device state into JSON + void jsonAddDeviceNamme(Z_attribute_list & attr_list) const; + void jsonAddIEEE(Z_attribute_list & attr_list) const; + 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 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; + void jsonPublishAttrList(const char * json_prefix, const Z_attribute_list &attr_list) const; + void jsonLightState(Z_attribute_list & attr_list) const; + // dump device attributes to ZbData void toAttributes(Z_attribute_list & attr_list) const; @@ -791,9 +803,7 @@ public: uint8_t getNextSeqNumber(uint16_t shortaddr); // Dump json - static String dumpLightState(const Z_Device & device); String dumpDevice(uint32_t dump_mode, const Z_Device & device) const; - static String dumpSingleDevice(uint32_t dump_mode, const class Z_Device & device, bool add_device_name = true, bool add_brackets = true); int32_t deviceRestore(JsonParserObject json); // Hue support @@ -810,7 +820,6 @@ public: // Append or clear attributes Json structure void jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list); - static void jsonPublishFlushAttrList(const Z_Device & device, const String & attr_list_string); void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const; void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list); diff --git a/tasmota/xdrv_23_zigbee_2a_devices_impl.ino b/tasmota/xdrv_23_zigbee_2a_devices_impl.ino index 7ad12557b..a7aa91fcc 100644 --- a/tasmota/xdrv_23_zigbee_2a_devices_impl.ino +++ b/tasmota/xdrv_23_zigbee_2a_devices_impl.ino @@ -497,39 +497,38 @@ void Z_Devices::jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list // // internal function to publish device information with respect to all `SetOption`s // -void Z_Devices::jsonPublishFlushAttrList(const Z_Device & device, const String & attr_list_string) { - const char * fname = zigbee_devices.getFriendlyName(device.shortaddr); - bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? +void Z_Device::jsonPublishAttrList(const char * json_prefix, const Z_attribute_list &attr_list) const { + bool use_fname = (Settings.flag4.zigbee_use_names) && (friendlyName); // should we replace shortaddr with friendlyname? TasmotaGlobal.mqtt_data[0] = 0; // clear string // Do we prefix with `ZbReceived`? if (!Settings.flag4.remove_zbreceived) { - Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":")); + Response_P(PSTR("{\"%s\":"), json_prefix); } // What key do we use, shortaddr or name? if (use_fname) { - Response_P(PSTR("%s{\"%s\":{"), TasmotaGlobal.mqtt_data, fname); + Response_P(PSTR("%s{\"%s\":{"), TasmotaGlobal.mqtt_data, friendlyName); } else { - Response_P(PSTR("%s{\"0x%04X\":{"), TasmotaGlobal.mqtt_data, device.shortaddr); + Response_P(PSTR("%s{\"0x%04X\":{"), TasmotaGlobal.mqtt_data, shortaddr); } // Add "Device":"0x...." - ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), device.shortaddr); + ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), shortaddr); // Add "Name":"xxx" if name is present - if (fname) { - ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), EscapeJSONString(fname).c_str()); + if (friendlyName) { + ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), EscapeJSONString(friendlyName).c_str()); } // Add all other attributes - ResponseAppend_P(PSTR("%s}}"), attr_list_string.c_str()); + ResponseAppend_P(PSTR("%s}}"), attr_list.toString(false).c_str()); if (!Settings.flag4.remove_zbreceived) { ResponseAppend_P(PSTR("}")); } if (Settings.flag4.zigbee_distinct_topics) { - if (Settings.flag4.zb_topic_fname && fname) { + if (Settings.flag4.zb_topic_fname && friendlyName) { //Clean special characters and check size of friendly name char stemp[TOPSZ]; - strlcpy(stemp, (!strlen(fname)) ? MQTT_TOPIC : fname, sizeof(stemp)); + strlcpy(stemp, (!strlen(friendlyName)) ? MQTT_TOPIC : friendlyName, sizeof(stemp)); MakeValidMqtt(0, stemp); //Create topic with Prefix3 and cleaned up friendly name char frtopic[TOPSZ]; @@ -537,7 +536,7 @@ void Z_Devices::jsonPublishFlushAttrList(const Z_Device & device, const String & MqttPublish(frtopic, Settings.flag.mqtt_sensor_retain); } else { char subtopic[16]; - snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), device.shortaddr); + snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr); MqttPublishPrefixTopic_P(TELE, subtopic, Settings.flag.mqtt_sensor_retain); } } else { @@ -557,7 +556,7 @@ void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup% gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint% - jsonPublishFlushAttrList(device, attr_list.toString()); + device.jsonPublishAttrList(PSTR(D_JSON_ZIGBEE_RECEIVED), attr_list); attr_list.reset(); // clear the attributes } } @@ -615,110 +614,125 @@ Z_Device & Z_Devices::parseDeviceFromName(const char * param, bool short_must_be } } -// Display the tracked status for a light -String Z_Devices::dumpLightState(const Z_Device & device) { - Z_attribute_list attr_list; - char hex[8]; +/*********************************************************************************************\ + * + * Methods below build a JSON representation of device data + * Used by: ZbLight, ZbStatus, ZbInfo + * +\*********************************************************************************************/ - const char * fname = device.friendlyName; +// Add "Device":"0x1234","Name":"FrienflyName" +void Z_Device::jsonAddDeviceNamme(Z_attribute_list & attr_list) const { + char hex[8]; + const char * fname = friendlyName; bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? - snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), device.shortaddr); + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr); attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex); if (fname) { attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(fname); } +} +// Add "IEEEAddr":"0x1234567812345678" +void Z_Device::jsonAddIEEE(Z_attribute_list & attr_list) const { + char hex[22]; + hex[0] = '0'; // prefix with '0x' + hex[1] = 'x'; + Uint64toHex(longaddr, &hex[2], 64); + attr_list.addAttribute(F("IEEEAddr")).setStr(hex); +} +// Add "ModelId":"","Manufacturer":"" +void Z_Device::jsonAddModelManuf(Z_attribute_list & attr_list) const { + if (modelId) { + attr_list.addAttribute(F(D_JSON_MODEL D_JSON_ID)).setStr(modelId); + } + if (manufacturerId) { + attr_list.addAttribute(F("Manufacturer")).setStr(manufacturerId); + } +} +// Add "Endpoints":[...] +void Z_Device::jsonAddEndpoints(Z_attribute_list & attr_list) const { + JsonGeneratorArray arr_ep; + for (uint32_t i = 0; i < endpoints_max; i++) { + uint8_t endpoint = endpoints[i]; + if (0x00 == endpoint) { break; } + arr_ep.add(endpoint); + } + attr_list.addAttribute(F("Endpoints")).setStrRaw(arr_ep.toString().c_str()); +} +// Add "Config":["",""...] +void Z_Device::jsonAddConfig(Z_attribute_list & attr_list) const { + JsonGeneratorArray arr_data; + for (auto & data_elt : data) { + char key[8]; + if (data_elt.validConfig()) { + snprintf_P(key, sizeof(key), "?%02X.%1X", data_elt.getEndpoint(), data_elt.getConfig()); + } else { + snprintf_P(key, sizeof(key), "?%02X", data_elt.getEndpoint()); + } + key[0] = Z_Data::DataTypeToChar(data_elt.getType()); + arr_data.addStr(key); + } + attr_list.addAttribute(F("Config")).setStrRaw(arr_data.toString().c_str()); +} +// Add All data attributes +void Z_Device::jsonAddDataAttributes(Z_attribute_list & attr_list) const { + // show internal data - mostly last known values + for (auto & data_elt : data) { + data_elt.toAttributes(attr_list); + } +} +// Add "BatteryPercentage", "LastSeen", "LastSeenEpoch", "LinkQuality" +void Z_Device::jsonAddDeviceAttributes(Z_attribute_list & attr_list) const { + attr_list.addAttribute(F("Reachable")).setBool(getReachable()); + if (validBatteryPercent()) { attr_list.addAttribute(PSTR("BatteryPercentage")).setUInt(batterypercent); } + if (validLastSeen()) { + if (Rtc.utc_time >= last_seen) { + attr_list.addAttribute(PSTR("LastSeen")).setUInt(Rtc.utc_time - last_seen); + } + attr_list.addAttribute(PSTR("LastSeenEpoch")).setUInt(last_seen); + } + if (validLqi()) { attr_list.addAttribute(PSTR(D_CMND_ZIGBEE_LINKQUALITY)).setUInt(lqi); } +} - if (device.valid()) { + +// Display the tracked status for a light +void Z_Device::jsonLightState(Z_attribute_list & attr_list) const { + if (valid()) { // dump all known values - attr_list.addAttribute(F("Reachable")).setBool(device.getReachable()); - if (device.validPower()) { attr_list.addAttribute(F("Power")).setUInt(device.getPower()); } - const Z_Data_Light & light = device.data.find(0); + attr_list.addAttribute(F("Reachable")).setBool(getReachable()); + if (validPower()) { attr_list.addAttribute(F("Power")).setUInt(getPower()); } + const Z_Data_Light & light = data.find(0); if (&light != nullptr) { - light.toAttributes(attr_list, Z_Data_Light::type); + light.toAttributes(attr_list); // Exception, we need to convert Hue to 0..360 instead of 0..254 if (light.validHue()) { attr_list.findOrCreateAttribute(PSTR("Hue")).setUInt(light.getHue()); } } - // Z_Data_Light::toAttributes(attr_list, device.data.find(0)); } - - Z_attribute_list attr_list_root; - Z_attribute * attr_root; - if (use_fname) { - attr_root = &attr_list_root.addAttribute(fname); - } else { - attr_root = &attr_list_root.addAttribute(hex); - } - attr_root->setStrRaw(attr_list.toString(true).c_str()); - return attr_list_root.toString(true); } -// Dump the internal memory of Zigbee devices +// Dump the internal memory of Zigbee devices - does not include "Device" and "Name" // Mode = 1: simple dump of devices addresses // Mode = 2: simple dump of devices addresses and names, endpoints, light // Mode = 3: dump last known data attributes -// add_device_name : do we add shortaddr/name ? -String Z_Devices::dumpSingleDevice(uint32_t dump_mode, const class Z_Device & device, bool add_device_name, bool add_brackets) { - uint16_t shortaddr = device.shortaddr; - char hex[22]; - - Z_attribute_list attr_list; - - if (add_device_name) { - snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr); - attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex); - - if (device.friendlyName > 0) { - attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(device.friendlyName); - } +// String Z_Device::dumpSingleDevice(uint32_t dump_mode, bool add_device_name, bool add_brackets) const { +void Z_Device::jsonDumpSingleDevice(Z_attribute_list & attr_list, uint32_t dump_mode, bool add_name) const { + if (add_name) { + jsonAddDeviceNamme(attr_list); } - if (dump_mode >= 2) { - hex[0] = '0'; // prefix with '0x' - hex[1] = 'x'; - Uint64toHex(device.longaddr, &hex[2], 64); - attr_list.addAttribute(F("IEEEAddr")).setStr(hex); - if (device.modelId) { - attr_list.addAttribute(F(D_JSON_MODEL D_JSON_ID)).setStr(device.modelId); - } - if (device.manufacturerId) { - attr_list.addAttribute(F("Manufacturer")).setStr(device.manufacturerId); - } - - JsonGeneratorArray arr_ep; - for (uint32_t i = 0; i < endpoints_max; i++) { - uint8_t endpoint = device.endpoints[i]; - if (0x00 == endpoint) { break; } - arr_ep.add(endpoint); - } - attr_list.addAttribute(F("Endpoints")).setStrRaw(arr_ep.toString().c_str()); - - JsonGeneratorArray arr_data; - for (auto & data_elt : device.data) { - char key[8]; - if (data_elt.validConfig()) { - snprintf_P(key, sizeof(key), "?%02X.%1X", data_elt.getEndpoint(), data_elt.getConfig()); - } else { - snprintf_P(key, sizeof(key), "?%02X", data_elt.getEndpoint()); - } - key[0] = Z_Data::DataTypeToChar(data_elt.getType()); - arr_data.addStr(key); - } - attr_list.addAttribute(F("Config")).setStrRaw(arr_data.toString().c_str()); + jsonAddIEEE(attr_list); + jsonAddModelManuf(attr_list); + jsonAddEndpoints(attr_list); + jsonAddConfig(attr_list); } if (dump_mode >= 3) { - // show internal data - mostly last known values - for (auto & data_elt : device.data) { - Z_Data_Type data_type = data_elt.getType(); - - data_elt.toAttributes(attr_list, data_type); - } + jsonAddDataAttributes(attr_list); // add device wide attributes - device.toAttributes(attr_list); + jsonAddDeviceAttributes(attr_list); } - return attr_list.toString(add_brackets); } // If &device == nullptr, then dump all @@ -729,11 +743,15 @@ String Z_Devices::dumpDevice(uint32_t dump_mode, const Z_Device & device) const if (dump_mode < 2) { // dump light mode for all devices for (const auto & device2 : _devices) { - json_arr.addStrRaw(dumpSingleDevice(dump_mode, device2).c_str()); + Z_attribute_list attr_list; + device2.jsonDumpSingleDevice(attr_list, dump_mode, true); + json_arr.addStrRaw(attr_list.toString(true).c_str()); } } } else { - json_arr.addStrRaw(dumpSingleDevice(dump_mode, device).c_str()); + Z_attribute_list attr_list; + device.jsonDumpSingleDevice(attr_list, dump_mode, true); + json_arr.addStrRaw(attr_list.toString(true).c_str()); } return json_arr.toString(); @@ -814,20 +832,6 @@ Z_Data_Light & Z_Devices::getLight(uint16_t shortaddr) { return getShortAddr(shortaddr).data.get(); } -/*********************************************************************************************\ - * Export device specific attributes to ZbData -\*********************************************************************************************/ -void Z_Device::toAttributes(Z_attribute_list & attr_list) const { - if (validBatteryPercent()) { attr_list.addAttribute(PSTR("BatteryPercentage")).setUInt(batterypercent); } - if (validLastSeen()) { - if (Rtc.utc_time >= last_seen) { - attr_list.addAttribute(PSTR("LastSeen")).setUInt(Rtc.utc_time - last_seen); - } - attr_list.addAttribute(PSTR("LastSeenEpoch")).setUInt(last_seen); - } - if (validLqi()) { attr_list.addAttribute(PSTR(D_CMND_ZIGBEE_LINKQUALITY)).setUInt(lqi); } -} - /*********************************************************************************************\ * Device specific data handlers \*********************************************************************************************/ diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 9a3114ab6..7ed706771 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -1967,7 +1967,8 @@ bool Z_parseAttributeKey(class Z_attribute & attr) { // Input: // the Json object to add attributes to // the type of object (necessary since the type system is unaware of the actual sub-type) -void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const { +void Z_Data::toAttributes(Z_attribute_list & attr_list) const { + Z_Data_Type type = getType(); // iterate through attributes to see which ones need to be exported for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) { const Z_AttributeConverter *converter = &Z_PostProcess[i]; diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index dcacff1ed..c1066b296 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -1111,10 +1111,11 @@ void CmndZbLight(void) { if (bulbtype < -1) { bulbtype = -1; } device.setLightChannels(bulbtype); } - String dump = zigbee_devices.dumpLightState(device); - Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_LIGHT "\":%s}"), dump.c_str()); + Z_attribute_list attr_list; + device.jsonLightState(attr_list); + + device.jsonPublishAttrList(PSTR(D_PRFX_ZB D_CMND_ZIGBEE_LIGHT), attr_list); // publish as ZbReceived - MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_LIGHT)); ResponseCmndDone(); } // @@ -1194,8 +1195,9 @@ void CmndZbInfo(void) { // everything is good, we can send the command - String device_info = Z_Devices::dumpSingleDevice(3, device, false, false); - Z_Devices::jsonPublishFlushAttrList(device, device_info); + Z_attribute_list attr_list; + device.jsonDumpSingleDevice(attr_list, 3, false); // don't add Device/Name + device.jsonPublishAttrList(PSTR(D_JSON_ZIGBEE_INFO), attr_list); // publish as ZbReceived ResponseCmndDone(); }