diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index a02cc1636..fd47efc7e 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -5,6 +5,7 @@ * Change rename "Data" to "Hash" and limit to 32 bits when receiving UNKNOWN IR protocol (see DECODE_HASH from IRremoteESP8266) * Add command Gpios 255/All to show all available GPIO components (#6407) * Change JSON output format for commands Adc, Adcs, Modules, Gpio and Gpios from list to dictionary (#6407) + * Add Zigbee support phase 3 - support for Xiaomi lumi.weather air quality sensor, Osram mini-switch * * 6.6.0.11 20190907 * Change Settings crc calculation allowing short term backward compatibility diff --git a/sonoff/i18n.h b/sonoff/i18n.h index 7659b9335..c3f4333bc 100644 --- a/sonoff/i18n.h +++ b/sonoff/i18n.h @@ -452,7 +452,9 @@ #define D_CMND_TUYA_MCU "TuyaMCU" // Commands xdrv_23_zigbee.ino +#define D_CMND_ZIGBEE_PERMITJOIN "ZigbeePermitJoin" #define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend" + #define D_JSON_ZIGBEE_STATUS "ZigbeeStatus" #define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived" #define D_JSON_ZIGBEEZNPSENT "ZigbeeZNPSent" #define D_JSON_ZIGBEEZCLRECEIVED "ZigbeeZCLReceived" diff --git a/sonoff/support.ino b/sonoff/support.ino index 1f9911e09..6fd41b8f0 100644 --- a/sonoff/support.ino +++ b/sonoff/support.ino @@ -325,6 +325,24 @@ char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, c return out; } +char* Uint64toHex(uint64_t value, char *str, uint16_t bits) +{ + ulltoa(value, str, 16); // Get 64bit value + + int fill = 8; + if ((bits > 3) && (bits < 65)) { + fill = bits / 4; // Max 16 + if (bits % 4) { fill++; } + } + int len = strlen(str); + fill -= len; + if (fill > 0) { + memmove(str + fill, str, len +1); + memset(str, '0', fill); + } + return str; +} + char* dtostrfd(double number, unsigned char prec, char *s) { if ((isnan(number)) || (isinf(number))) { // Fix for JSON output (https://stackoverflow.com/questions/1423081/json-left-out-infinity-and-nan-json-status-in-ecmascript) diff --git a/sonoff/support_static_buffer.ino b/sonoff/support_static_buffer.ino index d2817753b..38838c084 100644 --- a/sonoff/support_static_buffer.ino +++ b/sonoff/support_static_buffer.ino @@ -23,6 +23,8 @@ typedef struct SBuffer_impl { uint8_t buf[]; // the actual data } SBuffer_impl; + + typedef class SBuffer { protected: @@ -43,7 +45,8 @@ public: 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; } + inline uint8_t *buf(size_t i = 0) const { return &_buf->buf[i]; } + inline char *charptr(size_t i = 0) const { return (char*) &_buf->buf[i]; } virtual ~SBuffer(void) { delete[] _buf; @@ -57,6 +60,12 @@ public: } } + void set8(const size_t offset, const uint8_t data) { + if (offset < _buf->len) { + _buf->buf[offset] = data; + } + } + 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; @@ -124,6 +133,15 @@ public: } return 0; } + uint64_t get64(const size_t offset) const { + if (offset < len() - 7) { + return (uint64_t)_buf->buf[offset] | ((uint64_t)_buf->buf[offset+1] << 8) | + ((uint64_t)_buf->buf[offset+2] << 16) | ((uint64_t)_buf->buf[offset+3] << 24) | + ((uint64_t)_buf->buf[offset+4] << 32) | ((uint64_t)_buf->buf[offset+5] << 40) | + ((uint64_t)_buf->buf[offset+6] << 48) | ((uint64_t)_buf->buf[offset+7] << 56); + } + return 0; + } SBuffer subBuffer(const size_t start, size_t len) const { if (start >= _buf->len) { @@ -138,6 +156,32 @@ public: return buf2; } + static SBuffer SBufferFromHex(const char *hex, size_t len) { + size_t buf_len = (len + 3) / 2; + SBuffer buf2(buf_len); + uint8_t val; + + for (; len > 1; len -= 2) { + val = asc2byte(*hex++) << 4; + val |= asc2byte(*hex++); + buf2.add8(val); + } + return buf2; + } + +protected: + + static uint8_t asc2byte(char chr) { + uint8_t rVal = 0; + if (isdigit(chr)) { rVal = chr - '0'; } + else if (chr >= 'A' && chr <= 'F') { rVal = chr + 10 - 'A'; } + else if (chr >= 'a' && chr <= 'f') { rVal = chr + 10 - 'a'; } + return rVal; + } + + static void unHex(const char* in, uint8_t *out, size_t len) { + } + protected: SBuffer_impl * _buf; diff --git a/sonoff/xdrv_05_irremote.ino b/sonoff/xdrv_05_irremote.ino index 0e8a00982..30b2f532f 100644 --- a/sonoff/xdrv_05_irremote.ino +++ b/sonoff/xdrv_05_irremote.ino @@ -70,24 +70,6 @@ void IrSendInit(void) irsend->begin(); } -char* IrUint64toHex(uint64_t value, char *str, uint16_t bits) -{ - ulltoa(value, str, 16); // Get 64bit value - - int fill = 8; - if ((bits > 3) && (bits < 65)) { - fill = bits / 4; // Max 16 - if (bits % 4) { fill++; } - } - int len = strlen(str); - fill -= len; - if (fill > 0) { - memmove(str + fill, str, len +1); - memset(str, '0', fill); - } - return str; -} - #ifdef USE_IR_RECEIVE /*********************************************************************************************\ * IR Receive @@ -140,10 +122,10 @@ void IrReceiveCheck(void) if (results.bits % 8) { digits2++; } ToHex_P((unsigned char*)results.state, digits2, hvalue, sizeof(hvalue)); // Get n-bit value as hex 56341200 } else { - IrUint64toHex(results.value, hvalue, results.bits); // Get 64bit value as hex 00123456 + Uint64toHex(results.value, hvalue, results.bits); // Get 64bit value as hex 00123456 } } else { - IrUint64toHex(results.value, hvalue, 32); // UNKNOWN is always 32 bits hash + Uint64toHex(results.value, hvalue, 32); // UNKNOWN is always 32 bits hash } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_IRR "Echo %d, RawLen %d, Overflow %d, Bits %d, Value 0x%s, Decode %d"), @@ -938,7 +920,7 @@ uint32_t IrRemoteCmndIrSendJson(void) char dvalue[64]; char hvalue[20]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRS: protocol_text %s, protocol %s, bits %d, data %s (0x%s), repeat %d, protocol_code %d"), - protocol_text, protocol, bits, ulltoa(data, dvalue, 10), IrUint64toHex(data, hvalue, bits), repeat, protocol_code); + protocol_text, protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat, protocol_code); irsend_active = true; switch (protocol_code) { // Equals IRremoteESP8266.h enum decode_type_t diff --git a/sonoff/xdrv_05_irremote_full.ino b/sonoff/xdrv_05_irremote_full.ino index 85c81684b..6c2fe9452 100644 --- a/sonoff/xdrv_05_irremote_full.ino +++ b/sonoff/xdrv_05_irremote_full.ino @@ -72,27 +72,6 @@ uint64_t reverseBitsInBytes64(uint64_t b) { return a.i; } -char* IrUint64toHex(uint64_t value, char *str, uint16_t bits) -{ - ulltoa(value, str, 16); // Get 64bit value - - int fill = 8; - if ((bits > 3) && (bits < 65)) { - fill = bits / 4; // Max 16 - if (bits % 4) { fill++; } - } - int len = strlen(str); - fill -= len; - if (fill > 0) { - memmove(str + fill, str, len +1); - memset(str, '0', fill); - } - memmove(str + 2, str, strlen(str) +1); - str[0] = '0'; - str[1] = 'x'; - return str; -} - /*********************************************************************************************\ * IR Receive \*********************************************************************************************/ @@ -189,15 +168,15 @@ String sendIRJsonState(const struct decode_results &results) { } else { char hvalue[64]; if (UNKNOWN != results.decode_type) { - IrUint64toHex(results.value, hvalue, results.bits); // Get 64bit value as hex 0x00123456 + Uint64toHex(results.value, hvalue, results.bits); // Get 64bit value as hex 0x00123456 json += "\""; json += hvalue; json += "\",\"" D_JSON_IR_DATALSB "\":\""; - IrUint64toHex(reverseBitsInBytes64(results.value), hvalue, results.bits); // Get 64bit value as hex 0x00123456, LSB + Uint64toHex(reverseBitsInBytes64(results.value), hvalue, results.bits); // Get 64bit value as hex 0x00123456, LSB json += hvalue; json += "\""; } else { // UNKNOWN - IrUint64toHex(results.value, hvalue, 32); // Unknown is always 32 bits + Uint64toHex(results.value, hvalue, 32); // Unknown is always 32 bits json += "\""; json += hvalue; json += "\""; @@ -460,7 +439,7 @@ uint32_t IrRemoteCmndIrSendJson(void) char dvalue[32]; char hvalue[32]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRS: protocol %d, bits %d, data %s (%s), repeat %d"), - protocol, bits, ulltoa(data, dvalue, 10), IrUint64toHex(data, hvalue, bits), repeat); + protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat); irsend_active = true; // deactivate receive bool success = irsend->send(protocol, data, bits, repeat); diff --git a/sonoff/xdrv_23_zigbee_constants.ino b/sonoff/xdrv_23_zigbee_0_constants.ino similarity index 95% rename from sonoff/xdrv_23_zigbee_constants.ino rename to sonoff/xdrv_23_zigbee_0_constants.ino index d87cc4af5..651596878 100644 --- a/sonoff/xdrv_23_zigbee_constants.ino +++ b/sonoff/xdrv_23_zigbee_0_constants.ino @@ -404,4 +404,25 @@ enum Z_Util { Z_UTIL_ZCL_KEY_ESTABLISH_IND = 0xE1 }; +enum ZCL_Global_Commands { + ZCL_READ_ATTRIBUTES = 0x00, + ZCL_READ_ATTRIBUTES_RESPONSE = 0x01, + ZCL_WRITE_ATTRIBUTES = 0x02, + ZCL_WRITE_ATTRIBUTES_UNDIVIDED = 0x03, + ZCL_WRITE_ATTRIBUTES_RESPONSE = 0x04, + ZCL_WRITE_ATTRIBUTES_NORESPONSE = 0x05, + ZCL_CONFIGURE_REPORTING = 0x06, + ZCL_CONFIGURE_REPORTING_RESPONSE = 0x07, + ZCL_READ_REPORTING_CONFIGURATION = 0x08, + ZCL_READ_REPORTING_CONFIGURATION_RESPONSE = 0x09, + ZCL_REPORT_ATTRIBUTES = 0x0a, + ZCL_DEFAULT_RESPONSE = 0x0b, + ZCL_DISCOVER_ATTRIBUTES = 0x0c, + ZCL_DISCOVER_ATTRIBUTES_RESPONSE = 0x0d + +}; + +enum class ZclGlobalCommandId : uint8_t { +}; + #endif // USE_ZIGBEE diff --git a/sonoff/xdrv_23_zigbee_4_converters.ino b/sonoff/xdrv_23_zigbee_4_converters.ino new file mode 100644 index 000000000..3f13d08f6 --- /dev/null +++ b/sonoff/xdrv_23_zigbee_4_converters.ino @@ -0,0 +1,619 @@ +/* + xdrv_23_zigbee_converters.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 + +/*********************************************************************************************\ + * 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; + uint32_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, uint16_t clusterid = 0, uint16_t groupid = 0): + _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 + _cluster_id(clusterid), _group_id(groupid) + { + _frame_control.d8 = frame_control; + _payload.addBuffer(buf, buf_len); + }; + + + void publishMQTTReceived(uint16_t groupid, uint16_t clusterid, Z_ShortAddress srcaddr, + uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast, + uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber, + uint32_t timestamp) { + char hex_char[_payload.len()*2+2]; + ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char)); + Response_P(PSTR("{\"" D_JSON_ZIGBEEZCLRECEIVED "\":{" + "\"groupid\":%d," "\"clusterid\":%d," "\"srcaddr\":\"0x%04X\"," + "\"srcendpoint\":%d," "\"dstendpoint\":%d," "\"wasbroadcast\":%d," + "\"linkquality\":%d," "\"securityuse\":%d," "\"seqnumber\":%d," + "\"timestamp\":%d," + "\"fc\":\"0x%02X\",\"manuf\":\"0x%04X\",\"transact\":%d," + "\"cmdid\":\"0x%02X\",\"payload\":\"%s\""), + groupid, clusterid, srcaddr, + srcendpoint, dstendpoint, wasbroadcast, + linkquality, securityuse, seqnumber, + timestamp, + _frame_control, _manuf_code, _transact_seq, _cmd_id, + hex_char); + + ResponseJsonEnd(); // append '}' + ResponseJsonEnd(); // append '}' + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLSENT)); + XdrvRulesProcess(); + } + + static ZCLFrame parseRawFrame(SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid) { // 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, + clusterid, groupid); + return zcl_frame; + } + + bool isClusterSpecificCommand(void) { + return _frame_control.b.frame_type & 1; + } + + void parseRawAttributes(JsonObject& json, uint8_t offset = 0); + void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0); + void postProcessAttributes(JsonObject& json); + + inline void setGroupId(uint16_t groupid) { + _group_id = groupid; + } + + inline void setClusterId(uint16_t clusterid) { + _cluster_id = clusterid; + } + + inline uint8_t getCmdId(void) const { + return _cmd_id; + } + + inline uint16_t getClusterId(void) const { + return _cluster_id; + } + + const SBuffer &getPayload(void) const { + return _payload; + } + +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; + uint16_t _cluster_id = 0; + uint16_t _group_id = 0; + SBuffer _payload; +}; + + +// Zigbee ZCL converters + +// from https://github.com/Koenkk/zigbee-shepherd-converters/blob/638d29f0cace6343052b9a4e7fd60980fa785479/converters/fromZigbee.js#L55 +// Input voltage in mV, i.e. 3000 = 3.000V +// Output percentage from 0 to 100 as int +uint8_t toPercentageCR2032(uint32_t voltage) { + uint32_t percentage; + if (voltage < 2100) { + percentage = 0; + } else if (voltage < 2440) { + percentage = 6 - ((2440 - voltage) * 6) / 340; + } else if (voltage < 2740) { + percentage = 18 - ((2740 - voltage) * 12) / 300; + } else if (voltage < 2900) { + percentage = 42 - ((2900 - voltage) * 24) / 160; + } else if (voltage < 3000) { + percentage = 100 - ((3000 - voltage) * 58) / 100; + } else if (voltage >= 3000) { + percentage = 100; + } + return percentage; +} + + +uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer &buf, + uint32_t offset, uint32_t len) { + + uint32_t i = offset; + uint32_t attrtype = buf.get8(i++); + + // fallback - enter a null value + json[attrid_str] = (char*) nullptr; + + // now parse accordingly to attr type + switch (attrtype) { + case 0x00: // nodata + case 0xFF: // unk + break; + case 0x10: // bool + { + uint8_t val_bool = buf.get8(i++); + if (0xFF != val_bool) { + json[attrid_str] = (bool) (val_bool ? true : false); + } + } + break; + case 0x20: // uint8 + { + uint8_t uint8_val = buf.get8(i); + i += 1; + if (0xFF != uint8_val) { + json[attrid_str] = uint8_val; + } + } + break; + case 0x21: // uint16 + { + uint16_t uint16_val = buf.get16(i); + i += 2; + if (0xFFFF != uint16_val) { + json[attrid_str] = uint16_val; + } + } + break; + case 0x23: // uint16 + { + uint32_t uint32_val = buf.get32(i); + i += 4; + if (0xFFFFFFFF != uint32_val) { + json[attrid_str] = uint32_val; + } + } + break; + // Note: uint40, uint48, uint56, uint64 are not used in ZCL, so they are not implemented (yet) + case 0x24: // int40 + case 0x25: // int48 + case 0x26: // int56 + case 0x27: // int64 + i += attrtype - 0x1F; // 5 - 8; + break; + case 0x28: // uint8 + { + int8_t int8_val = buf.get8(i); + i += 1; + if (0x80 != int8_val) { + json[attrid_str] = int8_val; + } + } + break; + case 0x29: // uint16 + { + int16_t int16_val = buf.get16(i); + i += 2; + if (0x8000 != int16_val) { + json[attrid_str] = int16_val; + } + } + break; + case 0x2B: // uint16 + { + int32_t int32_val = buf.get32(i); + i += 4; + if (0x80000000 != int32_val) { + json[attrid_str] = int32_val; + } + } + break; + // Note: int40, int48, int56, int64 are not used in ZCL, so they are not implemented (yet) + case 0x2C: // int40 + case 0x2D: // int48 + case 0x2E: // int56 + case 0x2F: // int64 + i += attrtype - 0x27; // 5 - 8; + break; + + case 0x41: // octet string, 1 byte len + case 0x42: // char string, 1 byte len + case 0x43: // octet string, 2 bytes len + case 0x44: // char string, 2 bytes len + // For strings, default is to try to do a real string, but reverts to octet stream if null char is present or on some exceptions + { + bool parse_as_string = true; + uint32_t len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i); // len is 8 or 16 bits + i += (attrtype <= 0x42) ? 1 : 2; // increment pointer + + // check if we can safely use a string + if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; } + else { + for (uint32_t j = 0; j < len; j++) { + if (0x00 == buf.get8(i+j)) { + parse_as_string = false; + break; + } + } + } + + if (parse_as_string) { + char str[len+1]; + strncpy(str, buf.charptr(i), len); + str[len] = 0x00; + json[attrid_str] = str; + } else { + // print as HEX + char hex[2*len+1]; + ToHex_P(buf.buf(i), len, hex, sizeof(hex)); + json[attrid_str] = hex; + } + + i += len; + break; + } + i += buf.get8(i) + 1; + break; + + + // TODO + case 0x08: // data8 + i++; + break; + case 0x18: // map8 + i++; + break; + case 0x19: // map16 + i += 2; + break; + case 0x1B: // map32 + i += 4; + break; + // enum + case 0x30: // enum8 + case 0x31: // enum16 + i += attrtype - 0x2F; + break; + + case 0x39: // float + i += 4; + break; + + case 0xE0: // ToD + case 0xE1: // date + case 0xE2: // UTC + i += 4; + break; + + case 0xE8: // clusterId + case 0xE9: // attribId + i += 2; + break; + case 0xEA: // bacOID + i += 4; + break; + + case 0xF0: // EUI64 + i += 8; + break; + case 0xF1: // key128 + i += 16; + break; + + // Other un-implemented data types + case 0x09: // data16 + case 0x0A: // data24 + case 0x0B: // data32 + case 0x0C: // data40 + case 0x0D: // data48 + case 0x0E: // data56 + case 0x0F: // data64 + i += attrtype - 0x07; // 2-8 + break; + // map + case 0x1A: // map24 + case 0x1C: // map40 + case 0x1D: // map48 + case 0x1E: // map56 + case 0x1F: // map64 + i += attrtype - 0x17; + break; + // semi + case 0x38: // semi (float on 2 bytes) + i += 2; + break; + case 0x3A: // double precision + i += 8; + break; + } + + // String pp; // pretty print + // json[attrid_str].prettyPrintTo(pp); + // // now store the attribute + // AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: ZCL attribute decoded, id %s, type 0x%02X, val=%s"), + // attrid_str, attrtype, pp.c_str()); + return i - offset; // how much have we increased the index +} + + +// First pass, parse all attributes in their native format +// The key is 32 bits, high 16 bits is cluserid, low 16 bits is attribute id +void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) { + uint32_t i = offset; + uint32_t len = _payload.len(); + uint32_t attrid = _cluster_id << 16; // set high 16 bits with cluster id + + while (len + offset - i >= 3) { + attrid = (attrid & 0xFFFF0000) | _payload.get16(i); // get lower 16 bits + i += 2; + + char shortaddr[12]; + snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%08X"), attrid); + + // exception for Xiaomi lumi.weather - specific field to be treated as octet and not char + if (0x0000FF01 == attrid) { + if (0x42 == _payload.get8(i)) { + _payload.set8(i, 0x41); // change type from 0x42 to 0x41 + } + } + i += parseSingleAttribute(json, shortaddr, _payload, i, len); + } +} + +// Parse non-normalized attributes +// The key is 24 bits, high 16 bits is cluserid, low 8 bits is command id +void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) { + uint32_t i = offset; + uint32_t len = _payload.len(); + uint32_t attrid = _cluster_id << 8 | _cmd_id; + + char attrid_str[12]; + snprintf_P(attrid_str, sizeof(attrid_str), PSTR("0x%06X"), attrid); // 24 bits + + char hex_char[_payload.len()*2+2]; + ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char)); + + json[attrid_str] = hex_char; +} + +#define ZCL_MODELID "0x00000005" // Cluster 0x0000, attribute 0x05 +#define ZCL_TEMPERATURE "0x04020000" // Cluster 0x0402, attribute 0x00 +#define ZCL_PRESSURE "0x04030000" // Cluster 0x0403, attribute 0x00 +#define ZCL_PRESSURE_SCALED "0x04030010" // Cluster 0x0403, attribute 0x10 +#define ZCL_PRESSURE_SCALE "0x04030014" // Cluster 0x0403, attribute 0x14 +#define ZCL_HUMIDITY "0x04050000" // Cluster 0x0403, attribute 0x00 +#define ZCL_LUMI_WEATHER "0x0000FF01" // Cluster 0x0000, attribute 0xFF01 - proprietary + +#define ZCL_OO_OFF "0x000600" // Cluster 0x0006, cmd 0x00 - On/Off - Off +#define ZCL_OO_ON "0x000601" // Cluster 0x0006, cmd 0x01 - On/Off - On +#define ZCL_COLORTEMP_MOVE "0x03000A" // Cluster 0x0300, cmd 0x0A, Move to Color Temp +#define ZCL_LC_MOVE "0x000800" // Cluster 0x0008, cmd 0x00, Level Control Move to Level +#define ZCL_LC_MOVE_1 "0x000801" // Cluster 0x0008, cmd 0x01, Level Control Move +#define ZCL_LC_STEP "0x000802" // Cluster 0x0008, cmd 0x02, Level Control Step +#define ZCL_LC_STOP "0x000803" // Cluster 0x0008, cmd 0x03, Level Control Stop +#define ZCL_LC_MOVE_WOO "0x000804" // Cluster 0x0008, cmd 0x04, Level Control Move to Level, with On/Off +#define ZCL_LC_MOVE_1_WOO "0x000805" // Cluster 0x0008, cmd 0x05, Level Control Move, with On/Off +#define ZCL_LC_STEP_WOO "0x000806" // Cluster 0x0008, cmd 0x05, Level Control Step, with On/Off +#define ZCL_LC_STOP_WOO "0x000807" // Cluster 0x0008, cmd 0x07, Level Control Stop + +void ZCLFrame::postProcessAttributes(JsonObject& json) { + const __FlashStringHelper *key; + + // ModelID ZCL 3.2 + key = F(ZCL_MODELID); + if (json.containsKey(key)) { + json[F(D_JSON_MODEL D_JSON_ID)] = json[key]; + json.remove(key); + } + + // Temperature ZCL 4.4 + key = F(ZCL_TEMPERATURE); + if (json.containsKey(key)) { + // parse temperature + int32_t temperature = json[key]; + json.remove(key); + json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f; + } + + // Pressure ZCL 4.5 + key = F(ZCL_PRESSURE); + if (json.containsKey(key)) { + json[F(D_JSON_PRESSURE)] = json[key]; + json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa + json.remove(key); + } + json.remove(F(ZCL_PRESSURE_SCALE)); + json.remove(F(ZCL_PRESSURE_SCALED)); + + // Humidity ZCL 4.7 + key = F(ZCL_HUMIDITY); + if (json.containsKey(key)) { + // parse temperature + uint32_t humidity = json[key]; + json.remove(key); + json[F(D_JSON_HUMIDITY)] = humidity / 100.0f; + } + + // Osram Mini Switch + key = F(ZCL_OO_OFF); + if (json.containsKey(key)) { + json.remove(key); + json[F(D_CMND_POWER)] = F("Off"); + } + key = F(ZCL_OO_ON); + if (json.containsKey(key)) { + json.remove(key); + json[F(D_CMND_POWER)] = F("On"); + } + key = F(ZCL_COLORTEMP_MOVE); + if (json.containsKey(key)) { + String hex = json[key]; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + uint16_t color_temp = buf2.get16(0); + uint16_t transition_time = buf2.get16(2); + json.remove(key); + json[F("ColorTemp")] = color_temp; + json[F("TransitionTime")] = transition_time / 10.0f; + } + key = F(ZCL_LC_MOVE_WOO); + if (json.containsKey(key)) { + String hex = json[key]; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + uint8_t level = buf2.get8(0); + uint16_t transition_time = buf2.get16(1); + json.remove(key); + json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage + json[F("TransitionTime")] = transition_time / 10.0f; + if (0 == level) { + json[F(D_CMND_POWER)] = F("Off"); + } else { + json[F(D_CMND_POWER)] = F("On"); + } + } + key = F(ZCL_LC_MOVE); + if (json.containsKey(key)) { + String hex = json[key]; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + uint8_t level = buf2.get8(0); + uint16_t transition_time = buf2.get16(1); + json.remove(key); + json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage + json[F("TransitionTime")] = transition_time / 10.0f; + } + key = F(ZCL_LC_MOVE_1); + if (json.containsKey(key)) { + String hex = json[key]; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + uint8_t move_mode = buf2.get8(0); + uint8_t move_rate = buf2.get8(1); + json.remove(key); + json[F("Move")] = move_mode ? F("Down") : F("Up"); + json[F("Rate")] = move_rate; + } + key = F(ZCL_LC_MOVE_1_WOO); + if (json.containsKey(key)) { + String hex = json[key]; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + uint8_t move_mode = buf2.get8(0); + uint8_t move_rate = buf2.get8(1); + json.remove(key); + json[F("Move")] = move_mode ? F("Down") : F("Up"); + json[F("Rate")] = move_rate; + if (0 == move_mode) { + json[F(D_CMND_POWER)] = F("On"); + } + } + key = F(ZCL_LC_STEP); + if (json.containsKey(key)) { + String hex = json[key]; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + uint8_t step_mode = buf2.get8(0); + uint8_t step_size = buf2.get8(1); + uint16_t transition_time = buf2.get16(2); + json.remove(key); + json[F("Step")] = step_mode ? F("Down") : F("Up"); + json[F("StepSize")] = step_size; + json[F("TransitionTime")] = transition_time / 10.0f; + } + key = F(ZCL_LC_STEP_WOO); + if (json.containsKey(key)) { + String hex = json[key]; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + uint8_t step_mode = buf2.get8(0); + uint8_t step_size = buf2.get8(1); + uint16_t transition_time = buf2.get16(2); + json.remove(key); + json[F("Step")] = step_mode ? F("Down") : F("Up"); + json[F("StepSize")] = step_size; + json[F("TransitionTime")] = transition_time / 10.0f; + if (0 == step_mode) { + json[F(D_CMND_POWER)] = F("On"); + } + } + key = F(ZCL_LC_STOP); + if (json.containsKey(key)) { + json.remove(key); + json[F("Stop")] = 1; + } + key = F(ZCL_LC_STOP_WOO); + if (json.containsKey(key)) { + json.remove(key); + json[F("Stop")] = 1; + } + + // Lumi.weather proprietary field + key = F(ZCL_LUMI_WEATHER); + if (json.containsKey(key)) { + String hex = json[key]; + SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); + DynamicJsonBuffer jsonBuffer; + JsonObject& json_lumi = jsonBuffer.createObject(); + uint32_t i = 0; + uint32_t len = buf2.len(); + char shortaddr[8]; + + while (len - i >= 2) { + uint8_t attrid = buf2.get8(i++); + + snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%02X"), attrid); + + i += parseSingleAttribute(json_lumi, shortaddr, buf2, i, len); + } + // parse output + if (json_lumi.containsKey("0x64")) { // Temperature + int32_t temperature = json_lumi["0x64"]; + json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f; + } + if (json_lumi.containsKey("0x65")) { // Humidity + uint32_t humidity = json_lumi["0x65"]; + json[F(D_JSON_HUMIDITY)] = humidity / 100.0f; + } + if (json_lumi.containsKey("0x66")) { // Pressure + int32_t pressure = json_lumi["0x66"]; + json[F(D_JSON_PRESSURE)] = pressure / 100.0f; + json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa + } + if (json_lumi.containsKey("0x01")) { // Battery Voltage + uint32_t voltage = json_lumi["0x01"]; + json[F(D_JSON_VOLTAGE)] = voltage / 1000.0f; + json[F("Battery")] = toPercentageCR2032(voltage); + } + json.remove(key); + } + +} + +#endif // USE_ZIGBEE diff --git a/sonoff/xdrv_23_zigbee_impl.ino b/sonoff/xdrv_23_zigbee_9_impl.ino similarity index 71% rename from sonoff/xdrv_23_zigbee_impl.ino rename to sonoff/xdrv_23_zigbee_9_impl.ino index 1653ed78c..6f23cbc5a 100644 --- a/sonoff/xdrv_23_zigbee_impl.ino +++ b/sonoff/xdrv_23_zigbee_9_impl.ino @@ -23,17 +23,35 @@ 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 +// Status code used for ZigbeeStatus MQTT message +// Ex: {"ZigbeeStatus":{"code": 3,"message":"Configured, starting coordinator"}} +const uint8_t ZIGBEE_STATUS_OK = 0; // Zigbee started and working +const uint8_t ZIGBEE_STATUS_BOOT = 1; // CC2530 booting +const uint8_t ZIGBEE_STATUS_RESET_CONF = 2; // Resetting CC2530 configuration +const uint8_t ZIGBEE_STATUS_STARTING = 3; // Starting CC2530 as coordinator +const uint8_t ZIGBEE_STATUS_PERMITJOIN_CLOSE = 20; // Disable PermitJoin +const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_60 = 21; // Enable PermitJoin for 60 seconds +const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_XX = 22; // Enable PermitJoin until next boot +const uint8_t ZIGBEE_STATUS_DEVICE_VERSION = 50; // Status: CC2530 ZNP Version +const uint8_t ZIGBEE_STATUS_DEVICE_INFO = 51; // Status: CC2530 Device Configuration +const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version +const uint8_t ZIGBEE_STATUS_ABORT = 99; // Fatal error, Zigbee not working +//#define Z_USE_SOFTWARE_SERIAL + +#ifdef Z_USE_SOFTWARE_SERIAL +#include +SoftwareSerial *ZigbeeSerial = nullptr; +#else #include - TasmotaSerial *ZigbeeSerial = nullptr; +#endif -const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND; -void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend }; +const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN; + +void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin }; typedef int32_t (*ZB_Func)(uint8_t value); typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, class SBuffer &buf); @@ -74,6 +92,7 @@ enum Zigbee_StateMachine_Instruction_Set { 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_MQTT_STATUS, // send MQTT status string with code 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 @@ -95,12 +114,24 @@ enum Zigbee_StateMachine_Instruction_Set { #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_MQTT_STATUS(x, m) { .i = { ZGB_INSTR_MQTT_STATUS, (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) }, +// Labels used in the State Machine -- internal only +const uint8_t ZIGBEE_LABEL_START = 10; // Start ZNP +const uint8_t ZIGBEE_LABEL_READY = 20; // goto label 20 for main loop +const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21; // main loop +const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_CLOSE = 30; // disable permit join +const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60 = 31; // enable permit join for 60 seconds +const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX = 32; // enable permit join for 60 seconds +// errors +const uint8_t ZIGBEE_LABEL_ABORT = 99; // goto label 99 in case of fatal error +const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version + struct ZigbeeStatus { bool active = true; // is Zigbee active for this device, i.e. GPIOs configured bool state_machine = false; // the state machine is running @@ -124,74 +155,6 @@ 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)); - ResponseTime_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 \*********************************************************************************************/ @@ -209,8 +172,8 @@ private: // 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_RESET, Z_AREQ | Z_SYS, SYS_RESET, 0x00 ) // 410001 SYS_RESET_REQ Hardware reset +ZBM(ZBR_RESET, Z_AREQ | Z_SYS, SYS_RESET_IND ) // 4180 SYS_RESET_REQ Hardware 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 @@ -295,7 +258,7 @@ ZBM(ZBS_W_ZDODCB, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_ZDO_DIRECT_CB, 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 +ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT, 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 @@ -352,11 +315,14 @@ ZBM(ZBS_AF_REGISTER0B, Z_SREQ | Z_AF, AF_REGISTER, 0x0B /* endpoint */, Z_B0(Z_P // 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 +ZBM(ZBS_PERMITJOINREQ_OPEN_60, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F /* AddrMode */, // 25360FFFFC3C00 + 0xFC, 0xFF /* DstAddr */, 60 /* Duration */, 0x00 /* TCSignificance */) +ZBM(ZBS_PERMITJOINREQ_OPEN_XX, 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_CLOSE, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0x00 /* Duration */) // 45CB00 +ZBM(ZBR_PERMITJOIN_AREQ_OPEN_60, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 60 /* Duration */) // 45CB3C +ZBM(ZBR_PERMITJOIN_AREQ_OPEN_XX, 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 @@ -371,119 +337,209 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { 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_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting") + //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_WAIT_RECV_FUNC(1000, ZBR_VERSION, &Z_ReceiveCheckVersion) // Check version ZI_SEND(ZBS_PAN) // check PAN ID - ZI_WAIT_RECV(500, ZBR_PAN) + ZI_WAIT_RECV(1000, ZBR_PAN) ZI_SEND(ZBS_EXTPAN) // check EXT PAN ID - ZI_WAIT_RECV(500, ZBR_EXTPAN) + ZI_WAIT_RECV(1000, ZBR_EXTPAN) ZI_SEND(ZBS_CHANN) // check CHANNEL - ZI_WAIT_RECV(500, ZBR_CHANN) + ZI_WAIT_RECV(1000, ZBR_CHANN) ZI_SEND(ZBS_PFGK) // check PFGK - ZI_WAIT_RECV(500, ZBR_PFGK) + ZI_WAIT_RECV(1000, ZBR_PFGK) ZI_SEND(ZBS_PFGKEN) // check PFGKEN - ZI_WAIT_RECV(500, ZBR_PFGKEN) - ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee configuration ok") + ZI_WAIT_RECV(1000, 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_LABEL(ZIGBEE_LABEL_START) // START ZNP App + ZI_MQTT_STATUS(ZIGBEE_STATUS_STARTING, "Configured, starting coordinator") + //ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages 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_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_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo) + //ZI_WAIT_RECV(2000, ZBR_GETDEVICEINFO) // TODO memorize info + ZI_SEND(ZBS_ZDO_NODEDESCREQ) // Z_ZDO:nodeDescReq + ZI_WAIT_RECV(1000, 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) + ZI_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq + ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ) + ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_NONE) + ZI_SEND(ZBS_AF_REGISTER01) // Z_AF register for endpoint 01, profile 0x0104 Home Automation + ZI_WAIT_RECV(1000, ZBR_AF_REGISTER) + ZI_SEND(ZBS_AF_REGISTER0B) // Z_AF register for endpoint 0B, profile 0x0104 Home Automation + ZI_WAIT_RECV(1000, 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_SEND(ZBS_ZDO_ACTIVEEPREQ) // Z_ZDO:activeEpReq + ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ) + ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_OK) + ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join + ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ) + ZI_WAIT_UNTIL(1000, 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_SEND(ZBS_PERMITJOINREQ_OPEN_XX) // Opening Permit Join, normally through command + //ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ) + //ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful + //ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_XX) ZI_LABEL(ZIGBEE_LABEL_READY) + ZI_MQTT_STATUS(ZIGBEE_STATUS_OK, "Started") ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device ready, listening...") - ZI_CALL(&Z_State_Ready, 1) + ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages + ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP) 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_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE) + ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_CLOSE, "Disable Pairing mode") + ZI_SEND(ZBS_PERMITJOINREQ_CLOSE) // Closing the Permit Join + ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ) + //ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful + //ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_CLOSE) + ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP) + + ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60) + ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_60, "Enable Pairing mode for 60 seconds") + ZI_SEND(ZBS_PERMITJOINREQ_OPEN_60) + ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ) + //ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful + //ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_60) + ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP) + + ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX) + ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_XX, "Enable Pairing mode until next boot") + ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX) + ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ) + //ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP) // not sure it's useful + //ZI_WAIT_UNTIL(500, ZBR_PERMITJOIN_AREQ_OPEN_XX) + ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP) + + ZI_LABEL(50) // reformat device + ZI_MQTT_STATUS(ZIGBEE_STATUS_RESET_CONF, "Reseting configuration") + //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_SEND(ZBS_FACTRES) // factory reset + ZI_WAIT_RECV(1000, 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) + ZI_SEND(ZBS_W_PAN) // write PAN ID + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_W_EXTPAN) // write EXT PAN ID + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_W_CHANN) // write CHANNEL + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_W_LOGTYP) // write Logical Type = coordinator + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_W_PFGK) // write PRECFGKEY + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_W_PFGKEN) // write PRECFGKEY Enable + ZI_WAIT_RECV(1000, ZBR_W_OK) + ZI_SEND(ZBS_WNV_SECMODE) // write Security Mode + ZI_WAIT_RECV(1000, ZBR_WNV_OK) + ZI_SEND(ZBS_W_ZDODCB) // write Z_ZDO Direct CB + ZI_WAIT_RECV(1000, 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_SEND(ZBS_WNV_INITZNPHC) // Init NV ZNP Has Configured + ZI_WAIT_RECV(1000, ZBR_WNV_INIT_OK) + ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured + ZI_WAIT_RECV(1000, ZBR_WNV_OK) - ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device reconfigured") - ZI_GOTO(10) + //ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device reconfigured") + ZI_GOTO(ZIGBEE_LABEL_START) - ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort + ZI_LABEL(ZIGBEE_LABEL_UNSUPPORTED_VERSION) + ZI_MQTT_STATUS(ZIGBEE_STATUS_UNSUPPORTED_VERSION, "Only ZNP 1.2 is currently supported") + ZI_GOTO(ZIGBEE_LABEL_ABORT) + + ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort + ZI_MQTT_STATUS(ZIGBEE_STATUS_ABORT, "Abort") ZI_LOG(LOG_LEVEL_ERROR, "ZIG: Abort") ZI_STOP(ZIGBEE_LABEL_ABORT) }; +int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) { + // Ex= 6700.00.6263151D004B1200.0000.07.09.02.83869991 + // IEEE Adr (8 bytes) = 0x00124B001D156362 + // Short Addr (2 bytes) = 0x0000 + // Device Type (1 byte) = 0x07 (coord?) + // Device State (1 byte) = 0x09 (coordinator started) + // NumAssocDevices (1 byte) = 0x02 + // List of devices: 0x8683, 0x9199 + Z_IEEEAddress long_adr = buf.get64(3); + Z_ShortAddress short_adr = buf.get16(11); + uint8_t device_type = buf.get8(13); + uint8_t device_state = buf.get8(14); + uint8_t device_associated = buf.get8(15); -int32_t Z_Recv_Vers(int32_t res, class SBuffer &buf) { + char hex[20]; + Uint64toHex(long_adr, hex, 64); + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{" + "\"code\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\"" + ",\"DeviceType\":%d,\"DeviceState\":%d" + ",\"NumAssocDevices\":%d"), + ZIGBEE_STATUS_DEVICE_INFO, hex, short_adr, device_type, device_state, + device_associated); + + if (device_associated > 0) { + uint idx = 16; + ResponseAppend_P(PSTR(",\"AssocDevicesList\":[")); + for (uint32_t i = 0; i < device_associated; i++) { + if (i > 0) { ResponseAppend_P(PSTR(",")); } + ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(idx)); + idx += 2; + } + ResponseAppend_P(PSTR("]")); + } + + ResponseJsonEnd(); // append '}' + ResponseJsonEnd(); // append '}' + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS)); + XdrvRulesProcess(); + + return res; +} + +int32_t Z_ReceiveCheckVersion(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))) { + // 61020200-02.06.03.D9143401.0200000000 + // TranportRev = 02 + // Product = 00 + // MajorRel = 2 + // MinorRel = 6 + // MaintRel = 3 + // Revision = 20190425 d (0x013414D9) + uint8_t major_rel = buf.get8(4); + uint8_t minor_rel = buf.get8(5); + uint8_t maint_rel = buf.get8(6); + uint32_t revision = buf.get32(7); + + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{" + "\"code\":%d,\"MajorRel\":%d,\"MinorRel\":%d" + ",\"MaintRel\":%d,\"Revision\":%d}}"), + ZIGBEE_STATUS_DEVICE_VERSION, major_rel, minor_rel, + maint_rel, revision); + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS)); + XdrvRulesProcess(); + + if ((0x02 == major_rel) && (0x06 == minor_rel)) { return 0; // version 2.6.x is ok } else { - return -2; // abort + return ZIGBEE_LABEL_UNSUPPORTED_VERSION; // abort } } @@ -496,10 +552,44 @@ int32_t Z_Recv_Default(int32_t res, class SBuffer &buf) { } 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(); + uint16_t groupid = buf.get16(2); + uint16_t clusterid = buf.get16(4); + Z_ShortAddress srcaddr = buf.get16(6); + uint8_t srcendpoint = buf.get8(8); + uint8_t dstendpoint = buf.get8(9); + uint8_t wasbroadcast = buf.get8(10); + uint8_t linkquality = buf.get8(11); + uint8_t securityuse = buf.get8(12); + uint32_t timestamp = buf.get32(13); + uint8_t seqnumber = buf.get8(17); + + ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid); + + zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr, + srcendpoint, dstendpoint, wasbroadcast, + linkquality, securityuse, seqnumber, + timestamp); + + char shortaddr[8]; + snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr); + + DynamicJsonBuffer jsonBuffer; + JsonObject& json_root = jsonBuffer.createObject(); + JsonObject& json = json_root.createNestedObject(shortaddr); + if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) { + zcl_received.parseRawAttributes(json); + } else if (zcl_received.isClusterSpecificCommand()) { + zcl_received.parseClusterSpecificCommand(json); + } + zcl_received.postProcessAttributes(json); + + String msg(""); + msg.reserve(100); + json_root.printTo(msg); + + Response_P(PSTR("%s"), msg.c_str()); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED)); + XdrvRulesProcess(); } return -1; } @@ -658,11 +748,16 @@ void ZigbeeStateMachine_Run(void) { continue; } } - // TODO break; case ZGB_INSTR_LOG: AddLog_P(cur_d8, (char*) cur_ptr1); break; + case ZGB_INSTR_MQTT_STATUS: + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{\"code\":%d,\"message\":\"%s\"}}"), + cur_d8, (char*) cur_ptr1); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS)); + XdrvRulesProcess(); + break; case ZGB_INSTR_SEND: ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 /* len */); break; @@ -791,7 +886,7 @@ void ZigbeeInput(void) 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); + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInput discarding byte %02X"), zigbee_in_byte); continue; // discard } @@ -820,6 +915,9 @@ void ZigbeeInput(void) char hex_char[(zigbee_buffer->len() * 2) + 2]; ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char)); +#ifndef Z_USE_SOFTWARE_SERIAL + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Bytes follor_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric()); +#endif // buffer received, now check integrity if (zigbee_buffer->len() != zigbee_frame_len) { // Len is not correct, log and reject frame @@ -852,19 +950,25 @@ 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]); +#ifdef Z_USE_SOFTWARE_SERIAL + ZigbeeSerial = new SoftwareSerial(); + ZigbeeSerial->begin(115200, pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX], SWSERIAL_8N1, false, 256); // ZNP is 115200, RTS/CTS (ignored), 8N1 + ZigbeeSerial->enableIntTx(false); + zigbee_buffer = new SBuffer(ZIGBEE_BUFFER_SIZE); +#else 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(); - } + ZigbeeSerial->begin(115200); + if (ZigbeeSerial->hardwareSerial()) { + ClaimSerial(); + zigbee_buffer = new PreAllocatedSBuffer(sizeof(serial_in_buffer), serial_in_buffer); + } else { + zigbee_buffer = new SBuffer(ZIGBEE_BUFFER_SIZE); + } +#endif + zigbee.active = true; + zigbee.init_phase = true; // start the state machine + zigbee.state_machine = true; // start the state machine + ZigbeeSerial->flush(); } } @@ -874,7 +978,6 @@ void ZigbeeInit(void) 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; @@ -928,6 +1031,23 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) { XdrvRulesProcess(); } + +void CmndZigbeePermitJoin(void) +{ + uint32_t payload = XdrvMailbox.payload; + if (payload < 0) { payload = 0; } + if ((99 != payload) && (payload > 1)) { payload = 1; } + + if (1 == payload) { + ZigbeeGotoLabel(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60); + } else if (99 == payload){ + ZigbeeGotoLabel(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX); + } else { + ZigbeeGotoLabel(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE); + } + ResponseCmndDone(); +} + /*********************************************************************************************\ * Interface \*********************************************************************************************/