Tasmota/sonoff/xdrv_23_zigbee_9_impl.ino

345 lines
12 KiB
C++

/*
xdrv_23_zigbee.ino - zigbee support for Sonoff-Tasmota
Copyright (C) 2019 Theo Arends and Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ZIGBEE
#define XDRV_23 23
const uint32_t ZIGBEE_BUFFER_SIZE = 256; // Max ZNP frame is SOF+LEN+CMD1+CMD2+250+FCS = 255
const uint8_t ZIGBEE_SOF = 0xFE;
//#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 "|" D_CMND_ZIGBEE_PERMITJOIN
"|" D_CMND_ZIGBEE_DUMP;
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
&CmndZigbeeDump };
int32_t ZigbeeProcessInput(class SBuffer &buf) {
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
// apply the receive filter, acts as 'startsWith()'
bool recv_filter_match = true;
bool recv_prefix_match = false; // do the first 2 bytes match the response
if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) {
if (zigbee.recv_filter_len >= 2) {
recv_prefix_match = false;
if ( (pgm_read_byte(&zigbee.recv_filter[0]) == buf.get8(0)) &&
(pgm_read_byte(&zigbee.recv_filter[1]) == buf.get8(1)) ) {
recv_prefix_match = true;
}
}
for (uint32_t i = 0; i < zigbee.recv_filter_len; i++) {
if (pgm_read_byte(&zigbee.recv_filter[i]) != buf.get8(i)) {
recv_filter_match = false;
break;
}
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: ZigbeeProcessInput: recv_prefix_match = %d, recv_filter_match = %d"), recv_prefix_match, recv_filter_match);
}
// if there is a recv_callback, call it now
int32_t res = -1; // default to ok
// res = 0 - proceed to next state
// res > 0 - proceed to the specified state
// res = -1 - silently ignore the message
// res <= -2 - move to error state
// pre-compute the suggested value
if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) {
if (!recv_prefix_match) {
res = -1; // ignore
} else { // recv_prefix_match
if (recv_filter_match) {
res = 0; // ok
} else {
if (zigbee.recv_until) {
res = -1; // ignore until full match
} else {
res = -2; // error, because message is expected but wrong value
}
}
}
} else { // we don't have any filter, ignore message by default
res = -1;
}
if (recv_prefix_match) {
if (zigbee.recv_func) {
res = (*zigbee.recv_func)(res, buf);
}
}
if (-1 == res) {
// if frame was ignored up to now
if (zigbee.recv_unexpected) {
res = (*zigbee.recv_unexpected)(res, buf);
}
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: ZigbeeProcessInput: res = %d"), res);
// change state accordingly
if (0 == res) {
// if ok, continue execution
zigbee.state_waiting = false;
} else if (res > 0) {
ZigbeeGotoLabel(res); // if >0 then go to specified label
} else if (-1 == res) {
// -1 means ignore message
// just do nothing
} else {
// any other negative value means error
ZigbeeGotoLabel(zigbee.on_error_goto);
}
}
void ZigbeeInput(void)
{
static uint32_t zigbee_polling_window = 0;
static uint8_t fcs = ZIGBEE_SOF;
static uint32_t zigbee_frame_len = 5; // minimal zigbee frame lenght, will be updated when buf[1] is read
// Receive only valid ZNP frames:
// 00 - SOF = 0xFE
// 01 - Length of Data Field - 0..250
// 02 - CMD1 - first byte of command
// 03 - CMD2 - second byte of command
// 04..FD - Data Field
// FE (or last) - FCS Checksum
while (ZigbeeSerial->available()) {
yield();
uint8_t zigbee_in_byte = ZigbeeSerial->read();
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZigbeeInput byte=%d len=%d"), zigbee_in_byte, zigbee_buffer->len());
if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized
zigbee_frame_len = 5;
fcs = ZIGBEE_SOF;
}
if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) {
// waiting for SOF (Start Of Frame) byte, discard anything else
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInput discarding byte %02X"), zigbee_in_byte);
continue; // discard
}
if (zigbee_buffer->len() < zigbee_frame_len) {
zigbee_buffer->add8(zigbee_in_byte);
zigbee_polling_window = millis(); // Wait for more data
fcs ^= zigbee_in_byte;
}
if (zigbee_buffer->len() >= zigbee_frame_len) {
zigbee_polling_window = 0; // Publish now
break;
}
// recalculate frame length
if (02 == zigbee_buffer->len()) {
// We just received the Lenght byte
uint8_t len_byte = zigbee_buffer->get8(1);
if (len_byte > 250) len_byte = 250; // ZNP spec says len is 250 max
zigbee_frame_len = len_byte + 5; // SOF + LEN + CMD1 + CMD2 + FCS = 5 bytes overhead
}
}
if (zigbee_buffer->len() && (millis() > (zigbee_polling_window + ZIGBEE_POLLING))) {
char hex_char[(zigbee_buffer->len() * 2) + 2];
ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char));
#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
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received frame of wrong size %s, len %d, expected %d"), hex_char, zigbee_buffer->len(), zigbee_frame_len);
} else if (0x00 != fcs) {
// FCS is wrong, packet is corrupt, log and reject frame
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received bad FCS frame %s, %d"), hex_char, fcs);
} else {
// frame is correct
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received correct frame %s"), hex_char);
SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); // remove SOF, LEN and FCS
ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char));
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPRECEIVED));
XdrvRulesProcess();
// now process the message
ZigbeeProcessInput(znp_buffer);
}
zigbee_buffer->setLen(0); // empty buffer
}
}
/********************************************************************************************/
void ZigbeeInit(void)
{
zigbee.active = false;
if ((pin[GPIO_ZIGBEE_RX] < 99) && (pin[GPIO_ZIGBEE_TX] < 99)) {
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("Zigbee: GPIOs Rx:%d Tx:%d"), pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX]);
#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
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();
}
}
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
void CmndZigbeeDump(void) {
if (ZigbeeSerial) {
String dump = Z_DumpDevices();
Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, dump.c_str());
}
}
void CmndZigbeeZNPSend(void)
{
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
uint8_t code;
char *codes = RemoveSpace(XdrvMailbox.data);
int32_t size = strlen(XdrvMailbox.data);
SBuffer buf((size+1)/2);
while (size > 0) {
char stemp[3];
strlcpy(stemp, codes, sizeof(stemp));
code = strtol(stemp, nullptr, 16);
buf.add8(code);
size -= 2;
codes += 2;
}
ZigbeeZNPSend(buf.getBuffer(), buf.len());
}
ResponseCmndDone();
}
void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
if ((len < 2) || (len > 252)) {
// abort, message cannot be less than 2 bytes for CMD1 and CMD2
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEEZNPSENT ": bad message len %d"), len);
return;
}
uint8_t data_len = len - 2; // removing CMD1 and CMD2
if (ZigbeeSerial) {
uint8_t fcs = data_len;
ZigbeeSerial->write(ZIGBEE_SOF); // 0xFE
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend SOF %02X"), ZIGBEE_SOF);
ZigbeeSerial->write(data_len);
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend LEN %02X"), data_len);
for (uint32_t i = 0; i < len; i++) {
uint8_t b = pgm_read_byte(msg + i);
ZigbeeSerial->write(b);
fcs ^= b;
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend byt %02X"), b);
}
ZigbeeSerial->write(fcs); // finally send fcs checksum byte
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs);
}
// Now send a MQTT message to report the sent message
char hex_char[(len * 2) + 2];
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPSENT "\":\"%s\"}"),
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPSENT));
XdrvRulesProcess();
}
// Allow or Deny pairing of new Zigbee devices
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
\*********************************************************************************************/
bool Xdrv23(uint8_t function)
{
bool result = false;
if (zigbee.active) {
switch (function) {
case FUNC_LOOP:
if (ZigbeeSerial) { ZigbeeInput(); }
if (zigbee.state_machine) {
//ZigbeeStateMachine();
ZigbeeStateMachine_Run();
}
break;
case FUNC_PRE_INIT:
ZigbeeInit();
break;
case FUNC_COMMAND:
result = DecodeCommand(kZigbeeCommands, ZigbeeCommand);
break;
}
}
return result;
}
#endif // USE_ZIGBEE