diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index 1a68f7c98..72294645c 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -4,6 +4,7 @@ * Add support for Chint DDSU666 Modbus energy meter by Pablo Zerón * Add support for SM2135 as used in Action LSC Smart Led E14 (#6495) * Add command SetOption72 0/1 to switch between software (0) or hardware (1) energy total counter (#6561) + * Add Zigbee tracking of connected devices and auto-probing of Manuf/Model Ids * * 6.6.0.14 20190925 * Change command Tariffx to allow time entries like 23 (hours), 1320 (minutes) or 23:00. NOTE: As this is development branch previous tariffs are lost! (#6488) diff --git a/sonoff/i18n.h b/sonoff/i18n.h index 089140dd9..0d7cdf0a9 100644 --- a/sonoff/i18n.h +++ b/sonoff/i18n.h @@ -458,12 +458,13 @@ // Commands xdrv_23_zigbee.ino #define D_CMND_ZIGBEE_PERMITJOIN "ZigbeePermitJoin" -#define D_CMND_ZIGBEE_DUMP "ZigbeeDump" +#define D_CMND_ZIGBEE_STATUS "ZigbeeStatus" #define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend" #define D_JSON_ZIGBEE_STATUS "ZigbeeStatus" #define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived" #define D_JSON_ZIGBEEZNPSENT "ZigbeeZNPSent" - #define D_JSON_ZIGBEEZCLRECEIVED "ZigbeeZCLReceived" + #define D_JSON_ZIGBEEZCL_RECEIVED "ZigbeeZCLReceived" + #define D_JSON_ZIGBEEZCL_RAW_RECEIVED "ZigbeeZCLRawReceived" #define D_JSON_ZIGBEEZCLSENT "ZigbeeZCLSent" // Commands xdrv_25_A4988_Stepper.ino diff --git a/sonoff/xdrv_23_zigbee_0_constants.ino b/sonoff/xdrv_23_zigbee_0_constants.ino index 651596878..8ff4174eb 100644 --- a/sonoff/xdrv_23_zigbee_0_constants.ino +++ b/sonoff/xdrv_23_zigbee_0_constants.ino @@ -422,7 +422,7 @@ enum ZCL_Global_Commands { }; -enum class ZclGlobalCommandId : uint8_t { -}; +const uint16_t Z_ProfileIds[] PROGMEM = { 0x0104, 0x0109, 0xA10E, 0xC05E }; +const char Z_ProfileNames[] PROGMEM = "ZigBee Home Automation|ZigBee Smart Energy|ZigBee Green Power|ZigBee Light Link"; #endif // USE_ZIGBEE diff --git a/sonoff/xdrv_23_zigbee_3_devices.ino b/sonoff/xdrv_23_zigbee_3_devices.ino new file mode 100644 index 000000000..78339b073 --- /dev/null +++ b/sonoff/xdrv_23_zigbee_3_devices.ino @@ -0,0 +1,413 @@ +/* + xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota + + Copyright (C) 2019 Theo Arends and Stephan Hadinger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_ZIGBEE + +#include +#include + +typedef struct Z_Device { + uint16_t shortaddr; // unique key if not null, or unspecified if null + uint64_t longaddr; // 0x00 means unspecified + String manufacturerId; + String modelId; + String friendlyName; + std::vector endpoints; // encoded as high 16 bits is endpoint, low 16 bits is ProfileId + std::vector clusters_in; // encoded as high 16 bits is endpoint, low 16 bits is cluster number + std::vector clusters_out; // encoded as high 16 bits is endpoint, low 16 bits is cluster number +} Z_Device; + +// All devices are stored in a Vector +// Invariants: +// - shortaddr is unique if not null +// - longaddr is unique if not null +// - shortaddr and longaddr cannot be both null +// - clusters_in and clusters_out containt only endpoints listed in endpoints +class Z_Devices { +public: + Z_Devices() {}; + + // Add new device, provide ShortAddr and optional longAddr + // If it is already registered, update information, otherwise create the entry + void updateDevice(uint16_t shortaddr, uint64_t longaddr = 0); + + // Add an endpoint to a device + void addEndoint(uint16_t shortaddr, uint8_t endpoint); + + // Add endpoint profile + void addEndointProfile(uint16_t shortaddr, uint8_t endpoint, uint16_t profileId); + + // Add cluster + void addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster, bool out); + + uint8_t findClusterEndpointIn(uint16_t shortaddr, uint16_t cluster); + + void setManufId(uint16_t shortaddr, const char * str); + void setModelId(uint16_t shortaddr, const char * str); + void setFriendlyNameId(uint16_t shortaddr, const char * str); + + // Dump json + String dump(uint8_t dump_mode) const; + +private: + std::vector _devices = {}; + + template < typename T> + static bool findInVector(const std::vector & vecOfElements, const T & element); + + template < typename T> + static int32_t findEndpointInVector(const std::vector & vecOfElements, const T & element); + + // find the first endpoint match for a cluster + static int32_t findClusterEndpoint(const std::vector & vecOfElements, uint16_t element); + + Z_Device & getShortAddr(uint16_t shortaddr); // 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); + int32_t findLongAddr(uint64_t longaddr); + + // 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); +}; + +Z_Devices zigbee_devices = Z_Devices(); + +// https://thispointer.com/c-how-to-find-an-element-in-vector-and-get-its-index/ +template < typename T> +bool Z_Devices::findInVector(const std::vector & vecOfElements, const T & element) { + // Find given element in vector + auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element); + + if (it != vecOfElements.end()) { + return true; + } else { + return false; + } +} + +template < typename T> +int32_t Z_Devices::findEndpointInVector(const std::vector & vecOfElements, const T & element) { + // Find given element in vector + + int32_t found = 0; + for (auto &elem : vecOfElements) { + if ((elem >> 16) & 0xFF == element) { return found; } + found++; + } + + return -1; +} + +// +// Find the first endpoint match for a cluster, whether in or out +// Clusters are stored in the format 0x00EECCCC (EE=endpoint, CCCC=cluster number) +// In: +// _devices.clusters_in or _devices.clusters_out +// cluster number looked for +// Out: +// Index of found Endpoint_Cluster number, or -1 if not found +// +int32_t Z_Devices::findClusterEndpoint(const std::vector & vecOfElements, uint16_t cluster) { + int32_t found = 0; + for (auto &elem : vecOfElements) { + if ((elem & 0xFFFF) == cluster) { 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 (!shortaddr && !longaddr) { return *(Z_Device*) nullptr; } // it is not legal to create an enrty with both short/long addr null + Z_Device device = { shortaddr, longaddr, + String(), // ManufId + String(), // DeviceId + String(), // FriendlyName + std::vector(), + std::vector(), + std::vector() }; + _devices.push_back(device); + return _devices.back(); +} + +// +// Scan all devices to find a corresponding shortaddr +// Looks info device.shortaddr entry +// In: +// shortaddr (non null) +// Out: +// index in _devices of entry, -1 if not found +// +int32_t Z_Devices::findShortAddr(uint16_t shortaddr) { + if (!shortaddr) { return -1; } // does not make sense to look for 0x0000 shortaddr (localhost) + int32_t found = 0; + if (shortaddr) { + for (auto &elem : _devices) { + if (elem.shortaddr == shortaddr) { return found; } + found++; + } + } + return -1; +} +// +// Scan all devices to find a corresponding longaddr +// Looks info device.longaddr entry +// In: +// longaddr (non null) +// Out: +// index in _devices of entry, -1 if not found +// +int32_t Z_Devices::findLongAddr(uint64_t longaddr) { + if (!longaddr) { return -1; } + int32_t found = 0; + if (longaddr) { + for (auto &elem : _devices) { + if (elem.longaddr == longaddr) { return found; } + found++; + } + } + return -1; +} + +// +// We have a seen a shortaddr on the network, get the corresponding +// +Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) { + if (!shortaddr) { return *(Z_Device*) nullptr; } // this is not legal + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + return _devices[found]; + } +//Serial.printf("Device entry created for shortaddr = 0x%02X, found = %d\n", shortaddr, found); + return createDeviceEntry(shortaddr, 0); +} + +// 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; } + int32_t found = findLongAddr(longaddr); + if (found > 0) { + return _devices[found]; + } + return createDeviceEntry(0, longaddr); +} + +// +// We have just seen a device on the network, update the info based on short/long addr +// In: +// 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 l_found = findLongAddr(longaddr); // is there already a longaddr entry + + if ((s_found >= 0) && (l_found >= 0)) { // both shortaddr and longaddr are already registered + if (s_found == l_found) { + ; // short/long addr match, all good + } else { // they don't match + // the device with longaddr got a new shortaddr + _devices[l_found].shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr + // erase the previous shortaddr + _devices.erase(_devices.begin() + s_found); + } + } else if (s_found >= 0) { + // shortaddr already exists but longaddr not + // add the longaddr to the entry + _devices[s_found].longaddr = longaddr; + } else if (l_found >= 0) { + // longaddr entry exists, update shortaddr + _devices[l_found].shortaddr = shortaddr; + } else { + // neither short/lonf addr are found. + if (shortaddr || longaddr) { + createDeviceEntry(shortaddr, longaddr); + } + } +} + +// +// Add an endpoint to a shortaddr +// +void Z_Devices::addEndoint(uint16_t shortaddr, uint8_t endpoint) { + if (!shortaddr) { return; } + uint32_t ep_profile = (endpoint << 16); + Z_Device &device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + if (findEndpointInVector(device.endpoints, ep_profile) < 0) { // TODO search only on enpoint + device.endpoints.push_back(ep_profile); + } +} + +void Z_Devices::addEndointProfile(uint16_t shortaddr, uint8_t endpoint, uint16_t profileId) { + if (!shortaddr) { return; } + uint32_t ep_profile = (endpoint << 16) | profileId; + Z_Device &device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + int32_t found = findEndpointInVector(device.endpoints, ep_profile); + if (found < 0) { // TODO search only on enpoint + device.endpoints.push_back(ep_profile); + } else { + device.endpoints[found] = ep_profile; + } +} + +void Z_Devices::addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster, bool out) { + if (!shortaddr) { return; } + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + uint32_t ep_cluster = (endpoint << 16) | cluster; + if (!out) { + if (!findInVector(device.clusters_in, ep_cluster)) { + device.clusters_in.push_back(ep_cluster); + } + } else { // out + if (!findInVector(device.clusters_out, ep_cluster)) { + device.clusters_out.push_back(ep_cluster); + } + } +} + +// Look for the best endpoint match to send a command for a specific Cluster ID +// return 0x00 if none found +uint8_t Z_Devices::findClusterEndpointIn(uint16_t shortaddr, uint16_t cluster){ + Z_Device &device = getShortAddr(shortaddr); + if (&device == nullptr) { return 0; } // don't crash if not found + int32_t found = findClusterEndpoint(device.clusters_in, cluster); + if (found >= 0) { + return (device.clusters_in[found] >> 16) & 0xFF; + } else { + return 0; + } +} + + +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 + device.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 + device.modelId = str; +} +void Z_Devices::setFriendlyNameId(uint16_t shortaddr, const char * str) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + device.friendlyName = str; +} + +// Dump the internal memory of Zigbee devices +// Mode = 1: simple dump of devices addresses and names +// Mode = 2: Mode 1 + also dump the endpoints, profiles and clusters +String Z_Devices::dump(uint8_t dump_mode) const { + DynamicJsonBuffer jsonBuffer; + JsonArray& json = jsonBuffer.createArray(); + JsonArray& devices = json; + //JsonArray& devices = json.createNestedArray(F("ZigbeeDevices")); + + for (std::vector::const_iterator it = _devices.begin(); it != _devices.end(); ++it) { + const Z_Device& device = *it; + uint16_t shortaddr = device.shortaddr; + char hex[20]; + + JsonObject& dev = devices.createNestedObject(); + + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr); + dev[F("ShortAddr")] = hex; + + if (device.friendlyName.length() > 0) { + dev[F("FriendlyName")] = device.friendlyName; + } + + if (1 == dump_mode) { + Uint64toHex(device.longaddr, hex, 64); + dev[F("IEEEAddr")] = hex; + if (device.modelId.length() > 0) { + dev[F(D_JSON_MODEL D_JSON_ID)] = device.modelId; + } + if (device.manufacturerId.length() > 0) { + dev[F("Manufacturer")] = device.manufacturerId; + } + } + + // If dump_mode == 2, dump a lot more details + if (2 == dump_mode) { + JsonObject& dev_endpoints = dev.createNestedObject(F("Endpoints")); + for (std::vector::const_iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) { + uint32_t ep_profile = *ite; + uint8_t endpoint = (ep_profile >> 16) & 0xFF; + uint16_t profileId = ep_profile & 0xFFFF; + + snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint); + JsonObject& ep = dev_endpoints.createNestedObject(hex); + + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), profileId); + ep[F("ProfileId")] = hex; + + int32_t found = -1; + for (uint32_t i = 0; i < sizeof(Z_ProfileIds) / sizeof(Z_ProfileIds[0]); i++) { + if (pgm_read_word(&Z_ProfileIds[i]) == profileId) { + found = i; + break; + } + } + if (found > 0) { + GetTextIndexed(hex, sizeof(hex), found, Z_ProfileNames); + ep[F("ProfileIdName")] = hex; + } + + ep.createNestedArray(F("ClustersIn")); + ep.createNestedArray(F("ClustersOut")); + } + + for (std::vector::const_iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) { + uint16_t cluster = *itc & 0xFFFF; + uint8_t endpoint = (*itc >> 16) & 0xFF; + + snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint); + JsonArray &cluster_arr = dev_endpoints[hex][F("ClustersIn")]; + + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster); + cluster_arr.add(hex); + } + + for (std::vector::const_iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) { + uint16_t cluster = *itc & 0xFFFF; + uint8_t endpoint = (*itc >> 16) & 0xFF; + + snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint); + JsonArray &cluster_arr = dev_endpoints[hex][F("ClustersOut")]; + + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster); + cluster_arr.add(hex); + } + } + } + String payload = ""; + payload.reserve(200); + json.printTo(payload); + return payload; +} + +#endif // USE_ZIGBEE diff --git a/sonoff/xdrv_23_zigbee_4_converters.ino b/sonoff/xdrv_23_zigbee_5_converters.ino similarity index 88% rename from sonoff/xdrv_23_zigbee_4_converters.ino rename to sonoff/xdrv_23_zigbee_5_converters.ino index 09b2872b9..945322b61 100644 --- a/sonoff/xdrv_23_zigbee_4_converters.ino +++ b/sonoff/xdrv_23_zigbee_5_converters.ino @@ -55,7 +55,7 @@ public: uint32_t timestamp) { char hex_char[_payload.len()*2+2]; ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char)); - Response_P(PSTR("{\"" D_JSON_ZIGBEEZCLRECEIVED "\":{" + Response_P(PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{" "\"groupid\":%d," "\"clusterid\":%d," "\"srcaddr\":\"0x%04X\"," "\"srcendpoint\":%d," "\"dstendpoint\":%d," "\"wasbroadcast\":%d," "\"linkquality\":%d," "\"securityuse\":%d," "\"seqnumber\":%d," @@ -71,7 +71,7 @@ public: ResponseJsonEnd(); // append '}' ResponseJsonEnd(); // append '}' - MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLSENT)); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); } @@ -100,8 +100,9 @@ public: } void parseRawAttributes(JsonObject& json, uint8_t offset = 0); + void parseReadAttributes(JsonObject& json, uint8_t offset = 0); void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0); - void postProcessAttributes(JsonObject& json); + void postProcessAttributes(uint16_t shortaddr, JsonObject& json); inline void setGroupId(uint16_t groupid) { _group_id = groupid; @@ -208,7 +209,7 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer } } break; - case 0x23: // uint16 + case 0x23: // uint32 { uint32_t uint32_val = buf.get32(i); i += 4; @@ -403,6 +404,27 @@ void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) { } } +// ZCL_READ_ATTRIBUTES_RESPONSE +void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) { + uint32_t i = offset; + uint32_t len = _payload.len(); + + while (len - i >= 4) { + uint16_t attrid = _payload.get16(i); + i += 2; + uint8_t status = _payload.get8(i++); + + if (0 == status) { + char shortaddr[16]; + snprintf_P(shortaddr, sizeof(shortaddr), PSTR("%c_%04X_%04X"), + Hex36Char(_cmd_id), _cluster_id, attrid); + + i += parseSingleAttribute(json, shortaddr, _payload, i, len); + } + } +} + + // Parse non-normalized attributes // The key is "s_" followed by 16 bits clusterId, "_" followed by 8 bits command id void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) { @@ -421,7 +443,7 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) { // return value: // 0 = keep initial value // 1 = remove initial value -typedef int32_t (*Z_AttrConverter)(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param); +typedef int32_t (*Z_AttrConverter)(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param); typedef struct Z_AttributeConverter { const char * filter; const char * name; @@ -434,7 +456,19 @@ const float Z_10 PROGMEM = 10.0f; // list of post-processing directives const Z_AttributeConverter Z_PostProcess[] = { - { "A_0000_0005", D_JSON_MODEL D_JSON_ID, &Z_Copy, nullptr }, // ModelID + { "?_0000_0004", nullptr, &Z_ManufKeep, nullptr }, // record Manufacturer + { "?_0000_0005", nullptr, &Z_ModelKeep, nullptr }, // record Model + + { "?_0000_0000", "ZCLVersion", &Z_Copy, nullptr }, + { "?_0000_0001", "AppVersion", &Z_Copy, nullptr }, + { "?_0000_0002", "StackVersion", &Z_Copy, nullptr }, + { "?_0000_0003", "HWVersion", &Z_Copy, nullptr }, + { "?_0000_0004", "Manufacturer", &Z_Copy, nullptr }, + { "?_0000_0005", D_JSON_MODEL D_JSON_ID, &Z_Copy, nullptr }, + { "?_0000_0006", "DateCode", &Z_Copy, nullptr }, + { "?_0000_0007", "PowerSource", &Z_Copy, nullptr }, + { "?_0000_4000", "SWBuildID", &Z_Copy, nullptr }, + { "A_0000_????", nullptr, &Z_Remove, nullptr }, // Remove all other values { "A_0400_0000", D_JSON_ILLUMINANCE, &Z_Copy, nullptr }, // Illuminance (in Lux) { "A_0400_0004", "LightSensorType", &Z_Copy, nullptr }, // LightSensorType @@ -469,38 +503,50 @@ const Z_AttributeConverter Z_PostProcess[] = { // { "A_0B04_????", "", &Z_Remove, nullptr }, // Remove all other values }; + +// ====================================================================== +// Record Manuf +int32_t Z_ManufKeep(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { + zigbee_devices.setManufId(shortaddr, value.as()); + return 0; // keep original key +} +// +int32_t Z_ModelKeep(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { + zigbee_devices.setModelId(shortaddr, value.as()); + return 0; // keep original key +} + // ====================================================================== // Remove attribute -int32_t Z_Remove(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { +int32_t Z_Remove(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { return 1; // remove original key } // Copy value as-is -int32_t Z_Copy(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { +int32_t Z_Copy(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { json[new_name] = value; return 1; // remove original key } // Copy value as-is -int32_t Z_Const_Keep(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { +int32_t Z_Const_Keep(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { json[new_name] = (char*)param; return 0; // keep original key } // Convert int to float with divider -int32_t Z_ConvFloatDivider(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { +int32_t Z_ConvFloatDivider(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { float f_value = value; float *divider = (float*) param; json[new_name] = f_value / *divider; return 1; // remove original key } -int32_t Z_AqaraSensor(JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { +int32_t Z_AqaraSensor(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param) { String hex = value; SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); uint32_t i = 0; uint32_t len = buf2.len(); - char shortaddr[8]; char tmp[] = "tmp"; // for obscure reasons, it must be converted from const char* to char*, otherwise ArduinoJson gets confused JsonVariant sub_value; @@ -573,7 +619,7 @@ bool mini_glob_match(char const *pat, char const *str) { } } -void ZCLFrame::postProcessAttributes(JsonObject& json) { +void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { // iterate on json elements for (auto kv : json) { String key = kv.key; @@ -584,7 +630,7 @@ void ZCLFrame::postProcessAttributes(JsonObject& json) { const Z_AttributeConverter *converter = &Z_PostProcess[i]; if (mini_glob_match(converter->filter, key.c_str())) { - int32_t drop = (*converter->func)(json, key.c_str(), value, converter->name, converter->param); + int32_t drop = (*converter->func)(shortaddr, json, key.c_str(), value, converter->name, converter->param); if (drop) { json.remove(key); } diff --git a/sonoff/xdrv_23_zigbee_6_devices.ino b/sonoff/xdrv_23_zigbee_6_devices.ino deleted file mode 100644 index 128f11857..000000000 --- a/sonoff/xdrv_23_zigbee_6_devices.ino +++ /dev/null @@ -1,169 +0,0 @@ -/* - xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota - - Copyright (C) 2019 Theo Arends and Stephan Hadinger - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifdef USE_ZIGBEE - -#include -#include - -typedef struct Z_Device { - uint16_t shortaddr; - uint64_t longaddr; // 0x00 means unspecified - std::vector endpoints; - std::vector clusters_in; // encoded as high 16 bits is endpoint, low 16 bits is cluster number - std::vector clusters_out; // encoded as high 16 bits is endpoint, low 16 bits is cluster number -} Z_Device; - -std::map zigbee_devices = {}; - - -template < typename T> -bool findInVector(const std::vector & vecOfElements, const T & element) { - // Find given element in vector - auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element); - - if (it != vecOfElements.end()) { - return true; - } else { - return false; - } -} - -// insert an entry when it is known it is missing -void Z_InsertShortAddrEntry(uint16_t shortaddr, uint64_t longaddr) { - Z_Device device = { shortaddr, longaddr, - std::vector(), - std::vector(), - std::vector() }; - zigbee_devices[shortaddr] = device; -} - -void Z_AddDeviceLongAddr(uint16_t shortaddr, uint64_t longaddr) { - // is the short address already recorded? - if (0 == zigbee_devices.count(shortaddr)) { - // No, add an entry - Z_InsertShortAddrEntry(shortaddr, longaddr); - } else { - // Yes, update the longaddr if necessary - Z_Device &device = zigbee_devices[shortaddr]; - uint64_t prev_longaddr = device.longaddr; - if (prev_longaddr != longaddr) { - // new device, i.e. collision - device.longaddr = longaddr; - device.endpoints.clear(); - device.clusters_in.clear(); - device.clusters_out.clear(); - } - } -} - -void Z_AddDeviceEndpoint(uint16_t shortaddr, uint8_t endpoint) { - if (0 == zigbee_devices.count(shortaddr)) { - // No entry - Z_InsertShortAddrEntry(shortaddr, 0); - } - Z_Device &device = zigbee_devices[shortaddr]; - if (!findInVector(device.endpoints, endpoint)) { - device.endpoints.push_back(endpoint); - } -} - -void Z_AddDeviceCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster, bool out) { - if (0 == zigbee_devices.count(shortaddr)) { - // No entry - Z_InsertShortAddrEntry(shortaddr, 0); - } - Z_Device &device = zigbee_devices[shortaddr]; - if (!findInVector(device.endpoints, endpoint)) { - device.endpoints.push_back(endpoint); - } - uint32_t ep_cluster = (endpoint << 16) | cluster; - if (!out) { - if (!findInVector(device.clusters_in, ep_cluster)) { - device.clusters_in.push_back(ep_cluster); - } - } else { // out - if (!findInVector(device.clusters_out, ep_cluster)) { - device.clusters_out.push_back(ep_cluster); - } - } -} - -String Z_DumpDevices(void) { - DynamicJsonBuffer jsonBuffer; - JsonObject& json = jsonBuffer.createObject(); - JsonObject& devices = json.createNestedObject(F("ZigbeeDevices")); - - for (std::map::iterator it = zigbee_devices.begin(); it != zigbee_devices.end(); ++it) { - uint16_t shortaddr = it->first; - Z_Device& device = it->second; - char hex[20]; - - snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr); - JsonObject& dev = devices.createNestedObject(hex); - dev[F("ShortAddr")] = hex; - - Uint64toHex(device.longaddr, hex, 64); - dev[F("IEEEAddr")] = hex; - - JsonArray& dev_endpoints = dev.createNestedArray(F("Endpoints")); - for (std::vector::iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) { - uint8_t endpoint = *ite; - - snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint); - dev_endpoints.add(hex); - } - - JsonObject& dev_clusters_in = dev.createNestedObject(F("Clusters_in")); - for (std::vector::iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) { - uint16_t cluster = *itc & 0xFFFF; - uint8_t endpoint = (*itc >> 16) & 0xFF; - - snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint); - if (!dev_clusters_in.containsKey(hex)) { - dev_clusters_in.createNestedArray(hex); - } - JsonArray &cluster_arr = dev_clusters_in[hex]; - - snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster); - cluster_arr.add(hex); - } - - JsonObject& dev_clusters_out = dev.createNestedObject(F("Clusters_out")); - for (std::vector::iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) { - uint16_t cluster = *itc & 0xFFFF; - uint8_t endpoint = (*itc >> 16) & 0xFF; - - snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint); - if (!dev_clusters_out.containsKey(hex)) { - dev_clusters_out.createNestedArray(hex); - } - JsonArray &cluster_arr = dev_clusters_out[hex]; - - snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster); - cluster_arr.add(hex); - } - } - String payload = ""; - payload.reserve(200); - json.printTo(payload); - return payload; -} - -#endif // USE_ZIGBEE diff --git a/sonoff/xdrv_23_zigbee_7_statemachine.ino b/sonoff/xdrv_23_zigbee_7_statemachine.ino index 6d271f0a9..385fb3980 100644 --- a/sonoff/xdrv_23_zigbee_7_statemachine.ino +++ b/sonoff/xdrv_23_zigbee_7_statemachine.ino @@ -38,7 +38,7 @@ const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP versi const uint8_t ZIGBEE_STATUS_ABORT = 99; // Fatal error, Zigbee not working typedef int32_t (*ZB_Func)(uint8_t value); -typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, class SBuffer &buf); +typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, const class SBuffer &buf); typedef union Zigbee_Instruction { struct { @@ -283,8 +283,6 @@ ZBM(AREQ_ZDO_NODEDESCRSP, Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP) // 4582 // ServerMask (2 bytes) - 0100 - Primary Trust Center // MaxOutTransferSize (2 bytes) - A000 = 160 // DescriptorCapabilities (1 byte) - 00 -ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584 -ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585 // Z_ZDO:activeEpReq ZBM(ZBS_ZDO_ACTIVEEPREQ, Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, 0x00, 0x00, 0x00, 0x00) // 250500000000 @@ -313,20 +311,15 @@ ZBM(ZBR_PERMITJOINREQ, Z_SRSP | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, Z_Success) / ZBM(ZBR_PERMITJOIN_AREQ_CLOSE, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0x00 /* Duration */) // 45CB00 ZBM(ZBR_PERMITJOIN_AREQ_OPEN_60, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 60 /* Duration */) // 45CB3C ZBM(ZBR_PERMITJOIN_AREQ_OPEN_FF, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0xFF /* Duration */) // 45CBFF -ZBM(ZBR_PERMITJOIN_AREQ_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB ZBM(ZBR_PERMITJOIN_AREQ_RSP, Z_AREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_RSP, 0x00, 0x00 /* srcAddr*/, Z_Success ) // 45B6000000 -// Filters for ZCL frames -ZBM(ZBR_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481 -ZBM(ZBR_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1 - static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_LABEL(0) ZI_NOOP() ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT) ZI_ON_TIMEOUT_GOTO(ZIGBEE_LABEL_ABORT) ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default) - ZI_WAIT(15000) // wait for 15 seconds for Tasmota to stabilize + ZI_WAIT(10000) // wait for 10 seconds for Tasmota to stabilize ZI_ON_ERROR_GOTO(50) ZI_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting") @@ -337,7 +330,6 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_LOG(LOG_LEVEL_INFO, "ZIG: checking device configuration") ZI_SEND(ZBS_ZNPHC) // check value of ZNP Has Configured ZI_WAIT_RECV(2000, ZBR_ZNPHC) - ZI_WAIT(100) ZI_SEND(ZBS_VERSION) // check ZNP software version ZI_WAIT_RECV_FUNC(2000, ZBR_VERSION, &Z_ReceiveCheckVersion) // Check version ZI_SEND(ZBS_PAN) // check PAN ID diff --git a/sonoff/xdrv_23_zigbee_8_parsers.ino b/sonoff/xdrv_23_zigbee_8_parsers.ino index 2a304f8f1..6828cf96c 100644 --- a/sonoff/xdrv_23_zigbee_8_parsers.ino +++ b/sonoff/xdrv_23_zigbee_8_parsers.ino @@ -188,7 +188,7 @@ int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) { complexDescriptorAvailable ? "true" : "false" ); - MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED)); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); } @@ -205,7 +205,7 @@ int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) { for (uint32_t i = 0; i < activeEpCount; i++) { - Z_AddDeviceEndpoint(nwkAddr, activeEpList[i]); + zigbee_devices.addEndoint(nwkAddr, activeEpList[i]); } for (uint32_t i = 0; i < activeEpCount; i++) { @@ -220,11 +220,33 @@ int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) { ResponseAppend_P(PSTR("\"0x%02X\""), activeEpList[i]); } ResponseAppend_P(PSTR("]}}")); - MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED)); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); return -1; } +void Z_SendAFInfoRequest(uint16_t shortaddr, uint8_t endpoint, uint16_t clusterid, uint8_t transacid) { + SBuffer buf(100); + buf.add8(Z_SREQ | Z_AF); // 24 + buf.add8(AF_DATA_REQUEST); // 01 + buf.add16(shortaddr); + buf.add8(endpoint); // dest endpoint + buf.add8(0x01); // source endpoint + buf.add16(clusterid); + buf.add8(transacid); + buf.add8(0x30); // 30 options + buf.add8(0x1E); // 1E radius + + buf.add8(3 + 2*sizeof(uint16_t)); // Len = 0x07 + buf.add8(0x00); // Frame Control Field + buf.add8(transacid); // Transaction Sequance Number + buf.add8(ZCL_READ_ATTRIBUTES); // 00 Command + buf.add16(0x0004); // 0400 ManufacturerName + buf.add16(0x0005); // 0500 ModelIdentifier + + ZigbeeZNPSend(buf.getBuffer(), buf.len()); +} + int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) { // Received ZDO_SIMPLE_DESC_RSP @@ -240,18 +262,17 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) { uint8_t numOutCluster = buf.get8(15 + numInCluster*2); if (0 == status) { + zigbee_devices.addEndointProfile(nwkAddr, endpoint, profileId); for (uint32_t i = 0; i < numInCluster; i++) { - Z_AddDeviceCluster(nwkAddr, endpoint, buf.get16(15 + i*2), false); + zigbee_devices.addCluster(nwkAddr, endpoint, buf.get16(15 + i*2), false); } for (uint32_t i = 0; i < numOutCluster; i++) { - Z_AddDeviceCluster(nwkAddr, endpoint, buf.get16(16 + numInCluster*2 + i*2), true); + zigbee_devices.addCluster(nwkAddr, endpoint, buf.get16(16 + numInCluster*2 + i*2), true); } - // String dump = Z_DumpDevices(); - // Serial.printf(">>> Devices dump = %s\n", dump.c_str()); Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{" "\"Status\":%d,\"Endpoint\":\"0x%02X\"" - ",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVerion\":%d" + ",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVersion\":%d" "\"InClusters\":["), ZIGBEE_STATUS_SIMPLE_DESC, endpoint, profileId, deviceId, deviceVersion); @@ -265,8 +286,14 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) { ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(16 + numInCluster*2 + i*2)); } ResponseAppend_P(PSTR("]}}")); - MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED)); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); + + uint8_t cluster = zigbee_devices.findClusterEndpointIn(nwkAddr, 0x0000); +Serial.printf(">>> Endpoint is 0x%02X for cluster 0x%04X\n", cluster, 0x0000); + if (cluster) { + Z_SendAFInfoRequest(nwkAddr, cluster, 0x0000, 0x01); // TODO + } } return -1; } @@ -277,9 +304,7 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { Z_IEEEAddress ieeeAddr = buf.get64(6); uint8_t capabilities = buf.get8(14); - Z_AddDeviceLongAddr(nwkAddr, ieeeAddr); -// String dump = Z_DumpDevices(); -// Serial.printf(">>> Devices dump = %s\n", dump.c_str()); + zigbee_devices.updateDevice(nwkAddr, ieeeAddr); char hex[20]; Uint64toHex(ieeeAddr, hex, 64); @@ -292,7 +317,7 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { (capabilities & 0x40) ? "true" : "false" ); - MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED)); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); Z_SendActiveEpReq(nwkAddr); return -1; @@ -325,21 +350,47 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { JsonObject& json = json_root.createNestedObject(shortaddr); if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) { zcl_received.parseRawAttributes(json); + } else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) { + zcl_received.parseReadAttributes(json); } else if (zcl_received.isClusterSpecificCommand()) { zcl_received.parseClusterSpecificCommand(json); } - zcl_received.postProcessAttributes(json); - String msg(""); msg.reserve(100); json_root.printTo(msg); + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeZCLRawReceived: %s"), msg.c_str()); + zcl_received.postProcessAttributes(srcaddr, json); + + msg = ""; + json_root.printTo(msg); Response_P(PSTR("%s"), msg.c_str()); - MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED)); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); return -1; } +typedef struct Z_Dispatcher { + const uint8_t* match; + ZB_RecvMsgFunc func; +} Z_Dispatcher; + +// Filters for ZCL frames +ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481 +ZBM(AREQ_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1 +ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB +ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585 +ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584 + +const Z_Dispatcher Z_DispatchTable[] PROGMEM = { + { AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage }, + { AREQ_END_DEVICE_ANNCE_IND, &Z_ReceiveEndDeviceAnnonce }, + { AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus }, + { AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc }, + { AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp }, + { AREQ_ZDO_SIMPLEDESCRSP, &Z_ReceiveSimpleDesc }, +}; + int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) { // Default message handler for new messages AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Z_Recv_Default")); @@ -347,18 +398,10 @@ int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) { // if still during initialization phase, ignore any unexpected message return -1; // ignore message } else { - if (Z_ReceiveMatchPrefix(buf, ZBR_AF_INCOMING_MESSAGE)) { - return Z_ReceiveAfIncomingMessage(res, buf); - } else if (Z_ReceiveMatchPrefix(buf, ZBR_END_DEVICE_ANNCE_IND)) { - return Z_ReceiveEndDeviceAnnonce(res, buf); - } else if (Z_ReceiveMatchPrefix(buf, ZBR_PERMITJOIN_AREQ_OPEN_XX)) { - return Z_ReceivePermitJoinStatus(res, buf); - } else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_NODEDESCRSP)) { - return Z_ReceiveNodeDesc(res, buf); - } else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_ACTIVEEPRSP)) { - return Z_ReceiveActiveEp(res, buf); - } else if (Z_ReceiveMatchPrefix(buf, AREQ_ZDO_SIMPLEDESCRSP)) { - return Z_ReceiveSimpleDesc(res, buf); + for (uint32_t i = 0; i < sizeof(Z_DispatchTable)/sizeof(Z_Dispatcher); i++) { + if (Z_ReceiveMatchPrefix(buf, Z_DispatchTable[i].match)) { + (*Z_DispatchTable[i].func)(res, buf); + } } return -1; } diff --git a/sonoff/xdrv_23_zigbee_9_impl.ino b/sonoff/xdrv_23_zigbee_9_impl.ino index d570573c2..b41a2635b 100644 --- a/sonoff/xdrv_23_zigbee_9_impl.ino +++ b/sonoff/xdrv_23_zigbee_9_impl.ino @@ -36,10 +36,10 @@ TasmotaSerial *ZigbeeSerial = nullptr; const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN - "|" D_CMND_ZIGBEE_DUMP; + "|" D_CMND_ZIGBEE_STATUS; void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin, - &CmndZigbeeDump }; + &CmndZigbeeStatus }; int32_t ZigbeeProcessInput(class SBuffer &buf) { if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message @@ -234,10 +234,10 @@ void ZigbeeInit(void) * Commands \*********************************************************************************************/ -void CmndZigbeeDump(void) { +void CmndZigbeeStatus(void) { if (ZigbeeSerial) { - String dump = Z_DumpDevices(); - Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, dump.c_str()); + String dump = zigbee_devices.dump(XdrvMailbox.payload); + Response_P(PSTR("{\"%s%d\":%s}"), XdrvMailbox.command, XdrvMailbox.payload, dump.c_str()); } }