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