mirror of https://github.com/arendst/Tasmota.git
345 lines
12 KiB
C++
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_STATUS;
|
|
|
|
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
|
|
&CmndZigbeeStatus };
|
|
|
|
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 CmndZigbeeStatus(void) {
|
|
if (ZigbeeSerial) {
|
|
String dump = zigbee_devices.dump(XdrvMailbox.payload);
|
|
Response_P(PSTR("{\"%s%d\":%s}"), XdrvMailbox.command, XdrvMailbox.payload, 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
|