mirror of
https://github.com/OpenEPaperLink/OpenEPaperLink.git
synced 2026-03-21 15:06:38 +01:00
1013 lines
37 KiB
C++
1013 lines
37 KiB
C++
#include "serialap.h"
|
|
|
|
#include <Arduino.h>
|
|
#include <HardwareSerial.h>
|
|
|
|
#include "commstructs.h"
|
|
#include "contentmanager.h"
|
|
#include "flasher.h"
|
|
#include "leds.h"
|
|
#include "newproto.h"
|
|
#include "powermgt.h"
|
|
#include "settings.h"
|
|
#include "storage.h"
|
|
#include "web.h"
|
|
#include "zbs_interface.h"
|
|
|
|
QueueHandle_t rxCmdQueue;
|
|
SemaphoreHandle_t txActive;
|
|
|
|
// If a command is sent, it will wait for a reply here
|
|
#define CMD_REPLY_WAIT 0x00
|
|
#define CMD_REPLY_ACK 0x01
|
|
#define CMD_REPLY_NOK 0x02
|
|
#define CMD_REPLY_NOQ 0x03
|
|
volatile uint8_t cmdReplyValue = CMD_REPLY_WAIT;
|
|
|
|
#define AP_SERIAL_PORT Serial1
|
|
volatile bool rxSerialStopTask2 = false;
|
|
|
|
uint8_t channelList[6];
|
|
struct espSetChannelPower curChannel = {0, 11, 10};
|
|
|
|
#define RX_CMD_RQB 0x01
|
|
#define RX_CMD_ADR 0x02
|
|
#define RX_CMD_XFC 0x03
|
|
#define RX_CMD_XTO 0x04
|
|
#define RX_CMD_RDY 0x05
|
|
#define RX_CMD_RSET 0x06
|
|
#define RX_CMD_TRD 0x07
|
|
|
|
#define AP_ACTIVITY_MAX_INTERVAL 30 * 1000
|
|
volatile uint32_t lastAPActivity = 0;
|
|
struct APInfoS apInfo;
|
|
|
|
struct rxCmd {
|
|
uint8_t* data;
|
|
uint8_t len;
|
|
uint8_t type;
|
|
};
|
|
|
|
#define ZBS_RX_WAIT_HEADER 0
|
|
#define ZBS_RX_WAIT_PKT_LEN 1
|
|
#define ZBS_RX_WAIT_PKT_RX 2
|
|
#define ZBS_RX_WAIT_SEP1 3
|
|
#define ZBS_RX_WAIT_SEP2 4
|
|
#define ZBS_RX_WAIT_VER 6
|
|
#define ZBS_RX_BLOCK_REQUEST 7
|
|
#define ZBS_RX_WAIT_XFERCOMPLETE 8
|
|
#define ZBS_RX_WAIT_DATA_REQ 9
|
|
#define ZBS_RX_WAIT_JOINNETWORK 10
|
|
#define ZBS_RX_WAIT_XFERTIMEOUT 11
|
|
#define ZBS_RX_WAIT_MAC 12
|
|
#define ZBS_RX_WAIT_CHANNEL 13
|
|
#define ZBS_RX_WAIT_POWER 14
|
|
#define ZBS_RX_WAIT_PENDING 15
|
|
#define ZBS_RX_WAIT_NOP 16
|
|
#define ZBS_RX_WAIT_TYPE 17
|
|
#define ZBS_RX_WAIT_TAG_RETURN_DATA 18
|
|
#define ZBS_RX_WAIT_SUBCHANNEL 19
|
|
|
|
bool txStart() {
|
|
while (1) {
|
|
if (xPortInIsrContext()) {
|
|
if (xSemaphoreTakeFromISR(txActive, NULL) == pdTRUE) return true;
|
|
} else {
|
|
if (xSemaphoreTake(txActive, portTICK_PERIOD_MS)) return true;
|
|
}
|
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|
Serial.println("wait... tx busy");
|
|
}
|
|
// this never happens. Should we make a timeout?
|
|
return false;
|
|
}
|
|
void txEnd() {
|
|
if (xPortInIsrContext()) {
|
|
xSemaphoreGiveFromISR(txActive, NULL);
|
|
} else {
|
|
xSemaphoreGive(txActive);
|
|
}
|
|
}
|
|
bool waitCmdReply() {
|
|
uint32_t val = millis();
|
|
while (millis() < val + 200) {
|
|
switch (cmdReplyValue) {
|
|
case CMD_REPLY_WAIT:
|
|
break;
|
|
case CMD_REPLY_ACK:
|
|
lastAPActivity = millis();
|
|
if (apInfo.isOnline == false)
|
|
setAPstate(true, AP_STATE_ONLINE);
|
|
return true;
|
|
break;
|
|
case CMD_REPLY_NOK:
|
|
lastAPActivity = millis();
|
|
return false;
|
|
break;
|
|
case CMD_REPLY_NOQ:
|
|
lastAPActivity = millis();
|
|
return false;
|
|
break;
|
|
}
|
|
vTaskDelay(1 / portTICK_RATE_MS);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if (AP_PROCESS_PORT == FLASHER_AP_PORT)
|
|
int8_t APpowerPins[] = FLASHER_AP_POWER;
|
|
#define AP_RESET_PIN FLASHER_AP_RESET
|
|
#define AP_POWER_PIN FLASHER_AP_POWER
|
|
#endif
|
|
#ifdef HAS_EXT_FLASHER
|
|
#if (AP_PROCESS_PORT == FLASHER_EXT_PORT)
|
|
int8_t APpowerPins[] = FLASHER_EXT_POWER;
|
|
#define AP_RESET_PIN FLASHER_EXT_RESET
|
|
#define AP_POWER_PIN FLASHER_EXT_POWER
|
|
#endif
|
|
#if (AP_PROCESS_PORT == FLASHER_ALTRADIO_PORT)
|
|
int8_t APpowerPins[] = FLASHER_ALT_POWER;
|
|
#define AP_RESET_PIN FLASHER_ALT_RESET
|
|
#define AP_POWER_PIN FLASHER_ALT_POWER
|
|
#endif
|
|
#endif
|
|
|
|
void APEnterEarlyReset() {
|
|
pinMode(AP_RESET_PIN, OUTPUT);
|
|
digitalWrite(AP_RESET_PIN, LOW);
|
|
}
|
|
|
|
void setAPstate(bool isOnline, uint8_t state) {
|
|
apInfo.isOnline = isOnline;
|
|
apInfo.state = state;
|
|
#ifdef HAS_RGB_LED
|
|
CRGB colorMap[7] = {
|
|
CRGB::Orange,
|
|
CRGB::Green,
|
|
CRGB::Blue,
|
|
CRGB::Yellow,
|
|
CRGB::Aqua,
|
|
CRGB::Red,
|
|
CRGB::YellowGreen};
|
|
rgbIdleColor = colorMap[state];
|
|
rgbIdlePeriod = (isOnline ? 767 : 255);
|
|
if (isOnline) rgbIdle();
|
|
#endif
|
|
}
|
|
|
|
// Reset the tag
|
|
void APTagReset() {
|
|
Serial.println("Resetting tag");
|
|
uint8_t powerPins = sizeof(APpowerPins);
|
|
if (powerPins > 0 && APpowerPins[0] == -1)
|
|
powerPins = 0;
|
|
|
|
#ifdef FLASHER_DEBUG_PROG
|
|
pinMode(FLASHER_DEBUG_PROG, OUTPUT);
|
|
digitalWrite(FLASHER_DEBUG_PROG, HIGH);
|
|
#endif
|
|
pinMode(AP_RESET_PIN, OUTPUT);
|
|
digitalWrite(AP_RESET_PIN, LOW);
|
|
vTaskDelay(50 / portTICK_PERIOD_MS);
|
|
powerControl(false, (uint8_t*)APpowerPins, powerPins);
|
|
vTaskDelay(300 / portTICK_PERIOD_MS);
|
|
powerControl(true, (uint8_t*)APpowerPins, powerPins);
|
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
|
digitalWrite(AP_RESET_PIN, HIGH);
|
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
|
}
|
|
|
|
// Send data to the AP
|
|
uint16_t sendBlock(const void* data, const uint16_t len) {
|
|
time_t timeCanary = millis();
|
|
if (!apInfo.isOnline) return false;
|
|
if (!txStart()) return 0;
|
|
// don't retry now, as it collides with communication from the tag
|
|
for (uint8_t attempt = 0; attempt < 1; attempt++) {
|
|
cmdReplyValue = CMD_REPLY_WAIT;
|
|
AP_SERIAL_PORT.print(">D>");
|
|
if (waitCmdReply()) goto blksend;
|
|
Serial.printf("block send failed in try %d\n", attempt);
|
|
}
|
|
Serial.print("Failed sending block...\n");
|
|
txEnd();
|
|
return 0;
|
|
blksend:
|
|
uint8_t blockbuffer[sizeof(struct blockData)];
|
|
struct blockData* bd = (struct blockData*)blockbuffer;
|
|
bd->size = len;
|
|
bd->checksum = 0;
|
|
|
|
// calculate checksum
|
|
const uint8_t* dataBytes = reinterpret_cast<const uint8_t*>(data);
|
|
for (uint16_t c = 0; c < len; c++) {
|
|
bd->checksum += dataBytes[c];
|
|
}
|
|
|
|
// send blockData header
|
|
dataBytes = reinterpret_cast<const uint8_t*>(&blockbuffer);
|
|
const size_t bufferSize = sizeof(struct blockData);
|
|
uint8_t* modifiedHeader = static_cast<uint8_t*>(malloc(bufferSize));
|
|
if (modifiedHeader != nullptr) {
|
|
for (size_t i = 0; i < bufferSize; i++) {
|
|
modifiedHeader[i] = 0xAA ^ dataBytes[i];
|
|
}
|
|
AP_SERIAL_PORT.write(modifiedHeader, bufferSize);
|
|
free(modifiedHeader);
|
|
}
|
|
|
|
// send an entire block of data
|
|
uint16_t c;
|
|
dataBytes = reinterpret_cast<const uint8_t*>(data);
|
|
uint8_t* modifiedBuffer = static_cast<uint8_t*>(malloc(len));
|
|
if (modifiedBuffer != nullptr) {
|
|
for (c = 0; c < len; c++) {
|
|
modifiedBuffer[c] = 0xAA ^ dataBytes[c];
|
|
}
|
|
AP_SERIAL_PORT.write(modifiedBuffer, len);
|
|
free(modifiedBuffer);
|
|
}
|
|
|
|
// fill the rest of the block-length filled with something else (will end up as 0xFF in the buffer)
|
|
const size_t remainingBytes = BLOCK_DATA_SIZE - c;
|
|
if (remainingBytes > 0) {
|
|
uint8_t fillBuffer[remainingBytes];
|
|
memset(fillBuffer, 0x55, remainingBytes);
|
|
AP_SERIAL_PORT.write(fillBuffer, remainingBytes);
|
|
}
|
|
|
|
// dummy bytes in case some bytes were missed, makes sure the AP gets kicked out of data-loading mode
|
|
uint8_t dummyBuffer[32];
|
|
memset(dummyBuffer, 0xF5, 32);
|
|
AP_SERIAL_PORT.write(dummyBuffer, 32);
|
|
|
|
if (apInfo.type != ESP32_C6) delay(10);
|
|
txEnd();
|
|
Serial.println("Sendblock complete, " + String(millis() - timeCanary) + "ms");
|
|
return bd->checksum;
|
|
}
|
|
|
|
bool sendDataAvail(struct pendingData* pending) {
|
|
if (!apInfo.isOnline) return false;
|
|
if (!txStart()) return false;
|
|
addCRC(pending, sizeof(struct pendingData));
|
|
for (uint8_t attempt = 0; attempt < 5; attempt++) {
|
|
cmdReplyValue = CMD_REPLY_WAIT;
|
|
AP_SERIAL_PORT.print("SDA>");
|
|
for (uint8_t c = 0; c < sizeof(struct pendingData); c++) {
|
|
AP_SERIAL_PORT.write(((uint8_t*)pending)[c]);
|
|
}
|
|
if (waitCmdReply()) {
|
|
txEnd();
|
|
return true;
|
|
}
|
|
Serial.printf("SDA send failed in try %d\n", attempt);
|
|
delay(200);
|
|
}
|
|
Serial.print("SDA failed to send...\n");
|
|
txEnd();
|
|
return false;
|
|
}
|
|
bool sendCancelPending(struct pendingData* pending) {
|
|
if (!apInfo.isOnline) return false;
|
|
if (!txStart()) return false;
|
|
addCRC(pending, sizeof(struct pendingData));
|
|
for (uint8_t attempt = 0; attempt < 5; attempt++) {
|
|
cmdReplyValue = CMD_REPLY_WAIT;
|
|
AP_SERIAL_PORT.print("CXD>");
|
|
for (uint8_t c = 0; c < sizeof(struct pendingData); c++) {
|
|
AP_SERIAL_PORT.write(((uint8_t*)pending)[c]);
|
|
}
|
|
if (waitCmdReply()) {
|
|
txEnd();
|
|
return true;
|
|
}
|
|
Serial.printf("CXD send failed in try %d\n", attempt);
|
|
}
|
|
Serial.print("CXD failed to send...\n");
|
|
txEnd();
|
|
return false;
|
|
}
|
|
bool sendChannelPower(struct espSetChannelPower* scp) {
|
|
if ((apInfo.state != AP_STATE_ONLINE) && (apInfo.state != AP_STATE_COMING_ONLINE)) return false;
|
|
if (!txStart()) return false;
|
|
addCRC(scp, sizeof(struct espSetChannelPower));
|
|
for (uint8_t attempt = 0; attempt < 5; attempt++) {
|
|
cmdReplyValue = CMD_REPLY_WAIT;
|
|
AP_SERIAL_PORT.print("SCP>");
|
|
for (uint8_t c = 0; c < sizeof(struct espSetChannelPower); c++) {
|
|
AP_SERIAL_PORT.write(((uint8_t*)scp)[c]);
|
|
}
|
|
if (waitCmdReply()) {
|
|
txEnd();
|
|
apInfo.channel = scp->channel;
|
|
apInfo.power = scp->power;
|
|
return true;
|
|
}
|
|
Serial.printf("SCP send failed in try %d\n", attempt);
|
|
}
|
|
Serial.print("SCP failed to send...\n");
|
|
txEnd();
|
|
return false;
|
|
}
|
|
bool sendPing() {
|
|
if (apInfo.state == AP_STATE_FLASHING) return false;
|
|
Serial.print("ping");
|
|
int t = millis();
|
|
if (!txStart()) return false;
|
|
for (uint8_t attempt = 0; attempt < 3; attempt++) {
|
|
cmdReplyValue = CMD_REPLY_WAIT;
|
|
AP_SERIAL_PORT.print("RDY?");
|
|
if (waitCmdReply()) {
|
|
txEnd();
|
|
Serial.printf(" ok, %dms\n", millis() - t);
|
|
return true;
|
|
}
|
|
}
|
|
txEnd();
|
|
Serial.println(" failed");
|
|
return false;
|
|
}
|
|
bool sendGetInfo() {
|
|
if (!txStart()) return false;
|
|
for (uint8_t attempt = 0; attempt < 5; attempt++) {
|
|
cmdReplyValue = CMD_REPLY_WAIT;
|
|
AP_SERIAL_PORT.print("NFO?");
|
|
if (waitCmdReply()) {
|
|
txEnd();
|
|
return true;
|
|
}
|
|
}
|
|
txEnd();
|
|
return false;
|
|
}
|
|
bool sendHighspeed() {
|
|
if (!txStart()) return false;
|
|
for (uint8_t attempt = 0; attempt < 5; attempt++) {
|
|
cmdReplyValue = CMD_REPLY_WAIT;
|
|
AP_SERIAL_PORT.print("HSPD");
|
|
if (waitCmdReply()) {
|
|
txEnd();
|
|
return true;
|
|
}
|
|
}
|
|
txEnd();
|
|
return false;
|
|
}
|
|
|
|
// add RX'd request from the AP to the processor queue
|
|
void addRXQueue(uint8_t* data, uint8_t len, uint8_t type) {
|
|
struct rxCmd* rxcmd = new struct rxCmd;
|
|
rxcmd->data = data;
|
|
rxcmd->len = len;
|
|
rxcmd->type = type;
|
|
BaseType_t queuestatus = xQueueSend(rxCmdQueue, &rxcmd, 0);
|
|
if (queuestatus == pdFALSE) {
|
|
if (data) free(data);
|
|
free(rxcmd);
|
|
}
|
|
}
|
|
|
|
// Asynchronous command processor
|
|
void rxCmdProcessor(void* parameter) {
|
|
rxCmdQueue = xQueueCreate(30, sizeof(struct rxCmd*));
|
|
txActive = xSemaphoreCreateBinary();
|
|
xSemaphoreGive(txActive);
|
|
while (1) {
|
|
struct rxCmd* rxcmd = nullptr;
|
|
BaseType_t q = xQueueReceive(rxCmdQueue, &rxcmd, 10);
|
|
if (q == pdTRUE) {
|
|
switch (rxcmd->type) {
|
|
case RX_CMD_RQB:
|
|
processBlockRequest((struct espBlockRequest*)rxcmd->data);
|
|
#ifdef HAS_RGB_LED
|
|
// shortBlink(CRGB::Blue);
|
|
#endif
|
|
quickBlink(3);
|
|
break;
|
|
case RX_CMD_ADR:
|
|
processDataReq((struct espAvailDataReq*)rxcmd->data, true);
|
|
#ifdef HAS_RGB_LED
|
|
// shortBlink(CRGB::Aqua);
|
|
#endif
|
|
quickBlink(1);
|
|
break;
|
|
case RX_CMD_XFC:
|
|
processXferComplete((struct espXferComplete*)rxcmd->data, true);
|
|
#ifdef HAS_RGB_LED
|
|
// shortBlink(CRGB::Purple);
|
|
#endif
|
|
break;
|
|
case RX_CMD_XTO:
|
|
processXferTimeout((struct espXferComplete*)rxcmd->data, true);
|
|
break;
|
|
case RX_CMD_RSET:
|
|
Serial.println("AP did reset, resending pending\n");
|
|
refreshAllPending();
|
|
sendChannelPower(&curChannel);
|
|
break;
|
|
case RX_CMD_TRD:
|
|
// received tag return data
|
|
processTagReturnData((struct espTagReturnData*)rxcmd->data, rxcmd->len, true);
|
|
break;
|
|
}
|
|
if (rxcmd->data) free(rxcmd->data);
|
|
if (rxcmd) free(rxcmd);
|
|
}
|
|
}
|
|
}
|
|
void rxSerialTask(void* parameter) {
|
|
static char cmdbuffer[4] = {0};
|
|
static uint8_t* packetp = nullptr;
|
|
// static uint8_t pktlen = 0;
|
|
static uint8_t pktindex = 0; // length of the command
|
|
static uint8_t RXState = ZBS_RX_WAIT_HEADER;
|
|
static char lastchar = 0;
|
|
static uint8_t charindex = 0;
|
|
|
|
while (1) {
|
|
while (AP_SERIAL_PORT.available()) {
|
|
lastchar = AP_SERIAL_PORT.read();
|
|
switch (RXState) {
|
|
case ZBS_RX_WAIT_HEADER:
|
|
|
|
Serial.write(lastchar);
|
|
|
|
// shift characters in
|
|
for (uint8_t c = 0; c < 3; c++) {
|
|
cmdbuffer[c] = cmdbuffer[c + 1];
|
|
}
|
|
cmdbuffer[3] = lastchar;
|
|
|
|
if ((strncmp(cmdbuffer, "ACK>", 4) == 0)) cmdReplyValue = CMD_REPLY_ACK;
|
|
if ((strncmp(cmdbuffer, "NOK>", 4) == 0)) cmdReplyValue = CMD_REPLY_NOK;
|
|
if ((strncmp(cmdbuffer, "NOQ>", 4) == 0)) cmdReplyValue = CMD_REPLY_NOQ;
|
|
|
|
if ((strncmp(cmdbuffer, "VER>", 4) == 0)) {
|
|
pktindex = 0;
|
|
RXState = ZBS_RX_WAIT_VER;
|
|
charindex = 0;
|
|
memset(cmdbuffer, 0x00, 4);
|
|
}
|
|
if ((strncmp(cmdbuffer, "MAC>", 4) == 0)) {
|
|
RXState = ZBS_RX_WAIT_MAC;
|
|
charindex = 0;
|
|
memset(cmdbuffer, 0x00, 4);
|
|
}
|
|
if ((strncmp(cmdbuffer, "ZCH>", 4) == 0)) {
|
|
RXState = ZBS_RX_WAIT_CHANNEL;
|
|
charindex = 0;
|
|
memset(cmdbuffer, 0x00, 4);
|
|
}
|
|
#ifdef HAS_SUBGHZ
|
|
if ((strncmp(cmdbuffer, "SCH>", 4) == 0)) {
|
|
RXState = ZBS_RX_WAIT_SUBCHANNEL;
|
|
charindex = 0;
|
|
memset(cmdbuffer, 0x00, 4);
|
|
}
|
|
#endif
|
|
if ((strncmp(cmdbuffer, "ZPW>", 4) == 0)) {
|
|
RXState = ZBS_RX_WAIT_POWER;
|
|
charindex = 0;
|
|
memset(cmdbuffer, 0x00, 4);
|
|
}
|
|
if ((strncmp(cmdbuffer, "PEN>", 4) == 0)) {
|
|
RXState = ZBS_RX_WAIT_PENDING;
|
|
charindex = 0;
|
|
memset(cmdbuffer, 0x00, 4);
|
|
}
|
|
if ((strncmp(cmdbuffer, "NOP>", 4) == 0)) {
|
|
RXState = ZBS_RX_WAIT_NOP;
|
|
charindex = 0;
|
|
memset(cmdbuffer, 0x00, 4);
|
|
}
|
|
if ((strncmp(cmdbuffer, "TYP>", 4) == 0)) {
|
|
RXState = ZBS_RX_WAIT_TYPE;
|
|
charindex = 0;
|
|
memset(cmdbuffer, 0x00, 4);
|
|
}
|
|
if (strncmp(cmdbuffer, "RES>", 4) == 0) {
|
|
addRXQueue(NULL, 0, RX_CMD_RSET);
|
|
}
|
|
if (strncmp(cmdbuffer, "RQB>", 4) == 0) {
|
|
RXState = ZBS_RX_BLOCK_REQUEST;
|
|
charindex = 0;
|
|
pktindex = 0;
|
|
packetp = (uint8_t*)calloc(sizeof(struct espBlockRequest) + 8, 1);
|
|
memset(cmdbuffer, 0x00, 4);
|
|
lastAPActivity = millis();
|
|
if (apInfo.isOnline == false)
|
|
setAPstate(true, AP_STATE_ONLINE);
|
|
}
|
|
if (strncmp(cmdbuffer, "ADR>", 4) == 0) {
|
|
RXState = ZBS_RX_WAIT_DATA_REQ;
|
|
charindex = 0;
|
|
pktindex = 0;
|
|
packetp = (uint8_t*)calloc(sizeof(struct espAvailDataReq) + 8, 1);
|
|
memset(cmdbuffer, 0x00, 4);
|
|
lastAPActivity = millis();
|
|
if (apInfo.isOnline == false)
|
|
setAPstate(true, AP_STATE_ONLINE);
|
|
}
|
|
if (strncmp(cmdbuffer, "XFC>", 4) == 0) {
|
|
RXState = ZBS_RX_WAIT_XFERCOMPLETE;
|
|
pktindex = 0;
|
|
packetp = (uint8_t*)calloc(sizeof(struct espXferComplete) + 8, 1);
|
|
memset(cmdbuffer, 0x00, 4);
|
|
}
|
|
if (strncmp(cmdbuffer, "XTO>", 4) == 0) {
|
|
RXState = ZBS_RX_WAIT_XFERTIMEOUT;
|
|
pktindex = 0;
|
|
packetp = (uint8_t*)calloc(sizeof(struct espXferComplete) + 8, 1);
|
|
memset(cmdbuffer, 0x00, 4);
|
|
}
|
|
if (strncmp(cmdbuffer, "RDY>", 4) == 0) {
|
|
addRXQueue(NULL, 0, RX_CMD_RDY);
|
|
}
|
|
if (strncmp(cmdbuffer, "TRD>", 4) == 0) {
|
|
RXState = ZBS_RX_WAIT_TAG_RETURN_DATA;
|
|
pktindex = 0;
|
|
packetp = (uint8_t*)calloc(sizeof(struct espTagReturnData) + 8, 1);
|
|
memset(cmdbuffer, 0x00, 4);
|
|
lastAPActivity = millis();
|
|
if (apInfo.isOnline == false)
|
|
setAPstate(true, AP_STATE_ONLINE);
|
|
}
|
|
break;
|
|
case ZBS_RX_BLOCK_REQUEST:
|
|
packetp[pktindex] = lastchar;
|
|
pktindex++;
|
|
if (pktindex == sizeof(struct espBlockRequest)) {
|
|
addRXQueue(packetp, pktindex, RX_CMD_RQB);
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
}
|
|
break;
|
|
case ZBS_RX_WAIT_XFERCOMPLETE:
|
|
packetp[pktindex] = lastchar;
|
|
pktindex++;
|
|
if (pktindex == sizeof(struct espXferComplete)) {
|
|
addRXQueue(packetp, pktindex, RX_CMD_XFC);
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
}
|
|
break;
|
|
case ZBS_RX_WAIT_XFERTIMEOUT:
|
|
packetp[pktindex] = lastchar;
|
|
pktindex++;
|
|
if (pktindex == sizeof(struct espXferComplete)) {
|
|
addRXQueue(packetp, pktindex, RX_CMD_XTO);
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
}
|
|
break;
|
|
case ZBS_RX_WAIT_DATA_REQ:
|
|
packetp[pktindex] = lastchar;
|
|
pktindex++;
|
|
if (pktindex == sizeof(struct espAvailDataReq)) {
|
|
addRXQueue(packetp, pktindex, RX_CMD_ADR);
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
}
|
|
break;
|
|
case ZBS_RX_WAIT_TAG_RETURN_DATA: {
|
|
packetp[pktindex] = lastchar;
|
|
pktindex++;
|
|
if ((pktindex > 10) && (pktindex >= (packetp[9] + 10))) {
|
|
addRXQueue(packetp, pktindex, RX_CMD_TRD);
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
}
|
|
} break;
|
|
case ZBS_RX_WAIT_VER:
|
|
cmdbuffer[charindex] = lastchar;
|
|
charindex++;
|
|
if (charindex == 4) {
|
|
charindex = 0;
|
|
apInfo.version = (uint16_t)strtoul(cmdbuffer, NULL, 16);
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
}
|
|
break;
|
|
case ZBS_RX_WAIT_MAC:
|
|
cmdbuffer[charindex] = lastchar;
|
|
charindex++;
|
|
if (charindex == 2) {
|
|
charindex = 0;
|
|
apInfo.mac[pktindex] = (uint8_t)strtoul(cmdbuffer, NULL, 16);
|
|
pktindex++;
|
|
}
|
|
if (pktindex == 8) {
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
}
|
|
break;
|
|
case ZBS_RX_WAIT_CHANNEL:
|
|
cmdbuffer[charindex] = lastchar;
|
|
charindex++;
|
|
if (charindex == 2) {
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
apInfo.channel = (uint8_t)strtoul(cmdbuffer, NULL, 16);
|
|
}
|
|
break;
|
|
#ifdef HAS_SUBGHZ
|
|
case ZBS_RX_WAIT_SUBCHANNEL:
|
|
cmdbuffer[charindex] = lastchar;
|
|
charindex++;
|
|
if(charindex == 3) {
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
int Channel = atoi(cmdbuffer);
|
|
if(Channel != NO_SUBGHZ_CHANNEL) {
|
|
apInfo.hasSubGhz = true;
|
|
apInfo.SubGhzChannel = Channel;
|
|
}
|
|
else {
|
|
apInfo.hasSubGhz = false;
|
|
apInfo.SubGhzChannel = 0;
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
case ZBS_RX_WAIT_POWER:
|
|
cmdbuffer[charindex] = lastchar;
|
|
charindex++;
|
|
if (charindex == 2) {
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
apInfo.power = (uint8_t)strtoul(cmdbuffer, NULL, 16);
|
|
}
|
|
break;
|
|
case ZBS_RX_WAIT_PENDING:
|
|
cmdbuffer[charindex] = lastchar;
|
|
charindex++;
|
|
if (charindex == 2) {
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
apInfo.pendingBuffer = (uint8_t)strtoul(cmdbuffer, NULL, 16);
|
|
}
|
|
break;
|
|
case ZBS_RX_WAIT_NOP:
|
|
cmdbuffer[charindex] = lastchar;
|
|
charindex++;
|
|
if (charindex == 2) {
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
apInfo.nop = (uint8_t)strtoul(cmdbuffer, NULL, 16);
|
|
}
|
|
break;
|
|
case ZBS_RX_WAIT_TYPE:
|
|
cmdbuffer[charindex] = lastchar;
|
|
charindex++;
|
|
if (charindex == 2) {
|
|
RXState = ZBS_RX_WAIT_HEADER;
|
|
apInfo.type = (uint8_t)strtoul(cmdbuffer, NULL, 16);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
vTaskDelay(1 / portTICK_PERIOD_MS);
|
|
} // end of while(1)
|
|
}
|
|
|
|
#ifdef FLASHER_DEBUG_RXD
|
|
void rxSerialTask2(void* parameter) {
|
|
char lastchar = 0;
|
|
time_t startTime = millis();
|
|
int charCount = 0;
|
|
Serial2.begin(115200, SERIAL_8N1, FLASHER_DEBUG_TXD, FLASHER_DEBUG_RXD);
|
|
while (rxSerialStopTask2 == false) {
|
|
while (Serial2.available()) {
|
|
lastchar = Serial2.read();
|
|
charCount++;
|
|
|
|
// debug info
|
|
Serial.write(lastchar);
|
|
}
|
|
vTaskDelay(1 / portTICK_PERIOD_MS);
|
|
|
|
time_t currentTime = millis();
|
|
if (currentTime - startTime >= 1000) {
|
|
if (charCount > 6000) {
|
|
rxSerialStopTask2 = true;
|
|
Serial.println("Serial monitor stopped because of flooding (" + String(charCount) + " characters per second");
|
|
}
|
|
startTime = currentTime;
|
|
charCount = 0;
|
|
}
|
|
}
|
|
Serial2.end();
|
|
Serial.println("Exiting AP serial monitor");
|
|
vTaskDelete(NULL);
|
|
}
|
|
#endif
|
|
|
|
void ShowAPInfo() {
|
|
Serial.printf("\n| AP Info - type %02X |\n", apInfo.type);
|
|
Serial.printf("| Ch | 0x%02X |\n", apInfo.channel);
|
|
Serial.printf("| Power| %02X |\n", apInfo.power);
|
|
Serial.printf("| MAC | %02X%02X%02X%02X%02X%02X%02X%02X |\n", apInfo.mac[7], apInfo.mac[6], apInfo.mac[5], apInfo.mac[4], apInfo.mac[3], apInfo.mac[2], apInfo.mac[1], apInfo.mac[0]);
|
|
Serial.printf("| Ver | 0x%04X |\n", apInfo.version);
|
|
}
|
|
|
|
void notifySegmentedFlash() {
|
|
sendAPSegmentedData(apInfo.mac, (String) "Fl ash", 0x0800, false, true);
|
|
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
|
#ifdef POWER_NO_SOFT_POWER
|
|
sendAPSegmentedData(apInfo.mac, (String) "If done", 0x0800, false, true);
|
|
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
|
sendAPSegmentedData(apInfo.mac, (String) "RE boot", 0x0800, false, true);
|
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
|
#endif
|
|
}
|
|
void checkWaitPowerCycle() {
|
|
// check if we should wait for a power cycle. If we do, try to inform the user the best we can, and hang.
|
|
#ifdef POWER_NO_SOFT_POWER
|
|
setAPstate(false, AP_STATE_REQUIRED_POWER_CYCLE);
|
|
// If we have no soft power control, we'll now wait until the device is power-cycled
|
|
Serial.printf("Please power-cycle your AP/device\n");
|
|
#ifdef HAS_RGB_LED
|
|
showColorPattern(CRGB::Aqua, CRGB::Aqua, CRGB::Red);
|
|
#endif
|
|
while (1) {
|
|
vTaskDelay(3000 / portTICK_PERIOD_MS);
|
|
}
|
|
#endif
|
|
}
|
|
void segmentedShowIp() {
|
|
IPAddress IP = WiFi.localIP();
|
|
char temp[12];
|
|
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
|
sendAPSegmentedData(apInfo.mac, (String) "IP Addr", 0x0200, true, true);
|
|
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
|
sprintf(temp, "%03d IP %03d", IP[0], IP[1]);
|
|
sendAPSegmentedData(apInfo.mac, (String)temp, 0x0200, true, true);
|
|
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
|
sprintf(temp, "%03d IP %03d", IP[2], IP[3]);
|
|
sendAPSegmentedData(apInfo.mac, (String)temp, 0x0200, true, true);
|
|
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
|
}
|
|
|
|
bool bringAPOnline() {
|
|
if (apInfo.state == AP_STATE_FLASHING) return false;
|
|
setAPstate(false, AP_STATE_OFFLINE);
|
|
// try without rebooting
|
|
AP_SERIAL_PORT.updateBaudRate(115200);
|
|
uint32_t bootTimeout = millis();
|
|
bool APrdy = sendPing();
|
|
if (!APrdy) {
|
|
if (apInfo.state == AP_STATE_FLASHING) return false;
|
|
APTagReset();
|
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
|
bootTimeout = millis();
|
|
APrdy = false;
|
|
while ((!APrdy) && (millis() - bootTimeout < 10 * 1000) && (apInfo.state != AP_STATE_FLASHING)) {
|
|
APrdy = sendPing();
|
|
vTaskDelay(300 / portTICK_PERIOD_MS);
|
|
}
|
|
}
|
|
if (!APrdy) {
|
|
return false;
|
|
} else {
|
|
setAPstate(false, AP_STATE_COMING_ONLINE);
|
|
sendChannelPower(&curChannel);
|
|
vTaskDelay(200 / portTICK_PERIOD_MS);
|
|
if (!sendGetInfo()) {
|
|
setAPstate(false, AP_STATE_OFFLINE);
|
|
return false;
|
|
}
|
|
if (apInfo.type == ESP32_C6) {
|
|
if (sendHighspeed()) {
|
|
AP_SERIAL_PORT.flush();
|
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|
AP_SERIAL_PORT.updateBaudRate(2000000);
|
|
Serial.println("switched to 2000000 baud");
|
|
}
|
|
}
|
|
|
|
vTaskDelay(200 / portTICK_PERIOD_MS);
|
|
setAPstate(true, AP_STATE_ONLINE);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool checkRadio() {
|
|
#ifndef C6_OTA_FLASHING
|
|
return true;
|
|
#endif
|
|
// make a short between FLASHER_AP_TXD and FLASHER_AP_RXD to indicate that no radio is present
|
|
// e.g. for flasher only, or just to use the S3 to generate images for smaller AP's
|
|
pinMode(FLASHER_AP_TXD, OUTPUT);
|
|
pinMode(FLASHER_AP_RXD, INPUT_PULLDOWN);
|
|
digitalWrite(FLASHER_AP_TXD, LOW);
|
|
if (digitalRead(FLASHER_AP_RXD) != LOW) return true;
|
|
digitalWrite(FLASHER_AP_TXD, HIGH);
|
|
if (digitalRead(FLASHER_AP_RXD) != HIGH) return true;
|
|
pinMode(FLASHER_AP_TXD, INPUT_PULLDOWN);
|
|
return false;
|
|
}
|
|
|
|
void APTask(void* parameter) {
|
|
if (!checkRadio()) {
|
|
// no radio
|
|
Serial.println("Working without radio.");
|
|
addFadeMono(config.led);
|
|
setAPstate(true, AP_STATE_NORADIO);
|
|
refreshAllPending();
|
|
vTaskDelete(NULL);
|
|
return;
|
|
}
|
|
|
|
#if (AP_PROCESS_PORT == FLASHER_AP_PORT)
|
|
AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD);
|
|
#endif
|
|
#ifdef HAS_EXT_FLASHER
|
|
#if (AP_PROCESS_PORT == FLASHER_EXT_PORT)
|
|
AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_EXT_RXD, FLASHER_EXT_TXD);
|
|
#endif
|
|
#if (AP_PROCESS_PORT == FLASHER_ALTRADIO_PORT)
|
|
AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD);
|
|
#endif
|
|
#endif
|
|
|
|
xTaskCreate(rxCmdProcessor, "rxCmdProcessor", 6000, NULL, 15, NULL);
|
|
xTaskCreate(rxSerialTask, "rxSerialTask", 1750, NULL, 11, NULL);
|
|
#ifdef FLASHER_DEBUG_RXD
|
|
xTaskCreate(rxSerialTask2, "rxSerialTask2", 1750, NULL, 2, NULL);
|
|
#endif
|
|
|
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
|
bringAPOnline();
|
|
|
|
#ifndef C6_OTA_FLASHING
|
|
if (checkForcedAPFlash() && FLASHER_AP_MOSI != -1) {
|
|
if (apInfo.type == SOLUM_SEG_UK && apInfo.isOnline) {
|
|
notifySegmentedFlash();
|
|
}
|
|
Serial.printf("We're going to try to perform an 'AP forced flash' in\n");
|
|
flashCountDown(10);
|
|
Serial.printf("\nPerforming force flash of the AP\n");
|
|
setAPstate(false, AP_STATE_FLASHING);
|
|
doForcedAPFlash();
|
|
checkWaitPowerCycle();
|
|
bringAPOnline();
|
|
}
|
|
#endif
|
|
|
|
if (apInfo.isOnline) {
|
|
// AP works!
|
|
ShowAPInfo();
|
|
|
|
if (apInfo.type == SOLUM_SEG_UK) {
|
|
setAPstate(true, AP_STATE_COMING_ONLINE);
|
|
segmentedShowIp();
|
|
showAPSegmentedInfo(apInfo.mac, true);
|
|
setAPstate(true, AP_STATE_ONLINE);
|
|
updateContent(apInfo.mac);
|
|
}
|
|
|
|
uint16_t fsversion;
|
|
#ifndef C6_OTA_FLASHING
|
|
if (FLASHER_AP_MOSI != -1) {
|
|
fsversion = getAPUpdateVersion(apInfo.type);
|
|
if ((fsversion) && (apInfo.version != fsversion)) {
|
|
Serial.printf("Firmware version on LittleFS: %04X\n", fsversion);
|
|
|
|
Serial.printf("We're going to try to update the AP's FW in\n");
|
|
flashCountDown(30);
|
|
Serial.printf("\n");
|
|
notifySegmentedFlash();
|
|
setAPstate(false, AP_STATE_FLASHING);
|
|
if (doAPUpdate(apInfo.type)) {
|
|
checkWaitPowerCycle();
|
|
Serial.printf("Flash completed, let's try to boot the AP!\n");
|
|
if (bringAPOnline()) {
|
|
// AP works
|
|
ShowAPInfo();
|
|
setAPchannel();
|
|
} else {
|
|
Serial.printf("Failed to bring up the AP after flashing seemed successful... That's not supposed to happen!\n");
|
|
Serial.printf("This can be caused by a bad AP firmware, failed or failing hardware, or the inability to fully power-cycle the AP\n");
|
|
setAPstate(false, AP_STATE_FAILED);
|
|
#ifdef HAS_RGB_LED
|
|
showColorPattern(CRGB::Red, CRGB::Yellow, CRGB::Red);
|
|
#endif
|
|
}
|
|
} else {
|
|
setAPstate(false, AP_STATE_FAILED);
|
|
checkWaitPowerCycle();
|
|
Serial.println("Failed to update version on the AP :(\n");
|
|
#ifdef HAS_RGB_LED
|
|
showColorPattern(CRGB::Red, CRGB::Red, CRGB::Red);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
refreshAllPending();
|
|
} else {
|
|
#ifndef FLASH_TIMEOUT
|
|
#define FLASH_TIMEOUT 30
|
|
#endif
|
|
|
|
if (FLASHER_AP_MOSI == -1) {
|
|
Serial.printf("I wasn't able to connect to the AP radio. Did you flash it?\n");
|
|
Serial.printf("The build of this firmware expects an AP tag with TXD/RXD on ESP32 pins %d and %d, does this match with your wiring?\n", FLASHER_AP_RXD, FLASHER_AP_TXD);
|
|
#ifdef HAS_RGB_LED
|
|
showColorPattern(CRGB::Red, CRGB::Yellow, CRGB::Red);
|
|
#endif
|
|
if (apInfo.state != AP_STATE_FLASHING) // In case we are flashing already we do not want to end in a failed AP
|
|
setAPstate(false, AP_STATE_FAILED);
|
|
} else {
|
|
#ifndef C6_OTA_FLASHING
|
|
// AP unavailable, maybe time to flash?
|
|
setAPstate(false, AP_STATE_OFFLINE);
|
|
|
|
Serial.printf("I wasn't able to connect to a ZBS (AP) tag.\n");
|
|
Serial.printf("This could be the first time this AP is booted and the AP-tag may be unflashed.\n");
|
|
Serial.printf("If this tag was previously flashed succesfully but this message still shows up, there's probably something wrong with the serial connections.\n");
|
|
Serial.printf("The build of this firmware expects an AP tag with TXD/RXD on ESP32 pins %d and %d, does this match with your wiring?\n", FLASHER_AP_RXD, FLASHER_AP_TXD);
|
|
Serial.printf("Performing firmware flash in about %d seconds!\n", FLASH_TIMEOUT);
|
|
flashCountDown(FLASH_TIMEOUT);
|
|
if (doAPFlash()) {
|
|
checkWaitPowerCycle();
|
|
if (bringAPOnline()) {
|
|
// AP works
|
|
ShowAPInfo();
|
|
if (apInfo.type == SOLUM_SEG_UK) {
|
|
segmentedShowIp();
|
|
showAPSegmentedInfo(apInfo.mac, true);
|
|
}
|
|
refreshAllPending();
|
|
} else {
|
|
Serial.printf("Failed to bring up the AP after successful flashing... That's not supposed to happen!\n");
|
|
Serial.printf("This generally means that the flasher connections (MISO/MOSI/CLK/RESET/CS) are okay,\n");
|
|
Serial.printf("but we can't (yet) talk to the AP over serial lines. Verify the pins mentioned above.\n\n");
|
|
|
|
#ifndef POWER_NO_SOFT_POWER
|
|
Serial.printf("The firmware you're using expects soft power control over the AP tag; if it can't\n");
|
|
Serial.printf("power-cycle the AP-tag using GPIO pin %d, this can cause this very same issue.\n", APpowerPins[0]);
|
|
#endif
|
|
|
|
#ifdef HAS_RGB_LED
|
|
showColorPattern(CRGB::Red, CRGB::Yellow, CRGB::Red);
|
|
#endif
|
|
setAPstate(false, AP_STATE_FAILED);
|
|
}
|
|
} else {
|
|
// failed to flash
|
|
#ifdef HAS_RGB_LED
|
|
showColorPattern(CRGB::Red, CRGB::Red, CRGB::Red);
|
|
#endif
|
|
setAPstate(false, AP_STATE_FAILED);
|
|
Serial.println("Failed to flash the AP :(");
|
|
Serial.println("Seems like you're running into some issues with the wiring, or (very small chance) the tag itself");
|
|
Serial.println("This ESP32-build expects the following pins connected to the ZBS243:");
|
|
Serial.println("--- ZBS243 based tag ESP32 ---");
|
|
Serial.printf(" TXD ---------------- %02d\n", FLASHER_AP_RXD);
|
|
Serial.printf(" RXD ---------------- %02d\n", FLASHER_AP_TXD);
|
|
Serial.printf(" CS/SS ---------------- %02d\n", FLASHER_AP_SS);
|
|
Serial.printf(" MOSI ---------------- %02d\n", FLASHER_AP_MOSI);
|
|
Serial.printf(" MISO ---------------- %02d\n", FLASHER_AP_MISO);
|
|
Serial.printf(" CLK ---------------- %02d\n", FLASHER_AP_CLK);
|
|
Serial.printf(" RSET ---------------- %02d\n", FLASHER_AP_RESET);
|
|
#ifdef POWER_NO_SOFT_POWER
|
|
Serial.printf("Your firmware is configured without soft power control. This means you'll have to manually power-cycle the tag after flashing.\n");
|
|
#else
|
|
Serial.printf(" POWER ---------------- %02d\n", APpowerPins[0]);
|
|
#endif
|
|
Serial.println("Please verify your wiring and try again!");
|
|
}
|
|
#ifdef HAS_SDCARD
|
|
if (SD_CARD_CLK == FLASHER_AP_CLK ||
|
|
SD_CARD_MISO == FLASHER_AP_MISO ||
|
|
SD_CARD_MOSI == FLASHER_AP_MOSI) {
|
|
Serial.println("Reseting in 30 seconds to restore SPI state!\n");
|
|
flashCountDown(30);
|
|
ESP.restart();
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
|
|
uint8_t attempts = 0;
|
|
while (1) {
|
|
if (((apInfo.state == AP_STATE_ONLINE) || (apInfo.state == AP_STATE_FAILED)) && (millis() - lastAPActivity > AP_ACTIVITY_MAX_INTERVAL)) {
|
|
bool reply = sendPing();
|
|
if (!reply) {
|
|
attempts++;
|
|
} else {
|
|
if (apInfo.isOnline == false)
|
|
setAPstate(true, AP_STATE_ONLINE);
|
|
attempts = 0;
|
|
}
|
|
if (attempts > 5 && apInfo.state != AP_STATE_FLASHING) {
|
|
setAPstate(false, AP_STATE_WAIT_RESET);
|
|
if (!bringAPOnline()) {
|
|
// tried to reset the AP, but we failed... Maybe the AP-Tag died?
|
|
setAPstate(false, AP_STATE_FAILED);
|
|
#ifdef HAS_RGB_LED
|
|
showColorPattern(CRGB::Yellow, CRGB::Yellow, CRGB::Red);
|
|
#endif
|
|
lastAPActivity = millis(); // we set this to retrigger a recovery in AP_ACTIVITY_MAX_INTERVAL seconds
|
|
} else {
|
|
setAPstate(true, AP_STATE_ONLINE);
|
|
attempts = 0;
|
|
refreshAllPending();
|
|
}
|
|
}
|
|
}
|
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
|
}
|
|
} |