From ee2bb0330a3534a59a297e9a92862b622fd020b6 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Tue, 16 Jun 2020 20:01:14 +0200 Subject: [PATCH 1/2] Add Zigbee initial support for EmberZNet protocol (raw send/receive only) --- tasmota/CHANGELOG.md | 1 + tasmota/i18n.h | 4 + tasmota/my_user_config.h | 3 + tasmota/xdrv_23_zigbee_0_constants.ino | 9 + tasmota/xdrv_23_zigbee_7_statemachine.ino | 28 ++ tasmota/xdrv_23_zigbee_8_parsers.ino | 31 ++ tasmota/xdrv_23_zigbee_9_impl.ino | 347 ++++++++++++++++++++-- 7 files changed, 396 insertions(+), 27 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 330a9f966ab7..ec18a5d9680e 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -3,6 +3,7 @@ ### 8.3.1.5 20200616 - Add ESP32 ethernet commands ``EthType 0/1``, ``EthAddress 0..31`` and ``EthClockMode 0..3`` +- Add Zigbee initial support for EmberZNet protocol (raw send/receive only) ### 8.3.1.4 20200615 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 543a4c9b98c6..ccbb6b399000 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -505,10 +505,14 @@ #define D_CMND_ZIGBEE_RESET "Reset" #define D_JSON_ZIGBEE_CC2530 "CC2530" #define D_CMND_ZIGBEEZNPRECEIVE "ZNPReceive" // only for debug +#define D_CMND_ZIGBEE_EZSP_RECEIVE "EZSPReceive" // only for debug #define D_CMND_ZIGBEEZNPSEND "ZNPSend" +#define D_CMND_ZIGBEE_EZSP_SEND "EZSPSend" #define D_JSON_ZIGBEE_STATE "ZbState" #define D_JSON_ZIGBEEZNPRECEIVED "ZbZNPReceived" + #define D_JSON_ZIGBEE_EZSP_RECEIVED "ZbEZSPReceived" #define D_JSON_ZIGBEEZNPSENT "ZbZNPSent" + #define D_JSON_ZIGBEE_EZSP_SENT "ZbEZSPSent" #define D_JSON_ZIGBEEZCL_RECEIVED "ZbZCLReceived" #define D_JSON_ZIGBEEZCL_RAW_RECEIVED "ZbZCLRawReceived" #define D_JSON_ZIGBEE_DEVICE "Device" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 732432eb2d2f..05c30b6a5afe 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -652,6 +652,9 @@ // -- Zigbee interface ---------------------------- //#define USE_ZIGBEE // Enable serial communication with Zigbee CC2530 flashed with ZNP (+49k code, +3k mem) + #define USE_ZIGBEE_ZNP // Enable ZNP protocol, needed for CC2530 based devices + // #define USE_ZIGBEE_EZSP // [EXPERIMENTAL - DO NOT USE] Enable EZSP protocol, needed for EFR32 EmberZNet based devices, like Sonoff Zigbee bridge + // Note: USE_ZIGBEE_ZNP and USE_ZIGBEE_EZSP are mutually incompatible, you must select exactly one #define USE_ZIGBEE_PANID 0x1A63 // arbitrary PAN ID for Zigbee network, must be unique in the home // if PANID == 0xFFFF, then the device will act as a Zigbee router, the parameters below are ignored // if PANID == 0xFFFE, then the device will act as a Zigbee end-device (non-router), the parameters below are ignored diff --git a/tasmota/xdrv_23_zigbee_0_constants.ino b/tasmota/xdrv_23_zigbee_0_constants.ino index 57bf22499e49..92e88c708f0d 100644 --- a/tasmota/xdrv_23_zigbee_0_constants.ino +++ b/tasmota/xdrv_23_zigbee_0_constants.ino @@ -19,6 +19,13 @@ #ifdef USE_ZIGBEE +#if defined(USE_ZIGBEE_ZNP) && defined(USE_ZIGBEE_EZSP) + #error "#define USE_ZIGBEE_ZNP and #define USE_ZIGBEE_EZSP are mutually incompatible" +#endif +#if !defined(USE_ZIGBEE_ZNP) && !defined(USE_ZIGBEE_EZSP) + #error "You must select one of: #define USE_ZIGBEE_ZNP or #define USE_ZIGBEE_EZSP" +#endif + #define OCCUPANCY "Occupancy" // global define for Aqara typedef uint64_t Z_IEEEAddress; @@ -26,6 +33,7 @@ typedef uint16_t Z_ShortAddress; const uint16_t BAD_SHORTADDR = 0xFFFE; +#ifdef USE_ZIGBEE_ZNP enum ZnpCommandType { Z_POLL = 0x00, Z_SREQ = 0x20, @@ -45,6 +53,7 @@ enum ZnpSubsystem { Z_DEBUG = 0x08, Z_APP = 0x09 }; +#endif // USE_ZIGBEE_ZNP // Commands in the SYS subsystem enum SysCommand { diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index 4297a43759ef..cbfe822ab8de 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -168,6 +168,8 @@ SBuffer *zigbee_buffer = nullptr; #define USE_ZIGBEE_CHANNEL_MASK (1 << (USE_ZIGBEE_CHANNEL)) +#ifdef USE_ZIGBEE_ZNP + // ZBS_* Zigbee Send // ZBR_* Zigbee Recv ZBM(ZBS_RESET, Z_AREQ | Z_SYS, SYS_RESET, 0x00 ) // 410001 SYS_RESET_REQ Hardware reset @@ -611,6 +613,30 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_STOP(ZIGBEE_LABEL_ABORT) }; +#endif // USE_ZIGBEE_ZNP + +#ifdef USE_ZIGBEE_EZSP + +// Update the relevant commands with Settings +void Z_UpdateConfig(uint8_t zb_channel, uint16_t zb_pan_id, uint64_t zb_ext_panid, uint64_t zb_precfgkey_l, uint64_t zb_precfgkey_h) { +} + + +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(10500) // wait for 10 seconds for Tasmota to stabilize + + ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP) + ZI_WAIT_FOREVER() + ZI_GOTO(ZIGBEE_LABEL_READY) +}; + +#endif // USE_ZIGBEE_EZSP + uint8_t ZigbeeGetInstructionSize(uint8_t instr) { // in Zigbee_Instruction lines (words) if (instr >= ZGB_INSTR_12_BYTES) { return 3; @@ -770,7 +796,9 @@ void ZigbeeStateMachine_Run(void) { } break; case ZGB_INSTR_SEND: +#ifdef USE_ZIGBEE_ZNP ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 /* len */); +#endif // USE_ZIGBEE_ZNP break; case ZGB_INSTR_WAIT_UNTIL: zigbee.recv_until = true; // and reuse ZGB_INSTR_WAIT_RECV diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 53590a4c4f9c..7e9129d64109 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -552,6 +552,7 @@ int32_t Z_MgmtBindRsp(int32_t res, const class SBuffer &buf) { return -1; } +#ifdef USE_ZIGBEE_ZNP /*********************************************************************************************\ * Send specific ZNP messages \*********************************************************************************************/ @@ -589,6 +590,32 @@ void Z_SendAFInfoRequest(uint16_t shortaddr) { ZigbeeZNPSend(AFInfoReq, sizeof(AFInfoReq)); } +#endif // USE_ZIGBEE_ZNP + +#ifdef USE_ZIGBEE_EZSP +/*********************************************************************************************\ + * Send specific EZS¨ messages +\*********************************************************************************************/ + +// +// Send ZDO_IEEE_ADDR_REQ request to get IEEE long address +// +void Z_SendIEEEAddrReq(uint16_t shortaddr) { +} + +// +// Send ACTIVE_EP_REQ to collect active endpoints for this address +// +void Z_SendActiveEpReq(uint16_t shortaddr) { +} + +// +// Send AF Info Request +// +void Z_SendAFInfoRequest(uint16_t shortaddr) { +} + +#endif // USE_ZIGBEE_EZSP /*********************************************************************************************\ * Callbacks @@ -718,6 +745,8 @@ typedef struct Z_Dispatcher { ZB_RecvMsgFunc func; } Z_Dispatcher; +#ifdef USE_ZIGBEE_ZNP + // Ffilters based on ZNP frames ZBM(AREQ_AF_DATA_CONFIRM, Z_AREQ | Z_AF, AF_DATA_CONFIRM) // 4480 ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481 @@ -767,6 +796,8 @@ int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) { } } +#endif // USE_ZIGBEE_ZNP + /*********************************************************************************************\ * Functions called by State Machine \*********************************************************************************************/ diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 4da0a48f60d7..a28481277a07 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -21,18 +21,32 @@ #define XDRV_23 23 +#ifdef USE_ZIGBEE_ZNP const uint32_t ZIGBEE_BUFFER_SIZE = 256; // Max ZNP frame is SOF+LEN+CMD1+CMD2+250+FCS = 255 const uint8_t ZIGBEE_SOF = 0xFE; const uint8_t ZIGBEE_SOF_ALT = 0xFF; +#endif // USE_ZIGBEE_ZNP + +#ifdef USE_ZIGBEE_EZSP +const uint32_t ZIGBEE_BUFFER_SIZE = 256; +const uint8_t ZIGBEE_EZSP_CANCEL = 0x1A; // cancel byte +const uint8_t ZIGBEE_EZSP_EOF = 0x7E; // end of frame +const uint8_t ZIGBEE_EZSP_ESCAPE = 0x7D; // escape byte +#endif // USE_ZIGBEE_EZSP #include TasmotaSerial *ZigbeeSerial = nullptr; const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix - 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_ZIGBEEZNPRECEIVE "|" +#ifdef USE_ZIGBEE_ZNP + D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEEZNPRECEIVE "|" +#endif // USE_ZIGBEE_ZNP +#ifdef USE_ZIGBEE_EZSP + D_CMND_ZIGBEE_EZSP_SEND "|" D_CMND_ZIGBEE_EZSP_RECEIVE "|" +#endif // USE_ZIGBEE_EZSP + D_CMND_ZIGBEE_PERMITJOIN "|" + D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|" D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" @@ -40,9 +54,14 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix ; void (* const ZigbeeCommand[])(void) PROGMEM = { - &CmndZbZNPSend, &CmndZbPermitJoin, - &CmndZbStatus, &CmndZbReset, &CmndZbSend, - &CmndZbProbe, &CmndZbZNPReceive, +#ifdef USE_ZIGBEE_ZNP + &CmndZbZNPSend, &CmndZbZNPReceive, +#endif // USE_ZIGBEE_ZNP +#ifdef USE_ZIGBEE_EZSP + &CmndZbEZSPSend, &CmndZbEZSPReceive, +#endif // USE_ZIGBEE_EZSP + &CmndZbPermitJoin, + &CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbProbe, &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId, &CmndZbLight, &CmndZbRestore, &CmndZbBindState, @@ -52,11 +71,12 @@ void (* const ZigbeeCommand[])(void) PROGMEM = { // // Called at event loop, checks for incoming data from the CC2530 // -void ZigbeeInputLoop(void) -{ - static uint32_t zigbee_polling_window = 0; +void ZigbeeInputLoop(void) { + +#ifdef USE_ZIGBEE_ZNP + static uint32_t zigbee_polling_window = 0; // number of milliseconds since first byte static uint8_t fcs = ZIGBEE_SOF; - static uint32_t zigbee_frame_len = 5; // minimal zigbee frame lenght, will be updated when buf[1] is read + static uint32_t zigbee_frame_len = 5; // minimal zigbee frame length, will be updated when buf[1] is read // Receive only valid ZNP frames: // 00 - SOF = 0xFE // 01 - Length of Data Field - 0..250 @@ -140,6 +160,132 @@ void ZigbeeInputLoop(void) } zigbee_buffer->setLen(0); // empty buffer } +#endif // USE_ZIGBEE_ZNP + +#ifdef USE_ZIGBEE_EZSP + static uint32_t zigbee_polling_window = 0; // number of milliseconds since first byte + bool escape = false; // was the previous byte an escape? + bool frame_complete = false; // frame is ready and complete + // Receive only valid EZSP frames: + // 1A - Cancel - cancel all previous bytes + // 7D - Escape byte - following byte is escaped + // 7E - end of frame + + while (ZigbeeSerial->available()) { + yield(); + uint8_t zigbee_in_byte = ZigbeeSerial->read(); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: ZbInput byte=0x%02X len=%d"), zigbee_in_byte, zigbee_buffer->len()); + + // if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized + // escape = false; + // frame_complete = false; + // } + + if ((0x11 == zigbee_in_byte) || (0x13 == zigbee_in_byte)) { + continue; // ignore reserved bytes XON/XOFF + } + + if (ZIGBEE_EZSP_ESCAPE == zigbee_in_byte) { + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Escape byte received")); + escape = true; + continue; + } + + if (ZIGBEE_EZSP_CANCEL == zigbee_in_byte) { + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: ZbInput byte=0x1A, cancel byte received, discarding %d bytes"), zigbee_buffer->len()); + zigbee_buffer->setLen(0); // empty buffer + escape = false; + frame_complete = false; + continue; // re-loop + } + + if (ZIGBEE_EZSP_EOF == zigbee_in_byte) { + // end of frame + frame_complete = true; + break; + } + + if (zigbee_buffer->len() < ZIGBEE_BUFFER_SIZE) { + // check if escape + if (ZIGBEE_EZSP_ESCAPE == zigbee_in_byte) { + escape = true; + continue; + } + if (escape) { + // invert bit 5 + zigbee_in_byte ^= 0x10; + escape = false; + } + + zigbee_buffer->add8(zigbee_in_byte); + zigbee_polling_window = millis(); // Wait for more data + } // adding bytes + } // while (ZigbeeSerial->available()) + + uint32_t frame_len = zigbee_buffer->len(); + if (frame_complete || (frame_len && (millis() > (zigbee_polling_window + ZIGBEE_POLLING)))) { + char hex_char[frame_len * 2 + 2]; + ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char)); + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Bytes follow_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric()); + if ((frame_complete) && (frame_len >= 3)) { + // frame received and has at least 3 bytes (without EOF), checking CRC + // AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": received raw frame %s"), hex_char); + uint16_t crc = 0xFFFF; // frame CRC + // compute CRC + for (uint32_t i=0; iget8(i) << 8); + for (uint32_t i=0; i<8; i++) { + if (crc & 0x8000) { + crc = (crc << 1) ^ 0x1021; // polynom is x^16 + x^12 + x^5 + 1, CCITT standard + } else { + crc <<= 1; + } + } + } + + uint16_t crc_received = zigbee_buffer->get8(frame_len - 2) << 8 | zigbee_buffer->get8(frame_len - 1); + // remove 2 last bytes + + if (crc_received != crc) { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": bad crc (received 0x%04X, computed 0x%04X) %s"), crc_received, crc, hex_char); + } else { + // copy buffer + SBuffer ezsp_buffer = zigbee_buffer->subBuffer(0, frame_len - 2); // CRC + + // CRC is correct, apply de-stuffing if DATA frame + if (0 == (ezsp_buffer.get8(0) & 0x80)) { + // DATA frame + uint8_t rand = 0x42; + for (uint32_t i=1; i> 1) ^ 0xB8; } + else { rand = (rand >> 1); } + } + } + + ToHex_P((unsigned char*)ezsp_buffer.getBuffer(), ezsp_buffer.len(), hex_char, sizeof(hex_char)); + Response_P(PSTR("{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "\":\"%s\"}"), hex_char); + if (Settings.flag3.tuya_serial_mqtt_publish) { + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); + } else { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "%s"), mqtt_data); // TODO move to LOG_LEVEL_DEBUG when stable + } + // now process the message + ZigbeeProcessInput(ezsp_buffer); + } + } else { + // the buffer timed-out, print error and discard + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": time-out, discarding %s, %d"), hex_char); + } + zigbee_buffer->setLen(0); // empty buffer + escape = false; + frame_complete = false; + } + +#endif // USE_ZIGBEE_EZSP + } /********************************************************************************************/ @@ -201,15 +347,20 @@ uint32_t strToUInt(const JsonVariant &val) { return 0; // couldn't parse anything } +#ifdef USE_ZIGBEE_ZNP // Do a factory reset of the CC2530 const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM = { Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x01 /* STARTOPT_CLEAR_CONFIG */}; //"2605030101"; // Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 len, 0x01 STARTOPT_CLEAR_CONFIG +#endif // USE_ZIGBEE_ZNP + void CmndZbReset(void) { if (ZigbeeSerial) { switch (XdrvMailbox.payload) { case 1: +#ifdef USE_ZIGBEE_ZNP ZigbeeZNPSend(ZIGBEE_FACTORY_RESET, sizeof(ZIGBEE_FACTORY_RESET)); +#endif // USE_ZIGBEE_ZNP eraseZigbeeDevices(); restart_flag = 2; ResponseCmndChar_P(PSTR(D_JSON_ZIGBEE_CC2530 " " D_JSON_RESET_AND_RESTARTING)); @@ -220,6 +371,38 @@ void CmndZbReset(void) { } } +#ifdef USE_ZIGBEE_ZNP + +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 + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEEZNPSENT ": bad message len %d"), len); + return; + } + uint8_t data_len = len - 2; // removing CMD1 and CMD2 + + if (ZigbeeSerial) { + uint8_t fcs = data_len; + + ZigbeeSerial->write(ZIGBEE_SOF); // 0xFE + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend SOF %02X"), ZIGBEE_SOF); + ZigbeeSerial->write(data_len); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend LEN %02X"), data_len); + for (uint32_t i = 0; i < len; i++) { + uint8_t b = pgm_read_byte(msg + i); + ZigbeeSerial->write(b); + fcs ^= b; + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend byt %02X"), b); + } + ZigbeeSerial->write(fcs); // finally send fcs checksum byte + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs); + } + // Now send a MQTT message to report the sent message + char hex_char[(len * 2) + 2]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %s"), + ToHex_P(msg, len, hex_char, sizeof(hex_char))); +} + // // Same code for `ZbZNPSend` and `ZbZNPReceive` // building the complete message (intro, length) @@ -264,36 +447,138 @@ void CmndZbZNPSend(void) CmndZbZNPSendOrReceive(true); } -void ZigbeeZNPSend(const uint8_t *msg, size_t len) { - if ((len < 2) || (len > 252)) { +#endif // USE_ZIGBEE_ZNP + +#ifdef USE_ZIGBEE_EZSP + +// internal function to output a byte, and escape it (stuffing) if needed +void ZigbeeEZSPSend_Out(uint8_t out_byte) { + switch (out_byte) { + case 0x7E: // Flag byte + case 0x11: // XON + case 0x13: // XOFF + case 0x18: // Substitute byte + case 0x1A: // Cancel byte + case 0x7D: // Escape byte + ZigbeeSerial->write(ZIGBEE_EZSP_ESCAPE); // send Escape byte 0x7D + ZigbeeSerial->write(out_byte ^ 0x10); // send with bit 5 inverted + break; + default: + ZigbeeSerial->write(out_byte); // send unchanged + break; + } +} +// Send low-level EZSP frames +// +// The frame should contain the Control Byte and Data Field +// The frame shouldn't be escaped, nor randomized +// +// Before sending: +// - send Cancel byte (0x1A) if requested +// - randomize Data Field if DATA Frame +// - compute CRC16 +// - escape (stuff) reserved bytes +// - add EOF (0x7E) +// - send frame +// send_cancel: should we first send a EZSP_CANCEL (0x1A) before the message to clear any leftover +void ZigbeeEZSPSend(const uint8_t *msg, size_t len, bool send_cancel = false) { + if ((len < 1) || (len > 252)) { // abort, message cannot be less than 2 bytes for CMD1 and CMD2 - AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEEZNPSENT ": bad message len %d"), len); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEE_EZSP_SENT ": bad message len %d"), len); return; } uint8_t data_len = len - 2; // removing CMD1 and CMD2 if (ZigbeeSerial) { - uint8_t fcs = data_len; + if (send_cancel) { + ZigbeeSerial->write(ZIGBEE_EZSP_CANCEL); // 0x1A + } - ZigbeeSerial->write(ZIGBEE_SOF); // 0xFE - //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend SOF %02X"), ZIGBEE_SOF); - ZigbeeSerial->write(data_len); - //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend LEN %02X"), data_len); - for (uint32_t i = 0; i < len; i++) { - uint8_t b = pgm_read_byte(msg + i); - ZigbeeSerial->write(b); - fcs ^= b; - //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend byt %02X"), b); - } - ZigbeeSerial->write(fcs); // finally send fcs checksum byte - //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs); + bool data_frame = (0 == (msg[0] & 0x80)); + uint8_t rand = 0x42; // pseudo-randomizer initial value + uint16_t crc = 0xFFFF; // CRC16 CCITT initialization + + for (uint32_t i=0; i 0)) { + out_byte ^= rand; + if (rand & 1) { rand = (rand >> 1) ^ 0xB8; } + else { rand = (rand >> 1); } + } + + // compute CRC + crc = crc ^ ((uint16_t)out_byte << 8); + for (uint32_t i=0; i<8; i++) { + if (crc & 0x8000) { + crc = (crc << 1) ^ 0x1021; // polynom is x^16 + x^12 + x^5 + 1, CCITT standard + } else { + crc <<= 1; + } + } + + // output byte + ZigbeeEZSPSend_Out(out_byte); + } + // send CRC16 in big-endian + ZigbeeEZSPSend_Out(crc >> 8); + ZigbeeEZSPSend_Out(crc & 0xFF); + + // finally send End of Frame + ZigbeeSerial->write(ZIGBEE_EZSP_EOF); // 0x1A } // Now send a MQTT message to report the sent message char hex_char[(len * 2) + 2]; - AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %s"), + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEE_EZSP_SENT " %s"), ToHex_P(msg, len, hex_char, sizeof(hex_char))); } +// +// Same code for `ZbZNPSend` and `ZbZNPReceive` +// building the complete message (intro, length) +// +void CmndZbEZSPSendOrReceive(bool send) +{ + if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) { + uint8_t code; + + char *codes = RemoveSpace(XdrvMailbox.data); + int32_t size = strlen(XdrvMailbox.data); + + SBuffer buf((size+1)/2); + + while (size > 1) { + char stemp[3]; + strlcpy(stemp, codes, sizeof(stemp)); + code = strtol(stemp, nullptr, 16); + buf.add8(code); + size -= 2; + codes += 2; + } + if (send) { + // Command was `ZbEZSPSend` + ZigbeeEZSPSend(buf.getBuffer(), buf.len()); + } else { + // Command was `ZbEZSPReceive` + ZigbeeProcessInput(buf); + } + } + ResponseCmndDone(); +} + +// For debug purposes only, simulates a message received +void CmndZbEZSPReceive(void) +{ + CmndZbEZSPSendOrReceive(false); +} + +void CmndZbEZSPSend(void) +{ + CmndZbEZSPSendOrReceive(true); +} +#endif // USE_ZIGBEE_EZSP + // // Internal function, send the low-level frame // Input: @@ -311,6 +596,7 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) { // void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, uint16_t manuf, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId) { +#ifdef USE_ZIGBEE_ZNP SBuffer buf(32+len); buf.add8(Z_SREQ | Z_AF); // 24 buf.add8(AF_DATA_REQUEST_EXT); // 02 @@ -342,6 +628,7 @@ void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterI } ZigbeeZNPSend(buf.getBuffer(), buf.len()); +#endif // USE_ZIGBEE_ZNP } /********************************************************************************************/ @@ -914,6 +1201,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind if (&to_group && dstLongAddr) { ResponseCmndChar_P(PSTR("Cannot have both \"ToDevice\" and \"ToGroup\"")); return; } if (!&to_group && !dstLongAddr) { ResponseCmndChar_P(PSTR("Missing \"ToDevice\" or \"ToGroup\"")); return; } +#ifdef USE_ZIGBEE_ZNP SBuffer buf(34); buf.add8(Z_SREQ | Z_ZDO); if (unbind) { @@ -935,6 +1223,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind } ZigbeeZNPSend(buf.getBuffer(), buf.len()); +#endif // USE_ZIGBEE_ZNP ResponseCmndDone(); } @@ -961,6 +1250,7 @@ void CmndZbBindState(void) { uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } +#ifdef USE_ZIGBEE_ZNP SBuffer buf(10); buf.add8(Z_SREQ | Z_ZDO); // 25 buf.add8(ZDO_MGMT_BIND_REQ); // 33 @@ -968,6 +1258,7 @@ void CmndZbBindState(void) { buf.add8(0); // StartIndex = 0 ZigbeeZNPSend(buf.getBuffer(), buf.len()); +#endif // USE_ZIGBEE_ZNP ResponseCmndDone(); } @@ -1191,6 +1482,7 @@ void CmndZbPermitJoin(void) { duration = 0xFF; // unlimited time } +#ifdef USE_ZIGBEE_ZNP SBuffer buf(34); buf.add8(Z_SREQ | Z_ZDO); // 25 buf.add8(ZDO_MGMT_PERMIT_JOIN_REQ); // 36 @@ -1200,6 +1492,7 @@ void CmndZbPermitJoin(void) { buf.add8(0x00); // TCSignificance ZigbeeZNPSend(buf.getBuffer(), buf.len()); +#endif // USE_ZIGBEE_ZNP ResponseCmndDone(); } From bf6083ff1c0df61fccb26cea6fa04475755b2d5a Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Tue, 16 Jun 2020 20:17:47 +0200 Subject: [PATCH 2/2] Fix wrong escape bit --- tasmota/xdrv_23_zigbee_9_impl.ino | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index a28481277a07..1cc5f6efc2d7 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -206,14 +206,9 @@ void ZigbeeInputLoop(void) { } if (zigbee_buffer->len() < ZIGBEE_BUFFER_SIZE) { - // check if escape - if (ZIGBEE_EZSP_ESCAPE == zigbee_in_byte) { - escape = true; - continue; - } if (escape) { // invert bit 5 - zigbee_in_byte ^= 0x10; + zigbee_in_byte ^= 0x20; escape = false; } @@ -461,7 +456,7 @@ void ZigbeeEZSPSend_Out(uint8_t out_byte) { case 0x1A: // Cancel byte case 0x7D: // Escape byte ZigbeeSerial->write(ZIGBEE_EZSP_ESCAPE); // send Escape byte 0x7D - ZigbeeSerial->write(out_byte ^ 0x10); // send with bit 5 inverted + ZigbeeSerial->write(out_byte ^ 0x20); // send with bit 5 inverted break; default: ZigbeeSerial->write(out_byte); // send unchanged