From 749960533f45056061e195c175b70eb7a9cd5d15 Mon Sep 17 00:00:00 2001 From: Hadinger Date: Mon, 23 Dec 2019 16:53:54 +0100 Subject: [PATCH] Add Zigbee better support for Xiaomi Double Switch and Xiaomi Vibration sensor --- tasmota/CHANGELOG.md | 1 + tasmota/i18n.h | 1 + tasmota/xdrv_23_zigbee_3_devices.ino | 95 ++++++++++++++++++----- tasmota/xdrv_23_zigbee_5_converters.ino | 74 +++++++++++------- tasmota/xdrv_23_zigbee_7_statemachine.ino | 1 + tasmota/xdrv_23_zigbee_8_parsers.ino | 42 +++++++--- tasmota/xdrv_23_zigbee_9_impl.ino | 25 +++++- 7 files changed, 182 insertions(+), 57 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 73e035ff2..cb2bfe0c5 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -3,6 +3,7 @@ ### 8.0.0.2 20191223 - Changed Settings variable namings +- Add Zigbee better support for Xiaomi Double Switch and Xiaomi Vibration sensor ### 8.0.0.1 20191221 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 1844dd44b..68a19f75e 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -468,6 +468,7 @@ #define D_CMND_ZIGBEE_STATUS "ZigbeeStatus" #define D_CMND_ZIGBEE_RESET "ZigbeeReset" #define D_JSON_ZIGBEE_CC2530 "CC2530" +#define D_CMND_ZIGBEEZNPRECEIVE "ZigbeeZNPReceive" // only for debug #define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend" #define D_JSON_ZIGBEE_STATE "ZigbeeState" #define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived" diff --git a/tasmota/xdrv_23_zigbee_3_devices.ino b/tasmota/xdrv_23_zigbee_3_devices.ino index c42f4b603..1ec18d3e1 100644 --- a/tasmota/xdrv_23_zigbee_3_devices.ino +++ b/tasmota/xdrv_23_zigbee_3_devices.ino @@ -91,6 +91,8 @@ public: void jsonClear(uint16_t shortaddr); void jsonAppend(uint16_t shortaddr, JsonObject &values); const JsonObject *jsonGet(uint16_t shortaddr); + const void jsonPublish(uint16_t shortaddr); // publish the json message and clear buffer + bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values); private: std::vector _devices = {}; @@ -419,6 +421,67 @@ void Z_Devices::jsonClear(uint16_t shortaddr) { device.json_buffer->clear(); } +void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) { + to.remove(key); // force remove to have metadata like LinkQuality at the end + + if (val.is()) { + String sval = val.as(); // force a copy of the String value + to.set(key, sval); + } else if (val.is()) { + JsonArray &nested_arr = to.createNestedArray(key); + CopyJsonArray(nested_arr, val.as()); + } else if (val.is()) { + JsonObject &nested_obj = to.createNestedObject(key); + CopyJsonObject(nested_obj, val.as()); + } else { + to.set(key, val); + } +} + +void CopyJsonArray(JsonArray &to, const JsonArray &arr) { + for (auto v : arr) { + if (v.is()) { + String sval = v.as(); // force a copy of the String value + to.add(sval); + } else if (v.is()) { + } else if (v.is()) { + } else { + to.add(v); + } + } +} + +void CopyJsonObject(JsonObject &to, const JsonObject &from) { + for (auto kv : from) { + String key_string = kv.key; + JsonVariant &val = kv.value; + + CopyJsonVariant(to, key_string, val); + } +} + +// does the new payload conflicts with the existing payload, i.e. values would be overwritten +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) { + return false; // if no previous value, no conflict + } + + for (auto kv : values) { + String key_string = kv.key; + + if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { // exception = ignore duplicates for LinkQuality + if (device.json->containsKey(kv.key)) { + return true; // conflict! + } + } + } + return false; +} + void Z_Devices::jsonAppend(uint16_t shortaddr, JsonObject &values) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found @@ -428,24 +491,7 @@ void Z_Devices::jsonAppend(uint16_t shortaddr, JsonObject &values) { device.json = &(device.json_buffer->createObject()); } // copy all values from 'values' to 'json' - for (auto kv : values) { - String key_string = kv.key; - const char * key = key_string.c_str(); - JsonVariant &val = kv.value; - - device.json->remove(key_string); // force remove to have metadata like LinkQuality at the end - - if (val.is()) { - String sval = val.as(); // force a copy of the String value - device.json->set(key_string, sval); - } else if (val.is()) { - // todo - } else if (val.is()) { - // todo - } else { - device.json->set(key_string, kv.value); - } - } + CopyJsonObject(*device.json, values); } const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) { @@ -454,6 +500,19 @@ const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) { return device.json; } +const void Z_Devices::jsonPublish(uint16_t shortaddr) { + const JsonObject *json = zigbee_devices.jsonGet(shortaddr); + if (json == nullptr) { return; } // don't crash if not found + + String msg = ""; + json->printTo(msg); + zigbee_devices.jsonClear(shortaddr); + Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); +} + + // 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 diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 49d63808e..1423c53f1 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -100,6 +100,7 @@ public: return _frame_control.b.frame_type & 1; } + static void generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len); void parseRawAttributes(JsonObject& json, uint8_t offset = 0); void parseReadAttributes(JsonObject& json, uint8_t offset = 0); void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0); @@ -290,17 +291,20 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer bool parse_as_string = true; uint32_t len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i); // len is 8 or 16 bits i += (attrtype <= 0x42) ? 1 : 2; // increment pointer + if (i + len > buf.len()) { // make sure we don't get past the buffer + len = buf.len() - i; + } // check if we can safely use a string if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; } - else { - for (uint32_t j = 0; j < len; j++) { - if (0x00 == buf.get8(i+j)) { - parse_as_string = false; - break; - } - } - } + // else { + // for (uint32_t j = 0; j < len; j++) { + // if (0x00 == buf.get8(i+j)) { + // parse_as_string = false; + // break; + // } + // } + // } if (parse_as_string) { char str[len+1]; @@ -409,19 +413,28 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer return i - offset; // how much have we increased the index } +// Generate an attribute name based on cluster number, attribute, and suffix if duplicates +void ZCLFrame::generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len) { + uint32_t suffix = 1; + + snprintf_P(key, key_len, PSTR("%04X/%04X"), cluster, attr); + while (json.containsKey(key)) { + suffix++; + snprintf_P(key, key_len, PSTR("%04X/%04X+%d"), cluster, attr, suffix); // add "0008/0001+2" suffix if duplicate + } +} // First pass, parse all attributes in their native format void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) { uint32_t i = offset; uint32_t len = _payload.len(); - while (len - i >= 3) { + while (len >= i + 3) { uint16_t attrid = _payload.get16(i); i += 2; char key[16]; - snprintf_P(key, sizeof(key), PSTR("%04X/%04X"), - _cluster_id, attrid); + generateAttributeName(json, _cluster_id, attrid, key, sizeof(key)); // exception for Xiaomi lumi.weather - specific field to be treated as octet and not char if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) { @@ -445,8 +458,7 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) { if (0 == status) { char key[16]; - snprintf_P(key, sizeof(key), PSTR("%04X/%04X"), - _cluster_id, attrid); + generateAttributeName(json, _cluster_id, attrid, key, sizeof(key)); i += parseSingleAttribute(json, key, _payload, i, len); } @@ -472,7 +484,7 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) { // return value: // 0 = keep initial value // 1 = remove initial value -typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper* new_name, uint16_t cluster, uint16_t attr); +typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); typedef struct Z_AttributeConverter { uint16_t cluster; uint16_t attribute; @@ -775,13 +787,13 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { // ====================================================================== // Record Manuf -int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = value; zigbee_devices.setManufId(shortaddr, value.as()); return 1; } // -int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = value; zigbee_devices.setModelId(shortaddr, value.as()); return 1; @@ -789,29 +801,29 @@ int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& j // ====================================================================== // Remove attribute -int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { return 1; // remove original key } // Copy value as-is -int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = value; return 1; // remove original key } // Add pressure unit -int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = F(D_UNIT_PRESSURE); return 0; // keep original key } // Convert int to float and divide by 100 -int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = ((float)value) / 100.0f; return 1; // remove original key } // Convert int to float and divide by 10 -int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = ((float)value) / 10.0f; return 1; // remove original key } @@ -825,7 +837,7 @@ int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpo } // Aqara Vibration Sensor - special proprietary attributes -int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { //json[new_name] = value; switch (attr) { case 0x0055: @@ -879,7 +891,7 @@ int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObje return 1; // remove original key } -int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { String hex = value; SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); uint32_t i = 0; @@ -925,11 +937,19 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { String key_string = kv.key; const char * key = key_string.c_str(); JsonVariant& value = kv.value; - // Check that format looks like "CCCC/AAAA" + // Check that format looks like "CCCC/AAAA" or "CCCC/AAAA+d" char * delimiter = strchr(key, '/'); + char * delimiter2 = strchr(key, '+'); if (delimiter) { + uint16_t attribute; + uint16_t suffix = 1; uint16_t cluster = strtoul(key, &delimiter, 16); - uint16_t attribute = strtoul(delimiter+1, nullptr, 16); + if (!delimiter2) { + attribute = strtoul(delimiter+1, nullptr, 16); + } else { + attribute = strtoul(delimiter+1, &delimiter2, 16); + suffix = strtoul(delimiter2+1, nullptr, 10); + } // Iterate on filter for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) { @@ -939,7 +959,9 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { if ((conv_cluster == cluster) && ((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) { - int32_t drop = (*converter->func)(this, shortaddr, json, key, value, (const __FlashStringHelper*) converter->name, conv_cluster, conv_attribute); + String new_name_str = converter->name; + if (suffix > 1) { new_name_str += suffix; } // append suffix number + int32_t drop = (*converter->func)(this, shortaddr, json, key, value, new_name_str, conv_cluster, conv_attribute); if (drop) { json.remove(key); } diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index d4f3e459f..261a4ac6a 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -32,6 +32,7 @@ const uint8_t ZIGBEE_STATUS_DEVICE_ANNOUNCE = 30; // Device announces its const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; // Node descriptor const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints descriptor const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters) +const uint8_t ZIGBEE_STATUS_DEVICE_INDICATION = 34; // Device announces its address const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 89320b117..fcfeb06a7 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -357,6 +357,28 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { return -1; } +// 45CA +int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) { + Z_ShortAddress srcAddr = buf.get16(2); + Z_IEEEAddress ieeeAddr = buf.get64(4); + Z_ShortAddress parentNw = buf.get16(12); + + zigbee_devices.updateDevice(srcAddr, ieeeAddr); + + char hex[20]; + Uint64toHex(ieeeAddr, hex, 64); + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\"" + ",\"ParentNetwork\":\"0x%04X\"}}"), + ZIGBEE_STATUS_DEVICE_INDICATION, hex, srcAddr, parentNw + ); + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); + XdrvRulesProcess(); + //Z_SendActiveEpReq(srcAddr); + return -1; +} + // Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds. // Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s @@ -378,16 +400,11 @@ void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, c int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) { const JsonObject *json = zigbee_devices.jsonGet(shortaddr); if (json == nullptr) { return 0; } // don't crash if not found - // Post-provess for Aqara Presence Senson Z_AqaraOccupancy(shortaddr, cluster, endpoint, json); - String msg = ""; - json->printTo(msg); - zigbee_devices.jsonClear(shortaddr); - Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); - MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); - XdrvRulesProcess(); + zigbee_devices.jsonPublish(shortaddr); + return 1; } int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { @@ -438,8 +455,13 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { if (defer_attributes) { // Prepare for publish - zigbee_devices.jsonAppend(srcaddr, json); - zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes); + if (zigbee_devices.jsonIsConflict(srcaddr, json)) { + // there is conflicting values, force a publish of the previous message now and don't coalesce + zigbee_devices.jsonPublish(srcaddr); + } else { + zigbee_devices.jsonAppend(srcaddr, json); + zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes); + } } else { // Publish immediately msg = ""; @@ -459,6 +481,7 @@ typedef struct 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_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND) // 45CA 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 @@ -466,6 +489,7 @@ 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_END_DEVICE_TC_DEV_IND, &Z_ReceiveTCDevInd }, { AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus }, { AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc }, { AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp }, diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index b3681f00e..59cf382b0 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -31,12 +31,14 @@ TasmotaSerial *ZigbeeSerial = nullptr; const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" - D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ ; + D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE + ; void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin, &CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend, - &CmndZigbeeProbe, &CmndZigbeeRead }; + &CmndZigbeeProbe, &CmndZigbeeRead, &CmndZigbeeZNPReceive + }; int32_t ZigbeeProcessInput(class SBuffer &buf) { if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message @@ -268,7 +270,7 @@ void CmndZigbeeStatus(void) { } } -void CmndZigbeeZNPSend(void) +void CmndZigbeeZNPSendOrReceive(bool send) { if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) { uint8_t code; @@ -286,11 +288,26 @@ void CmndZigbeeZNPSend(void) size -= 2; codes += 2; } - ZigbeeZNPSend(buf.getBuffer(), buf.len()); + if (send) { + ZigbeeZNPSend(buf.getBuffer(), buf.len()); + } else { + ZigbeeProcessInput(buf); + } } ResponseCmndDone(); } +// For debug purposes only, simulates a message received +void CmndZigbeeZNPReceive(void) +{ + CmndZigbeeZNPSendOrReceive(false); +} + +void CmndZigbeeZNPSend(void) +{ + CmndZigbeeZNPSendOrReceive(true); +} + void ZigbeeZNPSend(const uint8_t *msg, size_t len) { if ((len < 2) || (len > 252)) { // abort, message cannot be less than 2 bytes for CMD1 and CMD2