mirror of https://github.com/arendst/Tasmota.git
Merge pull request #6427 from s-hadinger/zigbee_phase_3
Zigbee support phase 3 - support for Xiaomi lumi.weather air quality sensor, Osram mini-switch
This commit is contained in:
commit
180128dc4a
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<x>
|
||||
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
|
|
@ -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.h>
|
||||
SoftwareSerial *ZigbeeSerial = nullptr;
|
||||
#else
|
||||
#include <TasmotaSerial.h>
|
||||
|
||||
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
|
||||
\*********************************************************************************************/
|
Loading…
Reference in New Issue