2020-06-18 15:05:55 +01:00
|
|
|
/*
|
|
|
|
xdrv_31_tasmota_client.ino - Support for external microcontroller on serial
|
|
|
|
|
2021-01-01 12:44:04 +00:00
|
|
|
Copyright (C) 2021 Andre Thomas and Theo Arends
|
2020-06-18 15:05:55 +01:00
|
|
|
|
|
|
|
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_TASMOTA_CLIENT
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Tasmota to microcontroller
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
#define XDRV_31 31
|
2020-06-18 15:05:55 +01:00
|
|
|
|
2020-11-21 13:45:58 +00:00
|
|
|
#ifndef USE_TASMOTA_CLIENT_FLASH_SPEED
|
2020-11-25 12:07:04 +00:00
|
|
|
#define USE_TASMOTA_CLIENT_FLASH_SPEED 57600 // Usually 57600 for 3.3V variants and 115200 for 5V variants
|
2020-11-21 13:45:58 +00:00
|
|
|
#endif
|
|
|
|
#ifndef USE_TASMOTA_CLIENT_SERIAL_SPEED
|
2020-11-25 12:07:04 +00:00
|
|
|
#define USE_TASMOTA_CLIENT_SERIAL_SPEED 57600 // Depends on the sketch that is running on the Uno/Pro Mini
|
2020-11-21 13:45:58 +00:00
|
|
|
#endif
|
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
#define TASMOTA_CLIENT_LIB_VERSION 20191129
|
|
|
|
#define TASMOTA_CLIENT_TIMEOUT 250 // mSeconds
|
2020-06-18 15:05:55 +01:00
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
#define CONST_STK_CRC_EOP 0x20
|
|
|
|
|
|
|
|
#define CMND_STK_GET_SYNC 0x30
|
|
|
|
#define CMND_STK_SET_DEVICE 0x42
|
|
|
|
#define CMND_STK_SET_DEVICE_EXT 0x45
|
|
|
|
#define CMND_STK_ENTER_PROGMODE 0x50
|
|
|
|
#define CMND_STK_LEAVE_PROGMODE 0x51
|
|
|
|
#define CMND_STK_LOAD_ADDRESS 0x55
|
|
|
|
#define CMND_STK_PROG_PAGE 0x64
|
2020-06-18 15:05:55 +01:00
|
|
|
|
|
|
|
/*************************************************\
|
|
|
|
* Tasmota Client Specific Commands
|
|
|
|
\*************************************************/
|
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
#define CMND_START 0xFC
|
|
|
|
#define CMND_END 0xFD
|
2020-06-18 15:05:55 +01:00
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
#define CMND_FEATURES 0x01
|
2022-01-22 16:55:55 +00:00
|
|
|
#define CMND_GET_JSON 0x02
|
2020-11-25 12:07:04 +00:00
|
|
|
#define CMND_FUNC_EVERY_SECOND 0x03
|
|
|
|
#define CMND_FUNC_EVERY_100_MSECOND 0x04
|
|
|
|
#define CMND_CLIENT_SEND 0x05
|
|
|
|
#define CMND_PUBLISH_TELE 0x06
|
|
|
|
#define CMND_EXECUTE_CMND 0x07
|
2020-06-18 15:05:55 +01:00
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
#define PARAM_DATA_START 0xFE
|
|
|
|
#define PARAM_DATA_END 0xFF
|
2020-06-18 15:05:55 +01:00
|
|
|
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
struct SimpleHexParse {
|
|
|
|
uint8_t FlashPage[128];
|
|
|
|
uint8_t layoverBuffer[16];
|
|
|
|
uint8_t FlashPageIdx;
|
|
|
|
uint8_t layoverIdx;
|
|
|
|
uint8_t ptr_l;
|
|
|
|
uint8_t ptr_h;
|
|
|
|
bool firstrun;
|
|
|
|
bool EndOfFile;
|
|
|
|
} SHParse;
|
|
|
|
|
|
|
|
uint8_t SimpleHexParseGetByte(char* hexline, uint8_t idx) {
|
|
|
|
char buff[3];
|
|
|
|
buff[3] = '\0';
|
|
|
|
memcpy(&buff, &hexline[(idx*2)-2], 2);
|
|
|
|
return strtol(buff, 0, 16);
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
uint32_t SimpleHexParseLine(char *hexline) {
|
|
|
|
if (SHParse.layoverIdx) {
|
|
|
|
memcpy(&SHParse.FlashPage, &SHParse.layoverBuffer, SHParse.layoverIdx);
|
|
|
|
SHParse.FlashPageIdx = SHParse.layoverIdx;
|
|
|
|
SHParse.layoverIdx = 0;
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
2020-11-25 12:07:04 +00:00
|
|
|
|
|
|
|
// 10 00 00 00 0C945D000C9485000C9485000C948500 84
|
|
|
|
uint8_t len = SimpleHexParseGetByte(hexline, 1);
|
|
|
|
uint8_t addr_h = SimpleHexParseGetByte(hexline, 2);
|
|
|
|
uint8_t addr_l = SimpleHexParseGetByte(hexline, 3);
|
|
|
|
uint8_t rectype = SimpleHexParseGetByte(hexline, 4);
|
|
|
|
|
2021-06-05 10:47:09 +01:00
|
|
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Hexline |%s|, Len %d, Address 0x%02X%02X, RecType %d"), hexline, len, addr_h, addr_l, rectype);
|
2020-11-25 12:07:04 +00:00
|
|
|
|
2020-11-25 13:58:18 +00:00
|
|
|
if (len > 16) { return 5; } // Error: Line too long
|
|
|
|
if (rectype > 1) { return 6; } // Error: Invalid record type
|
2020-11-25 12:07:04 +00:00
|
|
|
|
2020-11-25 14:58:43 +00:00
|
|
|
for (uint32_t idx = 0; idx < len; idx++) {
|
2020-11-25 12:07:04 +00:00
|
|
|
if (SHParse.FlashPageIdx < sizeof(SHParse.FlashPage)) {
|
|
|
|
SHParse.FlashPage[SHParse.FlashPageIdx] = SimpleHexParseGetByte(hexline, idx+5);
|
|
|
|
SHParse.FlashPageIdx++;
|
|
|
|
} else { // We have layover bytes
|
|
|
|
SHParse.layoverBuffer[SHParse.layoverIdx] = SimpleHexParseGetByte(hexline, idx+5);
|
|
|
|
SHParse.layoverIdx++;
|
|
|
|
}
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
2020-11-25 12:07:04 +00:00
|
|
|
|
2020-06-18 15:05:55 +01:00
|
|
|
if (1 == rectype) {
|
2020-11-25 12:07:04 +00:00
|
|
|
SHParse.EndOfFile = true;
|
|
|
|
while (SHParse.FlashPageIdx < sizeof(SHParse.FlashPage)) {
|
|
|
|
SHParse.FlashPage[SHParse.FlashPageIdx] = 0xFF;
|
|
|
|
SHParse.FlashPageIdx++;
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
}
|
2020-11-25 12:07:04 +00:00
|
|
|
|
|
|
|
if (SHParse.FlashPageIdx == sizeof(SHParse.FlashPage)) {
|
|
|
|
if (SHParse.firstrun) {
|
|
|
|
SHParse.firstrun = false;
|
2020-06-18 15:05:55 +01:00
|
|
|
} else {
|
2020-11-25 12:07:04 +00:00
|
|
|
SHParse.ptr_l += 0x40;
|
|
|
|
if (SHParse.ptr_l == 0) {
|
|
|
|
SHParse.ptr_h++;
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct TCLIENT {
|
|
|
|
uint8_t inverted = LOW;
|
|
|
|
bool type = false;
|
|
|
|
bool SerialEnabled = false;
|
|
|
|
uint8_t waitstate = 0; // We use this so that features detection does not slow down other stuff on startup
|
|
|
|
bool unsupported = false;
|
|
|
|
} TClient;
|
|
|
|
|
|
|
|
typedef union {
|
|
|
|
uint32_t data;
|
|
|
|
struct {
|
|
|
|
uint32_t func_json_append : 1; // Client supports providing a JSON for TELEPERIOD
|
|
|
|
uint32_t func_every_second : 1; // Client supports receiving a FUNC_EVERY_SECOND callback with no response
|
|
|
|
uint32_t func_every_100_msecond : 1; // Client supports receiving a FUNC_EVERY_100_MSECOND callback with no response
|
|
|
|
uint32_t func_client_send : 1; // Client supports receiving commands with "client send xxx"
|
|
|
|
uint32_t spare4 : 1;
|
|
|
|
uint32_t spare5 : 1;
|
|
|
|
uint32_t spare6 : 1;
|
|
|
|
uint32_t spare7 : 1;
|
|
|
|
uint32_t spare8 : 1;
|
|
|
|
uint32_t spare9 : 1;
|
|
|
|
uint32_t spare10 : 1;
|
|
|
|
uint32_t spare11 : 1;
|
|
|
|
uint32_t spare12 : 1;
|
|
|
|
uint32_t spare13 : 1;
|
|
|
|
uint32_t spare14 : 1;
|
|
|
|
uint32_t spare15 : 1;
|
|
|
|
uint32_t spare16 : 1;
|
|
|
|
uint32_t spare17 : 1;
|
|
|
|
uint32_t spare18 : 1;
|
|
|
|
uint32_t spare19 : 1;
|
|
|
|
uint32_t spare20 : 1;
|
|
|
|
uint32_t spare21 : 1;
|
|
|
|
uint32_t spare22 : 1;
|
|
|
|
uint32_t spare23 : 1;
|
|
|
|
uint32_t spare24 : 1;
|
|
|
|
uint32_t spare25 : 1;
|
|
|
|
uint32_t spare26 : 1;
|
|
|
|
uint32_t spare27 : 1;
|
|
|
|
uint32_t spare28 : 1;
|
|
|
|
uint32_t spare29 : 1;
|
|
|
|
uint32_t spare30 : 1;
|
|
|
|
uint32_t spare31 : 1;
|
|
|
|
};
|
|
|
|
} TClientFeatureCfg;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The structure below must remain 4 byte aligned to be compatible with
|
|
|
|
* Tasmota as master
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct TCLIENT_FEATURES {
|
|
|
|
uint32_t features_version;
|
|
|
|
TClientFeatureCfg features;
|
|
|
|
} TClientSettings;
|
|
|
|
|
|
|
|
struct TCLIENT_COMMAND {
|
|
|
|
uint8_t command;
|
|
|
|
uint8_t parameter;
|
|
|
|
uint8_t unused2;
|
|
|
|
uint8_t unused3;
|
|
|
|
} TClientCommand;
|
|
|
|
|
|
|
|
TasmotaSerial *TasmotaClient_Serial;
|
|
|
|
|
|
|
|
void TasmotaClient_Reset(void) {
|
|
|
|
if (TClient.SerialEnabled) {
|
|
|
|
digitalWrite(Pin(GPIO_TASMOTACLIENT_RST), !TClient.inverted);
|
|
|
|
delay(1);
|
|
|
|
digitalWrite(Pin(GPIO_TASMOTACLIENT_RST), TClient.inverted);
|
|
|
|
delay(1);
|
|
|
|
digitalWrite(Pin(GPIO_TASMOTACLIENT_RST), !TClient.inverted);
|
|
|
|
delay(5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t TasmotaClient_waitForSerialData(int dataCount, int timeout) {
|
|
|
|
int timer = 0;
|
|
|
|
while (timer < timeout) {
|
|
|
|
if (TasmotaClient_Serial->available() >= dataCount) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
delay(1);
|
|
|
|
timer++;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
uint8_t TasmotaClient_receiveData(char* buffer, int size) {
|
|
|
|
uint8_t index = 255;
|
|
|
|
int timer = 0;
|
|
|
|
while (timer < TASMOTA_CLIENT_TIMEOUT) {
|
|
|
|
int data = TasmotaClient_Serial->read();
|
|
|
|
if (data >= 0) {
|
|
|
|
if (PARAM_DATA_START == data) { index = 0; } // Start of data
|
|
|
|
else if (PARAM_DATA_END == data) { break; } // End of data
|
|
|
|
else if (index < 255) {
|
|
|
|
buffer[index++] = (char)data; // Data
|
|
|
|
if (index == size) { break; } // No EoD received or done
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
delay(1);
|
|
|
|
timer++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (255 == index) { index = 0; }
|
|
|
|
|
2022-11-11 10:47:11 +00:00
|
|
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("TCL: ReceiveData %*_H"), index, (uint8_t*)buffer);
|
2020-11-25 12:07:04 +00:00
|
|
|
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
2020-06-18 15:05:55 +01:00
|
|
|
uint8_t TasmotaClient_sendBytes(uint8_t* bytes, int count) {
|
2022-11-11 10:47:11 +00:00
|
|
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("TCL: SendBytes %*_H"), count, (uint8_t*)&bytes);
|
2020-11-25 12:07:04 +00:00
|
|
|
|
2020-06-18 15:05:55 +01:00
|
|
|
TasmotaClient_Serial->write(bytes, count);
|
2020-11-25 12:07:04 +00:00
|
|
|
TasmotaClient_waitForSerialData(2, TASMOTA_CLIENT_TIMEOUT);
|
2020-06-18 15:05:55 +01:00
|
|
|
uint8_t sync = TasmotaClient_Serial->read();
|
|
|
|
uint8_t ok = TasmotaClient_Serial->read();
|
|
|
|
if ((sync == 0x14) && (ok == 0x10)) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t TasmotaClient_execCmd(uint8_t cmd) {
|
|
|
|
uint8_t bytes[] = { cmd, CONST_STK_CRC_EOP };
|
|
|
|
return TasmotaClient_sendBytes(bytes, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t TasmotaClient_execParam(uint8_t cmd, uint8_t* params, int count) {
|
|
|
|
uint8_t bytes[32];
|
|
|
|
bytes[0] = cmd;
|
|
|
|
int i = 0;
|
|
|
|
while (i < count) {
|
|
|
|
bytes[i + 1] = params[i];
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
bytes[i + 1] = CONST_STK_CRC_EOP;
|
|
|
|
return TasmotaClient_sendBytes(bytes, i + 2);
|
|
|
|
}
|
|
|
|
|
2020-11-25 13:58:18 +00:00
|
|
|
uint32_t TasmotaClient_Flash(uint8_t* data, size_t size) {
|
2020-11-25 12:07:04 +00:00
|
|
|
/*
|
2020-11-25 13:58:18 +00:00
|
|
|
// Don't do this as there is no re-init configured
|
2020-11-25 12:07:04 +00:00
|
|
|
TasmotaClient_Serial->end();
|
|
|
|
delay(10);
|
|
|
|
|
2020-06-18 15:05:55 +01:00
|
|
|
TasmotaClient_Serial->begin(USE_TASMOTA_CLIENT_FLASH_SPEED);
|
|
|
|
if (TasmotaClient_Serial->hardwareSerial()) {
|
|
|
|
ClaimSerial();
|
|
|
|
}
|
2020-11-25 12:07:04 +00:00
|
|
|
*/
|
2020-06-18 15:05:55 +01:00
|
|
|
TasmotaClient_Reset();
|
|
|
|
|
|
|
|
uint8_t timeout = 0;
|
2020-11-25 13:58:18 +00:00
|
|
|
while (timeout <= 50) {
|
2020-06-18 15:05:55 +01:00
|
|
|
if (TasmotaClient_execCmd(CMND_STK_GET_SYNC)) {
|
2020-11-25 13:58:18 +00:00
|
|
|
break;
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
timeout++;
|
|
|
|
delay(1);
|
|
|
|
}
|
2020-11-25 13:58:18 +00:00
|
|
|
if (timeout > 50) { return 1; } // Error: Bootloader could not be found
|
2020-06-18 15:05:55 +01:00
|
|
|
|
2021-01-23 15:26:23 +00:00
|
|
|
AddLog(LOG_LEVEL_INFO, PSTR("TCL: Found bootloader"));
|
2020-06-18 15:05:55 +01:00
|
|
|
|
2020-11-25 13:58:18 +00:00
|
|
|
uint8_t ProgParams[] = {0x86, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00};
|
|
|
|
if (!TasmotaClient_execParam(CMND_STK_SET_DEVICE, ProgParams, sizeof(ProgParams))) {
|
|
|
|
return 2; // Error: Could not configure device for programming (1)
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
|
2020-11-25 13:58:18 +00:00
|
|
|
uint8_t ExtProgParams[] = {0x05, 0x04, 0xd7, 0xc2, 0x00};
|
|
|
|
if (!TasmotaClient_execParam(CMND_STK_SET_DEVICE_EXT, ExtProgParams, sizeof(ExtProgParams))) {
|
|
|
|
return 3; // Error: Could not configure device for programming (2)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!TasmotaClient_execCmd(CMND_STK_ENTER_PROGMODE)) {
|
|
|
|
return 4; // Error: Failed to put bootloader into programming mode
|
2020-11-21 13:45:58 +00:00
|
|
|
}
|
|
|
|
|
2020-11-25 14:58:43 +00:00
|
|
|
uint8_t header[] = {CMND_STK_PROG_PAGE, 0x00, 0x80, 0x46};
|
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
SHParse.FlashPageIdx = 0;
|
|
|
|
SHParse.layoverIdx = 0;
|
|
|
|
SHParse.ptr_l = 0;
|
|
|
|
SHParse.ptr_h = 0;
|
|
|
|
SHParse.firstrun = true;
|
|
|
|
SHParse.EndOfFile = false;
|
|
|
|
|
|
|
|
char flash_buffer[512];
|
|
|
|
char thishexline[50];
|
2020-06-18 15:05:55 +01:00
|
|
|
uint32_t processed = 0;
|
2020-11-25 12:07:04 +00:00
|
|
|
uint32_t position = 0;
|
|
|
|
uint32_t error = 0;
|
2020-11-25 13:58:18 +00:00
|
|
|
|
2020-11-25 14:58:43 +00:00
|
|
|
uint32_t read = 0;
|
2020-11-25 12:07:04 +00:00
|
|
|
while (read < size) {
|
|
|
|
memcpy(flash_buffer, data + read, sizeof(flash_buffer));
|
2020-11-25 13:58:18 +00:00
|
|
|
read = read + sizeof(flash_buffer);
|
2020-11-25 12:07:04 +00:00
|
|
|
|
2022-11-11 10:47:11 +00:00
|
|
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("TCL: Flash %32_H"), (uint8_t*)flash_buffer);
|
2020-11-25 12:07:04 +00:00
|
|
|
|
|
|
|
for (uint32_t ca = 0; ca < sizeof(flash_buffer); ca++) {
|
2020-06-18 15:05:55 +01:00
|
|
|
processed++;
|
2020-11-25 12:07:04 +00:00
|
|
|
if ((processed <= size) && (!SHParse.EndOfFile)) {
|
|
|
|
// flash_buffer = :100000000C945D000C9485000C9485000C94850084<0x0D><0x0A>
|
2020-06-18 15:05:55 +01:00
|
|
|
if (':' == flash_buffer[ca]) {
|
|
|
|
position = 0;
|
|
|
|
}
|
2020-11-25 12:07:04 +00:00
|
|
|
else if (0x0D == flash_buffer[ca]) {
|
|
|
|
// 100000000C945D000C9485000C9485000C94850084
|
2020-06-18 15:05:55 +01:00
|
|
|
thishexline[position] = 0;
|
2020-11-25 12:07:04 +00:00
|
|
|
error = SimpleHexParseLine(thishexline);
|
2020-11-25 13:58:18 +00:00
|
|
|
if (error) { break; } // Error 5 and 6
|
2020-11-25 12:07:04 +00:00
|
|
|
if (SHParse.FlashPageIdx == sizeof(SHParse.FlashPage)) {
|
2020-11-25 14:58:43 +00:00
|
|
|
uint8_t params[] = {SHParse.ptr_l, SHParse.ptr_h};
|
|
|
|
TasmotaClient_execParam(CMND_STK_LOAD_ADDRESS, params, sizeof(params));
|
|
|
|
|
|
|
|
TasmotaClient_Serial->write(header, sizeof(header));
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < sizeof(SHParse.FlashPage); i++) {
|
|
|
|
TasmotaClient_Serial->write(SHParse.FlashPage[i]);
|
|
|
|
}
|
|
|
|
TasmotaClient_Serial->write(CONST_STK_CRC_EOP);
|
|
|
|
|
|
|
|
TasmotaClient_waitForSerialData(2, TASMOTA_CLIENT_TIMEOUT);
|
|
|
|
TasmotaClient_Serial->read();
|
|
|
|
TasmotaClient_Serial->read();
|
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
SHParse.FlashPageIdx = 0;
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
2020-11-25 12:07:04 +00:00
|
|
|
}
|
|
|
|
else if (0x0A != flash_buffer[ca]) {
|
2020-11-25 13:58:18 +00:00
|
|
|
if (!isalnum(flash_buffer[ca])) {
|
2021-01-23 15:26:23 +00:00
|
|
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Size %d, Processed %d"), size, processed);
|
2020-11-25 13:58:18 +00:00
|
|
|
error = 7; // Error: Invalid data
|
|
|
|
break;
|
|
|
|
}
|
2020-11-25 12:07:04 +00:00
|
|
|
if (position < sizeof(thishexline) -2) {
|
|
|
|
thishexline[position++] = flash_buffer[ca];
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-25 12:07:04 +00:00
|
|
|
if (error) { break; }
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
2020-11-25 14:58:43 +00:00
|
|
|
TasmotaClient_execCmd(CMND_STK_LEAVE_PROGMODE);
|
2020-11-25 13:58:18 +00:00
|
|
|
|
|
|
|
return error; // Error or Flash done!
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void TasmotaClient_Init(void) {
|
|
|
|
if (TClient.type) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (10 > TClient.waitstate) {
|
|
|
|
TClient.waitstate++;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!TClient.SerialEnabled) {
|
|
|
|
if (PinUsed(GPIO_TASMOTACLIENT_RXD) && PinUsed(GPIO_TASMOTACLIENT_TXD) &&
|
|
|
|
(PinUsed(GPIO_TASMOTACLIENT_RST) || PinUsed(GPIO_TASMOTACLIENT_RST_INV))) {
|
|
|
|
TasmotaClient_Serial = new TasmotaSerial(Pin(GPIO_TASMOTACLIENT_RXD), Pin(GPIO_TASMOTACLIENT_TXD), 1, 0, 200);
|
|
|
|
if (TasmotaClient_Serial->begin(USE_TASMOTA_CLIENT_SERIAL_SPEED)) {
|
|
|
|
if (TasmotaClient_Serial->hardwareSerial()) {
|
|
|
|
ClaimSerial();
|
|
|
|
}
|
2022-11-11 15:10:39 +00:00
|
|
|
#ifdef ESP32
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TCL: Serial UART%d"), TasmotaClient_Serial->getUart());
|
|
|
|
#endif
|
2020-06-18 15:05:55 +01:00
|
|
|
if (PinUsed(GPIO_TASMOTACLIENT_RST_INV)) {
|
2020-06-20 16:58:21 +01:00
|
|
|
SetPin(Pin(GPIO_TASMOTACLIENT_RST_INV), AGPIO(GPIO_TASMOTACLIENT_RST));
|
2020-06-18 15:05:55 +01:00
|
|
|
TClient.inverted = HIGH;
|
|
|
|
}
|
|
|
|
pinMode(Pin(GPIO_TASMOTACLIENT_RST), OUTPUT);
|
|
|
|
TClient.SerialEnabled = true;
|
|
|
|
TasmotaClient_Reset();
|
2021-01-23 15:26:23 +00:00
|
|
|
AddLog(LOG_LEVEL_INFO, PSTR("TCL: Enabled"));
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (TClient.SerialEnabled) { // All go for hardware now we need to detect features if there are any
|
|
|
|
TasmotaClient_sendCmnd(CMND_FEATURES, 0);
|
|
|
|
|
2020-11-25 12:07:04 +00:00
|
|
|
char buffer[sizeof(TClientSettings)];
|
|
|
|
uint8_t len = TasmotaClient_receiveData(buffer, sizeof(buffer)); // 99 17 34 01 02 00 00 00
|
|
|
|
if (len == sizeof(TClientSettings)) {
|
|
|
|
memcpy(&TClientSettings, &buffer, sizeof(TClientSettings));
|
2021-06-13 09:43:10 +01:00
|
|
|
if (TASMOTA_CLIENT_LIB_VERSION == TClientSettings.features_version) {
|
2020-11-25 12:07:04 +00:00
|
|
|
TClient.type = true;
|
2021-06-13 09:43:10 +01:00
|
|
|
AddLog(LOG_LEVEL_INFO, PSTR("TCL: Version %u"), TClientSettings.features_version);
|
2020-11-25 12:07:04 +00:00
|
|
|
} else {
|
2021-06-13 09:43:10 +01:00
|
|
|
if ((!TClient.unsupported) && (TClientSettings.features_version > 0)) {
|
|
|
|
AddLog(LOG_LEVEL_INFO, PSTR("TCL: Version %u not supported!"), TClientSettings.features_version);
|
2020-11-25 12:07:04 +00:00
|
|
|
TClient.unsupported = true;
|
|
|
|
}
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-03 13:29:08 +01:00
|
|
|
bool TasmotaClient_Available(void) {
|
|
|
|
return TClient.SerialEnabled;
|
|
|
|
}
|
|
|
|
|
2020-06-18 15:05:55 +01:00
|
|
|
void TasmotaClient_Show(void) {
|
2021-06-13 09:43:10 +01:00
|
|
|
if ((TClient.type) && (TClientSettings.features.func_json_append)) {
|
2022-01-22 16:55:55 +00:00
|
|
|
TasmotaClient_sendCmnd(CMND_GET_JSON, 0);
|
2020-11-25 12:07:04 +00:00
|
|
|
|
2023-04-16 13:39:29 +01:00
|
|
|
char buffer[250]; // Keep size below 255 to stay within 8-bits index and len
|
2020-11-25 12:07:04 +00:00
|
|
|
uint8_t len = TasmotaClient_receiveData(buffer, sizeof(buffer) -1);
|
|
|
|
|
2024-11-24 15:07:36 +00:00
|
|
|
if (len) {
|
|
|
|
buffer[len] = '\0';
|
|
|
|
ResponseAppend_P(PSTR(",\"TasmotaClient\":%s"), buffer);
|
|
|
|
}
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TasmotaClient_sendCmnd(uint8_t cmnd, uint8_t param) {
|
|
|
|
TClientCommand.command = cmnd;
|
|
|
|
TClientCommand.parameter = param;
|
|
|
|
char buffer[sizeof(TClientCommand)+2];
|
|
|
|
buffer[0] = CMND_START;
|
|
|
|
memcpy(&buffer[1], &TClientCommand, sizeof(TClientCommand));
|
|
|
|
buffer[sizeof(TClientCommand)+1] = CMND_END;
|
|
|
|
|
2022-11-11 10:47:11 +00:00
|
|
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("TCL: SendCmnd %*_H"), sizeof(buffer), (uint8_t*)&buffer);
|
2020-06-18 15:05:55 +01:00
|
|
|
|
2020-11-25 14:58:43 +00:00
|
|
|
for (uint32_t ca = 0; ca < sizeof(buffer); ca++) {
|
2020-06-18 15:05:55 +01:00
|
|
|
TasmotaClient_Serial->write(buffer[ca]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define D_PRFX_CLIENT "Client"
|
|
|
|
#define D_CMND_CLIENT_RESET "Reset"
|
|
|
|
#define D_CMND_CLIENT_SEND "Send"
|
|
|
|
|
|
|
|
const char kTasmotaClientCommands[] PROGMEM = D_PRFX_CLIENT "|"
|
|
|
|
D_CMND_CLIENT_RESET "|" D_CMND_CLIENT_SEND;
|
|
|
|
|
|
|
|
void (* const TasmotaClientCommand[])(void) PROGMEM = {
|
|
|
|
&CmndClientReset, &CmndClientSend };
|
|
|
|
|
|
|
|
void CmndClientReset(void) {
|
|
|
|
TasmotaClient_Reset();
|
|
|
|
TClient.type = false; // Force redetection
|
|
|
|
TClient.waitstate = 7; // give it at least 3 seconds to restart from bootloader
|
|
|
|
TClient.unsupported = false; // Reset unsupported flag
|
|
|
|
ResponseCmndDone();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CmndClientSend(void) {
|
2020-06-22 21:52:25 +01:00
|
|
|
if (TClient.SerialEnabled) {
|
|
|
|
if (0 < XdrvMailbox.data_len) {
|
|
|
|
TasmotaClient_sendCmnd(CMND_CLIENT_SEND, XdrvMailbox.data_len);
|
|
|
|
TasmotaClient_Serial->write(char(PARAM_DATA_START));
|
2020-11-25 14:58:43 +00:00
|
|
|
for (uint32_t idx = 0; idx < XdrvMailbox.data_len; idx++) {
|
2020-06-22 21:52:25 +01:00
|
|
|
TasmotaClient_Serial->write(XdrvMailbox.data[idx]);
|
|
|
|
}
|
|
|
|
TasmotaClient_Serial->write(char(PARAM_DATA_END));
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
2020-06-22 21:52:25 +01:00
|
|
|
ResponseCmndDone();
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TasmotaClient_ProcessIn(void) {
|
|
|
|
uint8_t cmnd = TasmotaClient_Serial->read();
|
|
|
|
if (CMND_START == cmnd) {
|
2020-11-25 14:58:43 +00:00
|
|
|
TasmotaClient_waitForSerialData(sizeof(TClientCommand), 50);
|
2020-06-18 15:05:55 +01:00
|
|
|
uint8_t buffer[sizeof(TClientCommand)];
|
2020-11-25 14:58:43 +00:00
|
|
|
for (uint32_t idx = 0; idx < sizeof(TClientCommand); idx++) {
|
2020-06-18 15:05:55 +01:00
|
|
|
buffer[idx] = TasmotaClient_Serial->read();
|
|
|
|
}
|
|
|
|
TasmotaClient_Serial->read(); // read trailing byte of command
|
|
|
|
memcpy(&TClientCommand, &buffer, sizeof(TClientCommand));
|
|
|
|
char inbuf[TClientCommand.parameter+1];
|
|
|
|
TasmotaClient_waitForSerialData(TClientCommand.parameter, 50);
|
|
|
|
TasmotaClient_Serial->read(); // Read leading byte
|
2020-11-25 14:58:43 +00:00
|
|
|
for (uint32_t idx = 0; idx < TClientCommand.parameter; idx++) {
|
2020-06-18 15:05:55 +01:00
|
|
|
inbuf[idx] = TasmotaClient_Serial->read();
|
|
|
|
}
|
|
|
|
TasmotaClient_Serial->read(); // Read trailing byte
|
|
|
|
inbuf[TClientCommand.parameter] = '\0';
|
|
|
|
|
|
|
|
if (CMND_PUBLISH_TELE == TClientCommand.command) { // We need to publish stat/ with incoming stream as content
|
|
|
|
Response_P(PSTR("{\"TasmotaClient\":"));
|
|
|
|
ResponseAppend_P("%s", inbuf);
|
|
|
|
ResponseJsonEnd();
|
2020-11-25 14:58:43 +00:00
|
|
|
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR("TasmotaClient"));
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
if (CMND_EXECUTE_CMND == TClientCommand.command) { // We need to execute the incoming command
|
2020-11-25 14:58:43 +00:00
|
|
|
ExecuteCommand(inbuf, SRC_TCL);
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Interface
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2022-11-11 09:44:56 +00:00
|
|
|
bool Xdrv31(uint32_t function) {
|
2020-06-18 15:05:55 +01:00
|
|
|
bool result = false;
|
|
|
|
|
|
|
|
switch (function) {
|
|
|
|
case FUNC_EVERY_100_MSECOND:
|
|
|
|
if (TClient.type) {
|
|
|
|
if (TasmotaClient_Serial->available()) {
|
|
|
|
TasmotaClient_ProcessIn();
|
|
|
|
}
|
2021-06-13 09:43:10 +01:00
|
|
|
if (TClientSettings.features.func_every_100_msecond) {
|
2020-06-18 15:05:55 +01:00
|
|
|
TasmotaClient_sendCmnd(CMND_FUNC_EVERY_100_MSECOND, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FUNC_EVERY_SECOND:
|
2021-06-13 09:43:10 +01:00
|
|
|
if ((TClient.type) && (TClientSettings.features.func_every_second)) {
|
2020-06-18 15:05:55 +01:00
|
|
|
TasmotaClient_sendCmnd(CMND_FUNC_EVERY_SECOND, 0);
|
|
|
|
}
|
|
|
|
TasmotaClient_Init();
|
|
|
|
break;
|
|
|
|
case FUNC_JSON_APPEND:
|
2021-06-13 09:43:10 +01:00
|
|
|
if ((TClient.type) && (TClientSettings.features.func_json_append)) {
|
2020-06-18 15:05:55 +01:00
|
|
|
TasmotaClient_Show();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FUNC_COMMAND:
|
|
|
|
result = DecodeCommand(kTasmotaClientCommands, TasmotaClientCommand);
|
|
|
|
break;
|
2023-12-27 21:03:56 +00:00
|
|
|
case FUNC_ACTIVE:
|
|
|
|
result = true;
|
|
|
|
break;
|
2020-06-18 15:05:55 +01:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // USE_TASMOTA_CLIENT
|