diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index 5af2e716f..11520b0a2 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -4,6 +4,7 @@ * Add Full support of all protocols in IRremoteESP8266, to be used on dedicated-IR Tasmota version. Warning: +81k Flash when compiling with USE_IR_REMOTE_FULL * Add compile time define USE_WS2812_HARDWARE to select hardware type WS2812, WS2812X, WS2813, SK6812, LC8812 or APA106 (DMA mode only) * Add 'sonoff-ir' pre-packaged IR-dedicated firmware and 'sonoff-ircustom' to customize firmware with IR Full protocol support + * Add Zigbee support phase 2 - cc2530 initialization and basic ZCL decoding * * 6.6.0.8 20190827 * Add Tuya Energy monitoring by Shantur Rathore diff --git a/sonoff/i18n.h b/sonoff/i18n.h index ee7ffdc6c..34a9ff61e 100644 --- a/sonoff/i18n.h +++ b/sonoff/i18n.h @@ -444,6 +444,12 @@ #define D_CMND_LATITUDE "Latitude" #define D_CMND_LONGITUDE "Longitude" +// Commands xdrv_23_zigbee.ino +#define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend" + #define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived" + #define D_JSON_ZIGBEEZNPSENT "ZigbeeZNPSent" + #define D_JSON_ZIGBEEZCLRECEIVED "ZigbeeZCLReceived" + #define D_JSON_ZIGBEEZCLSENT "ZigbeeZCLSent" /********************************************************************************************/ #define D_ASTERISK_PWD "****" diff --git a/sonoff/support_static_buffer.ino b/sonoff/support_static_buffer.ino new file mode 100644 index 000000000..d2817753b --- /dev/null +++ b/sonoff/support_static_buffer.ino @@ -0,0 +1,159 @@ +/* + support_buffer.ino - Static binary buffer for Zigbee + + 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 . +*/ + +typedef struct SBuffer_impl { + uint16_t size; // size in bytes of the buffer + uint16_t len; // current size of the data in buffer. Invariant: len <= size + uint8_t buf[]; // the actual data +} SBuffer_impl; + +typedef class SBuffer { + +protected: + SBuffer(void) { + // unused empty constructor except from subclass + } + +public: + SBuffer(const size_t size) { + _buf = (SBuffer_impl*) new char[size+4]; // add 4 bytes for size and len + _buf->size = size; + _buf->len = 0; + //*((uint32_t*)_buf) = size; // writing both size and len=0 in a single 32 bits write + } + + inline size_t getSize(void) const { return _buf->size; } + inline size_t size(void) const { return _buf->size; } + inline size_t getLen(void) const { return _buf->len; } + inline size_t len(void) const { return _buf->len; } + inline uint8_t *getBuffer(void) const { return _buf->buf; } + inline uint8_t *buf(void) const { return _buf->buf; } + + virtual ~SBuffer(void) { + delete[] _buf; + } + + inline void setLen(const size_t len) { + uint16_t old_len = _buf->len; + _buf->len = (len <= _buf->size) ? len : _buf->size; + if (old_len < _buf->len) { + memset((void*) &_buf->buf[old_len], 0, _buf->len - old_len); + } + } + + size_t add8(const uint8_t data) { // append 8 bits value + if (_buf->len < _buf->size) { // do we have room for 1 byte + _buf->buf[_buf->len++] = data; + } + return _buf->len; + } + size_t add16(const uint16_t data) { // append 16 bits value + if (_buf->len < _buf->size - 1) { // do we have room for 2 bytes + _buf->buf[_buf->len++] = data; + _buf->buf[_buf->len++] = data >> 8; + } + return _buf->len; + } + size_t add32(const uint32_t data) { // append 32 bits value + if (_buf->len < _buf->size - 3) { // do we have room for 2 bytes + _buf->buf[_buf->len++] = data; + _buf->buf[_buf->len++] = data >> 8; + _buf->buf[_buf->len++] = data >> 16; + _buf->buf[_buf->len++] = data >> 24; + } + return _buf->len; + } + + size_t addBuffer(const SBuffer &buf2) { + if (len() + buf2.len() <= size()) { + for (uint32_t i = 0; i < buf2.len(); i++) { + _buf->buf[_buf->len++] = buf2.buf()[i]; + } + } + return _buf->len; + } + + size_t addBuffer(const char *buf2, size_t len2) { + if (len() + len2 <= size()) { + for (uint32_t i = 0; i < len2; i++) { + _buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]); + } + } + return _buf->len; + } + + uint8_t get8(size_t offset) const { + if (offset < _buf->len) { + return _buf->buf[offset]; + } else { + return 0; + } + } + uint8_t read8(const size_t offset) const { + if (offset < len()) { + return _buf->buf[offset]; + } + return 0; + } + uint16_t get16(const size_t offset) const { + if (offset < len() - 1) { + return _buf->buf[offset] | (_buf->buf[offset+1] << 8); + } + return 0; + } + uint32_t get32(const size_t offset) const { + if (offset < len() - 3) { + return _buf->buf[offset] | (_buf->buf[offset+1] << 8) | + (_buf->buf[offset+2] << 16) | (_buf->buf[offset+3] << 24); + } + return 0; + } + + SBuffer subBuffer(const size_t start, size_t len) const { + if (start >= _buf->len) { + len = 0; + } else if (start + len > _buf->len) { + len = _buf->len - start; + } + + SBuffer buf2(len); + memcpy(buf2.buf(), buf()+start, len); + buf2._buf->len = len; + return buf2; + } + +protected: + SBuffer_impl * _buf; + +} SBuffer; + +typedef class PreAllocatedSBuffer : public SBuffer { + +public: + PreAllocatedSBuffer(const size_t size, void * buffer) { + _buf = (SBuffer_impl*) buffer; + _buf->size = size - 4; + _buf->len = 0; + } + + ~PreAllocatedSBuffer(void) { + // don't deallocate + _buf = nullptr; + } +} PreAllocatedSBuffer; diff --git a/sonoff/xdrv_23_zigbee.ino b/sonoff/xdrv_23_zigbee.ino deleted file mode 100644 index 71eb1aaaa..000000000 --- a/sonoff/xdrv_23_zigbee.ino +++ /dev/null @@ -1,412 +0,0 @@ -/* - xdrv_23_zigbee.ino - zigbee serial 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 - -#define XDRV_23 23 - -const uint32_t ZIGBEE_BUFFER_SIZE = 256; // Max ZNP frame is SOF+LEN+CMD1+CMD2+250+FCS = 255 -const uint8_t ZIGBEE_SOF = 0xFE; - -// State machine states -enum class ZnpStates { - S_START = 0, - S_READY -}; - -// ZNP Constants taken from https://github.com/Frans-Willem/AqaraHub/blob/master/src/znp/znp.h -enum class ZnpCommandType { POLL = 0, SREQ = 2, AREQ = 4, SRSP = 6 }; - -enum class ZnpSubsystem { - RPC_Error = 0, - SYS = 1, - MAC = 2, - NWK = 3, - AF = 4, - ZDO = 5, - SAPI = 6, - UTIL = 7, - DEBUG = 8, - APP = 9 -}; - -enum class ZnpStatus : uint8_t { - Success = 0x00, - Failure = 0x01, - InvalidParameter = 0x02, - MemError = 0x03, - BufferFull = 0x11 -}; - -typedef uint64_t IEEEAddress; -typedef uint16_t ShortAddress; - -enum class AddrMode : uint8_t { - NotPresent = 0, - Group = 1, - ShortAddress = 2, - IEEEAddress = 3, - Broadcast = 0xFF -}; - -// Commands in the SYS subsystem -enum class SysCommand : uint8_t { - RESET = 0x00, - PING = 0x01, - VERSION = 0x02, - SET_EXTADDR = 0x03, - GET_EXTADDR = 0x04, - RAM_READ = 0x05, - RAM_WRITE = 0x06, - OSAL_NV_ITEM_INIT = 0x07, - OSAL_NV_READ = 0x08, - OSAL_NV_WRITE = 0x09, - OSAL_START_TIMER = 0x0A, - OSAL_STOP_TIMER = 0x0B, - RANDOM = 0x0C, - ADC_READ = 0x0D, - GPIO = 0x0E, - STACK_TUNE = 0x0F, - SET_TIME = 0x10, - GET_TIME = 0x11, - OSAL_NV_DELETE = 0x12, - OSAL_NV_LENGTH = 0x13, - TEST_RF = 0x40, - TEST_LOOPBACK = 0x41, - RESET_IND = 0x80, - OSAL_TIMER_EXPIRED = 0x81, -}; - -// Commands in the AF subsystem -enum class AfCommand : uint8_t { - REGISTER = 0x00, - DATA_REQUEST = 0x01, - DATA_REQUEST_EXT = 0x02, - DATA_REQUEST_SRC_RTG = 0x03, - INTER_PAN_CTL = 0x10, - DATA_STORE = 0x11, - DATA_RETRIEVE = 0x12, - APSF_CONFIG_SET = 0x13, - DATA_CONFIRM = 0x80, - REFLECT_ERROR = 0x83, - INCOMING_MSG = 0x81, - INCOMING_MSG_EXT = 0x82 -}; - -// Commands in the ZDO subsystem -enum class ZdoCommand : uint8_t { - NWK_ADDR_REQ = 0x00, - IEEE_ADDR_REQ = 0x01, - NODE_DESC_REQ = 0x02, - POWER_DESC_REQ = 0x03, - SIMPLE_DESC_REQ = 0x04, - ACTIVE_EP_REQ = 0x05, - MATCH_DESC_REQ = 0x06, - COMPLEX_DESC_REQ = 0x07, - USER_DESC_REQ = 0x08, - DEVICE_ANNCE = 0x0A, - USER_DESC_SET = 0x0B, - SERVER_DISC_REQ = 0x0C, - END_DEVICE_BIND_REQ = 0x20, - BIND_REQ = 0x21, - UNBIND_REQ = 0x22, - SET_LINK_KEY = 0x23, - REMOVE_LINK_KEY = 0x24, - GET_LINK_KEY = 0x25, - MGMT_NWK_DISC_REQ = 0x30, - MGMT_LQI_REQ = 0x31, - MGMT_RTQ_REQ = 0x32, - MGMT_BIND_REQ = 0x33, - MGMT_LEAVE_REQ = 0x34, - MGMT_DIRECT_JOIN_REQ = 0x35, - MGMT_PERMIT_JOIN_REQ = 0x36, - MGMT_NWK_UPDATE_REQ = 0x37, - MSG_CB_REGISTER = 0x3E, - MGS_CB_REMOVE = 0x3F, - STARTUP_FROM_APP = 0x40, - AUTO_FIND_DESTINATION = 0x41, - EXT_REMOVE_GROUP = 0x47, - EXT_REMOVE_ALL_GROUP = 0x48, - EXT_FIND_ALL_GROUPS_ENDPOINT = 0x49, - EXT_FIND_GROUP = 0x4A, - EXT_ADD_GROUP = 0x4B, - EXT_COUNT_ALL_GROUPS = 0x4C, - NWK_ADDR_RSP = 0x80, - IEEE_ADDR_RSP = 0x81, - NODE_DESC_RSP = 0x82, - POWER_DESC_RSP = 0x83, - SIMPLE_DESC_RSP = 0x84, - ACTIVE_EP_RSP = 0x85, - MATCH_DESC_RSP = 0x86, - COMPLEX_DESC_RSP = 0x87, - USER_DESC_RSP = 0x88, - USER_DESC_CONF = 0x89, - SERVER_DISC_RSP = 0x8A, - END_DEVICE_BIND_RSP = 0xA0, - BIND_RSP = 0xA1, - UNBIND_RSP = 0xA2, - MGMT_NWK_DISC_RSP = 0xB0, - MGMT_LQI_RSP = 0xB1, - MGMT_RTG_RSP = 0xB2, - MGMT_BIND_RSP = 0xB3, - MGMT_LEAVE_RSP = 0xB4, - MGMT_DIRECT_JOIN_RSP = 0xB5, - MGMT_PERMIT_JOIN_RSP = 0xB6, - STATE_CHANGE_IND = 0xC0, - END_DEVICE_ANNCE_IND = 0xC1, - MATCH_DESC_RSP_SENT = 0xC2, - STATUS_ERROR_RSP = 0xC3, - SRC_RTG_IND = 0xC4, - LEAVE_IND = 0xC9, - TC_DEV_IND = 0xCA, - PERMIT_JOIN_IND = 0xCB, - MSG_CB_INCOMING = 0xFF -}; - -// Commands in the SAPI subsystem -enum class SapiCommand : uint8_t { - START_REQUEST = 0x00, - BIND_DEVICE = 0x01, - ALLOW_BIND = 0x02, - SEND_DATA_REQUEST = 0x03, - READ_CONFIGURATION = 0x04, - WRITE_CONFIGURATION = 0x05, - GET_DEVICE_INFO = 0x06, - FIND_DEVICE_REQUEST = 0x07, - PERMIT_JOINING_REQUEST = 0x08, - SYSTEM_RESET = 0x09, - START_CONFIRM = 0x80, - BIND_CONFIRM = 0x81, - ALLOW_BIND_CONFIRM = 0x82, - SEND_DATA_CONFIRM = 0x83, - FIND_DEVICE_CONFIRM = 0x85, - RECEIVE_DATA_INDICATION = 0x87, -}; - -// Commands in the UTIL subsystem -enum class UtilCommand : uint8_t { - GET_DEVICE_INFO = 0x00, - GET_NV_INFO = 0x01, - SET_PANID = 0x02, - SET_CHANNELS = 0x03, - SET_SECLEVEL = 0x04, - SET_PRECFGKEY = 0x05, - CALLBACK_SUB_CMD = 0x06, - KEY_EVENT = 0x07, - TIME_ALIVE = 0x09, - LED_CONTROL = 0x0A, - TEST_LOOPBACK = 0x10, - DATA_REQ = 0x11, - SRC_MATCH_ENABLE = 0x20, - SRC_MATCH_ADD_ENTRY = 0x21, - SRC_MATCH_DEL_ENTRY = 0x22, - SRC_MATCH_CHECK_SRC_ADDR = 0x23, - SRC_MATCH_ACK_ALL_PENDING = 0x24, - SRC_MATCH_CHECK_ALL_PENDING = 0x25, - ADDRMGR_EXT_ADDR_LOOKUP = 0x40, - ADDRMGR_NWK_ADDR_LOOKUP = 0x41, - APSME_LINK_KEY_DATA_GET = 0x44, - APSME_LINK_KEY_NV_ID_GET = 0x45, - ASSOC_COUNT = 0x48, - ASSOC_FIND_DEVICE = 0x49, - ASSOC_GET_WITH_ADDRESS = 0x4A, - APSME_REQUEST_KEY_CMD = 0x4B, - ZCL_KEY_EST_INIT_EST = 0x80, - ZCL_KEY_EST_SIGN = 0x81, - UTIL_SYNC_REQ = 0xE0, - ZCL_KEY_ESTABLISH_IND = 0xE1 -}; - -enum class Capability : uint16_t { - SYS = 0x0001, - MAC = 0x0002, - NWK = 0x0004, - AF = 0x0008, - ZDO = 0x0010, - SAPI = 0x0020, - UTIL = 0x0040, - DEBUG = 0x0080, - APP = 0x0100, - ZOAD = 0x1000 -}; - -enum class ConfigurationOption : uint8_t { - STARTUP_OPTION = 0x03, - POLL_RATE = 0x24, - QUEUED_POLL_RATE = 0x25, - RESPONSE_POLL_RATE = 0x26, - POLL_FAILURE_RETRIES = 0x29, - INDIRECT_MSG_TIMEOUT = 0x2B, - ROUTE_EXPIRY_TIME = 0x2C, - EXTENDED_PAN_ID = 0x2D, - BCAST_RETRIES = 0x2E, - PASSIVE_ACK_TIMEOUT = 0x2F, - BCAST_DELIVERY_TIME = 0x30, - APS_FRAME_RETRIES = 0x43, - APS_ACK_WAIT_DURATION = 0x44, - BINDING_TIME = 0x46, - PRECFGKEY = 0x62, - PRECFGKEYS_ENABLE = 0x63, - SECURITY_MODE = 0x64, - USERDESC = 0x81, - PANID = 0x83, - CHANLIST = 0x84, - LOGICAL_TYPE = 0x87, - ZDO_DIRECT_CB = 0x8F -}; - -#define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived" - -#define D_PRFX_ZIGBEE "Zigbee" -#define D_CMND_ZIGBEEZNPSEND "ZNPSend" - -const char kZigbeeCommands[] PROGMEM = D_PRFX_ZIGBEE "|" // Prefix - D_CMND_ZIGBEEZNPSEND; - -void (* const ZigbeeCommand[])(void) PROGMEM = - { &CmndZigbeeZNPSend }; - -#include - -TasmotaSerial *ZigbeeSerial = nullptr; - -unsigned long zigbee_polling_window = 0; -uint8_t *zigbee_buffer = nullptr; -uint32_t zigbee_in_byte_counter = 0; -uint32_t zigbee_frame_len = 256; -bool zigbee_active = true; -bool zigbee_raw = false; - -void ZigbeeInput(void) -{ - // Receive only valid ZNP frames: - // 00 - SOF = 0xFE - // 01 - Length of Data Field - 0..250 - // 02 - CMD1 - first byte of command - // 03 - CMD2 - second byte of command - // 04..FD - Data Field - // FE (or last) - FCS Checksum - - while (ZigbeeSerial->available()) { - yield(); - uint8_t zigbee_in_byte = ZigbeeSerial->read(); - - if ((0 == zigbee_in_byte_counter) && (ZIGBEE_SOF != zigbee_in_byte)) { - // waiting for SOF (Start Of Frame) byte, discard anything else - continue; // discard - } - - if (zigbee_in_byte_counter < zigbee_frame_len) { - zigbee_buffer[zigbee_in_byte_counter++] = zigbee_in_byte; - zigbee_polling_window = millis(); // Wait for more data - } else { - zigbee_polling_window = 0; // Publish now - break; - } - - // recalculate frame length - if (02 == zigbee_in_byte_counter) { - // We just received the Lenght byte - uint8_t len_byte = zigbee_buffer[1]; - if (len_byte > 250) len_byte = 250; // ZNP spec says len is 250 max - - zigbee_frame_len = len_byte + 5; // SOF + LEN + CMD1 + CMD2 + FCS = 5 bytes overhead - } - } - - if (zigbee_in_byte_counter && (millis() > (zigbee_polling_window + ZIGBEE_POLLING))) { - char hex_char[(zigbee_in_byte_counter * 2) + 2]; - Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), - ToHex_P((unsigned char*)zigbee_buffer, zigbee_in_byte_counter, hex_char, sizeof(hex_char))); - MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPRECEIVED)); - XdrvRulesProcess(); - zigbee_in_byte_counter = 0; - zigbee_frame_len = 254; - } -} - -/********************************************************************************************/ - -void ZigbeeInit(void) -{ - zigbee_active = false; - if ((pin[GPIO_ZIGBEE_RX] < 99) && (pin[GPIO_ZIGBEE_TX] < 99)) { - ZigbeeSerial = new TasmotaSerial(pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX]); - if (ZigbeeSerial->begin(115200)) { // ZNP is 115200, RTS/CTS (ignored), 8N1 - if (ZigbeeSerial->hardwareSerial()) { - ClaimSerial(); - zigbee_buffer = (uint8_t*) serial_in_buffer; // Use idle serial buffer to save RAM - } else { - zigbee_buffer = (uint8_t*) malloc(ZIGBEE_BUFFER_SIZE); - } - zigbee_active = true; - ZigbeeSerial->flush(); - } - } -} - -/*********************************************************************************************\ - * Commands -\*********************************************************************************************/ - -void CmndZigbeeZNPSend(void) -{ - if (XdrvMailbox.data_len > 0) { - uint8_t code; - - char *codes = RemoveSpace(XdrvMailbox.data); - int32_t size = strlen(XdrvMailbox.data); - - while (size > 0) { - char stemp[3]; - strlcpy(stemp, codes, sizeof(stemp)); - code = strtol(stemp, nullptr, 16); - ZigbeeSerial->write(code); - size -= 2; - codes += 2; - } - } - ResponseCmndDone(); -} - -/*********************************************************************************************\ - * Interface -\*********************************************************************************************/ - -bool Xdrv23(uint8_t function) -{ - bool result = false; - - if (zigbee_active) { - switch (function) { - case FUNC_LOOP: - if (ZigbeeSerial) { ZigbeeInput(); } - break; - case FUNC_PRE_INIT: - ZigbeeInit(); - break; - case FUNC_COMMAND: - result = DecodeCommand(kZigbeeCommands, ZigbeeCommand); - break; - } - } - return result; -} - -#endif // USE_ZIGBEE diff --git a/sonoff/xdrv_23_zigbee_constants.ino b/sonoff/xdrv_23_zigbee_constants.ino new file mode 100644 index 000000000..d87cc4af5 --- /dev/null +++ b/sonoff/xdrv_23_zigbee_constants.ino @@ -0,0 +1,407 @@ +/* + xdrv_23_zigbee_constants.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 + +typedef uint64_t Z_IEEEAddress; +typedef uint16_t Z_ShortAddress; + +enum ZnpCommandType { + Z_POLL = 0x00, + Z_SREQ = 0x20, + Z_AREQ = 0x40, + Z_SRSP = 0x60 +}; + +enum ZnpSubsystem { + Z_RPC_Error = 0x00, + Z_SYS = 0x01, + Z_MAC = 0x02, + Z_NWK = 0x03, + Z_AF = 0x04, + Z_ZDO = 0x05, + Z_SAPI = 0x06, + Z_UTIL = 0x07, + Z_DEBUG = 0x08, + Z_APP = 0x09 +}; + +// Commands in the SYS subsystem +enum SysCommand { + SYS_RESET = 0x00, + SYS_PING = 0x01, + SYS_VERSION = 0x02, + SYS_SET_EXTADDR = 0x03, + SYS_GET_EXTADDR = 0x04, + SYS_RAM_READ = 0x05, + SYS_RAM_WRITE = 0x06, + SYS_OSAL_NV_ITEM_INIT = 0x07, + SYS_OSAL_NV_READ = 0x08, + SYS_OSAL_NV_WRITE = 0x09, + SYS_OSAL_START_TIMER = 0x0A, + SYS_OSAL_STOP_TIMER = 0x0B, + SYS_RANDOM = 0x0C, + SYS_ADC_READ = 0x0D, + SYS_GPIO = 0x0E, + SYS_STACK_TUNE = 0x0F, + SYS_SET_TIME = 0x10, + SYS_GET_TIME = 0x11, + SYS_OSAL_NV_DELETE = 0x12, + SYS_OSAL_NV_LENGTH = 0x13, + SYS_TEST_RF = 0x40, + SYS_TEST_LOOPBACK = 0x41, + SYS_RESET_IND = 0x80, + SYS_OSAL_TIMER_EXPIRED = 0x81, +}; +// Commands in the SAPI subsystem +enum SapiCommand { + SAPI_START_REQUEST = 0x00, + SAPI_BIND_DEVICE = 0x01, + SAPI_ALLOW_BIND = 0x02, + SAPI_SEND_DATA_REQUEST = 0x03, + SAPI_READ_CONFIGURATION = 0x04, + SAPI_WRITE_CONFIGURATION = 0x05, + SAPI_GET_DEVICE_INFO = 0x06, + SAPI_FIND_DEVICE_REQUEST = 0x07, + SAPI_PERMIT_JOINING_REQUEST = 0x08, + SAPI_SYSTEM_RESET = 0x09, + SAPI_START_CONFIRM = 0x80, + SAPI_BIND_CONFIRM = 0x81, + SAPI_ALLOW_BIND_CONFIRM = 0x82, + SAPI_SEND_DATA_CONFIRM = 0x83, + SAPI_FIND_DEVICE_CONFIRM = 0x85, + SAPI_RECEIVE_DATA_INDICATION = 0x87, +}; +enum Z_configuration { + CONF_EXTADDR = 0x01, + CONF_BOOTCOUNTER = 0x02, + CONF_STARTUP_OPTION = 0x03, + CONF_START_DELAY = 0x04, + CONF_NIB = 0x21, + CONF_DEVICE_LIST = 0x22, + CONF_ADDRMGR = 0x23, + CONF_POLL_RATE = 0x24, + CONF_QUEUED_POLL_RATE = 0x25, + CONF_RESPONSE_POLL_RATE = 0x26, + CONF_REJOIN_POLL_RATE = 0x27, + CONF_DATA_RETRIES = 0x28, + CONF_POLL_FAILURE_RETRIES = 0x29, + CONF_STACK_PROFILE = 0x2A, + CONF_INDIRECT_MSG_TIMEOUT = 0x2B, + CONF_ROUTE_EXPIRY_TIME = 0x2C, + CONF_EXTENDED_PAN_ID = 0x2D, + CONF_BCAST_RETRIES = 0x2E, + CONF_PASSIVE_ACK_TIMEOUT = 0x2F, + CONF_BCAST_DELIVERY_TIME = 0x30, + CONF_NWK_MODE = 0x31, + CONF_CONCENTRATOR_ENABLE = 0x32, + CONF_CONCENTRATOR_DISCOVERY = 0x33, + CONF_CONCENTRATOR_RADIUS = 0x34, + CONF_CONCENTRATOR_RC = 0x36, + CONF_NWK_MGR_MODE = 0x37, + CONF_SRC_RTG_EXPIRY_TIME = 0x38, + CONF_ROUTE_DISCOVERY_TIME = 0x39, + CONF_NWK_ACTIVE_KEY_INFO = 0x3A, + CONF_NWK_ALTERN_KEY_INFO = 0x3B, + CONF_ROUTER_OFF_ASSOC_CLEANUP = 0x3C, + CONF_NWK_LEAVE_REQ_ALLOWED = 0x3D, + CONF_NWK_CHILD_AGE_ENABLE = 0x3E, + CONF_DEVICE_LIST_KA_TIMEOUT = 0x3F, + CONF_BINDING_TABLE = 0x41, + CONF_GROUP_TABLE = 0x42, + CONF_APS_FRAME_RETRIES = 0x43, + CONF_APS_ACK_WAIT_DURATION = 0x44, + CONF_APS_ACK_WAIT_MULTIPLIER = 0x45, + CONF_BINDING_TIME = 0x46, + CONF_APS_USE_EXT_PANID = 0x47, + CONF_APS_USE_INSECURE_JOIN = 0x48, + CONF_COMMISSIONED_NWK_ADDR = 0x49, + CONF_APS_NONMEMBER_RADIUS = 0x4B, + CONF_APS_LINK_KEY_TABLE = 0x4C, + CONF_APS_DUPREJ_TIMEOUT_INC = 0x4D, + CONF_APS_DUPREJ_TIMEOUT_COUNT = 0x4E, + CONF_APS_DUPREJ_TABLE_SIZE = 0x4F, + CONF_DIAGNOSTIC_STATS = 0x50, + CONF_SECURITY_LEVEL = 0x61, + CONF_PRECFGKEY = 0x62, + CONF_PRECFGKEYS_ENABLE = 0x63, + CONF_SECURITY_MODE = 0x64, + CONF_SECURE_PERMIT_JOIN = 0x65, + CONF_APS_LINK_KEY_TYPE = 0x66, + CONF_APS_ALLOW_R19_SECURITY = 0x67, + CONF_IMPLICIT_CERTIFICATE = 0x69, + CONF_DEVICE_PRIVATE_KEY = 0x6A, + CONF_CA_PUBLIC_KEY = 0x6B, + CONF_KE_MAX_DEVICES = 0x6C, + CONF_USE_DEFAULT_TCLK = 0x6D, + CONF_RNG_COUNTER = 0x6F, + CONF_RANDOM_SEED = 0x70, + CONF_TRUSTCENTER_ADDR = 0x71, + CONF_USERDESC = 0x81, + CONF_NWKKEY = 0x82, + CONF_PANID = 0x83, + CONF_CHANLIST = 0x84, + CONF_LEAVE_CTRL = 0x85, + CONF_SCAN_DURATION = 0x86, + CONF_LOGICAL_TYPE = 0x87, + CONF_NWKMGR_MIN_TX = 0x88, + CONF_NWKMGR_ADDR = 0x89, + CONF_ZDO_DIRECT_CB = 0x8F, + CONF_TCLK_TABLE_START = 0x0101, + ZNP_HAS_CONFIGURED = 0xF00 +}; + +// enum Z_nvItemIds { +// SCENE_TABLE = 145, +// MIN_FREE_NWK_ADDR = 146, +// MAX_FREE_NWK_ADDR = 147, +// MIN_FREE_GRP_ID = 148, +// MAX_FREE_GRP_ID = 149, +// MIN_GRP_IDS = 150, +// MAX_GRP_IDS = 151, +// OTA_BLOCK_REQ_DELAY = 152, +// SAPI_ENDPOINT = 161, +// SAS_SHORT_ADDR = 177, +// SAS_EXT_PANID = 178, +// SAS_PANID = 179, +// SAS_CHANNEL_MASK = 180, +// SAS_PROTOCOL_VER = 181, +// SAS_STACK_PROFILE = 182, +// SAS_STARTUP_CTRL = 183, +// SAS_TC_ADDR = 193, +// SAS_TC_MASTER_KEY = 194, +// SAS_NWK_KEY = 195, +// SAS_USE_INSEC_JOIN = 196, +// SAS_PRECFG_LINK_KEY = 197, +// SAS_NWK_KEY_SEQ_NUM = 198, +// SAS_NWK_KEY_TYPE = 199, +// SAS_NWK_MGR_ADDR = 200, +// SAS_CURR_TC_MASTER_KEY = 209, +// SAS_CURR_NWK_KEY = 210, +// SAS_CURR_PRECFG_LINK_KEY = 211, +// TCLK_TABLE_START = 257, +// TCLK_TABLE_END = 511, +// APS_LINK_KEY_DATA_START = 513, +// APS_LINK_KEY_DATA_END = 767, +// DUPLICATE_BINDING_TABLE = 768, +// DUPLICATE_DEVICE_LIST = 769, +// DUPLICATE_DEVICE_LIST_KA_TIMEOUT = 770, +//}; + +// +enum Z_Status { + Z_Success = 0x00, + Z_Failure = 0x01, + Z_InvalidParameter = 0x02, + Z_MemError = 0x03, + Z_Created = 0x09, + Z_BufferFull = 0x11 +}; + +enum Z_App_Profiles { + Z_PROF_IPM = 0x0101, // Industrial Plant Monitoring + Z_PROF_HA = 0x0104, // Home Automation -- the only supported right now + Z_PROF_CBA = 0x0105, // Commercial Building Automation + Z_PROF_TA = 0x0107, // Telecom Applications + Z_PROF_PHHC = 0x0108, // Personal Home & Hospital Care + Z_PROF_AMI = 0x0109, // Advanced Metering Initiative +}; + +enum Z_Device_Ids { + Z_DEVID_CONF_TOOL = 0x0005, + // from https://www.rfwireless-world.com/Terminology/Zigbee-Profile-ID-list.html + // Generic 0x0000 ON/OFF Switch + // 0x0001 Level Control Switch + // 0x0002 ON/OFF Output + // 0x0003 Level Controllable Output + // 0x0004 Scene Selector + // 0x0005 Configuration Tool + // 0x0006 Remote control + // 0x0007 Combined Interface + // 0x0008 Range Extender + // 0x0009 Mains Power Outlet + // Lighting 0x0100 ON/OFF Light + // 0x0101 Dimmable Light + // 0x0102 Color Dimmable Light + // 0x0103 ON/OFF Light Switch + // 0x0104 Dimmer Switch + // 0x0105 Color Dimmer Switch + // 0x0106 Light Sensor + // 0x0107 Occupancy Sensor + // Closures 0x0200 Shade + // 0x0201 Shade Controller + // HVAC 0x0300 Heating/Cooling Unit + // 0x0301 Thermostat + // 0x0302 Temperature Sensor + // 0x0303 Pump + // 0x0304 Pump Controller + // 0x0305 Pressure Sensor + // 0x0306 Flow sensor + // Intruder Alarm Systems 0x0400 IAS Control and Indicating Equipment + // 0x0401 IAS Ancillary Control Equipment + // 0x0402 IAS Zone + // 0x0403 IAS Warning Device +}; + +// enum class AddrMode : uint8_t { +// NotPresent = 0, +// Group = 1, +// ShortAddress = 2, +// IEEEAddress = 3, +// Broadcast = 0xFF +// }; +// +// +// +// Commands in the AF subsystem +enum AfCommand : uint8_t { + AF_REGISTER = 0x00, + AF_DATA_REQUEST = 0x01, + AF_DATA_REQUEST_EXT = 0x02, + AF_DATA_REQUEST_SRC_RTG = 0x03, + AF_INTER_PAN_CTL = 0x10, + AF_DATA_STORE = 0x11, + AF_DATA_RETRIEVE = 0x12, + AF_APSF_CONFIG_SET = 0x13, + AF_DATA_CONFIRM = 0x80, + AF_REFLECT_ERROR = 0x83, + AF_INCOMING_MSG = 0x81, + AF_INCOMING_MSG_EXT = 0x82 +}; +// +// Commands in the ZDO subsystem +enum : uint8_t { + ZDO_NWK_ADDR_REQ = 0x00, + ZDO_IEEE_ADDR_REQ = 0x01, + ZDO_NODE_DESC_REQ = 0x02, + ZDO_POWER_DESC_REQ = 0x03, + ZDO_SIMPLE_DESC_REQ = 0x04, + ZDO_ACTIVE_EP_REQ = 0x05, + ZDO_MATCH_DESC_REQ = 0x06, + ZDO_COMPLEX_DESC_REQ = 0x07, + ZDO_USER_DESC_REQ = 0x08, + ZDO_DEVICE_ANNCE = 0x0A, + ZDO_USER_DESC_SET = 0x0B, + ZDO_SERVER_DISC_REQ = 0x0C, + ZDO_END_DEVICE_BIND_REQ = 0x20, + ZDO_BIND_REQ = 0x21, + ZDO_UNBIND_REQ = 0x22, + ZDO_SET_LINK_KEY = 0x23, + ZDO_REMOVE_LINK_KEY = 0x24, + ZDO_GET_LINK_KEY = 0x25, + ZDO_MGMT_NWK_DISC_REQ = 0x30, + ZDO_MGMT_LQI_REQ = 0x31, + ZDO_MGMT_RTQ_REQ = 0x32, + ZDO_MGMT_BIND_REQ = 0x33, + ZDO_MGMT_LEAVE_REQ = 0x34, + ZDO_MGMT_DIRECT_JOIN_REQ = 0x35, + ZDO_MGMT_PERMIT_JOIN_REQ = 0x36, + ZDO_MGMT_NWK_UPDATE_REQ = 0x37, + ZDO_MSG_CB_REGISTER = 0x3E, + ZDO_MGS_CB_REMOVE = 0x3F, + ZDO_STARTUP_FROM_APP = 0x40, + ZDO_AUTO_FIND_DESTINATION = 0x41, + ZDO_EXT_REMOVE_GROUP = 0x47, + ZDO_EXT_REMOVE_ALL_GROUP = 0x48, + ZDO_EXT_FIND_ALL_GROUPS_ENDPOINT = 0x49, + ZDO_EXT_FIND_GROUP = 0x4A, + ZDO_EXT_ADD_GROUP = 0x4B, + ZDO_EXT_COUNT_ALL_GROUPS = 0x4C, + ZDO_NWK_ADDR_RSP = 0x80, + ZDO_IEEE_ADDR_RSP = 0x81, + ZDO_NODE_DESC_RSP = 0x82, + ZDO_POWER_DESC_RSP = 0x83, + ZDO_SIMPLE_DESC_RSP = 0x84, + ZDO_ACTIVE_EP_RSP = 0x85, + ZDO_MATCH_DESC_RSP = 0x86, + ZDO_COMPLEX_DESC_RSP = 0x87, + ZDO_USER_DESC_RSP = 0x88, + ZDO_USER_DESC_CONF = 0x89, + ZDO_SERVER_DISC_RSP = 0x8A, + ZDO_END_DEVICE_BIND_RSP = 0xA0, + ZDO_BIND_RSP = 0xA1, + ZDO_UNBIND_RSP = 0xA2, + ZDO_MGMT_NWK_DISC_RSP = 0xB0, + ZDO_MGMT_LQI_RSP = 0xB1, + ZDO_MGMT_RTG_RSP = 0xB2, + ZDO_MGMT_BIND_RSP = 0xB3, + ZDO_MGMT_LEAVE_RSP = 0xB4, + ZDO_MGMT_DIRECT_JOIN_RSP = 0xB5, + ZDO_MGMT_PERMIT_JOIN_RSP = 0xB6, + ZDO_STATE_CHANGE_IND = 0xC0, + ZDO_END_DEVICE_ANNCE_IND = 0xC1, + ZDO_MATCH_DESC_RSP_SENT = 0xC2, + ZDO_STATUS_ERROR_RSP = 0xC3, + ZDO_SRC_RTG_IND = 0xC4, + ZDO_LEAVE_IND = 0xC9, + ZDO_TC_DEV_IND = 0xCA, + ZDO_PERMIT_JOIN_IND = 0xCB, + ZDO_MSG_CB_INCOMING = 0xFF +}; + +//https://e2e.ti.com/support/wireless-connectivity/zigbee-and-thread/f/158/t/475920 +enum ZdoStates { + ZDO_DEV_HOLD = 0x00, // Initialized - not started automatically + ZDO_DEV_INIT = 0x01, // Initialized - not connected to anything + ZDO_DEV_NWK_DISC = 0x02, // Discovering PANIDs to join + ZDO_DEV_NWK_JOINING = 0x03, // Joining a PAN + ZDO_DEV_NWK_REJOIN = 0x04, // ReJoining a PAN, only for end devices + ZDO_DEV_END_DEVICE_UNAUTH = 0x05, // Joined but not yet authenticated by trust center + ZDO_DEV_END_DEVICE = 0x06, // Started as a device after authentication. Note: you'll see this for both Routers or End Devices. + ZDO_DEV_ROUTER = 0x07, // Started as a Zigbee Router + ZDO_DEV_COORD_STARTING = 0x08, // Starting as a Zigbee Coordinator + ZDO_DEV_ZB_COORD = 0x09, // Started as a a Zigbee Coordinator + ZDO_DEV_NWK_ORPHAN = 0x0A, // Device has lost information about its parent. +}; +// +// Commands in the UTIL subsystem +enum Z_Util { + Z_UTIL_GET_DEVICE_INFO = 0x00, + Z_UTIL_GET_NV_INFO = 0x01, + Z_UTIL_SET_PANID = 0x02, + Z_UTIL_SET_CHANNELS = 0x03, + Z_UTIL_SET_SECLEVEL = 0x04, + Z_UTIL_SET_PRECFGKEY = 0x05, + Z_UTIL_CALLBACK_SUB_CMD = 0x06, + Z_UTIL_KEY_EVENT = 0x07, + Z_UTIL_TIME_ALIVE = 0x09, + Z_UTIL_LED_CONTROL = 0x0A, + Z_UTIL_TEST_LOOPBACK = 0x10, + Z_UTIL_DATA_REQ = 0x11, + Z_UTIL_SRC_MATCH_ENABLE = 0x20, + Z_UTIL_SRC_MATCH_ADD_ENTRY = 0x21, + Z_UTIL_SRC_MATCH_DEL_ENTRY = 0x22, + Z_UTIL_SRC_MATCH_CHECK_SRC_ADDR = 0x23, + Z_UTIL_SRC_MATCH_ACK_ALL_PENDING = 0x24, + Z_UTIL_SRC_MATCH_CHECK_ALL_PENDING = 0x25, + Z_UTIL_ADDRMGR_EXT_ADDR_LOOKUP = 0x40, + Z_UTIL_ADDRMGR_NWK_ADDR_LOOKUP = 0x41, + Z_UTIL_APSME_LINK_KEY_DATA_GET = 0x44, + Z_UTIL_APSME_LINK_KEY_NV_ID_GET = 0x45, + Z_UTIL_ASSOC_COUNT = 0x48, + Z_UTIL_ASSOC_FIND_DEVICE = 0x49, + Z_UTIL_ASSOC_GET_WITH_ADDRESS = 0x4A, + Z_UTIL_APSME_REQUEST_KEY_CMD = 0x4B, + Z_UTIL_ZCL_KEY_EST_INIT_EST = 0x80, + Z_UTIL_ZCL_KEY_EST_SIGN = 0x81, + Z_UTIL_UTIL_SYNC_REQ = 0xE0, + Z_UTIL_ZCL_KEY_ESTABLISH_IND = 0xE1 +}; + +#endif // USE_ZIGBEE diff --git a/sonoff/xdrv_23_zigbee_impl.ino b/sonoff/xdrv_23_zigbee_impl.ino new file mode 100644 index 000000000..1f44a7ab5 --- /dev/null +++ b/sonoff/xdrv_23_zigbee_impl.ino @@ -0,0 +1,959 @@ +/* + 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 + +#define XDRV_23 23 + +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_LABEL_ABORT = 99; // goto label 99 in case of fatal error +const uint8_t ZIGBEE_LABEL_READY = 20; // goto label 99 in case of fatal error + + +#include + +TasmotaSerial *ZigbeeSerial = nullptr; + +const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND; + +void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend }; + +typedef int32_t (*ZB_Func)(uint8_t value); +typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, class SBuffer &buf); + +typedef union Zigbee_Instruction { + struct { + uint8_t i; // instruction + uint8_t d8; // 8 bits data + uint16_t d16; // 16 bits data + } i; + const void *p; // pointer + // const void *m; // for type checking only, message + // const ZB_Func f; + // const ZB_RecvMsgFunc fr; +} Zigbee_Instruction; +// +// Zigbee_Instruction z1 = { .i = {1,2,3}}; +// Zigbee_Instruction z3 = { .p = nullptr }; + +typedef struct Zigbee_Instruction_Type { + uint8_t instr; + uint8_t data; +} Zigbee_Instruction_Type; + +enum Zigbee_StateMachine_Instruction_Set { + // 2 bytes instructions + ZGB_INSTR_4_BYTES = 0, + ZGB_INSTR_NOOP = 0, // do nothing + ZGB_INSTR_LABEL, // define a label + ZGB_INSTR_GOTO, // goto label + ZGB_INSTR_ON_ERROR_GOTO, // goto label if error + ZGB_INSTR_ON_TIMEOUT_GOTO, // goto label if timeout + ZGB_INSTR_WAIT, // wait for x ms (in chunks of 100ms) + ZGB_INSTR_WAIT_FOREVER, // wait forever but state machine still active + ZGB_INSTR_STOP, // stop state machine with optional error code + + // 6 bytes instructions + ZGB_INSTR_8_BYTES = 0x80, + ZGB_INSTR_CALL = 0x80, // call a function + ZGB_INSTR_LOG, // log a message, if more detailed logging required, call a function + ZGB_INSTR_SEND, // send a ZNP message + ZGB_INSTR_WAIT_UNTIL, // wait until the specified message is received, ignore all others + ZGB_INSTR_WAIT_RECV, // wait for a message according to the filter + ZGB_ON_RECV_UNEXPECTED, // function to handle unexpected messages, or nullptr + + // 10 bytes instructions + ZGB_INSTR_12_BYTES = 0xF0, + ZGB_INSTR_WAIT_RECV_CALL, // wait for a filtered message and call function upon receive +}; + +#define ZI_NOOP() { .i = { ZGB_INSTR_NOOP, 0x00, 0x0000} }, +#define ZI_LABEL(x) { .i = { ZGB_INSTR_LABEL, (x), 0x0000} }, +#define ZI_GOTO(x) { .i = { ZGB_INSTR_GOTO, (x), 0x0000} }, +#define ZI_ON_ERROR_GOTO(x) { .i = { ZGB_INSTR_ON_ERROR_GOTO, (x), 0x0000} }, +#define ZI_ON_TIMEOUT_GOTO(x) { .i = { ZGB_INSTR_ON_TIMEOUT_GOTO, (x), 0x0000} }, +#define ZI_WAIT(x) { .i = { ZGB_INSTR_WAIT, 0x00, (x)} }, +#define ZI_WAIT_FOREVER() { .i = { ZGB_INSTR_WAIT_FOREVER, 0x00, 0x0000} }, +#define ZI_STOP(x) { .i = { ZGB_INSTR_STOP, (x), 0x0000} }, + +#define ZI_CALL(f, x) { .i = { ZGB_INSTR_CALL, (x), 0x0000} }, { .p = (const void*)(f) }, +#define ZI_LOG(x, m) { .i = { ZGB_INSTR_LOG, (x), 0x0000 } }, { .p = ((const void*)(m)) }, +#define ZI_ON_RECV_UNEXPECTED(f) { .i = { ZGB_ON_RECV_UNEXPECTED, 0x00, 0x0000} }, { .p = (const void*)(f) }, +#define ZI_SEND(m) { .i = { ZGB_INSTR_SEND, sizeof(m), 0x0000} }, { .p = (const void*)(m) }, +#define ZI_WAIT_RECV(x, m) { .i = { ZGB_INSTR_WAIT_RECV, sizeof(m), (x)} }, { .p = (const void*)(m) }, +#define ZI_WAIT_UNTIL(x, m) { .i = { ZGB_INSTR_WAIT_UNTIL, sizeof(m), (x)} }, { .p = (const void*)(m) }, +#define ZI_WAIT_RECV_FUNC(x, m, f) { .i = { ZGB_INSTR_WAIT_RECV_CALL, sizeof(m), (x)} }, { .p = (const void*)(m) }, { .p = (const void*)(f) }, + +struct ZigbeeStatus { + bool active = true; // is Zigbee active for this device, i.e. GPIOs configured + bool state_machine = false; // the state machine is running + bool state_waiting = false; // the state machine is waiting for external event or timeout + bool state_no_timeout = false; // the current wait loop does not generate a timeout but only continues running + bool ready = false; // cc2530 initialization is complet, ready to operate + uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; // on error goto label, 99 default to abort + uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; // on timeout goto label, 99 default to abort + int16_t pc = 0; // program counter, -1 means abort + uint32_t next_timeout = 0; // millis for the next timeout + + uint8_t *recv_filter = nullptr; // receive filter message + bool recv_until = false; // ignore all messages until the received frame fully matches + size_t recv_filter_len = 0; + ZB_RecvMsgFunc recv_func = nullptr; // function to call when message is expected + ZB_RecvMsgFunc recv_unexpected = nullptr; // function called when unexpected message is received + + bool init_phase = true; // initialization phase, before accepting zigbee traffic +}; +struct ZigbeeStatus zigbee; + +SBuffer *zigbee_buffer = nullptr; + + + +/*********************************************************************************************\ + * ZCL +\*********************************************************************************************/ + +typedef union ZCLHeaderFrameControl_t { + struct { + uint8_t frame_type : 2; // 00 = across entire profile, 01 = cluster specific + uint8_t manuf_specific : 1; // Manufacturer Specific Sub-field + uint8_t direction : 1; // 0 = tasmota to zigbee, 1 = zigbee to tasmota + uint8_t disable_def_resp : 1; // don't send back default response + uint8_t reserved : 3; + } b; + uint8_t d8; // raw 8 bits field +} ZCLHeaderFrameControl_t; + +class ZCLFrame { +public: + + ZCLFrame(uint8_t frame_control, uint16_t manuf_code, uint8_t transact_seq, uint8_t cmd_id, + const char *buf, size_t buf_len ): + _cmd_id(cmd_id), _manuf_code(manuf_code), _transact_seq(transact_seq), + _payload(buf_len ? buf_len : 250) // allocate the data frame from source or preallocate big enough + { + _frame_control.d8 = frame_control; + _payload.addBuffer(buf, buf_len); + }; + + void publishMQTTReceived(void) { + 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 "\":{\"fc\":\"0x%02X\",\"manuf\":\"0x%04X\",\"transact\":%d," + "\"cmdid\":\"0x%02X\",\"payload\":\"%s\"}}"), + _frame_control, _manuf_code, _transact_seq, _cmd_id, + hex_char); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLSENT)); + XdrvRulesProcess(); + } + + static ZCLFrame parseRawFrame(SBuffer &buf, uint8_t offset, uint8_t len) { // parse a raw frame and build the ZCL frame object + uint32_t i = offset; + ZCLHeaderFrameControl_t frame_control; + uint16_t manuf_code = 0; + uint8_t transact_seq; + uint8_t cmd_id; + + frame_control.d8 = buf.get8(i++); + if (frame_control.b.manuf_specific) { + manuf_code = buf.get16(i); + i += 2; + } + transact_seq = buf.get8(i++); + cmd_id = buf.get8(i++); + ZCLFrame zcl_frame(frame_control.d8, manuf_code, transact_seq, cmd_id, + (const char *)(buf.buf() + i), len + offset - i); + return zcl_frame; + } + +private: + ZCLHeaderFrameControl_t _frame_control = { .d8 = 0 }; + uint16_t _manuf_code = 0; // optional + uint8_t _transact_seq = 0; // transaction sequence number + uint8_t _cmd_id = 0; + SBuffer _payload; +}; + + +/*********************************************************************************************\ + * State Machine +\*********************************************************************************************/ + +#define Z_B0(a) (uint8_t)( ((a) ) & 0xFF ) +#define Z_B1(a) (uint8_t)( ((a) >> 8) & 0xFF ) +#define Z_B2(a) (uint8_t)( ((a) >> 16) & 0xFF ) +#define Z_B3(a) (uint8_t)( ((a) >> 24) & 0xFF ) +#define Z_B4(a) (uint8_t)( ((a) >> 32) & 0xFF ) +#define Z_B5(a) (uint8_t)( ((a) >> 40) & 0xFF ) +#define Z_B6(a) (uint8_t)( ((a) >> 48) & 0xFF ) +#define Z_B7(a) (uint8_t)( ((a) >> 56) & 0xFF ) +// Macro to define message to send and receive +#define ZBM(n, x...) const uint8_t n[] PROGMEM = { x }; + +// ZBS_* Zigbee Send +// ZBR_* Zigbee Recv +ZBM(ZBS_RESET, Z_AREQ | Z_SYS, SYS_RESET, 0x01 ) // 410001 SYS_RESET_REQ Software reset +ZBM(ZBR_RESET, Z_AREQ | Z_SYS, SYS_RESET_IND ) // 4180 SYS_RESET_REQ Software reset response + +ZBM(ZBS_VERSION, Z_SREQ | Z_SYS, SYS_VERSION ) // 2102 Z_SYS:version +ZBM(ZBR_VERSION, Z_SRSP | Z_SYS, SYS_VERSION ) // 6102 Z_SYS:version + +// Check if ZNP_HAS_CONFIGURED is set +ZBM(ZBS_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8, 0x00 /* offset */ ) // 2108000F00 - 6108000155 +ZBM(ZBR_ZNPHC, Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_Success, 0x01 /* len */, 0x55) // 6108000155 +// If not set, the response is 61-08-02-00 = Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_InvalidParameter, 0x00 /* len */ + +ZBM(ZBS_PAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PANID ) // 260483 +ZBM(ZBR_PAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PANID, 0x02 /* len */, + Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 6604008302xxxx + +ZBM(ZBS_EXTPAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_EXTENDED_PAN_ID ) // 26042D +ZBM(ZBR_EXTPAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_EXTENDED_PAN_ID, + 0x08 /* len */, + Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID), + Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID), + ) // 6604002D08xxxxxxxxxxxxxxxx + +ZBM(ZBS_CHANN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_CHANLIST ) // 260484 +ZBM(ZBR_CHANN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_CHANLIST, + 0x04 /* len */, + Z_B0(USE_ZIGBEE_CHANNEL), Z_B1(USE_ZIGBEE_CHANNEL), Z_B2(USE_ZIGBEE_CHANNEL), Z_B3(USE_ZIGBEE_CHANNEL), + ) // 6604008404xxxxxxxx + +ZBM(ZBS_PFGK, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEY ) // 260462 +ZBM(ZBR_PFGK, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEY, + 0x10 /* len */, + Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L), + Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L), + Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H), + Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H), + /*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F, + 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 660400621001030507090B0D0F00020406080A0C0D + +ZBM(ZBS_PFGKEN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEYS_ENABLE ) // 260463 +ZBM(ZBR_PFGKEN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEYS_ENABLE, + 0x01 /* len */, 0x00 ) // 660400630100 + +// commands to "format" the device +// Write configuration - write success +ZBM(ZBR_W_OK, Z_SRSP | Z_SAPI, SAPI_WRITE_CONFIGURATION, Z_Success ) // 660500 - Write Configuration +ZBM(ZBR_WNV_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_WRITE, Z_Success ) // 610900 - NV Write + +// Factory reset +ZBM(ZBS_FACTRES, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 /* len */, 0x02 ) // 2605030102 +// Write PAN ID +ZBM(ZBS_W_PAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PANID, 0x02 /* len */, Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) ) // 26058302xxxx +// Write EXT PAN ID +ZBM(ZBS_W_EXTPAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_EXTENDED_PAN_ID, 0x08 /* len */, + Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID), + Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID) + ) // 26052D086263151D004B1200 +// Write Channel ID +ZBM(ZBS_W_CHANN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_CHANLIST, 0x04 /* len */, + Z_B0(USE_ZIGBEE_CHANNEL), Z_B1(USE_ZIGBEE_CHANNEL), Z_B2(USE_ZIGBEE_CHANNEL), Z_B3(USE_ZIGBEE_CHANNEL), + /*0x00, 0x08, 0x00, 0x00*/ ) // 26058404xxxxxxxx +// Write Logical Type = 00 = coordinator +ZBM(ZBS_W_LOGTYP, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_LOGICAL_TYPE, 0x01 /* len */, 0x00 ) // 2605870100 +// Write precfgkey +ZBM(ZBS_W_PFGK, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEY, + 0x10 /* len */, + Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L), + Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L), + Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H), + Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H), + /*0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F, + 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0D*/ ) // 2605621001030507090B0D0F00020406080A0C0D +// Write precfgkey enable +ZBM(ZBS_W_PFGKEN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEYS_ENABLE, 0x01 /* len */, 0x00 ) // 2605630100 +// Write Security Mode +ZBM(ZBS_WNV_SECMODE, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(CONF_TCLK_TABLE_START), Z_B1(CONF_TCLK_TABLE_START), + 0x00 /* offset */, 0x20 /* len */, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c, + 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) // 2109010100200FFFFFFFFFFFFFFFF5A6967426565416C6C69616E636530390000000000000000 +// Write Z_ZDO Direct CB +ZBM(ZBS_W_ZDODCB, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_ZDO_DIRECT_CB, 0x01 /* len */, 0x01 ) // 26058F0101 +// NV Init ZNP Has Configured +ZBM(ZBS_WNV_INITZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_ITEM_INIT, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8, + 0x01, 0x00 /* InitLen 16 bits */, 0x01 /* len */, 0x00 ) // 2107000F01000100 - 610709 +// Init succeeded +ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_WRITE, Z_Created ) // 610709 - NV Write +// Write ZNP Has Configured +ZBM(ZBS_WNV_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(ZNP_HAS_CONFIGURED), Z_B1(ZNP_HAS_CONFIGURED), + 0x00 /* offset */, 0x01 /* len */, 0x55 ) // 2109000F000155 - 610900 +// Z_ZDO:startupFromApp +ZBM(ZBS_STARTUPFROMAPP, Z_SREQ | Z_ZDO, ZDO_STARTUP_FROM_APP, 100, 0 /* delay */) // 25406400 +ZBM(ZBR_STARTUPFROMAPP, Z_SRSP | Z_ZDO, ZDO_STARTUP_FROM_APP ) // 6540 + 01 for new network, 00 for exisitng network, 02 for error +ZBM(AREQ_STARTUPFROMAPP, Z_AREQ | Z_ZDO, ZDO_STATE_CHANGE_IND, ZDO_DEV_ZB_COORD ) // 45C009 + 08 = starting, 09 = started +// GetDeviceInfo +ZBM(ZBS_GETDEVICEINFO, Z_SREQ | Z_UTIL, Z_UTIL_GET_DEVICE_INFO ) // 2700 +ZBM(ZBR_GETDEVICEINFO, Z_SRSP | Z_UTIL, Z_UTIL_GET_DEVICE_INFO, Z_Success ) // Ex= 6700.00.6263151D004B1200.0000.07.09.00 + // IEEE Adr (8 bytes) = 6263151D004B1200 + // Short Addr (2 bytes) = 0000 + // Device Type (1 byte) = 07 (coord?) + // Device State (1 byte) = 09 (coordinator started) + // NumAssocDevices (1 byte) = 00 + +// Read Pan ID +//ZBM(ZBS_READ_NV_PANID, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, PANID & 0xFF, PANID >> 8, 0x00 /* offset */ ) // 2108830000 + +// Z_ZDO:nodeDescReq +ZBM(ZBS_ZDO_NODEDESCREQ, Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ, 0x00, 0x00 /* dst addr */, 0x00, 0x00 /* NWKAddrOfInterest */) // 250200000000 +ZBM(ZBR_ZDO_NODEDESCREQ, Z_SRSP | Z_ZDO, ZDO_NODE_DESC_REQ, Z_Success ) // 650200 +// Async resp ex: 4582.0000.00.0000.00.40.8F.0000.50.A000.0100.A000.00 +ZBM(AREQ_ZDO_NODEDESCREQ, Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP) // 4582 +// SrcAddr (2 bytes) 0000 +// Status (1 byte) 00 Success +// NwkAddr (2 bytes) 0000 +// LogicalType (1 byte) - 00 Coordinator +// APSFlags (1 byte) - 40 0=APSFlags 4=NodeFreqBands +// MACCapabilityFlags (1 byte) - 8F ALL +// ManufacturerCode (2 bytes) - 0000 +// MaxBufferSize (1 byte) - 50 NPDU +// MaxTransferSize (2 bytes) - A000 = 160 +// ServerMask (2 bytes) - 0100 - Primary Trust Center +// MaxOutTransferSize (2 bytes) - A000 = 160 +// DescriptorCapabilities (1 byte) - 00 + +// Z_ZDO:activeEpReq +ZBM(ZBS_ZDO_ACTIVEEPREQ, Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, 0x00, 0x00, 0x00, 0x00) // 250500000000 +ZBM(ZBR_ZDO_ACTIVEEPREQ, Z_SRSP | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_Success) // 65050000 +ZBM(ZBR_ZDO_ACTIVEEPRSP_NONE, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success, + 0x00, 0x00 /* nwkaddr */, 0x00 /* activeepcount */) // 45050000 - no Ep running +ZBM(ZBR_ZDO_ACTIVEEPRSP_OK, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 /* srcAddr */, Z_Success, + 0x00, 0x00 /* nwkaddr */, 0x02 /* activeepcount */, 0x0B, 0x01 /* the actual endpoints */) // 25050000 - no Ep running + +// Z_AF:register profile:104, ep:01 +ZBM(ZBS_AF_REGISTER01, Z_SREQ | Z_AF, AF_REGISTER, 0x01 /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 24000401050000000000 + 0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */, + 0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */) +ZBM(ZBR_AF_REGISTER, Z_SRSP | Z_AF, AF_REGISTER, Z_Success) // 640000 +ZBM(ZBS_AF_REGISTER0B, Z_SREQ | Z_AF, AF_REGISTER, 0x0B /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 2400040B050000000000 + 0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */, + 0x00 /* AppNumInClusters */, 0x00 /* AppNumInClusters */) +// Z_ZDO:mgmtPermitJoinReq +ZBM(ZBS_PERMITJOINREQ_CLOSE, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x02 /* AddrMode */, // 25360200000000 + 0x00, 0x00 /* DstAddr */, 0x00 /* Duration */, 0x00 /* TCSignificance */) +ZBM(ZBS_PERMITJOINREQ_OPEN, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFCFF00 + 0xFC, 0xFF /* DstAddr */, 0xFF /* Duration */, 0x00 /* TCSignificance */) +ZBM(ZBR_PERMITJOINREQ, Z_SRSP | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, Z_Success) // 653600 +ZBM(ZBR_PERMITJOIN_AREQ_CLOSE, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0x00 /* Duration */) // 45CB00 +ZBM(ZBR_PERMITJOIN_AREQ_OPEN, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0xFF /* Duration */) // 45CBFF +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 + +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_ON_ERROR_GOTO(50) + + ZI_LOG(LOG_LEVEL_INFO, "ZIG: rebooting device") + ZI_SEND(ZBS_RESET) // reboot cc2530 just in case we rebooted ESP8266 but not cc2530 + ZI_WAIT_RECV(5000, ZBR_RESET) // timeout 5s + 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_SEND(ZBS_VERSION) // check ZNP software version + ZI_WAIT_RECV(500, ZBR_VERSION) + ZI_SEND(ZBS_PAN) // check PAN ID + ZI_WAIT_RECV(500, ZBR_PAN) + ZI_SEND(ZBS_EXTPAN) // check EXT PAN ID + ZI_WAIT_RECV(500, ZBR_EXTPAN) + ZI_SEND(ZBS_CHANN) // check CHANNEL + ZI_WAIT_RECV(500, ZBR_CHANN) + ZI_SEND(ZBS_PFGK) // check PFGK + ZI_WAIT_RECV(500, ZBR_PFGK) + ZI_SEND(ZBS_PFGKEN) // check PFGKEN + ZI_WAIT_RECV(500, ZBR_PFGKEN) + ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee configuration ok") + // all is good, we can start + + ZI_LABEL(10) // START ZNP App + ZI_CALL(&Z_State_Ready, 1) + ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT) + // Z_ZDO:startupFromApp + ZI_LOG(LOG_LEVEL_INFO, "ZIG: starting zigbee coordinator") + ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator + ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) // wait for sync ack of command + ZI_WAIT_UNTIL(5000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started + ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo + ZI_WAIT_RECV(500, ZBR_GETDEVICEINFO) // TODO memorize info + ZI_SEND(ZBS_ZDO_NODEDESCREQ) // Z_ZDO:nodeDescReq + ZI_WAIT_RECV(500, ZBR_ZDO_NODEDESCREQ) + ZI_WAIT_UNTIL(5000, AREQ_ZDO_NODEDESCREQ) + ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq + ZI_WAIT_RECV(500, ZBR_ZDO_ACTIVEEPREQ) + ZI_WAIT_UNTIL(500, ZBR_ZDO_ACTIVEEPRSP_NONE) + ZI_SEND(ZBS_AF_REGISTER01) // Z_AF register for endpoint 01, profile 0x0104 Home Automation + ZI_WAIT_RECV(500, ZBR_AF_REGISTER) + ZI_SEND(ZBS_AF_REGISTER0B) // Z_AF register for endpoint 0B, profile 0x0104 Home Automation + ZI_WAIT_RECV(500, ZBR_AF_REGISTER) + // Z_ZDO:nodeDescReq ?? Is is useful to redo it? TODO + // redo Z_ZDO:activeEpReq to check that Ep are available + ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq + ZI_WAIT_RECV(500, ZBR_ZDO_ACTIVEEPREQ) + ZI_WAIT_UNTIL(500, ZBR_ZDO_ACTIVEEPRSP_OK) + ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join + ZI_WAIT_RECV(500, ZBR_PERMITJOINREQ) + ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful + //ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE) + ZI_SEND(ZBS_PERMITJOINREQ_OPEN) // Opening Permit Join, normally through command TODO + ZI_WAIT_RECV(500, ZBR_PERMITJOINREQ) + ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful + //ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN) + + ZI_LABEL(ZIGBEE_LABEL_READY) + ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device ready, listening...") + ZI_CALL(&Z_State_Ready, 1) + ZI_WAIT_FOREVER() + ZI_GOTO(ZIGBEE_LABEL_READY) + + ZI_LABEL(50) // reformat device + ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee bad configuration of device, doing a factory reset") + ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT) + ZI_SEND(ZBS_FACTRES) // factory reset + ZI_WAIT_RECV(500, ZBR_W_OK) + ZI_SEND(ZBS_RESET) // reset device + ZI_WAIT_RECV(5000, ZBR_RESET) + ZI_SEND(ZBS_W_PAN) // write PAN ID + ZI_WAIT_RECV(500, ZBR_W_OK) + ZI_SEND(ZBS_W_EXTPAN) // write EXT PAN ID + ZI_WAIT_RECV(500, ZBR_W_OK) + ZI_SEND(ZBS_W_CHANN) // write CHANNEL + ZI_WAIT_RECV(500, ZBR_W_OK) + ZI_SEND(ZBS_W_LOGTYP) // write Logical Type = coordinator + ZI_WAIT_RECV(500, ZBR_W_OK) + ZI_SEND(ZBS_W_PFGK) // write PRECFGKEY + ZI_WAIT_RECV(500, ZBR_W_OK) + ZI_SEND(ZBS_W_PFGKEN) // write PRECFGKEY Enable + ZI_WAIT_RECV(500, ZBR_W_OK) + ZI_SEND(ZBS_WNV_SECMODE) // write Security Mode + ZI_WAIT_RECV(500, ZBR_WNV_OK) + ZI_SEND(ZBS_W_ZDODCB) // write Z_ZDO Direct CB + ZI_WAIT_RECV(500, ZBR_W_OK) + // Now mark the device as ready, writing 0x55 in memory slot 0x0F00 + ZI_SEND(ZBS_WNV_INITZNPHC) // Init NV ZNP Has Configured + ZI_WAIT_RECV(500, ZBR_WNV_INIT_OK) + ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured + ZI_WAIT_RECV(500, ZBR_WNV_OK) + + ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device reconfigured") + ZI_GOTO(10) + + ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort + ZI_LOG(LOG_LEVEL_ERROR, "ZIG: Abort") + ZI_STOP(ZIGBEE_LABEL_ABORT) +}; + + +int32_t Z_Recv_Vers(int32_t res, class SBuffer &buf) { + // check that the version is supported + // typical version for ZNP 1.2 + // 61020200-020603D91434010200000000 + // TranportRev = 02 + // Product = 00 + // MajorRel = 2 + // MinorRel = 6 + // MaintRel = 3 + // Revision = 20190425 d (0x013414D9) + if ((0x02 == buf.get8(4)) && (0x06 == buf.get8(5))) { + return 0; // version 2.6.x is ok + } else { + return -2; // abort + } +} + +int32_t Z_Recv_Default(int32_t res, class SBuffer &buf) { + // Default message handler for new messages + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Z_Recv_Default")); + if (zigbee.init_phase) { + // if still during initialization phase, ignore any unexpected message + return -1; // ignore message + } else { + if ( (pgm_read_byte(&ZBR_AF_INCOMING_MESSAGE[0]) == buf.get8(0)) && + (pgm_read_byte(&ZBR_AF_INCOMING_MESSAGE[1]) == buf.get8(1)) ) { + // AF_INCOMING_MSG, extract ZCL part TODO + // skip first 19 bytes + ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18)); + zcl_received.publishMQTTReceived(); + } + return -1; + } +} + +int32_t Z_State_Ready(uint8_t value) { + zigbee.init_phase = false; // initialization phase complete + return 0; // continue +} + +uint8_t ZigbeeGetInstructionSize(uint8_t instr) { // in Zigbee_Instruction lines (words) + if (instr >= ZGB_INSTR_12_BYTES) { + return 3; + } else if (instr >= ZGB_INSTR_8_BYTES) { + return 2; + } else { + return 1; + } +} + +void ZigbeeGotoLabel(uint8_t label) { + // look for the label scanning entire code + uint16_t goto_pc = 0xFFFF; // 0xFFFF means not found + uint8_t cur_instr = 0; + uint8_t cur_d8 = 0; + uint8_t cur_instr_len = 1; // size of current instruction in words + + for (uint32_t i = 0; i < sizeof(zb_prog)/sizeof(zb_prog[0]); i += cur_instr_len) { + const Zigbee_Instruction *cur_instr_line = &zb_prog[i]; + cur_instr = pgm_read_byte(&cur_instr_line->i.i); + cur_d8 = pgm_read_byte(&cur_instr_line->i.d8); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZGB GOTO: pc %d instr %d"), i, cur_instr); + + if (ZGB_INSTR_LABEL == cur_instr) { + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: found label %d at pc %d"), cur_d8, i); + if (label == cur_d8) { + // label found, goto to this pc + zigbee.pc = i; + zigbee.state_machine = true; + zigbee.state_waiting = false; + return; + } + } + // get instruction length + cur_instr_len = ZigbeeGetInstructionSize(cur_instr); + } + + // no label found, abort + AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Goto label not found, label=%d pc=%d"), label, zigbee.pc); + if (ZIGBEE_LABEL_ABORT != label) { + // if not already looking for ZIGBEE_LABEL_ABORT, goto ZIGBEE_LABEL_ABORT + ZigbeeGotoLabel(ZIGBEE_LABEL_ABORT); + } else { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Label Abort (%d) not present, aborting Zigbee"), ZIGBEE_LABEL_ABORT); + zigbee.state_machine = false; + zigbee.active = false; + } +} + +void ZigbeeStateMachine_Run(void) { + uint8_t cur_instr = 0; + uint8_t cur_d8 = 0; + uint16_t cur_d16 = 0; + const void* cur_ptr1 = nullptr; + const void* cur_ptr2 = nullptr; + uint32_t now = millis(); + + if (zigbee.state_waiting) { // state machine is waiting for external event or timeout + // checking if timeout expired + if ((zigbee.next_timeout) && (now > zigbee.next_timeout)) { // if next_timeout == 0 then wait forever + //AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout occured pc=%d"), zigbee.pc); + if (!zigbee.state_no_timeout) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: timeout, goto label %d"), zigbee.on_timeout_goto); + ZigbeeGotoLabel(zigbee.on_timeout_goto); + } else { + zigbee.state_waiting = false; // simply stop waiting + } + } + } + + while ((zigbee.state_machine) && (!zigbee.state_waiting)) { + // reinit receive filters and functions (they only work for a single instruction) + zigbee.recv_filter = nullptr; + zigbee.recv_func = nullptr; + zigbee.recv_until = false; + zigbee.state_no_timeout = false; // reset the no_timeout for next instruction + + if (zigbee.pc > (sizeof(zb_prog)/sizeof(zb_prog[0]))) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Invalid pc: %d, aborting"), zigbee.pc); + zigbee.pc = -1; + } + if (zigbee.pc < 0) { + zigbee.state_machine = false; + return; + } + + // load current instruction details + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Executing instruction pc=%d"), zigbee.pc); + const Zigbee_Instruction *cur_instr_line = &zb_prog[zigbee.pc]; + cur_instr = pgm_read_byte(&cur_instr_line->i.i); + cur_d8 = pgm_read_byte(&cur_instr_line->i.d8); + cur_d16 = pgm_read_word(&cur_instr_line->i.d16); + if (cur_instr >= ZGB_INSTR_8_BYTES) { + cur_instr_line++; + cur_ptr1 = cur_instr_line->p; + } + if (cur_instr >= ZGB_INSTR_12_BYTES) { + cur_instr_line++; + cur_ptr2 = cur_instr_line->p; + } + + zigbee.pc += ZigbeeGetInstructionSize(cur_instr); // move pc to next instruction, before any goto + + switch (cur_instr) { + case ZGB_INSTR_NOOP: + case ZGB_INSTR_LABEL: // do nothing + break; + case ZGB_INSTR_GOTO: + ZigbeeGotoLabel(cur_d8); + break; + case ZGB_INSTR_ON_ERROR_GOTO: + zigbee.on_error_goto = cur_d8; + break; + case ZGB_INSTR_ON_TIMEOUT_GOTO: + zigbee.on_timeout_goto = cur_d8; + break; + case ZGB_INSTR_WAIT: + zigbee.next_timeout = now + cur_d16; + zigbee.state_waiting = true; + zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done + break; + case ZGB_INSTR_WAIT_FOREVER: + zigbee.next_timeout = 0; + zigbee.state_waiting = true; + //zigbee.state_no_timeout = true; // do not generate a timeout error when waiting is done + break; + case ZGB_INSTR_STOP: + zigbee.state_machine = false; + if (cur_d8) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("ZIG: Stopping (%d)"), cur_d8); + } + break; + case ZGB_INSTR_CALL: + if (cur_ptr1) { + uint32_t res; + res = (*((ZB_Func)cur_ptr1))(cur_d8); + if (res > 0) { + ZigbeeGotoLabel(res); + continue; // avoid incrementing PC after goto + } else if (res == 0) { + // do nothing + } else if (res == -1) { + // do nothing + } else { + ZigbeeGotoLabel(zigbee.on_error_goto); + continue; + } + } + // TODO + break; + case ZGB_INSTR_LOG: + AddLog_P(cur_d8, (char*) cur_ptr1); + break; + case ZGB_INSTR_SEND: + ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 /* len */); + break; + case ZGB_INSTR_WAIT_UNTIL: + zigbee.recv_until = true; // and reuse ZGB_INSTR_WAIT_RECV + case ZGB_INSTR_WAIT_RECV: + zigbee.recv_filter = (uint8_t *) cur_ptr1; + zigbee.recv_filter_len = cur_d8; // len + zigbee.next_timeout = now + cur_d16; + zigbee.state_waiting = true; + break; + case ZGB_ON_RECV_UNEXPECTED: + zigbee.recv_unexpected = (ZB_RecvMsgFunc) cur_ptr1; + break; + case ZGB_INSTR_WAIT_RECV_CALL: + zigbee.recv_filter = (uint8_t *) cur_ptr1; + zigbee.recv_filter_len = cur_d8; // len + zigbee.recv_func = (ZB_RecvMsgFunc) cur_ptr2; + zigbee.next_timeout = now + cur_d16; + zigbee.state_waiting = true; + break; + } + } +} + +int32_t ZigbeeProcessInput(class SBuffer &buf) { + if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message + + // apply the receive filter, acts as 'startsWith()' + bool recv_filter_match = true; + bool recv_prefix_match = false; // do the first 2 bytes match the response + if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) { + if (zigbee.recv_filter_len >= 2) { + recv_prefix_match = false; + if ( (pgm_read_byte(&zigbee.recv_filter[0]) == buf.get8(0)) && + (pgm_read_byte(&zigbee.recv_filter[1]) == buf.get8(1)) ) { + recv_prefix_match = true; + } + } + + for (uint32_t i = 0; i < zigbee.recv_filter_len; i++) { + if (pgm_read_byte(&zigbee.recv_filter[i]) != buf.get8(i)) { + recv_filter_match = false; + break; + } + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: ZigbeeProcessInput: recv_prefix_match = %d, recv_filter_match = %d"), recv_prefix_match, recv_filter_match); + } + + // if there is a recv_callback, call it now + int32_t res = -1; // default to ok + // res = 0 - proceed to next state + // res > 0 - proceed to the specified state + // res = -1 - silently ignore the message + // res <= -2 - move to error state + // pre-compute the suggested value + if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) { + if (!recv_prefix_match) { + res = -1; // ignore + } else { // recv_prefix_match + if (recv_filter_match) { + res = 0; // ok + } else { + if (zigbee.recv_until) { + res = -1; // ignore until full match + } else { + res = -2; // error, because message is expected but wrong value + } + } + } + } else { // we don't have any filter, ignore message by default + res = -1; + } + + if (recv_prefix_match) { + if (zigbee.recv_func) { + res = (*zigbee.recv_func)(res, buf); + } + } + if (-1 == res) { + // if frame was ignored up to now + if (zigbee.recv_unexpected) { + res = (*zigbee.recv_unexpected)(res, buf); + } + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: ZigbeeProcessInput: res = %d"), res); + + // change state accordingly + if (0 == res) { + // if ok, continue execution + zigbee.state_waiting = false; + } else if (res > 0) { + ZigbeeGotoLabel(res); // if >0 then go to specified label + } else if (-1 == res) { + // -1 means ignore message + // just do nothing + } else { + // any other negative value means error + ZigbeeGotoLabel(zigbee.on_error_goto); + } +} + +void ZigbeeInput(void) +{ + static uint32_t zigbee_polling_window = 0; + 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 + // Receive only valid ZNP frames: + // 00 - SOF = 0xFE + // 01 - Length of Data Field - 0..250 + // 02 - CMD1 - first byte of command + // 03 - CMD2 - second byte of command + // 04..FD - Data Field + // FE (or last) - FCS Checksum + + while (ZigbeeSerial->available()) { + yield(); + uint8_t zigbee_in_byte = ZigbeeSerial->read(); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZigbeeInput byte=%d len=%d"), zigbee_in_byte, zigbee_buffer->len()); + + if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized + zigbee_frame_len = 5; + fcs = ZIGBEE_SOF; + } + + if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) { + // waiting for SOF (Start Of Frame) byte, discard anything else + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZigbeeInput discarding byte %02X"), zigbee_in_byte); + continue; // discard + } + + if (zigbee_buffer->len() < zigbee_frame_len) { + zigbee_buffer->add8(zigbee_in_byte); + zigbee_polling_window = millis(); // Wait for more data + fcs ^= zigbee_in_byte; + } + + if (zigbee_buffer->len() >= zigbee_frame_len) { + zigbee_polling_window = 0; // Publish now + break; + } + + // recalculate frame length + if (02 == zigbee_buffer->len()) { + // We just received the Lenght byte + uint8_t len_byte = zigbee_buffer->get8(1); + if (len_byte > 250) len_byte = 250; // ZNP spec says len is 250 max + + zigbee_frame_len = len_byte + 5; // SOF + LEN + CMD1 + CMD2 + FCS = 5 bytes overhead + } + } + + if (zigbee_buffer->len() && (millis() > (zigbee_polling_window + ZIGBEE_POLLING))) { + char hex_char[(zigbee_buffer->len() * 2) + 2]; + ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char)); + + // buffer received, now check integrity + if (zigbee_buffer->len() != zigbee_frame_len) { + // Len is not correct, log and reject frame + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received frame of wrong size %s, len %d, expected %d"), hex_char, zigbee_buffer->len(), zigbee_frame_len); + } else if (0x00 != fcs) { + // FCS is wrong, packet is corrupt, log and reject frame + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received bad FCS frame %s, %d"), hex_char, fcs); + } else { + // frame is correct + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received correct frame %s"), hex_char); + + SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); // remove SOF, LEN and FCS + + ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char)); + Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPRECEIVED)); + XdrvRulesProcess(); + + // now process the message + ZigbeeProcessInput(znp_buffer); + } + zigbee_buffer->setLen(0); // empty buffer + } +} + +/********************************************************************************************/ + +void ZigbeeInit(void) +{ + zigbee.active = false; + if ((pin[GPIO_ZIGBEE_RX] < 99) && (pin[GPIO_ZIGBEE_TX] < 99)) { + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("Zigbee: GPIOs Rx:%d Tx:%d"), pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX]); + ZigbeeSerial = new TasmotaSerial(pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX], 0, 0, 256); // set a receive buffer of 256 bytes + if (ZigbeeSerial->begin(115200)) { // ZNP is 115200, RTS/CTS (ignored), 8N1 + if (ZigbeeSerial->hardwareSerial()) { + ClaimSerial(); + zigbee_buffer = new PreAllocatedSBuffer(sizeof(serial_in_buffer), serial_in_buffer); + } else { + zigbee_buffer = new SBuffer(ZIGBEE_BUFFER_SIZE); + } + zigbee.active = true; + zigbee.init_phase = true; // start the state machine + zigbee.state_machine = true; // start the state machine + ZigbeeSerial->flush(); + } + } +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +void CmndZigbeeZNPSend(void) +{ + AddLog_P2(LOG_LEVEL_INFO, PSTR("CmndZigbeeZNPSend: entering, data_len = %d"), XdrvMailbox.data_len); // TODO + 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 > 0) { + char stemp[3]; + strlcpy(stemp, codes, sizeof(stemp)); + code = strtol(stemp, nullptr, 16); + buf.add8(code); + size -= 2; + codes += 2; + } + ZigbeeZNPSend(buf.getBuffer(), buf.len()); + } + ResponseCmndDone(); +} + +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]; + Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPSENT "\":\"%s\"}"), + ToHex_P(msg, len, hex_char, sizeof(hex_char))); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPSENT)); + XdrvRulesProcess(); +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv23(uint8_t function) +{ + bool result = false; + + if (zigbee.active) { + switch (function) { + case FUNC_LOOP: + if (ZigbeeSerial) { ZigbeeInput(); } + if (zigbee.state_machine) { + //ZigbeeStateMachine(); + ZigbeeStateMachine_Run(); + } + break; + case FUNC_PRE_INIT: + ZigbeeInit(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kZigbeeCommands, ZigbeeCommand); + break; + } + } + return result; +} + +#endif // USE_ZIGBEE