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