/*
xdrv_23_zigbee_9_serial.ino - zigbee: serial communication with MCU
Copyright (C) 2021 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
#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
const uint32_t ZIGBEE_LED_RECEIVE = 0; // LED<1> blinks when receiving
const uint32_t ZIGBEE_LED_SEND = 0; // LED<2> blinks when receiving
class EZSP_Serial_t {
public:
uint8_t to_send = 0; // 0..7, frame number of next packet to send, nothing to send if equal to to_end
uint8_t to_end = 0; // 0..7, frame number of next packet to send
uint8_t to_ack = 0; // 0..7, frame number of last packet acknowledged + 1
uint8_t from_ack = 0; // 0..7, frame to ack
uint8_t ezsp_seq = 0; // 0..255, EZSP sequence number
SBuffer *to_packets[8] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
};
EZSP_Serial_t EZSP_Serial;
//
// Blink Led Status
//
const uint32_t Z_LED_STATUS_ON_MILLIS = 50; // keep led on at least 50 ms
bool Z_LedStatusSet(bool onoff) {
static bool led_status_on = false;
static uint32_t led_on_time = 0;
if (onoff) {
SetLedPowerIdx(ZIGBEE_LED_RECEIVE, 1);
led_status_on = true;
led_on_time = millis();
} else if ((led_status_on) && (TimePassedSince(led_on_time) >= Z_LED_STATUS_ON_MILLIS)) {
SetLedPowerIdx(ZIGBEE_LED_RECEIVE, 0);
led_status_on = false;
}
return led_status_on;
}
#endif // USE_ZIGBEE_EZSP
#include
TasmotaSerial *ZigbeeSerial = nullptr;
/********************************************************************************************/
//
// Called at event loop, checks for incoming data from the CC2530
//
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 length, will be updated when buf[1] is read
// Receive only valid ZNP frames:
// 00 - SOF = 0xFE
// 01 - Length of Data Field - 0..250
// 02 - CMD1 - first byte of command
// 03 - CMD2 - second byte of command
// 04..FD - Data Field
// FE (or last) - FCS Checksum
while (ZigbeeSerial->available()) {
yield();
uint8_t zigbee_in_byte = ZigbeeSerial->read();
//AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("ZbInput byte=%d len=%d"), zigbee_in_byte, zigbee_buffer->len());
if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized
zigbee_frame_len = 5;
fcs = ZIGBEE_SOF;
// there is a rare race condition when an interrupt occurs when receiving the first byte
// in this case the first bit (lsb) is missed and Tasmota receives 0xFF instead of 0xFE
// We forgive this mistake, and next bytes are automatically resynchronized
if (ZIGBEE_SOF_ALT == zigbee_in_byte) {
AddLog(LOG_LEVEL_INFO, PSTR("ZbInput forgiven first byte %02X (only for statistics)"), zigbee_in_byte);
zigbee_in_byte = ZIGBEE_SOF;
}
}
if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) {
// waiting for SOF (Start Of Frame) byte, discard anything else
AddLog(LOG_LEVEL_INFO, PSTR("ZbInput discarding byte %02X"), zigbee_in_byte);
continue; // discard
}
if (zigbee_buffer->len() < zigbee_frame_len) {
zigbee_buffer->add8(zigbee_in_byte);
zigbee_polling_window = millis(); // Wait for more data
fcs ^= zigbee_in_byte;
}
if (zigbee_buffer->len() >= zigbee_frame_len) {
zigbee_polling_window = 0; // Publish now
break;
}
// recalculate frame length
if (02 == zigbee_buffer->len()) {
// We just received the Lenght byte
uint8_t len_byte = zigbee_buffer->get8(1);
if (len_byte > 250) len_byte = 250; // ZNP spec says len is 250 max
zigbee_frame_len = len_byte + 5; // SOF + LEN + CMD1 + CMD2 + FCS = 5 bytes overhead
}
}
if (zigbee_buffer->len() && (millis() > (zigbee_polling_window + ZIGBEE_POLLING))) {
// AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Bytes follow_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric());
// buffer received, now check integrity
if (zigbee_buffer->len() != zigbee_frame_len) {
// Len is not correct, log and reject frame
AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received frame of wrong size %_B, len %d, expected %d"), zigbee_buffer, zigbee_buffer->len(), zigbee_frame_len);
} else if (0x00 != fcs) {
// FCS is wrong, packet is corrupt, log and reject frame
AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received bad FCS frame %_B, %d"), zigbee_buffer, fcs);
} else {
// frame is correct
//AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received correct frame %s"), hex_char);
SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); // remove SOF, LEN and FCS
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%_B\"}"), &znp_buffer);
if (Settings.flag3.tuya_serial_mqtt_publish) {
MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_SENSOR));
} else {
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), TasmotaGlobal.mqtt_data);
}
// now process the message
ZigbeeProcessInput(znp_buffer);
}
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
static 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
Z_LedStatusSet(false);
while (ZigbeeSerial->available()) {
Z_LedStatusSet(true); // turn on receive LED<1>
yield();
uint8_t zigbee_in_byte = ZigbeeSerial->read();
// AddLog_P(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_P(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Escape byte received"));
escape = true;
continue;
}
if (ZIGBEE_EZSP_CANCEL == zigbee_in_byte) {
// AddLog_P(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)))) {
// AddLog_P(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_P(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; iget8(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_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": bad crc (received 0x%04X, computed 0x%04X) %_B"), crc_received, crc, zigbee_buffer);
} 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> 1) ^ 0xB8; }
else { rand = (rand >> 1); }
}
}
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "2\":\"%_B\"}"), &ezsp_buffer);
// now process the message
ZigbeeProcessInputRaw(ezsp_buffer);
}
} else {
// the buffer timed-out, print error and discard
AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": time-out, discarding %_B"), zigbee_buffer);
}
zigbee_buffer->setLen(0); // empty buffer
escape = false;
frame_complete = false;
}
#endif // USE_ZIGBEE_EZSP
}
/********************************************************************************************/
// Initialize internal structures
void ZigbeeInitSerial(void)
{
// AddLog_P(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem1 = %d"), ESP_getFreeHeap());
zigbee.active = false;
if (PinUsed(GPIO_ZIGBEE_RX) && PinUsed(GPIO_ZIGBEE_TX)) {
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "GPIOs Rx:%d Tx:%d"), Pin(GPIO_ZIGBEE_RX), Pin(GPIO_ZIGBEE_TX));
// if TasmotaGlobal.seriallog_level is 0, we allow GPIO 13/15 to switch to Hardware Serial
ZigbeeSerial = new TasmotaSerial(Pin(GPIO_ZIGBEE_RX), Pin(GPIO_ZIGBEE_TX), TasmotaGlobal.seriallog_level ? 1 : 2, 0, 256); // set a receive buffer of 256 bytes
ZigbeeSerial->begin(115200);
if (ZigbeeSerial->hardwareSerial()) {
ClaimSerial();
uint32_t aligned_buffer = ((uint32_t)TasmotaGlobal.serial_in_buffer + 3) & ~3;
zigbee_buffer = new PreAllocatedSBuffer(sizeof(TasmotaGlobal.serial_in_buffer) - 3, (char*) aligned_buffer);
} else {
// AddLog_P(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem2 = %d"), ESP_getFreeHeap());
zigbee_buffer = new SBuffer(ZIGBEE_BUFFER_SIZE);
// AddLog_P(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem3 = %d"), ESP_getFreeHeap());
}
if (PinUsed(GPIO_ZIGBEE_RST)) {
pinMode(Pin(GPIO_ZIGBEE_RST), OUTPUT);
digitalWrite(Pin(GPIO_ZIGBEE_RST), 1);
}
if (PinUsed(GPIO_ZIGBEE_RST, 1)) {
pinMode(Pin(GPIO_ZIGBEE_RST, 1), OUTPUT);
digitalWrite(Pin(GPIO_ZIGBEE_RST, 1), 1);
}
zigbee.active = true;
zigbee.init_phase = true; // start the state machine
zigbee.state_machine = true; // start the state machine
ZigbeeSerial->flush();
}
// AddLog_P(LOG_LEVEL_INFO, PSTR("ZigbeeInit Mem9 = %d"), ESP_getFreeHeap());
}
#ifdef USE_ZIGBEE_ZNP
// flush any ongoing frame, sending 256 times 0xFF
void ZigbeeZNPFlush(void) {
if (ZigbeeSerial) {
for (uint32_t i = 0; i < 256; i++) {
ZigbeeSerial->write(0xFF);
}
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " 0xFF x 255"));
}
}
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_P(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_P(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend SOF %02X"), ZIGBEE_SOF);
ZigbeeSerial->write(data_len);
//AddLog_P(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_P(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend byt %02X"), b);
}
ZigbeeSerial->write(fcs); // finally send fcs checksum byte
//AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs);
}
// Now send a MQTT message to report the sent message
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %*_H"), len, msg);
}
//
// Same code for `ZbZNPSend` and `ZbZNPReceive`
// building the complete message (intro, length)
//
void CmndZbZNPSendOrReceive(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 `ZbZNPSend`
ZigbeeZNPSend(buf.getBuffer(), buf.len());
} else {
// Command was `ZbZNPReceive`
ZigbeeProcessInput(buf);
}
}
ResponseCmndDone();
}
// For debug purposes only, simulates a message received
void CmndZbZNPReceive(void)
{
CmndZbZNPSendOrReceive(false);
}
void CmndZbZNPSend(void)
{
CmndZbZNPSendOrReceive(true);
}
#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
// case 0xFF: // special wake-up
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 ZigbeeEZSPSendRaw(const uint8_t *msg, size_t len, bool send_cancel) {
if ((len < 1) || (len > 252)) {
// abort, message cannot be less than 2 bytes for CMD1 and CMD2
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEE_EZSP_SENT ": bad message len %d"), len);
return;
}
// turn send led on
Z_LedStatusSet(true);
if (ZigbeeSerial) {
if (send_cancel) {
ZigbeeSerial->write(ZIGBEE_EZSP_CANCEL); // 0x1A
}
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 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
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEE_EZSP_SENT_RAW " %*_H"), len, msg);
}
// Send an EZSP command and data
// Ex: Version with min v8 = 000008
void ZigbeeEZSPSendCmd(const uint8_t *msg, size_t len) {
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZbEZSPSend %*_H"), len, msg);
SBuffer cmd(len+3); // prefix with seq number (1 byte) and frame control bytes (2 bytes)
cmd.add8(EZSP_Serial.ezsp_seq++);
cmd.add8(0x00); // Low byte of Frame Control
cmd.add8(0x01); // High byte of Frame Control, frameFormatVersion = 1
cmd.addBuffer(msg, len);
// send
ZigbeeEZSPSendDATA(cmd.getBuffer(), cmd.len());
}
// Send an EZSP DATA frame, automatically calculating the correct frame numbers
void ZigbeeEZSPSendDATA_frm(bool send_cancel, uint8_t to_frm, uint8_t from_ack) {
SBuffer *buf = EZSP_Serial.to_packets[to_frm];
if (!buf) {
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Buffer for packet %d is not allocated"), EZSP_Serial.to_send);
return;
}
uint8_t control_byte = ((to_frm & 0x07) << 4) + (from_ack & 0x07);
buf->set8(0, control_byte); // change control_byte
// send
ZigbeeEZSPSendRaw(buf->getBuffer(), buf->len(), send_cancel);
}
// Send an EZSP DATA frame, automatically calculating the correct frame numbers
void ZigbeeEZSPSendDATA(const uint8_t *msg, size_t len) {
// prepare buffer by adding 1 byte prefix
SBuffer *buf = new SBuffer(len+1); // prepare for control_byte prefix
buf->add8(0x00); // placeholder for control_byte
buf->addBuffer(msg, len);
//
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: adding packet to_send, to_ack:%d, to_send:%d, to_end:%d"),
EZSP_Serial.to_ack, EZSP_Serial.to_send, EZSP_Serial.to_end);
uint8_t to_frm = EZSP_Serial.to_end;
if (EZSP_Serial.to_packets[to_frm]) {
delete EZSP_Serial.to_packets[to_frm];
EZSP_Serial.to_packets[to_frm] = nullptr;
}
EZSP_Serial.to_packets[to_frm] = buf;
EZSP_Serial.to_end = (to_frm + 1) & 0x07; // move cursor
// ZigbeeEZSPSendDATA_frm(send_cancel, to_frm, EZSP_Serial.from_ack);
// increment to_frame
//EZSP_Serial.to_ack = (EZSP_Serial.to_ack + 1) & 0x07;
//EZSP_Serial.to_frm = (EZSP_Serial.to_frm + 1) & 0x07;
}
// Receive a high-level EZSP command/response, starting with 16-bits frame ID
void ZigbeeProcessInputEZSP(SBuffer &buf) {
// verify errors in first 2 bytes.
// TODO
// uint8_t sequence_num = buf.get8(0);
uint16_t frame_control = buf.get16(1);
bool truncated = frame_control & 0x02;
bool overflow = frame_control & 0x01;
// bool callbackPending = frame_control & 0x04;
bool security_enabled = frame_control & 0x8000;
if (truncated || overflow || security_enabled) {
AddLog_P(LOG_LEVEL_INFO, PSTR("ZIG: specific frame_control 0x%04X"), frame_control);
}
// remove first 2 bytes, be
for (uint32_t i=0; i> 4) + 1) & 0x07;
uint8_t ack_byte = 0x80 | EZSP_Serial.from_ack;
ZigbeeEZSPSendRaw(&ack_byte, 1, false); // send a 1-byte ACK
// build the EZSP frame
// remove first byte
for (uint8_t i=0; i 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`
if (2 == XdrvMailbox.index) { ZigbeeEZSPSendDATA(buf.getBuffer(), buf.len()); }
else if (3 == XdrvMailbox.index) { ZigbeeEZSPSendRaw(buf.getBuffer(), buf.len(), true); }
else { ZigbeeEZSPSendCmd(buf.getBuffer(), buf.len()); }
} else {
// Command was `ZbEZSPReceive`
if (2 == XdrvMailbox.index) { ZigbeeProcessInput(buf); }
else if (3 == XdrvMailbox.index) { ZigbeeProcessInputRaw(buf); }
else { ZigbeeProcessInputEZSP(buf); } // TODO
}
}
ResponseCmndDone();
}
// Variants with managed ASH frame numbers
// 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:
// - shortaddr: 16-bits short address, or 0x0000 if group address
// - groupaddr: 16-bits group address, or 0x0000 if unicast using shortaddr
// - clusterIf: 16-bits cluster number
// - endpoint: 8-bits target endpoint (source is always 0x01), unused for group addresses. Should not be 0x00 except when sending to group address.
// - cmd: 8-bits ZCL command number
// - clusterSpecific: boolean, is the message general cluster or cluster specific, used to create the FC byte of ZCL
// - msg: pointer to byte array, payload of ZCL message (len is following), ignored if nullptr
// - len: length of the 'msg' payload
// - needResponse: boolean, true = we ask the target to respond, false = the target should not respond
// - transac: 8-bits, transation id of message (should be incremented at each message), used both for Zigbee message number and ZCL message number
// Returns: None
//
void ZigbeeZCLSend_Raw(const ZCLMessage &zcl) {
SBuffer buf(32+zcl.buf.len());
#ifdef USE_ZIGBEE_ZNP
buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST_EXT); // 02
if (!zcl.validShortaddr()) { // if no shortaddr we assume group address
buf.add8(Z_Addr_Group); // 01
buf.add64(zcl.groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded
buf.add8(0xFF); // dest endpoint is not used for group addresses
} else {
buf.add8(Z_Addr_ShortAddress); // 02
buf.add64(zcl.shortaddr); // dest address, only 2 LSB, upper 6 MSB are discarded
buf.add8(zcl.endpoint); // dest endpoint
}
buf.add16(0x0000); // dest Pan ID, 0x0000 = intra-pan
buf.add8(0x01); // source endpoint
buf.add16(zcl.cluster);
buf.add8(zcl.transac); // transac
buf.add8(0x30); // 30 options
buf.add8(0x1E); // 1E radius
buf.add16(3 + zcl.buf.len() + (zcl.manuf ? 2 : 0));
buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field
if (zcl.manuf) {
buf.add16(zcl.manuf); // add Manuf Id if not null
}
buf.add8(zcl.transac); // Transaction Sequence Number
buf.add8(zcl.cmd);
buf.addBuffer(zcl.buf);
ZigbeeZNPSend(buf.getBuffer(), buf.len());
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
if (zcl.validShortaddr()) {
// send unicast message to an address
buf.add16(EZSP_sendUnicast); // 3400
buf.add8(EMBER_OUTGOING_DIRECT); // 00
buf.add16(zcl.shortaddr); // dest addr
// ApsFrame
buf.add16(Z_PROF_HA); // Home Automation profile
buf.add16(zcl.cluster); // cluster
buf.add8(0x01); // srcEp
buf.add8(zcl.endpoint); // dstEp
if (zcl.direct) {
buf.add16(0x0000); // APS frame
} else {
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
}
buf.add16(zcl.groupaddr); // groupId
buf.add8(zcl.transac);
// end of ApsFrame
buf.add8(0x01); // tag TODO
buf.add8(3 + zcl.buf.len() + (zcl.manuf ? 2 : 0));
buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field
if (zcl.manuf) {
buf.add16(zcl.manuf); // add Manuf Id if not null
}
buf.add8(zcl.transac); // Transaction Sequance Number
buf.add8(zcl.cmd);
buf.addBuffer(zcl.buf);
} else {
// send broadcast group address, aka groupcast
buf.add16(EZSP_sendMulticast); // 3800
// ApsFrame
buf.add16(Z_PROF_HA); // Home Automation profile
buf.add16(zcl.cluster); // cluster
buf.add8(0x01); // srcEp
buf.add8(zcl.endpoint); // broadcast endpoint for groupcast
if (zcl.direct) {
buf.add16(0x0000); // APS frame
} else {
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
}
buf.add16(zcl.groupaddr); // groupId
buf.add8(zcl.transac);
// end of ApsFrame
buf.add8(0); // hops, 0x00 = EMBER_MAX_HOPS
buf.add8(7); // nonMemberRadius, 7 = infinite
buf.add8(0x01); // tag TODO
buf.add8(3 + zcl.buf.len() + (zcl.manuf ? 2 : 0));
buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field
if (zcl.manuf) {
buf.add16(zcl.manuf); // add Manuf Id if not null
}
buf.add8(zcl.transac); // Transaction Sequance Number
buf.add8(zcl.cmd);
buf.addBuffer(zcl.buf);
}
ZigbeeEZSPSendCmd(buf.buf(), buf.len());
#endif // USE_ZIGBEE_EZSP
}
//
// Send any buffered data to the NCP
//
// Used only with EZSP, as there is no replay of procotol control with ZNP
void ZigbeeOutputLoop(void) {
#ifdef USE_ZIGBEE_EZSP
// while (EZSP_Serial.to_send != EZSP_Serial.to_end) {
if (EZSP_Serial.to_send != EZSP_Serial.to_end) { // we send only one packet per tick to lower the chance of NAK
AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Something to_send, to_ack:%d, to_send:%d, to_end:%d"),
EZSP_Serial.to_ack, EZSP_Serial.to_send, EZSP_Serial.to_end);
// we have a frame waiting to be sent
ZigbeeEZSPSendDATA_frm(true, EZSP_Serial.to_send, EZSP_Serial.from_ack);
// increment sent counter
EZSP_Serial.to_send = (EZSP_Serial.to_send + 1) & 0x07;
}
#endif // USE_ZIGBEE_EZSP
}
#endif // USE_ZIGBEE