tag flasher / timestamp content / bugfixes (#217)

- added webinterface for tag flasher
- added tcp transport for communicating with tag flasher (OTG USB also still works)
- added content 'timestamp', makes use of preloaded images and buttons on the 2.9" M3
- webinterface is now aware of C6 and flasher capabilities
- AP can run without ieee801.15.4 radio (i.e. flasher only) by shorting FLASHER_AP_TXD and FLASHER_AP_RXD
- added tcp transport option to OEPL-Flasher.py (serial also still works)
- added new environment OpenEPaperLink_Mini_AP_v4
- lots of finetuning and bug fixes
This commit is contained in:
Nic Limper
2024-02-04 20:30:52 +01:00
committed by GitHub
parent 472f148b0c
commit b4836e81f3
48 changed files with 1864 additions and 445 deletions

View File

@@ -41,8 +41,14 @@ jobs:
pio run --environment OpenEPaperLink_AP_and_Flasher
pio run --target buildfs --environment OpenEPaperLink_AP_and_Flasher
- name: Build ESP32_S3_16_8_YELLOW_AP]
- name: Build ESP32_S3_16_8_YELLOW_AP
run: |
cd ESP32_AP-Flasher
pio run --environment ESP32_S3_16_8_YELLOW_AP
pio run --target buildfs --environment ESP32_S3_16_8_YELLOW_AP
- name: OpenEPaperLink_Mini_AP_v4
run: |
cd ESP32_AP-Flasher
pio run --environment OpenEPaperLink_Mini_AP_v4
pio run --target buildfs --environment OpenEPaperLink_Mini_AP_v4

View File

@@ -46,24 +46,6 @@ jobs:
# cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/ESP32_AP-Flasher
# python gzip_wwwfiles.py
- name: Build firmware for Simple_AP
run: |
cd ESP32_AP-Flasher
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
pio run --environment Simple_AP
pio run --target buildfs --environment Simple_AP
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP/boot_app0.bin
cp .pio/build/Simple_AP/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP/firmware.bin
cp .pio/build/Simple_AP/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP/bootloader.bin
cp .pio/build/Simple_AP/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP/partitions.bin
cp .pio/build/Simple_AP/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP/littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/Simple_AP
esptool.py --chip esp32 merge_bin -o merged-firmware.bin --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 bootloader.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 firmware.bin 0x290000 littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
cp Simple_AP/firmware.bin espbinaries/Simple_AP.bin
cp Simple_AP/merged-firmware.bin espbinaries/Simple_AP_full.bin
- name: Build firmware for OpenEPaperLink_Mini_AP
run: |
cd ESP32_AP-Flasher
@@ -176,6 +158,24 @@ jobs:
cp OpenEPaperLink_PoE_AP/firmware.bin espbinaries/OpenEPaperLink_PoE_AP.bin
cp OpenEPaperLink_PoE_AP/merged-firmware.bin espbinaries/OpenEPaperLink_PoE_AP_full.bin
- name: Build firmware for OpenEPaperLink_Mini_AP_v4
run: |
cd ESP32_AP-Flasher
export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA"
pio run --environment OpenEPaperLink_Mini_AP_v4
pio run --target buildfs --environment OpenEPaperLink_Mini_AP_v4
mkdir /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP_v4
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP_v4/boot_app0.bin
cp .pio/build/OpenEPaperLink_Mini_AP_v4/firmware.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP_v4/firmware.bin
cp .pio/build/OpenEPaperLink_Mini_AP_v4/bootloader.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP_v4/bootloader.bin
cp .pio/build/OpenEPaperLink_Mini_AP_v4/partitions.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP_v4/partitions.bin
cp .pio/build/OpenEPaperLink_Mini_AP_v4/littlefs.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP_v4/littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink/OpenEPaperLink_Mini_AP_v4
esptool.py --chip esp32-s3 merge_bin -o merged-firmware.bin --flash_mode dio --flash_freq 80m --flash_size 16MB 0x0000 bootloader.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 firmware.bin 0x00910000 littlefs.bin
cd /home/runner/work/OpenEPaperLink/OpenEPaperLink
cp OpenEPaperLink_Mini_AP_v4/firmware.bin espbinaries/OpenEPaperLink_Mini_AP_v4.bin
cp OpenEPaperLink_Mini_AP_v4/merged-firmware.bin espbinaries/OpenEPaperLink_Mini_AP_v4_full.bin
- name: generate release json file
run: |
mkdir jsonfiles

View File

@@ -13,7 +13,7 @@
},
"shortlut": 0,
"options": ["button", "led"],
"contentids": [22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 27],
"contentids": [22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 26, 27],
"usetemplate": 1,
"template": {
"1": {

View File

@@ -0,0 +1,29 @@
{
"name": "TFT 160x80",
"width": 160,
"height": 80,
"rotatebuffer": 1,
"bpp": 16,
"colors": 4,
"colortable": {
"white": [ 255, 255, 255 ],
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ],
"gray": [ 150, 150, 150 ]
},
"shortlut": 0,
"options": [],
"contentids": [ 22, 21 ],
"template": {
"21": [
{ "box": [ 0, 0, 160, 80, 1 ] },
{ "text": [ 1, 3, "OpenEpaperLink AP", "calibrib16.vlw", 2, 0, 0, 1 ] },
{ "text": [ 1, 28, "IP:", "REFSAN12.vlw", "#888888", 0, 0, 1 ] },
{ "text": [ 45, 28, "{ap_ip}", "REFSAN12.vlw", 0, 0, 0, 1 ] },
{ "text": [ 1, 45, "Ch:", "REFSAN12.vlw", "#888888", 0, 0, 1 ] },
{ "text": [ 45, 45, "{ap_ch}", "REFSAN12.vlw", 0, 0, 0, "1" ] },
{ "text": [ 1, 62, "Tags:", "REFSAN12.vlw", "#888888", 0, 0, 1 ] },
{ "text": [ 45, 62, "{ap_tagcount}", "REFSAN12.vlw", 0, 0, 0, "1" ] }
]
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -8,7 +8,7 @@ bool doAPFlash();
bool doAPUpdate(uint8_t type);
void flashCountDown(uint8_t c);
#ifdef OPENEPAPERLINK_PCB
#ifdef HAS_EXT_FLASHER
bool extTagConnected();
bool doTagFlash();
#endif
@@ -20,6 +20,7 @@ class flasher {
char md5char[34];
uint8_t tagtype;
uint8_t *infoblock = nullptr;
bool includeInfoBlock = false;
// Infoblock structure:
// 0x00-0x0F - Calibration data
@@ -35,7 +36,7 @@ class flasher {
flasher();
~flasher();
bool connectTag(uint8_t port);
void getFirmwareMD5();
bool getFirmwareMD5();
bool getFirmwareMac();
bool findTagByMD5();
bool findTagByType(uint8_t type);

View File

@@ -1,12 +1,13 @@
#include <Arduino.h>
#include <TFT_eSPI.h>
#ifdef YELLOW_IPS_AP
#ifdef HAS_TFT
extern TFT_eSPI tft2;
extern int32_t tftid;
extern uint8_t YellowSense;
extern bool tftOverride;
void TFTLog(String text);
void sendAvail(uint8_t wakeupReason);
#endif

View File

@@ -34,6 +34,8 @@ void shortBlink(CRGB cname);
void showColorPattern(CRGB colorone, CRGB colortwo, CRGB colorthree);
void rgbIdle();
void addFadeColor(CRGB cname);
#endif
void quickBlink(uint8_t repeat);
void quickBlink(uint8_t repeat);
void addFadeMono(uint8_t value);

View File

@@ -16,6 +16,7 @@ extern bool checkCRC(void* p, uint8_t len);
extern void processBlockRequest(struct espBlockRequest* br);
extern void prepareCancelPending(const uint8_t dst[8]);
extern void prepareIdleReq(const uint8_t* dst, uint16_t nextCheckin);
extern void prepareDataAvail(const uint8_t* dst);
extern void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, const uint8_t* dst);
extern bool prepareDataAvail(String& filename, uint8_t dataType, uint8_t dataTypeArgument, const uint8_t* dst, uint16_t nextCheckin, bool resend = false);
extern void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP);
@@ -41,5 +42,5 @@ uint16_t countQueueItem(const uint8_t* targetMac);
extern PendingItem* getQueueItem(const uint8_t* targetMac);
extern PendingItem* getQueueItem(const uint8_t* targetMac, const uint64_t dataVer);
void checkQueue(const uint8_t* targetMac);
bool queueDataAvail(struct pendingData* pending);
bool queueDataAvail(struct pendingData* pending, bool local);
uint8_t* getDataForFile(fs::File& file);

View File

@@ -1,5 +1,3 @@
#include <Arduino.h>
//void doLeds();
void powerControl(bool powerState, uint8_t* pin, uint8_t pincount);

View File

@@ -9,6 +9,7 @@ extern struct espSetChannelPower curChannel;
#define AP_STATE_REQUIRED_POWER_CYCLE 4
#define AP_STATE_FAILED 5
#define AP_STATE_COMING_ONLINE 6
#define AP_STATE_NORADIO 7
struct APInfoS {
bool isOnline = false;

View File

@@ -1,7 +1,7 @@
#include <Arduino.h>
#define FLASHER_AP_PORT 0
#ifdef OPENEPAPERLINK_PCB
#ifdef HAS_EXT_FLASHER
#define FLASHER_EXT_PORT 1
#define FLASHER_ALTRADIO_PORT 2
#endif

View File

@@ -55,7 +55,7 @@ class tagRecord {
struct Config {
uint8_t channel;
char alias[32];
int16_t led;
uint8_t led;
uint8_t tft;
uint8_t language;
uint8_t maxsleep;

View File

@@ -1,4 +1,10 @@
#include <Arduino.h>
#define TRANSPORT_USB 0
#define TRANSPORT_TCP 1
void usbFlasherTask(void* parameter);
#ifdef HAS_USB
void usbFlasherTask(void* parameter);
#endif
void flasherDataHandler(uint8_t* data, size_t len, uint8_t transportType);
void processFlasherCommand(struct flasherCommand* cmd, uint8_t transportType);

View File

@@ -13,6 +13,7 @@ void wsSendTaginfo(const uint8_t *mac, uint8_t syncMode);
void wsSendSysteminfo();
void wsSendAPitem(struct APlist *apitem);
void wsSerial(const String &text);
void wsSerial(const String &text, const String &color);
uint8_t wsClientCount();
extern AsyncWebSocket ws;

View File

@@ -0,0 +1,30 @@
#include <Arduino.h>
#ifdef HAS_EXT_FLASHER
#include "web.h"
#define WEBFLASH_ENABLE_AUTOFLASH 1
#define WEBFLASH_ENABLE_USBFLASHER 2
#define WEBFLASH_FOCUS 3
#define WEBFLASH_BLUR 4
class Logger : public Print {
public:
Logger();
~Logger();
size_t write(uint8_t c) override;
size_t write(const uint8_t* buffer, size_t size) override;
size_t printf(const char* format, ...);
size_t println(const char* text);
private:
};
void webFlasherTask(void* parameter);
void handleWSdata(uint8_t* data, size_t len, AsyncWebSocketClient* client);
void sendDataToClient(const uint8_t* data, size_t len);
#endif

View File

@@ -13,7 +13,7 @@ public:
void setSpeed(uint32_t speed);
void set_power(uint8_t state);
void enable_debug();
void reset();
void reset(bool leavepower = true);
void send_byte(uint8_t data);
uint8_t read_byte();
void write_byte(uint8_t cmd, uint8_t addr, uint8_t data);

View File

@@ -59,6 +59,7 @@ build_flags =
-D BOARD_HAS_PSRAM
-D SAVE_SPACE
-D POWER_NO_SOFT_POWER
-D SAVE_SPACE
-D FLASHER_AP_SS=11
-D FLASHER_AP_CLK=9
-D FLASHER_AP_MOSI=10
@@ -71,7 +72,7 @@ build_flags =
-D FLASHER_LED=15
-D FLASHER_RGB_LED=33
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>-<ips_display.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>-<ips_display.cpp>-<webflasher.cpp>
board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680
@@ -107,7 +108,7 @@ build_flags =
-D FLASHER_LED=15
-D FLASHER_RGB_LED=-1
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>-<ips_display.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>-<ips_display.cpp>-<webflasher.cpp>
board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680
@@ -126,7 +127,7 @@ build_unflags =
build_flags =
-std=gnu++17
${env.build_flags}
-D OPENEPAPERLINK_PCB
-D HAS_EXT_FLASHER
-D ARDUINO_USB_MODE=0
-D CONFIG_ESP32S3_SPIRAM_SUPPORT=1
-D CONFIG_SPIRAM_USE_MALLOC=1
@@ -197,8 +198,9 @@ build_flags =
-D FLASHER_AP_TXD=17
-D FLASHER_AP_RXD=16
-D FLASHER_LED=22
-D SAVE_SPACE
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>-<ips_display.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>-<ips_display.cpp>-<webflasher.cpp>
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an ESP32-S3 16MB Flash 8MB RAM
; ----------------------------------------------------------------------------------------
@@ -215,7 +217,7 @@ lib_deps =
build_flags =
-std=gnu++17
${env.build_flags}
-D YELLOW_IPS_AP
-D HAS_TFT
-D CORE_DEBUG_LEVEL=0
-D ARDUINO_USB_MODE=0
-D CONFIG_ESP32S3_SPIRAM_SUPPORT=1
@@ -254,8 +256,9 @@ build_flags =
-D SERIAL_FLASHER_INTERFACE_UART=1
-D SERIAL_FLASHER_BOOT_HOLD_TIME_MS=50
-D SERIAL_FLASHER_RESET_HOLD_TIME_MS=100
-D C6_OTA_FLASHING
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<webflasher.cpp>
board_build.flash_mode=qio
board_build.arduino.memory_type = qio_opi
board_build.psram_type=qspi_opi
@@ -302,7 +305,7 @@ build_flags =
-D SERIAL_FLASHER_RESET_HOLD_TIME_MS=100
-D C6_OTA_FLASHING
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<webflasher.cpp>
board_build.flash_mode=qio
board_build.arduino.memory_type = qio_opi
board_build.psram_type=qspi_opi
@@ -348,12 +351,102 @@ build_flags =
-D SD_CARD_MOSI=14
-D SD_CARD_SS=12
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>-<webflasher.cpp>
board_build.flash_mode=qio
board_upload.maximum_size = 16777216
board_upload.maximum_ram_size = 327680
board_upload.flash_size = 16MB
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an ESP32-S3 16MB Flash 8MB RAM
; ----------------------------------------------------------------------------------------
[env:OpenEPaperLink_Mini_AP_v4]
board = esp32-s3-devkitc-1
board_build.partitions = large_spiffs_16MB.csv
monitor_dtr = 0
monitor_rts = 0
build_unflags =
-std=gnu++11
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
-D ILI9341_DRIVER
lib_deps =
${env.lib_deps}
build_flags =
-std=gnu++17
${env.build_flags}
-D HAS_TFT
-D HAS_EXT_FLASHER
-D CORE_DEBUG_LEVEL=1
-D ARDUINO_USB_CDC_ON_BOOT=1
-D CONFIG_ESP32S3_SPIRAM_SUPPORT=1
-D CONFIG_SPIRAM_USE_MALLOC=1
-D POWER_NO_SOFT_POWER
-D BOARD_HAS_PSRAM
-D POWER_RAMPING
-D POWER_HIGH_SIDE_DRIVER
-D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
-D FLASHER_AP_SS=-1
-D FLASHER_AP_CLK=-1
-D FLASHER_AP_MOSI=-1
-D FLASHER_AP_MISO=-1
-D FLASHER_AP_RESET=47
-D FLASHER_AP_POWER={-1}
-D FLASHER_AP_TEST=-1
-D FLASHER_AP_TXD=17
-D FLASHER_AP_RXD=18
-D FLASHER_DEBUG_TXD=15
-D FLASHER_DEBUG_RXD=7
-D FLASHER_DEBUG_PROG=21
-D FLASHER_LED=16
-D FLASHER_EXT_SS=40
-D FLASHER_EXT_CLK=41
-D FLASHER_EXT_MOSI=2
-D FLASHER_EXT_MISO=42
-D FLASHER_EXT_RESET=5
-D FLASHER_EXT_POWER={6}
-D FLASHER_EXT_TXD=38
-D FLASHER_EXT_RXD=39
-D FLASHER_EXT_TEST=4
-D FLASHER_ALT_SS=-1
-D FLASHER_ALT_CLK=-1
-D FLASHER_ALT_MOSI=-1
-D FLASHER_ALT_MISO=-1
-D FLASHER_ALT_RESET=-1
-D FLASHER_ALT_POWER={-1}
-D FLASHER_ALT_TXD=-1
-D FLASHER_ALT_RXD=-1
-D FLASHER_ALT_TEST=-1
-D HAS_RGB_LED
-D FLASHER_RGB_LED=48
-D ST7735_DRIVER
-D ST7735_GREENTAB160x80
-D TFT_INVERSION_ON
-D TFT_WIDTH=80
-D TFT_HEIGHT=160
-D TFT_MISO=-1
-D TFT_MOSI=13
-D TFT_SCLK=12
-D TFT_CS=10
-D TFT_DC=11
-D TFT_RST=1
-D TFT_RGB_ORDER=TFT_BGR
-D USE_HSPI_PORT
-D LOAD_FONT2
-D LOAD_FONT4
-D LOAD_GLCD
-D MD5_ENABLED=1
-D SERIAL_FLASHER_INTERFACE_UART=1
-D SERIAL_FLASHER_BOOT_HOLD_TIME_MS=50
-D SERIAL_FLASHER_RESET_HOLD_TIME_MS=100
-D C6_OTA_FLASHING
build_src_filter =
+<*>
board_build.flash_mode=qio
board_build.arduino.memory_type = qio_opi
board_build.psram_type=qspi_opi
board_upload.maximum_size = 16777216
board_upload.maximum_ram_size = 327680
board_upload.flash_size = 16MB
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an SONOFF ZB Bridge-P
; ----------------------------------------------------------------------------------------
;[env:Sonoff_zb_bridge_P_AP]

View File

@@ -7,6 +7,7 @@
#define CONTENT_BIGCAL
#define CONTENT_NFCLUT
#define CONTENT_DAYAHEAD
#define CONTENT_TIMESTAMP
#endif
#define CONTENT_CAL
#define CONTENT_BUIENRADAR
@@ -41,6 +42,12 @@
// https://csvjson.com/json_beautifier
bool needRedraw(uint8_t contentMode, uint8_t wakeupReason) {
// contentmode 26, timestamp
if ((wakeupReason == WAKEUP_REASON_BUTTON1 || wakeupReason == WAKEUP_REASON_BUTTON2) && contentMode == 26) return true;
return false;
}
void contentRunner() {
if (config.runStatus == RUNSTATUS_STOP) return;
@@ -49,11 +56,11 @@ void contentRunner() {
for (tagRecord *taginfo : tagDB) {
if (taginfo->RSSI &&
(now >= taginfo->nextupdate || taginfo->wakeupReason == WAKEUP_REASON_NFC) &&
(now >= taginfo->nextupdate || needRedraw(taginfo->contentMode, taginfo->wakeupReason)) &&
config.runStatus == RUNSTATUS_RUN &&
Storage.freeSpace() > 31000 && !util::isSleeping(config.sleepTime1, config.sleepTime2)) {
drawNew(taginfo->mac, taginfo);
// taginfo->wakeupReason = 0;
taginfo->wakeupReason = 0;
}
if (taginfo->expectedNextCheckin > now - 10 && taginfo->expectedNextCheckin < now + 30 && taginfo->pendingIdle == 0 && taginfo->pendingCount == 0) {
@@ -73,7 +80,7 @@ void contentRunner() {
minutesUntilNextUpdate = (nextWakeTime - now) / 60 - 2;
}
if (minutesUntilNextUpdate > 1 && (wsClientCount() == 0 || config.stopsleep == 0)) {
taginfo->pendingIdle = minutesUntilNextUpdate;
taginfo->pendingIdle = minutesUntilNextUpdate * 60;
if (taginfo->isExternal == false) {
Serial.printf("sleeping for %d more minutes\n", minutesUntilNextUpdate);
prepareIdleReq(taginfo->mac, minutesUntilNextUpdate);
@@ -180,7 +187,7 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) {
char hexmac[17];
mac2hex(mac, hexmac);
String filename = "/temp/" + String(hexmac) + ".raw";
#ifdef YELLOW_IPS_AP
#ifdef HAS_TFT
if (isAp) {
filename = "direct";
}
@@ -287,6 +294,11 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) {
} else {
wsErr("Error accessing " + filename);
}
} else {
// configfilename is empty. Probably the tag needs to redisplay the image after a reboot.
Serial.println("Resend static image");
// fixme: doesn't work yet
// prepareDataAvail(mac);
}
taginfo->nextupdate = 3216153600;
} break;
@@ -508,10 +520,13 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) {
taginfo->nextupdate = 3216153600;
break;
#ifdef CONTENT_TIMESTAMP
case 26: // timestamp
taginfo->nextupdate = 3216153600;
drawTimestamp(filename, cfgobj, taginfo, imageParams);
updateTagImage(filename, mac, 0, taginfo, imageParams);
taginfo->nextupdate = 3216153600;
break;
#endif
#ifdef CONTENT_DAYAHEAD
case 27: // Day Ahead:
@@ -1459,7 +1474,7 @@ bool getDayAheadFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo,
StaticJsonDocument<512> loc;
getTemplate(loc, 27, taginfo->hwType);
// This is a link to a Google Apps Script script, which fetches (and caches) the tariff from https://transparency.entsoe.eu/
// This is a link to a Google Apps Script script, which fetches (and caches) the tariff from https://transparency.entsoe.eu/
// I made it available to provide easy access to the data, but please don't use this link in any projects other than OpenEpaperLink.
String URL = "https://script.google.com/macros/s/AKfycbwMmeGAaPrWzVZrESSpmPmD--O132PzW_acnBsuEottKNATTqCRn6h8zN0Yts7S56ggsg/exec?country=" + cfgobj["country"].as<String>();
@@ -1546,8 +1561,8 @@ bool getDayAheadFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo,
spr.fillRect(barX + i * barwidth + 3, 5, barwidth - 6, 10, imageParams.highlightColor);
spr.fillTriangle(barX + i * barwidth, 15,
barX + i * barwidth + barwidth - 1, 15,
barX + i * barwidth + barwidth / 2, 15 + barwidth, imageParams.highlightColor);
spr.drawLine(barX + i * barwidth + barwidth / 2, 20 + barwidth, barX + i * barwidth + barwidth / 2, spr.height(), getColor("pink"));
barX + i * barwidth + (barwidth - 1) / 2, 15 + barwidth, imageParams.highlightColor);
spr.drawLine(barX + i * barwidth + (barwidth - 1) / 2, 20 + barwidth, barX + i * barwidth + (barwidth - 1) / 2, spr.height(), getColor("pink"));
pricenow = price;
}
}
@@ -1702,9 +1717,142 @@ void drawAPinfo(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa
spr.deleteSprite();
}
#ifdef CONTENT_TIMESTAMP
void drawTimestamp(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams) {
// todo
Serial.println("make Timestamp");
time_t now;
time(&now);
struct tm timeinfo;
StaticJsonDocument<512> loc;
getTemplate(loc, 1, taginfo->hwType);
TFT_eSprite spr = TFT_eSprite(&tft);
initSprite(spr, imageParams.width, imageParams.height, imageParams);
if (!cfgobj["#init"]) {
Serial.println("init");
// init preload images
char hexmac[17];
mac2hex(taginfo->mac, hexmac);
String filename2 = "/temp/" + String(hexmac) + "-2.raw";
drawString(spr, cfgobj["button1"].as<String>(), spr.width() / 2, 40, "calibrib30.vlw", TC_DATUM, TFT_BLACK);
drawString(spr, "Well done!", spr.width() / 2, 90, "calibrib30.vlw", TC_DATUM, TFT_BLACK);
spr2buffer(spr, filename2, imageParams);
struct imageDataTypeArgStruct arg = {0};
arg.preloadImage = 1;
arg.specialType = 17; // button 2
arg.lut = 0;
prepareDataAvail(filename2, imageParams.dataType, *((uint8_t *)&arg), taginfo->mac, 5 | 0x8000 );
spr.fillRect(0, 0, spr.width(), spr.height(), TFT_WHITE);
filename2 = "/temp/" + String(hexmac) + "-3.raw";
drawString(spr, cfgobj["button2"].as<String>(), spr.width() / 2, 40, "calibrib30.vlw", TC_DATUM, TFT_BLACK);
drawString(spr, "Well done!", spr.width() / 2, 90, "calibrib30.vlw", TC_DATUM, TFT_BLACK);
spr2buffer(spr, filename2, imageParams);
arg.preloadImage = 1;
arg.specialType = 16; // button 1
arg.lut = 0;
prepareDataAvail(filename2, imageParams.dataType, *((uint8_t *)&arg), taginfo->mac, 5 | 0x8000 );
cfgobj["#init"] = "1";
}
spr.fillRect(0, 0, spr.width(), spr.height(), TFT_WHITE);
drawString(spr, cfgobj["title"], spr.width() / 2, 10, "calibrib30.vlw", TC_DATUM, TFT_BLACK);
spr.drawLine(0, 40, spr.width(), 40, TFT_BLACK);
drawString(spr, cfgobj["button1"], 32, 145, "calibrib16.vlw", TC_DATUM, TFT_BLACK);
drawString(spr, cfgobj["button2"], 122, 145, "calibrib16.vlw", TC_DATUM, TFT_BLACK);
spr.fillTriangle(27, 160, 37, 160, 32, 165, TFT_BLACK);
spr.fillTriangle(127, 160, 117, 160, 122, 165, TFT_BLACK);
uint8_t mode = cfgobj["mode"].as<int>();
switch (taginfo->wakeupReason) {
case WAKEUP_REASON_BUTTON2:
Serial.println("button 1");
cfgobj["last1"] = now;
if (mode == 0) {
// 1 timestamp
cfgobj["last2"] = cfgobj["button1"].as<String>();
}
break;
case WAKEUP_REASON_BUTTON1:
Serial.println("button 2");
if (mode == 0) {
// 1 timestamp
cfgobj["last1"] = now;
cfgobj["last2"] = cfgobj["button2"].as<String>();
} else {
cfgobj["last2"] = now;
// 2 timestamps
}
break;
}
char dateString1[40];
uint32_t nextaction = cfgobj["nextaction"].as<uint32_t>();
String dateformat = languageDateFormat[0] + " %H:%M";
time_t timestamp = cfgobj["last1"].as<uint32_t>();
localtime_r(&timestamp, &timeinfo);
strftime(dateString1, sizeof(dateString1), dateformat.c_str(), &timeinfo);
if (timestamp == 0) strcpy(dateString1, "never");
if (mode == 0) {
drawString(spr, "last:", 10, 50, "calibrib16.vlw", TL_DATUM, TFT_BLACK);
drawString(spr, dateString1, spr.width() / 2, 50, "bahnschrift30.vlw", TC_DATUM, TFT_BLACK);
drawString(spr, cfgobj["last2"].as<String>(), spr.width() / 2, 80, "bahnschrift30.vlw", TC_DATUM, TFT_BLACK);
if (nextaction > 0 && timestamp > 0) {
timestamp += nextaction * 24 * 3600;
if (timestamp < taginfo->nextupdate) taginfo->nextupdate = timestamp;
localtime_r(&timestamp, &timeinfo);
strftime(dateString1, sizeof(dateString1), languageDateFormat[0].c_str(), &timeinfo);
drawString(spr, "next:", 10, 115, "calibrib16.vlw", TL_DATUM, TFT_BLACK);
drawString(spr, dateString1, 50, 115, "calibrib16.vlw", TL_DATUM, timestamp < now ? imageParams.highlightColor : TFT_BLACK);
}
} else {
drawString(spr, cfgobj["button1"].as<String>(), 10, 50, "calibrib16.vlw", TL_DATUM, TFT_BLACK);
drawString(spr, dateString1, 20, 67, "fonts/bahnschrift20", TL_DATUM, TFT_BLACK);
if (nextaction > 0 && timestamp > 0) {
timestamp += nextaction * 24 * 3600;
if (timestamp < taginfo->nextupdate) taginfo->nextupdate = timestamp;
localtime_r(&timestamp, &timeinfo);
strftime(dateString1, sizeof(dateString1), languageDateFormat[0].c_str(), &timeinfo);
drawString(spr, "next", 200, 50, "calibrib16.vlw", TL_DATUM, TFT_BLACK);
drawString(spr, dateString1, 210, 67, "fonts/bahnschrift20", TL_DATUM, timestamp < now ? imageParams.highlightColor : TFT_BLACK);
}
char dateString2[40];
time_t timestamp = cfgobj["last2"].as<uint32_t>();
localtime_r(&timestamp, &timeinfo);
strftime(dateString2, sizeof(dateString2), dateformat.c_str(), &timeinfo);
if (timestamp == 0) strcpy(dateString2, "never");
drawString(spr, cfgobj["button2"].as<String>(), 10, 90, "calibrib16.vlw", TL_DATUM, TFT_BLACK);
drawString(spr, dateString2, 20, 107, "fonts/bahnschrift20", TL_DATUM, TFT_BLACK);
if (nextaction > 0 && timestamp > 0) {
timestamp += nextaction * 24 * 3600;
localtime_r(&timestamp, &timeinfo);
strftime(dateString2, sizeof(dateString2), languageDateFormat[0].c_str(), &timeinfo);
drawString(spr, "next", 200, 90, "calibrib16.vlw", TL_DATUM, TFT_BLACK);
drawString(spr, dateString2, 210, 107, "fonts/bahnschrift20", TL_DATUM, timestamp < now ? imageParams.highlightColor : TFT_BLACK);
}
}
spr2buffer(spr, filename, imageParams);
spr.deleteSprite();
}
#endif
bool getJsonTemplateFile(String &filename, String jsonfile, tagRecord *&taginfo, imgParam &imageParams) {
if (jsonfile.c_str()[0] != '/') {

View File

@@ -5,23 +5,29 @@
#include <MD5Builder.h>
#include "LittleFS.h"
#include "storage.h"
// #include <FS.h>
#include "leds.h"
#include "settings.h"
#include "storage.h"
#include "time.h"
#include "zbs_interface.h"
#ifdef HAS_EXT_FLASHER
#include "webflasher.h"
#define Seriallog logger
extern Logger logger;
#else
#define Seriallog Serial
#endif
#define FINGERPRINT_FLASH_SIZE 10240
#ifdef OPENEPAPERLINK_PCB
#ifdef HAS_EXT_FLASHER
bool extTagConnected() {
// checks if the TEST (P1.0) pin on the ZBS243 will come up high. If it doesn't, there's probably a tag connected.
pinMode(FLASHER_EXT_TEST, INPUT_PULLDOWN);
vTaskDelay(5 / portTICK_PERIOD_MS);
vTaskDelay(10 / portTICK_PERIOD_MS);
pinMode(FLASHER_EXT_TEST, INPUT_PULLUP);
vTaskDelay(5 / portTICK_PERIOD_MS);
vTaskDelay(10 / portTICK_PERIOD_MS);
return !digitalRead(FLASHER_EXT_TEST);
}
#endif
@@ -55,11 +61,10 @@ void dump(uint8_t *a, uint16_t l) {
int8_t powerPinsAP[] = FLASHER_AP_POWER;
int8_t pinsAP[] = {FLASHER_AP_CLK, FLASHER_AP_MISO, FLASHER_AP_MOSI, FLASHER_AP_RESET, FLASHER_AP_RXD, FLASHER_AP_SS, FLASHER_AP_TEST, FLASHER_AP_TXD};
#ifdef OPENEPAPERLINK_PCB
#ifdef HAS_EXT_FLASHER
int8_t powerPinsExt[] = FLASHER_EXT_POWER;
int8_t powerPinsAlt[] = FLASHER_ALT_POWER;
uint8_t pinsExt[] = {FLASHER_EXT_CLK, FLASHER_EXT_MISO, FLASHER_EXT_MOSI, FLASHER_EXT_RESET, FLASHER_EXT_RXD, FLASHER_EXT_SS, FLASHER_EXT_TEST, FLASHER_EXT_TXD};
#endif
flasher::flasher() {
@@ -90,7 +95,7 @@ bool flasher::connectTag(uint8_t port) {
power_pins = validatePowerPinCount(powerPinsAP, sizeof(powerPinsAP));
result = zbs->begin(FLASHER_AP_SS, FLASHER_AP_CLK, FLASHER_AP_MOSI, FLASHER_AP_MISO, FLASHER_AP_RESET, (uint8_t *)powerPinsAP, power_pins, FLASHER_AP_SPEED);
break;
#ifdef OPENEPAPERLINK_PCB
#ifdef HAS_EXT_FLASHER
case 1:
power_pins = validatePowerPinCount(powerPinsExt, sizeof(powerPinsExt));
result = zbs->begin(FLASHER_EXT_SS, FLASHER_EXT_CLK, FLASHER_EXT_MOSI, FLASHER_EXT_MISO, FLASHER_EXT_RESET, (uint8_t *)powerPinsExt, power_pins, FLASHER_AP_SPEED);
@@ -101,18 +106,17 @@ bool flasher::connectTag(uint8_t port) {
break;
#endif
default:
Serial.printf("Tried to connect to port %d, but this port isn't available. Some dev borked it up, probably Jelmer.\n", port);
return false;
}
if (!result) Serial.printf("I tried connecting to port %d, but I couldn't establish a link to the tag. That's all I know.\n", port);
return result;
}
void flasher::getFirmwareMD5() {
bool flasher::getFirmwareMD5() {
uint8_t *buffer = (uint8_t *)malloc(FINGERPRINT_FLASH_SIZE);
if (buffer == nullptr) {
Serial.print("couldn't malloc bytes for firmware MD5\n");
return;
Seriallog.print("couldn't malloc bytes for firmware MD5\n");
return false;
}
zbs->select_flash(0);
@@ -131,8 +135,9 @@ void flasher::getFirmwareMD5() {
for (uint8_t c = 0; c < 16; c++) {
sprintf(md5char + (2 * c), "%02X", md5[c]);
}
Serial.printf("MD5=%s\n", md5char);
Seriallog.printf("MD5=%s\n", md5char);
free(buffer);
return true;
}
bool flasher::getInfoBlockMac() {
@@ -140,13 +145,11 @@ bool flasher::getInfoBlockMac() {
for (uint16_t c = 7; c < 8; c--) {
mac[7 - c] = zbs->read_flash(c + 0x10);
}
Serial.printf("Infopage mac=");
uint16_t macsum = 0;
for (uint8_t c = 0; c < 8; c++) {
macsum += mac[c];
Serial.printf("%02X", mac[c]);
}
Serial.printf("\n");
Seriallog.printf("Infopage mac=%02X%02X%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], mac[6], mac[7]);
if (macsum == 0) return false;
if (macsum > 0x5F9) return false;
return true;
@@ -162,7 +165,7 @@ bool flasher::getInfoBlockMD5() {
macsum += md5[c];
sprintf(md5char + (2 * c), "%02X", md5[c]);
}
Serial.printf("Infoblock MD5=%s\n", md5char);
Seriallog.printf("Infoblock MD5=%s\n", md5char);
if (macsum == 0) return false; // invalid mac
if (macsum > 0xF00) return false; // *probably* an invalid mac
return true;
@@ -183,9 +186,9 @@ bool flasher::findTagByMD5() {
const char *jsonmd5 = elem["MD5"];
if (jsonmd5 != nullptr) {
if (strncmp(md5char, jsonmd5, 32) == 0) {
Serial.print("MD5 Matches > ");
Seriallog.print("MD5 Matches > ");
const char *name = elem["name"];
Serial.println(name);
Seriallog.println(name);
mac_suffix = strtoul(elem["mac_suffix"], 0, 16);
mac_format = elem["mac_format"];
mac_offset = elem["mac_offset"];
@@ -197,7 +200,7 @@ bool flasher::findTagByMD5() {
}
Serial.print("Failed to find this tag's current firmware MD5 in the json database. If this tag is already OpenEpaperLink, this is to be expected.\n");
} else {
Serial.print("Failed to read json file\n");
Seriallog.print("Failed to read json file /tag_md5_db.json\n");
}
readfile.close();
return false;
@@ -212,9 +215,9 @@ bool flasher::findTagByType(uint8_t type) {
if (elem["type"] != nullptr) {
uint8_t jtype = elem["type"];
if (jtype == type) {
Serial.print("Type Matches > ");
Seriallog.print("Type Matches > ");
const char *name = elem["name"];
Serial.println(name);
Seriallog.println(name);
const char *jsonmd5 = elem["MD5"];
for (uint8_t c = 0; c < 16; c++) {
@@ -236,9 +239,9 @@ bool flasher::findTagByType(uint8_t type) {
}
}
}
Serial.print("Failed to find this tag's type in the json database.\n");
Seriallog.print("Failed to find this tag's type in the json database.\n");
} else {
Serial.print("Failed to read json file\n");
Seriallog.print("Failed to read json file\n");
}
readfile.close();
return false;
@@ -320,7 +323,7 @@ bool flasher::writeFlash(uint8_t *flashbuffer, uint16_t size) {
if (!zbs->select_flash(0)) return false;
zbs->erase_flash();
if (!zbs->select_flash(0)) return false;
Serial.printf("Starting flash, size=%d\n", size);
Seriallog.printf("Starting flash, size=%d\n", size);
for (uint16_t c = 0; c < size; c++) {
if (flashbuffer[c] == 0xFF) goto flashWriteSuccess;
for (uint8_t i = 0; i < MAX_WRITE_ATTEMPTS; i++) {
@@ -333,11 +336,11 @@ bool flasher::writeFlash(uint8_t *flashbuffer, uint16_t size) {
flashWriteSuccess:
if (c % 256 == 0) {
#ifdef HAS_RGB_LED
shortBlink(CRGB::Yellow);
shortBlink(CRGB::White);
#else
quickBlink(2);
#endif
Serial.printf("\rNow flashing, %d/%d ", c, size);
Seriallog.printf("\rNow flashing, %d/%d ", c, size);
vTaskDelay(1 / portTICK_PERIOD_MS);
}
}
@@ -412,7 +415,7 @@ bool flasher::writeFlashFromPackOffset(fs::File *file, uint16_t length) {
if (!zbs->select_flash(0)) return false;
zbs->erase_flash();
if (!zbs->select_flash(0)) return false;
Serial.printf("Starting flash, size=%d\n", length);
Seriallog.printf("Starting flash, size=%d\n", length);
uint8_t *buf = (uint8_t *)malloc(256);
uint16_t offset = 0;
@@ -425,20 +428,21 @@ bool flasher::writeFlashFromPackOffset(fs::File *file, uint16_t length) {
length = 0;
}
#ifdef HAS_RGB_LED
shortBlink(CRGB::Yellow);
shortBlink(CRGB::White);
#else
quickBlink(2);
#endif
Serial.printf("\rFlashing, %d bytes left ", length);
Seriallog.printf("\r[Flashing %d bytes] ", length);
bool res = writeBlock256(offset, buf);
offset += 256;
if (!res) {
Serial.printf("Failed writing block to tag, probably a hardware failure\n");
Seriallog.printf("Failed writing block to tag, probably a hardware failure\n");
return false;
}
vTaskDelay(1 / portTICK_PERIOD_MS);
}
Serial.printf("\nFlashing done\n");
Seriallog.printf("\nFlashing done\n");
return true;
}
@@ -452,8 +456,8 @@ bool flasher::writeFlashFromPack(String filename, uint8_t type) {
uint8_t jtype = elem["type"];
if (jtype == type) {
const char *name = elem["name"];
Serial.print("Flashing from FW pack: ");
Serial.println(name);
Seriallog.print("Flashing from FW pack: ");
Seriallog.println(name);
uint32_t offset = elem["offset"];
uint16_t length = elem["length"];
@@ -464,10 +468,10 @@ bool flasher::writeFlashFromPack(String filename, uint8_t type) {
}
}
}
Serial.print("Failed to find this tag's type in the FW pack database.\n");
Seriallog.print("Failed to find this tag's type in the FW pack database.\n");
} else {
Serial.println(err.c_str());
Serial.print("Failed to read json header from FW pack\n");
Seriallog.println(err.c_str());
Seriallog.print("Failed to read json header from FW pack\n");
}
readfile.close();
return false;
@@ -501,6 +505,7 @@ bool flasher::writeBlock(uint16_t offset, uint8_t *data, uint16_t len, bool info
return true;
}
#ifndef C6_OTA_FLASHING
uint16_t getAPUpdateVersion(uint8_t type) {
StaticJsonDocument<512> doc;
fs::File readfile = contentFS->open("/AP_FW_Pack.bin", "r");
@@ -625,12 +630,13 @@ bool doAPUpdate(uint8_t type) {
delete f;
return res;
}
#endif
void flashCountDown(uint8_t c) {
Serial.printf("\r%d ", c);
Seriallog.printf("\r%d ", c);
for (c -= 1; c < 254; c--) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.printf("\r%d ", c);
Seriallog.printf("\r%d ", c);
}
}
@@ -639,7 +645,7 @@ void flashCountDown(uint8_t c) {
bool doTagFlash() {
class flasher *f = new flasher();
if (!f->connectTag(FLASHER_EXT_PORT)) {
Serial.printf("Sorry, failed to connect to this tag...\n");
Seriallog.printf("Sorry, failed to connect to this tag...\n");
return false;
}
@@ -647,7 +653,7 @@ bool doTagFlash() {
if (f->findTagByMD5()) {
// this tag currently contains original firmware, found its fingerprint
Serial.printf("Found original firmware tag, recognized its fingerprint (%s)\n", f->md5char);
Seriallog.printf("Found original firmware tag, recognized its fingerprint (%s)\n", f->md5char);
f->readInfoBlock();
f->getFirmwareMac();
f->prepareInfoBlock();
@@ -658,7 +664,7 @@ bool doTagFlash() {
// did find an infoblock MD5 that looks valid
if (f->findTagByMD5()) {
// did find the md5 in the database
Serial.printf("Found an already-flashed tag, recognized its fingerprint (%s)\n", f->md5char);
Seriallog.printf("Found an already-flashed tag, recognized its fingerprint (%s)\n", f->md5char);
f->getInfoBlockMac();
f->getInfoBlockType();
f->readInfoBlock();
@@ -666,14 +672,14 @@ bool doTagFlash() {
f->zbs->reset();
} else {
// couldn't find the md5 from the infoblock
Serial.printf("Found an already-flashed tag, but we couldn't find its fingerprint (%s) in the database\n", f->md5char);
Seriallog.printf("Found an already-flashed tag, but we couldn't find its fingerprint (%s) in the database\n", f->md5char);
return false;
}
} else {
// We couldn't recognize the tag from it's fingerprint...
Serial.printf("Found a tag but didn't recognize its fingerprint\n", f->md5char);
Seriallog.printf("Found a tag but didn't recognize its fingerprint\n", f->md5char);
f->backupFlash();
Serial.printf("Saved this MD5 binary to filesystem\n");
Seriallog.printf("Saved this MD5 binary to filesystem\n");
}
delete f;
return false;

View File

@@ -7,7 +7,7 @@
#include "storage.h"
#include "tag_db.h"
#ifdef YELLOW_IPS_AP
#ifdef HAS_TFT
#include "ips_display.h"
@@ -15,15 +15,17 @@
#define TFT_BACKLIGHT 14
TFT_eSPI tft2 = TFT_eSPI();
int32_t tftid = -1;
uint8_t YellowSense = 0;
bool tftLogscreen = true;
bool tftOverride = false;
void TFTLog(String text) {
if (tftLogscreen == false) {
tft2.fillScreen(TFT_BLACK);
tft2.setCursor(0, 5, 2);
tft2.setCursor(0, 0, (tft2.width() == 160 ? 1 : 2));
tftLogscreen = true;
} else {
if (tft2.width() == 160) tft2.setCursor(0, tft2.getCursorY(), 1);
}
if (text.isEmpty()) return;
tft2.setTextColor(TFT_SILVER);
@@ -34,12 +36,18 @@ void TFTLog(String text) {
int httpIndex = text.indexOf("http");
tft2.print(text.substring(0, httpIndex));
tft2.setTextColor(TFT_YELLOW);
text = text.substring(httpIndex);
if (tft2.width() == 160) {
tft2.setCursor(0, tft2.getCursorY() + 8, 2);
text = text.substring(httpIndex + 7);
} else {
text = text.substring(httpIndex);
}
} else if (text.indexOf(":") != -1) {
int colonIndex = text.indexOf(":");
tft2.setTextColor(TFT_SILVER);
tft2.print(text.substring(0, colonIndex + 1));
tft2.setTextColor(TFT_WHITE);
if (tft2.width() == 160) tft2.setCursor(0, tft2.getCursorY() + 8, 2);
text = text.substring(colonIndex + 1);
} else if (text.endsWith("!")) {
tft2.setTextColor(TFT_GREEN);
@@ -64,36 +72,34 @@ void sendAvail(uint8_t wakeupReason) {
memcpy(&eadr.src, mac, 6);
eadr.adr.lastPacketRSSI = WiFi.RSSI();
eadr.adr.currentChannel = config.channel;
eadr.adr.hwType = 0xE0;
eadr.adr.hwType = (tft2.width() == 160 ? 0xE1 : 0xE0);
eadr.adr.wakeupReason = wakeupReason;
eadr.adr.capabilities = 0;
eadr.adr.tagSoftwareVersion = 0;
eadr.adr.customMode = 0;
processDataReq(&eadr, true);
if (wakeupReason) tftid = findId(eadr.src);
}
void yellow_ap_display_init(void) {
pinMode(YELLOW_SENSE, INPUT_PULLDOWN);
vTaskDelay(100 / portTICK_PERIOD_MS);
if (digitalRead(YELLOW_SENSE) == HIGH) YellowSense = 1;
pinMode(TFT_BACKLIGHT, OUTPUT);
digitalWrite(TFT_BACKLIGHT, HIGH);
ledcSetup(6, 5000, 8);
ledcAttachPin(TFT_BACKLIGHT, 6);
ledcWrite(6, config.tft);
digitalWrite(TFT_BACKLIGHT, LOW);
tft2.init();
tft2.setRotation(YellowSense == 1 ? 1 : 3);
tft2.fillScreen(TFT_BLACK);
tft2.setCursor(10, 5, 2);
tft2.setCursor(12, 0, (tft2.width() == 160 ? 1 : 2));
tft2.setTextColor(TFT_WHITE);
tft2.println("*** Initialising... ***");
tftLogscreen = true;
ledcSetup(6, 5000, 8);
ledcAttachPin(TFT_BACKLIGHT, 6);
if (tft2.width() == 160) {
GPIO.func_out_sel_cfg[TFT_BACKLIGHT].inv_sel = 1;
}
ledcWrite(6, config.tft);
}
void yellow_ap_display_loop(void) {
@@ -110,9 +116,16 @@ void yellow_ap_display_loop(void) {
sendAvail(0xFC);
first_run = 1;
}
if (millis() - last_update >= 1000) {
tagRecord* tag = tagDB.at(tftid);
if (tag->pendingCount > 0) {
if (millis() - last_update >= 3000) {
uint8_t wifimac[8];
WiFi.macAddress(wifimac);
memset(&wifimac[6], 0, 2);
tagRecord* tag = tagRecord::findByMAC(wifimac);
if (tag == nullptr) {
last_update = millis();
return;
}
if (tag->pendingCount > 0 && tftOverride == false) {
String filename = tag->filename;
fs::File file = contentFS->open(filename);
if (!file) {
@@ -122,7 +135,7 @@ void yellow_ap_display_loop(void) {
}
TFT_eSprite spr = TFT_eSprite(&tft2);
if (tag->len == tft2.width() * tft2.height() * 2) spr.setColorDepth(16);
spr.setColorDepth(16);
if (tag->len == tft2.width() * tft2.height()) spr.setColorDepth(8);
spr.createSprite(tft2.width(), tft2.height());
void* spriteData = spr.getPointer();

View File

@@ -8,6 +8,7 @@
#include "leds.h"
#include "settings.h"
#include "tag_db.h"
#include "serialap.h"
QueueHandle_t ledQueue;
int maxledbrightness = 255;
@@ -58,7 +59,6 @@ void addFadeColor(CRGB cname) {
}
void shortBlink(CRGB cname) {
#ifndef YELLOW_IPS_AP
struct ledInstructionRGB* rgb = new struct ledInstructionRGB;
rgb->ledColor = CRGB::Black;
rgb->fadeTime = 0;
@@ -75,7 +75,6 @@ void shortBlink(CRGB cname) {
rgb->fadeTime = 0;
rgb->length = 3;
addToRGBQueue(rgb, false);
#endif
}
void flushRGBQueue() {
@@ -91,6 +90,8 @@ void showColorPattern(CRGB colorone, CRGB colortwo, CRGB colorthree) {
const int patternLengths[] = {600, 120, 200, 120, 200, 120};
const CRGB patternColors[] = {CRGB::Black, colorone, CRGB::Black, colortwo, CRGB::Black, colorthree};
while (xQueueReceive(rgbLedQueue, &rgb, 0) == pdPASS) { }
for (int i = 0; i < sizeof(patternLengths) / sizeof(patternLengths[0]); i++) {
rgb = new struct ledInstructionRGB;
rgb->ledColor = patternColors[i];
@@ -131,7 +132,7 @@ void rgbIdleStep() {
void setBrightness(int brightness) {
maxledbrightness = brightness;
#ifdef YELLOW_IPS_AP
#ifdef HAS_TFT
ledcWrite(6, config.tft);
#endif
#ifdef HAS_RGB_LED
@@ -140,14 +141,14 @@ void setBrightness(int brightness) {
}
void updateBrightnessFromConfig() {
if (config.led != 0) {
int newbrightness = config.led;
if (newbrightness < 0) newbrightness = 0;
if (newbrightness != maxledbrightness) {
setBrightness(newbrightness);
}
int newbrightness = config.led;
if (newbrightness != maxledbrightness) {
setBrightness(newbrightness);
}
#ifdef HAS_TFT
ledcWrite(6, config.tft);
#endif
if (apInfo.state == AP_STATE_NORADIO) addFadeMono(config.led);
}
void addToMonoQueue(struct ledInstruction* mono) {
@@ -176,7 +177,7 @@ void showMono(uint8_t brightness) {
void quickBlink(uint8_t repeat) {
for (int i = 0; i < repeat; i++) {
struct ledInstruction* mono = new struct ledInstruction;
#ifdef YELLOW_IPS_AP
#ifdef HAS_TFT
mono->value = 255;
#else
mono->value = maxledbrightness;
@@ -222,7 +223,7 @@ void ledTask(void* parameter) {
struct ledInstruction* monoled = nullptr;
addFadeMono(0);
#ifdef YELLOW_IPS_AP
#ifdef HAS_TFT
addFadeMono(255);
#else
addFadeMono(maxledbrightness);

View File

@@ -13,7 +13,11 @@
#include "tagdata.h"
#include "wifimanager.h"
#ifdef HAS_USB
#ifdef HAS_EXT_FLASHER
#include "webflasher.h"
#endif
#if defined HAS_USB || defined HAS_EXT_FLASHER
#include "usbflasher.h"
#endif
@@ -28,10 +32,6 @@ util::Timer intervalSysinfo(seconds(5));
util::Timer intervalVars(seconds(10));
util::Timer intervalSaveDB(minutes(5));
#ifdef OPENEPAPERLINK_PCB
util::Timer tagConnectTimer(seconds(1));
#endif
SET_LOOP_TASK_STACK_SIZE(16 * 1024);
void delayedStart(void* parameter) {
@@ -47,7 +47,7 @@ void delayedStart(void* parameter) {
void setup() {
Serial.begin(115200);
Serial.print(">\n");
#ifdef YELLOW_IPS_AP
#ifdef HAS_TFT
extern void yellow_ap_display_init(void);
yellow_ap_display_init();
#endif
@@ -108,16 +108,12 @@ void setup() {
}
*/
#ifdef HAS_USB
// We'll need to start the 'usbflasher' task for boards with a second (USB) port. This can be used as a 'flasher' interface, using a python script on the host
xTaskCreate(usbFlasherTask, "usbflasher", 10000, NULL, configMAX_PRIORITIES - 10, NULL);
#endif
initAPconfig();
updateLanguageFromConfig();
updateBrightnessFromConfig();
config.runStatus = RUNSTATUS_INIT;
init_web();
xTaskCreate(initTime, "init time", 5000, NULL, 2, NULL);
@@ -135,10 +131,18 @@ void setup() {
} else {
cleanupCurrent();
}
xTaskCreate(APTask, "AP Process", 6000, NULL, 2, NULL);
xTaskCreate(APTask, "AP Process", 6000, NULL, 5, NULL);
vTaskDelay(10 / portTICK_PERIOD_MS);
config.runStatus = RUNSTATUS_INIT;
#ifdef HAS_USB
// We'll need to start the 'usbflasher' task for boards with a second (USB) port. This can be used as a 'flasher' interface, using a python script on the host
xTaskCreate(usbFlasherTask, "usbflasher", 10000, NULL, 5, NULL);
#endif
#ifdef HAS_EXT_FLASHER
xTaskCreate(webFlasherTask, "webflasher", 8000, NULL, 3, NULL);
#endif
esp_reset_reason_t resetReason = esp_reset_reason();
if (resetReason == ESP_RST_PANIC) {
Serial.println("Panic! Pausing content generation for 30 seconds");
@@ -164,29 +168,14 @@ void loop() {
if (intervalSaveDB.doRun() && config.runStatus != RUNSTATUS_STOP) {
saveDB("/current/tagDB.json");
}
if (intervalContentRunner.doRun() && apInfo.state == AP_STATE_ONLINE) {
if (intervalContentRunner.doRun() && (apInfo.state == AP_STATE_ONLINE || apInfo.state == AP_STATE_NORADIO)) {
contentRunner();
}
#ifdef YELLOW_IPS_AP
#ifdef HAS_TFT
extern void yellow_ap_display_loop(void);
yellow_ap_display_loop();
#endif
#ifdef OPENEPAPERLINK_PCB
if (tagConnectTimer.doRun() && extTagConnected()) {
flashCountDown(3);
pinMode(FLASHER_EXT_TEST, OUTPUT);
digitalWrite(FLASHER_EXT_TEST, LOW);
doTagFlash();
vTaskDelay(10000 / portTICK_PERIOD_MS);
pinMode(FLASHER_EXT_TEST, INPUT);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
#endif
vTaskDelay(100 / portTICK_PERIOD_MS);
}

View File

@@ -10,6 +10,10 @@
#include "storage.h"
#include "util.h"
#ifdef HAS_TFT
#include "ips_display.h"
#endif
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
@@ -276,11 +280,22 @@ void rewriteHeader(File &f_out) {
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
long t = millis();
#ifdef YELLOW_IPS_AP
#ifdef HAS_TFT
extern uint8_t YellowSense;
if (fileout == "direct") {
tft.setRotation(YellowSense == 1 ? 1 : 3);
spr.pushSprite(0, 0);
if (tftOverride == false) {
TFT_eSprite spr2 = TFT_eSprite(&tft2);
tft2.setRotation(YellowSense == 1 ? 1 : 3);
spr2.createSprite(spr.width(), spr.height());
spr2.setColorDepth(spr.getColorDepth());
void *spriteData = spr.getPointer();
void *spriteData2 = spr2.getPointer();
size_t dataSize = spr.width() * spr.height() * (spr.getColorDepth() / 8);
memcpy(spriteData2, spriteData, dataSize);
spr2.pushSprite(0, 0);
}
return;
}
#endif

View File

@@ -82,10 +82,38 @@ void prepareIdleReq(const uint8_t* dst, uint16_t nextCheckin) {
pending.attemptsLeft = 10 + config.maxsleep;
Serial.printf(">SDA %02X%02X%02X%02X%02X%02X%02X%02X NOP\n", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
queueDataAvail(&pending);
queueDataAvail(&pending, true);
}
}
void prepareDataAvail(const uint8_t* dst) {
// resend
tagRecord* taginfo = tagRecord::findByMAC(dst);
if (taginfo == nullptr) {
if (config.lock) return;
wsErr("Tag not found, this shouldn't happen.");
return;
}
taginfo->pendingCount++;
taginfo->pendingIdle = 0;
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
pending.availdatainfo.dataSize = taginfo->len;
pending.availdatainfo.dataType = taginfo->dataType;
pending.availdatainfo.nextCheckIn = 0;
memcpy(&pending.availdatainfo.dataVer, taginfo->md5, sizeof(uint64_t));
pending.attemptsLeft = 10;
queueDataAvail(&pending, !taginfo->isExternal);
if (taginfo->isExternal) {
udpsync.netSendDataAvail(&pending);
}
wsSendTaginfo(dst, SYNC_TAGSTATUS);
}
void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, const uint8_t* dst) {
tagRecord* taginfo = tagRecord::findByMAC(dst);
if (taginfo == nullptr) {
@@ -121,23 +149,22 @@ void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, const uint8
memcpy(pending.targetMac, dst, 8);
pending.availdatainfo.dataSize = len;
pending.availdatainfo.dataType = dataType;
pending.availdatainfo.nextCheckIn = 0;
pending.availdatainfo.nextCheckIn = 5 | 0x8000; // 5 seconds
pending.availdatainfo.dataVer = *((uint64_t*)md5bytes);
pending.attemptsLeft = 10;
queueDataAvail(&pending, !taginfo->isExternal);
if (taginfo->isExternal) {
udpsync.netSendDataAvail(&pending);
} else {
queueDataAvail(&pending);
}
wsSendTaginfo(dst, SYNC_TAGSTATUS);
}
bool prepareDataAvail(String& filename, uint8_t dataType, uint8_t dataTypeArgument, const uint8_t* dst, uint16_t nextCheckin, bool resend) {
if (nextCheckin > config.maxsleep) nextCheckin = config.maxsleep;
if (wsClientCount() && config.stopsleep == 1) nextCheckin = 0;
#ifdef YELLOW_IPS_AP
if ((nextCheckin & 0x8000) == 0 && nextCheckin > config.maxsleep) nextCheckin = config.maxsleep;
if ((nextCheckin & 0x8000) == 0 && wsClientCount() && (config.stopsleep == 1)) nextCheckin = 0;
#ifdef HAS_TFT
if (filename == "direct") {
char dst_path[64];
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.raw\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
@@ -196,7 +223,8 @@ bool prepareDataAvail(String& filename, uint8_t dataType, uint8_t dataTypeArgume
time_t now;
time(&now);
taginfo->pendingIdle = nextCheckin * 60 + 60;
taginfo->pendingIdle = (nextCheckin & 0x8000) ? (nextCheckin & 0x7FFF) + 5 : (nextCheckin * 60) + 60;
clearPending(taginfo);
} else {
wsLog("firmware upload pending");
@@ -216,9 +244,9 @@ bool prepareDataAvail(String& filename, uint8_t dataType, uint8_t dataTypeArgume
pending.availdatainfo.nextCheckIn = nextCheckin;
pending.attemptsLeft = attempts;
checkMirror(taginfo, &pending);
queueDataAvail(&pending, !taginfo->isExternal);
if (taginfo->isExternal == false) {
Serial.printf(">SDA %02X%02X%02X%02X%02X%02X%02X%02X TYPE 0x%02X\n", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0], pending.availdatainfo.dataType);
queueDataAvail(&pending);
} else {
udpsync.netSendDataAvail(&pending);
}
@@ -241,10 +269,12 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
char hexmac[17];
mac2hex(pending->targetMac, hexmac);
String filename = "/current/" + String(hexmac) + "_" + String(millis() % 1000000) + ".pending";
String imageUrl = "http://" + remoteIP.toString() + "/getdata?mac=" + String(hexmac);
wsLog("prepareExternalDataAvail GET " + imageUrl);
char md5[17];
mac2hex(reinterpret_cast<uint8_t*>(&pending->availdatainfo.dataVer), md5);
char imageUrl[80];
snprintf(imageUrl, sizeof(imageUrl), "http://%s/getdata?mac=%s&md5=%s", remoteIP.toString().c_str(), hexmac, md5);
wsLog("prepareExternalDataAvail GET " + String(imageUrl));
HTTPClient http;
logLine("http prepareExternalDataAvail " + imageUrl);
http.begin(imageUrl);
int httpCode = http.GET();
if (httpCode == 200) {
@@ -254,9 +284,9 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
file.close();
xSemaphoreGive(fsMutex);
} else if (httpCode == 404) {
imageUrl = "http://" + remoteIP.toString() + "/current/" + String(hexmac) + ".raw";
snprintf(imageUrl, sizeof(imageUrl), "http://%s/current/%s.raw", remoteIP.toString().c_str(), hexmac);
// imageUrl = "http://" + remoteIP.toString() + "/current/" + String(hexmac) + ".raw";
http.end();
logLine("http prepareExternalDataAvail " + imageUrl);
http.begin(imageUrl);
httpCode = http.GET();
if (httpCode == 200) {
@@ -266,6 +296,9 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
file.close();
xSemaphoreGive(fsMutex);
}
} else {
logLine("prepareExternalDataAvail " + String(imageUrl) + " error " + String(httpCode));
wsLog("error " + String(httpCode));
}
http.end();
@@ -299,10 +332,13 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
case DATATYPE_CUSTOM_LUT_OTA: {
char hexmac[17];
mac2hex(pending->targetMac, hexmac);
String dataUrl = "http://" + remoteIP.toString() + "/getdata?mac=" + String(hexmac);
wsLog("GET " + dataUrl);
char dataUrl[80];
char md5[17];
mac2hex(reinterpret_cast<uint8_t*>(&pending->availdatainfo.dataVer), md5);
snprintf(dataUrl, sizeof(dataUrl), "http://%s/getdata?mac=%s&md5=%s", remoteIP.toString().c_str(), hexmac, md5);
wsLog("GET " + String(dataUrl));
HTTPClient http;
logLine("http DATATYPE_CUSTOM_LUT_OTA " + dataUrl);
logLine("http DATATYPE_CUSTOM_LUT_OTA " + String(dataUrl));
http.begin(dataUrl);
int httpCode = http.GET();
if (httpCode == 200) {
@@ -325,7 +361,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
}
}
checkMirror(taginfo, pending);
queueDataAvail(pending);
queueDataAvail(pending, !taginfo->isExternal);
wsSendTaginfo(pending->targetMac, SYNC_NOSYNC);
}
@@ -365,7 +401,6 @@ void processBlockRequest(struct espBlockRequest* br) {
if (br->blockId >= totalblocks) {
br->blockId = totalblocks - 1;
}
uint32_t len = queueItem->len - (BLOCK_DATA_SIZE * br->blockId);
if (len > BLOCK_DATA_SIZE) len = BLOCK_DATA_SIZE;
uint16_t checksum = sendBlock(queueItem->data + (br->blockId * BLOCK_DATA_SIZE), len);
@@ -431,7 +466,7 @@ void processXferComplete(struct espXferComplete* xfc, bool local) {
}
// more in the queue?
checkQueue(xfc->src);
if (local) checkQueue(xfc->src);
wsSendTaginfo(xfc->src, SYNC_TAGSTATUS);
if (local) udpsync.netProcessXferComplete(xfc);
@@ -507,7 +542,7 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP
taginfo->expectedNextCheckin = 3216153600;
taginfo->pendingIdle = 0;
} else {
taginfo->expectedNextCheckin = now + 60 * taginfo->pendingIdle;
taginfo->expectedNextCheckin = now + taginfo->pendingIdle;
taginfo->pendingIdle = 0;
}
taginfo->lastseen = now;
@@ -537,7 +572,7 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP
taginfo->temperature = eadr->adr.temperature;
taginfo->batteryMv = eadr->adr.batteryMv;
taginfo->hwType = eadr->adr.hwType;
taginfo->wakeupReason = eadr->adr.wakeupReason;
if (eadr->adr.wakeupReason > 0) taginfo->wakeupReason = eadr->adr.wakeupReason;
taginfo->capabilities = eadr->adr.capabilities;
taginfo->currentChannel = eadr->adr.currentChannel;
taginfo->tagSoftwareVersion = eadr->adr.tagSoftwareVersion;
@@ -618,8 +653,9 @@ bool sendAPSegmentedData(const uint8_t* dst, String data, uint16_t icons, bool i
pending.attemptsLeft = 120;
Serial.printf(">AP Segmented Data %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
if (local) {
return queueDataAvail(&pending);
return queueDataAvail(&pending, true);
} else {
queueDataAvail(&pending, false);
udpsync.netSendDataAvail(&pending);
return true;
}
@@ -636,8 +672,9 @@ bool showAPSegmentedInfo(const uint8_t* dst, bool local) {
pending.attemptsLeft = 120;
Serial.printf(">SDA %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
if (local) {
return queueDataAvail(&pending);
return queueDataAvail(&pending, true);
} else {
queueDataAvail(&pending, false);
udpsync.netSendDataAvail(&pending);
return true;
}
@@ -663,8 +700,9 @@ bool sendTagCommand(const uint8_t* dst, uint8_t cmd, bool local, const uint8_t*
}
if (local) {
return queueDataAvail(&pending);
return queueDataAvail(&pending, true);
} else {
queueDataAvail(&pending, false);
udpsync.netSendDataAvail(&pending);
return true;
}
@@ -755,7 +793,7 @@ bool checkMirror(struct tagRecord* taginfo, struct pendingData* pending) {
pending2.attemptsLeft = pending->attemptsLeft;
if (taginfo2->isExternal == false) {
queueDataAvail(&pending2);
queueDataAvail(&pending2, true);
} else {
char dst_path[64];
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X_%lu.pending", taginfo2->mac[7], taginfo2->mac[6], taginfo2->mac[5], taginfo2->mac[4], taginfo2->mac[3], taginfo2->mac[2], taginfo2->mac[1], taginfo2->mac[0], millis() % 1000000);
@@ -765,6 +803,7 @@ bool checkMirror(struct tagRecord* taginfo, struct pendingData* pending) {
file.write(taginfo2->data, taginfo2->len);
file.close();
xSemaphoreGive(fsMutex);
queueDataAvail(&pending2, false);
udpsync.netSendDataAvail(&pending2);
} else {
xSemaphoreGive(fsMutex);
@@ -850,12 +889,12 @@ void checkQueue(const uint8_t* targetMac) {
if (queueItem == nullptr) {
return;
}
if (queueCount > 1) queueItem->pendingdata.availdatainfo.nextCheckIn = 0;
if (queueCount > 1) queueItem->pendingdata.availdatainfo.nextCheckIn = 5 | 0x8000;
sendDataAvail(&queueItem->pendingdata);
}
}
bool queueDataAvail(struct pendingData* pending) {
bool queueDataAvail(struct pendingData* pending, bool local) {
PendingItem newPending;
newPending.pendingdata.availdatainfo = pending->availdatainfo;
newPending.pendingdata.attemptsLeft = pending->attemptsLeft;
@@ -882,7 +921,7 @@ bool queueDataAvail(struct pendingData* pending) {
Serial.println("Reading file " + String(newPending.filename));
file.close();
} else {
Serial.println("Something's wrong... not found: " + String(newPending.filename));
Serial.println("Warning: not found: " + String(newPending.filename));
}
}
newPending.len = taginfo->len;
@@ -891,10 +930,11 @@ bool queueDataAvail(struct pendingData* pending) {
enqueueItem(newPending);
// first in line, send to tag
Serial.printf("queue item added, first in line, total %d elements\n", pendingQueue.size());
sendDataAvail(pending);
if (local) sendDataAvail(pending);
} else {
enqueueItem(newPending);
Serial.printf("queue item added, total %d elements\n", pendingQueue.size());
// to do: notify C6 to shorten the checkin time for the current SDA
}
return true;

View File

@@ -41,12 +41,16 @@ void handleSysinfoRequest(AsyncWebServerRequest* request) {
doc["psramsize"] = ESP.getPsramSize();
doc["flashsize"] = ESP.getFlashChipSize();
doc["rollback"] = Update.canRollBack();
#if defined YELLOW_IPS_AP || defined C6_OTA_FLASHING
doc["C6"] = 1;
#if defined C6_OTA_FLASHING
doc["hasC6"] = 1;
#else
doc["C6"] = 0;
doc["hasC6"] = 0;
#endif
#ifdef HAS_EXT_FLASHER
doc["hasFlasher"] = 1;
#else
doc["hasFlasher"] = 0;
#endif
const size_t bufferSize = measureJson(doc) + 1;
AsyncResponseStream* response = request->beginResponseStream("application/json", bufferSize);
serializeJson(doc, *response);
@@ -266,7 +270,7 @@ void C6firmwareUpdateTask(void* parameter) {
Serial1.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD);
rxSerialStopTask2 = false;
#ifdef FLASHER_DEBUG_RXD
xTaskCreate(rxSerialTask2, "rxSerialTask2", 1750, NULL, configMAX_PRIORITIES - 4, NULL);
xTaskCreate(rxSerialTask2, "rxSerialTask2", 1750, NULL, 2, NULL);
#endif
vTaskDelay(1000 / portTICK_PERIOD_MS);
@@ -285,7 +289,7 @@ void C6firmwareUpdateTask(void* parameter) {
}
void handleUpdateC6(AsyncWebServerRequest* request) {
#if defined YELLOW_IPS_AP || defined C6_OTA_FLASHING
#if defined C6_OTA_FLASHING
uint8_t doDownload = 1;
if (request->hasParam("download", true)) {
doDownload = atoi(request->getParam("download", true)->value().c_str());

View File

@@ -4,7 +4,7 @@
#include "settings.h"
#ifdef OPENEPAPERLINK_PCB
#ifdef HAS_EXT_FLASHER
#include "soc/rtc_cntl_reg.h"
#include "soc/soc.h"
#endif
@@ -29,7 +29,7 @@ void simpleAPPower(uint8_t* pin, uint8_t pincount, bool state) {
// when drained if the board applies power, will cause the 3v3 rail to sag enough to reset the ESP32. This is obviously not great. To prevent this from happening,
// we ramp up/down the voltage with PWM. Ramping down really is unnecessary, as the board has a resistor to dump the charge into.
void rampTagPower(uint8_t* pin, bool up) {
#ifdef OPENEPAPERLINK_PCB
#ifdef HAS_EXT_FLASHER
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
#endif
if (up) {
@@ -69,7 +69,7 @@ void rampTagPower(uint8_t* pin, bool up) {
ledcDetachPin(pin[0]);
digitalWrite(pin[0], HIGH);
}
#ifdef OPENEPAPERLINK_PCB
#ifdef HAS_EXT_FLASHER
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1);
#endif
}

View File

@@ -1,8 +1,8 @@
#include "serialap.h"
#include <Arduino.h>
#include <HardwareSerial.h>
#include "serialap.h"
#include "commstructs.h"
#include "contentmanager.h"
#include "flasher.h"
@@ -118,7 +118,7 @@ int8_t APpowerPins[] = FLASHER_AP_POWER;
#define AP_RESET_PIN FLASHER_AP_RESET
#define AP_POWER_PIN FLASHER_AP_POWER
#endif
#ifdef OPENEPAPERLINK_PCB
#ifdef HAS_EXT_FLASHER
#if (AP_PROCESS_PORT == FLASHER_EXT_PORT)
int8_t APpowerPins[] = FLASHER_EXT_POWER;
#define AP_RESET_PIN FLASHER_EXT_RESET
@@ -172,9 +172,11 @@ void APTagReset() {
// 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;
for (uint8_t attempt = 0; attempt < 5; attempt++) {
// 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;
@@ -190,32 +192,51 @@ blksend:
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 += ((uint8_t*)data)[c];
bd->checksum += dataBytes[c];
}
// send blockData header
for (uint8_t c = 0; c < sizeof(struct blockData); c++) {
AP_SERIAL_PORT.write(0xAA ^ blockbuffer[c]);
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;
for (c = 0; c < len; c++) {
AP_SERIAL_PORT.write(0xAA ^ ((uint8_t*)data)[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)
for (; c < BLOCK_DATA_SIZE; c++) {
AP_SERIAL_PORT.write(0x55);
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
for (c = 0; c < 32; c++) {
AP_SERIAL_PORT.write(0xF5);
}
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;
}
@@ -353,21 +374,21 @@ void rxCmdProcessor(void* parameter) {
case RX_CMD_RQB:
processBlockRequest((struct espBlockRequest*)rxcmd->data);
#ifdef HAS_RGB_LED
shortBlink(CRGB::Blue);
// shortBlink(CRGB::Blue);
#endif
quickBlink(3);
break;
case RX_CMD_ADR:
processDataReq((struct espAvailDataReq*)rxcmd->data, true);
#ifdef HAS_RGB_LED
shortBlink(CRGB::Aqua);
// shortBlink(CRGB::Aqua);
#endif
quickBlink(1);
break;
case RX_CMD_XFC:
processXferComplete((struct espXferComplete*)rxcmd->data, true);
#ifdef HAS_RGB_LED
shortBlink(CRGB::Purple);
// shortBlink(CRGB::Purple);
#endif
break;
case RX_CMD_XTO:
@@ -402,7 +423,9 @@ void rxSerialTask(void* parameter) {
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];
@@ -614,6 +637,8 @@ void rxSerialTask2(void* parameter) {
while (Serial2.available()) {
lastchar = Serial2.read();
charCount++;
// debug info
Serial.write(lastchar);
}
vTaskDelay(1 / portTICK_PERIOD_MS);
@@ -723,17 +748,34 @@ bool bringAPOnline() {
}
}
bool checkRadio() {
// 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) {
xTaskCreate(rxCmdProcessor, "rxCmdProcessor", 4000, NULL, configMAX_PRIORITIES - 10, NULL);
xTaskCreate(rxSerialTask, "rxSerialTask", 1750, NULL, configMAX_PRIORITIES - 4, NULL);
#ifdef FLASHER_DEBUG_RXD
xTaskCreate(rxSerialTask2, "rxSerialTask2", 1750, NULL, configMAX_PRIORITIES - 4, NULL);
#endif
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 OPENEPAPERLINK_PCB
#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
@@ -742,8 +784,15 @@ void APTask(void* parameter) {
#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
bringAPOnline();
#ifndef C6_OTA_FLASHING
if (checkForcedAPFlash() && FLASHER_AP_MOSI != -1) {
if (apInfo.type == SOLUM_SEG_UK && apInfo.isOnline) {
notifySegmentedFlash();
@@ -756,6 +805,7 @@ void APTask(void* parameter) {
checkWaitPowerCycle();
bringAPOnline();
}
#endif
if (apInfo.isOnline) {
// AP works!
@@ -770,6 +820,7 @@ void APTask(void* parameter) {
}
uint16_t fsversion;
#ifndef C6_OTA_FLASHING
if (FLASHER_AP_MOSI != -1) {
fsversion = getAPUpdateVersion(apInfo.type);
if ((fsversion) && (apInfo.version != fsversion)) {
@@ -805,6 +856,8 @@ void APTask(void* parameter) {
}
}
}
#endif
refreshAllPending();
} else {
#ifndef FLASH_TIMEOUT
@@ -817,9 +870,10 @@ void APTask(void* parameter) {
#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
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);
@@ -886,13 +940,14 @@ void APTask(void* parameter) {
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)) {
if (((apInfo.state == AP_STATE_ONLINE) || (apInfo.state == AP_STATE_FAILED)) && (millis() - lastAPActivity > AP_ACTIVITY_MAX_INTERVAL)) {
bool reply = sendPing();
if (!reply) {
attempts++;

View File

@@ -14,11 +14,12 @@ void timeSyncCallback(struct timeval* tv) {
}
void initTime(void* parameter) {
if (WiFi.status() == WL_CONNECTED) {
sntp_set_time_sync_notification_cb(timeSyncCallback);
sntp_set_sync_interval(300 * 1000);
configTzTime(config.timeZone, "nl.pool.ntp.org", "europe.pool.ntp.org", "time.nist.gov");
if (WiFi.status() != WL_CONNECTED) {
vTaskDelay(500 / portTICK_PERIOD_MS);
}
sntp_set_time_sync_notification_cb(timeSyncCallback);
sntp_set_sync_interval(300 * 1000);
configTzTime(config.timeZone, "time.cloudflare.com", "pool.ntp.org", "time.nist.gov");
logStartUp();
struct tm timeinfo;
while (millis() < 30000) {

View File

@@ -314,22 +314,22 @@ void initAPconfig() {
}
configFile.close();
}
config.channel = APconfig["channel"] | 0;
config.channel = APconfig.containsKey("channel") ? APconfig["channel"] : 0;
if (APconfig["alias"]) strlcpy(config.alias, APconfig["alias"], sizeof(config.alias));
config.led = APconfig["led"] | 255;
config.tft = APconfig["tft"] | 255;
config.language = APconfig["language"] | 0;
config.maxsleep = APconfig["maxsleep"] | 10;
config.stopsleep = APconfig["stopsleep"] | 1;
config.preview = APconfig["preview"] | 1;
config.lock = APconfig["lock"] | 0;
config.sleepTime1 = APconfig["sleeptime1"] | 0;
config.sleepTime2 = APconfig["sleeptime2"] | 0;
config.led = APconfig.containsKey("led") ? APconfig["led"] : 255;
config.tft = APconfig.containsKey("tft") ? APconfig["tft"] : 255;
config.language = APconfig.containsKey("language") ? APconfig["language"] : 0;
config.maxsleep = APconfig.containsKey("maxsleep") ? APconfig["maxsleep"] : 10;
config.stopsleep = APconfig.containsKey("stopsleep") ? APconfig["stopsleep"] : 1;
config.preview = APconfig.containsKey("preview") ? APconfig["preview"] : 1;
config.lock = APconfig.containsKey("lock") ? APconfig["lock"] : 0;
config.sleepTime1 = APconfig.containsKey("sleeptime1") ? APconfig["sleeptime1"] : 0;
config.sleepTime2 = APconfig.containsKey("sleeptime2") ? APconfig["sleeptime2"] : 0;
// default wifi power 8.5 dbM
// see https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiGeneric.h#L111
config.wifiPower = APconfig["wifipower"] | 34;
config.repo = APconfig["repo"] | "jjwbruijn/OpenEPaperLink";
config.env = APconfig["env"] | STR(BUILD_ENV_NAME);
config.wifiPower = APconfig.containsKey("wifipower") ? APconfig["wifipower"] : 34;
config.repo = APconfig.containsKey("repo") ? APconfig["repo"].as<String>() : String("jjwbruijn/OpenEPaperLink");
config.env = APconfig.containsKey("env") ? APconfig["env"].as<String>() : String(STR(BUILD_ENV_NAME));
if (APconfig["timezone"]) {
strlcpy(config.timeZone, APconfig["timezone"], sizeof(config.timeZone));
} else {
@@ -425,12 +425,14 @@ bool setVarDB(const std::string& key, const String& value, const bool notify) {
}
String getBaseName(const String& filename) {
int lastDotIndex = filename.lastIndexOf('.');
return lastDotIndex != -1 ? filename.substring(0, lastDotIndex) : filename;
// int lastDotIndex = filename.lastIndexOf('.');
// return lastDotIndex != -1 ? filename.substring(0, lastDotIndex) : filename;
return filename.substring(0, 16);
}
void cleanupCurrent() {
// clean unknown previews
Serial.println("Cleaning up temporary files");
File dir = contentFS->open("/current");
File file = dir.openNextFile();
while (file) {
@@ -444,9 +446,10 @@ void cleanupCurrent() {
break;
}
}
if (!found) {
if (!found || filename.endsWith(".pending")) {
filename = file.path();
file.close();
Serial.println("remove " + filename);
contentFS->remove(filename);
}
}

View File

@@ -152,7 +152,7 @@ void UDPcomm::getAPList() {
wsSendAPitem(&APitem);
if (config.alias == 0) {
xTaskCreate(autoselect, "autoselect", 5000, NULL, configMAX_PRIORITIES - 10, NULL);
xTaskCreate(autoselect, "autoselect", 5000, NULL, 2, NULL);
}
uint8_t buffer[sizeof(struct APlist) + 1];

View File

@@ -1,14 +1,26 @@
#include <Arduino.h>
#include "usbflasher.h"
#ifdef HAS_USB
// flashing via 2nd USB port
#include "USB.h"
USBCDC USBSerial;
#define cmdSerial USBSerial
#else
#define cmdSerial Serial
#endif
// #include "esp32-hal-tinyusb.h"
#include "flasher.h"
#include "leds.h"
#include "powermgt.h"
#include "settings.h"
#include "swd.h"
#include "web.h"
#include "webflasher.h"
#include "zbs_interface.h"
USBCDC USBSerial;
QueueHandle_t flasherCmdQueue;
uint32_t usbConnectedStartTime = 0;
@@ -21,6 +33,7 @@ bool serialPassthroughState = false;
#define FLASHER_WAIT_DATA 4
#define FLASHER_WAIT_CRCH 5
#define FLASHER_WAIT_CRCL 6
#define FLASHER_RESET 7
struct flasherCommand {
uint8_t command = 0;
@@ -29,7 +42,7 @@ struct flasherCommand {
};
int8_t powerPins[] = FLASHER_AP_POWER;
#ifdef OPENEPAPERLINK_PCB
#ifdef HAS_EXT_FLASHER
int8_t powerPins2[] = FLASHER_EXT_POWER;
int8_t powerPins3[] = FLASHER_ALT_POWER;
#endif
@@ -39,48 +52,48 @@ bool autoFlash(flasher* f) {
if (f->findTagByMD5()) {
// this tag currently contains original firmware, found its fingerprint
USBSerial.printf("Found original firmware tag, recognized its fingerprint (%s)\n", f->md5char);
cmdSerial.printf("Found original firmware tag, recognized its fingerprint (%s)\n", f->md5char);
f->readInfoBlock();
f->getFirmwareMac();
f->prepareInfoBlock();
f->writeInfoBlock();
USBSerial.printf("Attempting to perform a flash...\n");
cmdSerial.printf("Attempting to perform a flash...\n");
if (f->writeFlashFromPack("/Tag_FW_Pack.bin", f->tagtype)) {
USBSerial.printf("Successfully flashed the tag!\n");
cmdSerial.printf("Successfully flashed the tag!\n");
return true;
} else {
USBSerial.printf("Couldn't flash the tag, for some reason...\n");
cmdSerial.printf("Couldn't flash the tag, for some reason...\n");
}
} else if (f->getInfoBlockMD5()) {
// did find an infoblock MD5 that looks valid
if (f->findTagByMD5()) {
// did find the md5 in the database
USBSerial.printf("Found an already-flashed tag, recognized its fingerprint (%s)\n", f->md5char);
cmdSerial.printf("Found an already-flashed tag, recognized its fingerprint (%s)\n", f->md5char);
f->getInfoBlockMac();
f->getInfoBlockType();
f->readInfoBlock();
USBSerial.printf("Attempting to perform a flash...\n");
cmdSerial.printf("Attempting to perform a flash...\n");
if (f->writeFlashFromPack("/Tag_FW_Pack.bin", f->tagtype)) {
USBSerial.printf("Successfully flashed the tag!\n");
cmdSerial.printf("Successfully flashed the tag!\n");
return true;
} else {
USBSerial.printf("Couldn't flash the tag, for some reason...\n");
cmdSerial.printf("Couldn't flash the tag, for some reason...\n");
}
} else {
// couldn't find the md5 from the infoblock
USBSerial.printf("Found an already-flashed tag, but we couldn't find its fingerprint (%s) in the database\n", f->md5char);
cmdSerial.printf("Found an already-flashed tag, but we couldn't find its fingerprint (%s) in the database\n", f->md5char);
return false;
}
} else {
// We couldn't recognize the tag from it's fingerprint...
USBSerial.printf("Found a tag but didn't recognize its fingerprint\n", f->md5char);
cmdSerial.printf("Found a tag but didn't recognize its fingerprint\n", f->md5char);
f->backupFlash();
USBSerial.printf("Saved this MD5 binary to filesystem\n");
cmdSerial.printf("Saved this MD5 binary to filesystem\n");
}
return false;
}
void sendFlasherAnswer(uint8_t answer_cmd, uint8_t* ans_buff, uint32_t len) {
void sendFlasherAnswer(uint8_t answer_cmd, uint8_t* ans_buff, uint32_t len, uint8_t transportType) {
uint8_t* answer_buffer = (uint8_t*)calloc(3 + 2 + 2 + len + 2 + 13, 1);
if (answer_buffer == nullptr) return;
uint32_t CRC_value = 0xAB34;
@@ -101,29 +114,42 @@ void sendFlasherAnswer(uint8_t answer_cmd, uint8_t* ans_buff, uint32_t len) {
answer_buffer[3 + 2 + 2 + len] = CRC_value >> 8;
answer_buffer[3 + 2 + 2 + len + 1] = CRC_value;
USBSerial.write(answer_buffer, 3 + 2 + 2 + len + 2);
if (transportType == TRANSPORT_USB) {
cmdSerial.write(answer_buffer, 3 + 2 + 2 + len + 2);
} else {
sendDataToClient(answer_buffer, 3 + 2 + 2 + len + 2);
}
// for(uint16_t c = 0; c< 3+2+2+len+2; c++){
//}
free(answer_buffer);
}
void flasherUartHandler(uint8_t* data, uint8_t len) {
static struct flasherCommand* cmd;
void flasherDataHandler(uint8_t* data, size_t len, uint8_t transportType) {
static struct flasherCommand* cmd = nullptr;
static uint8_t flasherSerialState = FLASHER_WAIT_A;
static uint32_t flasherCmdDataIndex = 0;
static uint16_t flasherCRC = 0xAB34;
static uint32_t flasherLastCmd = 0;
static uint8_t curLenIndex = 0;
if ((flasherSerialState != FLASHER_WAIT_A) && (millis() - flasherLastCmd >= 225)) {
flasherSerialState = FLASHER_WAIT_A;
// we should probably do something with stale commands containing data (data leak!)
if ((flasherSerialState != FLASHER_WAIT_A) && (millis() - flasherLastCmd >= 3000)) {
flasherSerialState = FLASHER_RESET;
}
while (len--) {
uint8_t usbbyte = *(data++);
switch (flasherSerialState) {
case FLASHER_RESET:
if (transportType == TRANSPORT_TCP && cmd != nullptr) {
if (cmd->data != nullptr) {
free(cmd->data);
cmd->data = nullptr;
}
delete cmd;
cmd = nullptr;
}
flasherSerialState = FLASHER_WAIT_A;
case FLASHER_WAIT_A:
if (usbbyte == 'A') {
flasherSerialState = FLASHER_WAIT_T;
@@ -139,7 +165,7 @@ void flasherUartHandler(uint8_t* data, uint8_t len) {
flasherCRC = 0xAB34;
flasherCmdDataIndex = 0;
} else {
flasherSerialState = FLASHER_WAIT_A;
flasherSerialState = FLASHER_RESET;
}
break;
case FLASHER_WAIT_CMD:
@@ -158,9 +184,11 @@ void flasherUartHandler(uint8_t* data, uint8_t len) {
cmd->data = (uint8_t*)calloc(cmd->len, 1);
if (cmd->data == nullptr) {
delete cmd;
cmd = nullptr;
flasherSerialState = FLASHER_WAIT_A;
} else {
flasherSerialState = FLASHER_WAIT_DATA;
}
flasherSerialState = FLASHER_WAIT_DATA;
} else {
// 0 len, so skip to CRC immediately
flasherSerialState = FLASHER_WAIT_CRCH;
@@ -181,17 +209,16 @@ void flasherUartHandler(uint8_t* data, uint8_t len) {
case FLASHER_WAIT_CRCL:
flasherCRC -= ((uint16_t)usbbyte);
if (flasherCRC) {
cmd = nullptr;
// we should probably delete the cmd and associated data here (data leak)
flasherSerialState = FLASHER_RESET;
wsSerial("failed CRC");
} else {
BaseType_t queuestatus = xQueueSend(flasherCmdQueue, &cmd, 0);
if (queuestatus == pdFALSE) {
if (cmd->data != nullptr) free(cmd->data);
delete cmd;
if (transportType == TRANSPORT_USB) {
BaseType_t queuestatus = xQueueSend(flasherCmdQueue, &cmd, 0);
} else {
processFlasherCommand(cmd, TRANSPORT_TCP);
}
cmd = nullptr;
flasherSerialState = FLASHER_RESET;
}
flasherSerialState = FLASHER_WAIT_A;
break;
}
}
@@ -204,6 +231,7 @@ void resetFlasherState() {
serialPassthroughState = false;
}
#ifdef HAS_USB
static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
if (event_base == ARDUINO_USB_EVENTS) {
arduino_usb_event_data_t* data = (arduino_usb_event_data_t*)event_data;
@@ -250,11 +278,11 @@ static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t eve
// Serial.printf("CDC RX [%u]:", data->rx.len);
{
uint8_t buf[data->rx.len];
size_t len = USBSerial.read(buf, data->rx.len);
size_t len = cmdSerial.read(buf, data->rx.len);
if (serialPassthroughState) {
Serial2.write(buf, len);
} else {
flasherUartHandler(buf, len);
flasherDataHandler(buf, len, TRANSPORT_USB);
}
}
break;
@@ -267,6 +295,7 @@ static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t eve
}
}
}
#endif
typedef enum {
CMD_GET_VERSION = 1,
@@ -292,51 +321,65 @@ typedef enum {
CMD_COMPLETE = 88,
} ZBS_UART_PROTO;
uint32_t FLASHER_VERSION = 0x00000030;
uint32_t FLASHER_VERSION = 0x00000031;
#define CONTROLLER_ZBS243 0
#define CONTROLLER_NRF82511 1
uint8_t selectedController = 0;
uint8_t selectedFlasherPort;
uint32_t currentFlasherOffset;
flasher* zbsflasherp;
nrfswd* nrfflasherp;
flasher* zbsflasherp = nullptr;
nrfswd* nrfflasherp = nullptr;
void processFlasherCommand(struct flasherCommand* cmd) {
void processFlasherCommand(struct flasherCommand* cmd, uint8_t transportType) {
uint8_t* tempbuffer;
uint8_t temp_buff[16];
uint32_t spi_speed = 0;
uint8_t powerPinCount = 1;
static uint32_t curspeed = 0;
uint8_t numPowerPins;
#ifdef HAS_RGB_LED
shortBlink(CRGB::White);
#else
quickBlink(2);
#endif
switch (cmd->command) {
case CMD_GET_VERSION:
wsSerial("> get version");
temp_buff[0] = FLASHER_VERSION >> 24;
temp_buff[1] = FLASHER_VERSION >> 16;
temp_buff[2] = FLASHER_VERSION >> 8;
temp_buff[3] = FLASHER_VERSION;
sendFlasherAnswer(cmd->command, temp_buff, 4);
sendFlasherAnswer(cmd->command, temp_buff, 4, transportType);
break;
case CMD_RESET_ESP:
sendFlasherAnswer(cmd->command, NULL, 0);
wsSerial("reset");
sendFlasherAnswer(cmd->command, NULL, 0, transportType);
delay(100);
ESP.restart();
break;
case CMD_SET_POWER:
wsSerial("> power");
switch (selectedFlasherPort) {
case 0:
powerControl(cmd->data[0], (uint8_t*)powerPins, 1);
numPowerPins = sizeof(powerPins);
powerControl(cmd->data[0], (uint8_t*)powerPins, numPowerPins);
break;
case 1:
powerControl(cmd->data[0], (uint8_t*)powerPins2, 1);
numPowerPins = sizeof(powerPins2);
powerControl(cmd->data[0], (uint8_t*)powerPins2, numPowerPins);
break;
case 2:
powerControl(cmd->data[0], (uint8_t*)powerPins3, 1);
numPowerPins = sizeof(powerPins3);
powerControl(cmd->data[0], (uint8_t*)powerPins3, numPowerPins);
break;
}
sendFlasherAnswer(CMD_SET_POWER, NULL, 0);
sendFlasherAnswer(CMD_SET_POWER, NULL, 0, transportType);
break;
case CMD_RESET:
wsSerial("> reset tag");
if (zbsflasherp != nullptr) {
zbsflasherp->zbs->reset();
delete zbsflasherp;
@@ -347,124 +390,135 @@ void processFlasherCommand(struct flasherCommand* cmd) {
delete nrfflasherp;
nrfflasherp = nullptr;
}
sendFlasherAnswer(CMD_RESET, NULL, 0);
sendFlasherAnswer(CMD_RESET, NULL, 0, transportType);
break;
case CMD_ERASE_FLASH:
wsSerial("> erase flash");
if (selectedController == CONTROLLER_NRF82511) {
} else if (selectedController == CONTROLLER_ZBS243) {
if (zbsflasherp == nullptr) return;
zbsflasherp->zbs->erase_flash();
}
sendFlasherAnswer(CMD_ERASE_FLASH, NULL, 0);
sendFlasherAnswer(CMD_ERASE_FLASH, NULL, 0, transportType);
break;
case CMD_ERASE_INFOPAGE:
wsSerial("> erase infopage");
if (selectedController == CONTROLLER_NRF82511) {
nrfflasherp->erase_uicr();
} else if (selectedController == CONTROLLER_ZBS243) {
if (zbsflasherp == nullptr) return;
zbsflasherp->zbs->erase_infoblock();
}
sendFlasherAnswer(CMD_ERASE_INFOPAGE, NULL, 0);
sendFlasherAnswer(CMD_ERASE_INFOPAGE, NULL, 0, transportType);
break;
case CMD_SELECT_PORT:
wsSerial("> select port");
selectedFlasherPort = cmd->data[0];
Serial.printf("Port selected = %d\n", cmd->data[0]);
break;
case CMD_SELECT_ZBS243:
wsSerial("> connect zbs");
zbsflasherp = new flasher;
temp_buff[0] = zbsflasherp->connectTag(selectedFlasherPort);
sendFlasherAnswer(CMD_SELECT_ZBS243, temp_buff, 1);
sendFlasherAnswer(CMD_SELECT_ZBS243, temp_buff, 1, transportType);
currentFlasherOffset = 0;
selectedController = CONTROLLER_ZBS243;
break;
case CMD_SELECT_NRF82511:
wsSerial("> connect nrf");
switch (selectedFlasherPort) {
case 0:
powerControl(true, (uint8_t*)powerPins, 1);
numPowerPins = sizeof(powerPins);
powerControl(true, (uint8_t*)powerPins, numPowerPins);
nrfflasherp = new nrfswd(FLASHER_AP_MISO, FLASHER_AP_CLK);
break;
case 1:
powerControl(true, (uint8_t*)powerPins2, 1);
numPowerPins = sizeof(powerPins2);
powerControl(true, (uint8_t*)powerPins2, numPowerPins);
nrfflasherp = new nrfswd(FLASHER_EXT_MISO, FLASHER_EXT_CLK);
break;
case 2:
powerControl(true, (uint8_t*)powerPins3, 1);
numPowerPins = sizeof(powerPins3);
powerControl(true, (uint8_t*)powerPins3, numPowerPins);
nrfflasherp = new nrfswd(FLASHER_ALT_MISO, FLASHER_ALT_CLK);
break;
}
nrfflasherp->init();
temp_buff[0] = (nrfflasherp->isConnected && !nrfflasherp->isLocked);
sendFlasherAnswer(CMD_SELECT_NRF82511, temp_buff, 1);
sendFlasherAnswer(CMD_SELECT_NRF82511, temp_buff, 1, transportType);
currentFlasherOffset = 0;
selectedController = CONTROLLER_NRF82511;
break;
case CMD_READ_FLASH:
wsSerial("> read flash");
uint8_t* bufferp;
uint32_t cur_len;
if (selectedController == CONTROLLER_NRF82511) {
if (nrfflasherp == nullptr) return;
if (currentFlasherOffset >= nrfflasherp->nrf_info.flash_size) {
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1);
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
} else {
bufferp = (uint8_t*)malloc(1024);
if (bufferp == nullptr) return;
cur_len = (nrfflasherp->nrf_info.flash_size - currentFlasherOffset >= 1024) ? 1024 : nrfflasherp->nrf_info.flash_size - currentFlasherOffset;
nrfflasherp->nrf_read_bank(currentFlasherOffset, (uint32_t*)bufferp, cur_len);
currentFlasherOffset += cur_len;
sendFlasherAnswer(CMD_READ_FLASH, bufferp, cur_len);
sendFlasherAnswer(CMD_READ_FLASH, bufferp, cur_len, transportType);
if (bufferp != nullptr) free(bufferp);
}
} else if (selectedController == CONTROLLER_ZBS243) {
if (zbsflasherp == nullptr) return;
if (currentFlasherOffset >= 65536) {
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1);
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
} else {
bufferp = (uint8_t*)malloc(1024);
if (bufferp == nullptr) return;
cur_len = (65536 - currentFlasherOffset >= 1024) ? 1024 : 65536 - currentFlasherOffset;
zbsflasherp->readBlock(currentFlasherOffset, bufferp, cur_len, false);
currentFlasherOffset += cur_len;
sendFlasherAnswer(CMD_READ_FLASH, bufferp, cur_len);
sendFlasherAnswer(CMD_READ_FLASH, bufferp, cur_len, transportType);
if (bufferp != nullptr) free(bufferp);
}
}
break;
case CMD_READ_INFOPAGE:
wsSerial("> read infopage");
uint8_t* ibufferp;
uint32_t icur_len;
if (selectedController == CONTROLLER_NRF82511) {
if (nrfflasherp == nullptr) return;
if (currentFlasherOffset >= 4096) {
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1);
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
} else {
ibufferp = (uint8_t*)malloc(1024);
if (ibufferp == nullptr) return;
icur_len = (4096 - currentFlasherOffset >= 256) ? 256 : 4096 - currentFlasherOffset;
nrfflasherp->nrf_read_bank(0x10001000 + currentFlasherOffset, (uint32_t*)ibufferp, icur_len);
currentFlasherOffset += icur_len;
sendFlasherAnswer(CMD_READ_INFOPAGE, ibufferp, icur_len);
sendFlasherAnswer(CMD_READ_INFOPAGE, ibufferp, icur_len, transportType);
if (ibufferp != nullptr) free(ibufferp);
}
} else if (selectedController == CONTROLLER_ZBS243) {
if (zbsflasherp == nullptr) return;
if (currentFlasherOffset >= 1024) {
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1);
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
} else {
ibufferp = (uint8_t*)malloc(1024);
if (ibufferp == nullptr) return;
icur_len = (1024 - currentFlasherOffset >= 256) ? 256 : 1024 - currentFlasherOffset;
zbsflasherp->readBlock(currentFlasherOffset, ibufferp, icur_len, true);
currentFlasherOffset += icur_len;
sendFlasherAnswer(CMD_READ_INFOPAGE, ibufferp, icur_len);
sendFlasherAnswer(CMD_READ_INFOPAGE, ibufferp, icur_len, transportType);
if (ibufferp != nullptr) free(ibufferp);
}
}
break;
case CMD_WRITE_FLASH:
wsSerial("> write flash");
if (selectedController == CONTROLLER_NRF82511) {
if (nrfflasherp == nullptr) return;
if (currentFlasherOffset >= nrfflasherp->nrf_info.flash_size) {
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1);
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
} else {
for (uint32_t c = currentFlasherOffset; c < (currentFlasherOffset + cmd->len);) {
// very ugly and naive way to find out what page we're in, and erase all relevant pages before writing
@@ -479,55 +533,58 @@ void processFlasherCommand(struct flasherCommand* cmd) {
nrfflasherp->nrf_write_bank(currentFlasherOffset, (uint32_t*)cmd->data, cmd->len);
Serial.printf("wrote page to nrf\n");
currentFlasherOffset += cmd->len;
sendFlasherAnswer(CMD_WRITE_FLASH, NULL, 0);
sendFlasherAnswer(CMD_WRITE_FLASH, NULL, 0, transportType);
}
} else if (selectedController == CONTROLLER_ZBS243) {
if (zbsflasherp == nullptr) return;
if (currentFlasherOffset >= 65536) {
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1);
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
} else {
zbsflasherp->writeBlock(currentFlasherOffset, cmd->data, cmd->len, false);
currentFlasherOffset += cmd->len;
sendFlasherAnswer(CMD_WRITE_FLASH, NULL, 0);
sendFlasherAnswer(CMD_WRITE_FLASH, NULL, 0, transportType);
}
}
break;
case CMD_WRITE_INFOPAGE:
wsSerial("> write infopage");
if (selectedController == CONTROLLER_NRF82511) {
if (nrfflasherp == nullptr) return;
if (currentFlasherOffset >= 4096) {
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1);
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
} else {
nrfflasherp->nrf_write_bank(0x10001000 + currentFlasherOffset, (uint32_t*)cmd->data, cmd->len);
Serial.printf("wrote page to nrf\n");
currentFlasherOffset += cmd->len;
sendFlasherAnswer(CMD_WRITE_INFOPAGE, NULL, 0);
sendFlasherAnswer(CMD_WRITE_INFOPAGE, NULL, 0, transportType);
}
} else if (selectedController == CONTROLLER_ZBS243) {
if (zbsflasherp == nullptr) return;
if (currentFlasherOffset >= 1024) {
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1);
sendFlasherAnswer(CMD_COMPLETE, temp_buff, 1, transportType);
} else {
zbsflasherp->writeBlock(currentFlasherOffset, cmd->data, cmd->len, true);
currentFlasherOffset += cmd->len;
sendFlasherAnswer(CMD_WRITE_INFOPAGE, NULL, 0);
sendFlasherAnswer(CMD_WRITE_INFOPAGE, NULL, 0, transportType);
}
}
break;
case CMD_PASS_THROUGH:
wsSerial("> pass through");
Serial2.begin(115200, SERIAL_8N1, FLASHER_EXT_RXD, FLASHER_EXT_TXD);
USBSerial.println(">>>");
cmdSerial.println(">>>");
serialPassthroughState = true;
break;
case CMD_AUTOFLASH:
wsSerial("> autoflash");
if (selectedController == CONTROLLER_ZBS243) {
autoFlash(zbsflasherp);
zbsflasherp->zbs->reset();
delete zbsflasherp;
zbsflasherp = 0;
USBSerial.write(0x04);
zbsflasherp = nullptr;
cmdSerial.write(0x04);
} else {
USBSerial.println("Not yet implemented!");
cmdSerial.println("Not yet implemented!");
}
break;
}
@@ -554,41 +611,54 @@ void tagDebugPassthrough() {
uint8_t* buf = (uint8_t*)malloc(len);
Serial2.read(buf, len);
Serial.write(buf, len);
USBSerial.write(buf, len);
cmdSerial.write(buf, len);
free(buf);
}
}
#ifdef HAS_USB
void usbFlasherTask(void* parameter) {
flasherCmdQueue = xQueueCreate(10, sizeof(struct flasherCommand*));
#if ARDUINO_USB_MODEflash
#warning Wrong USB mode is in use, check settings in platformio.ini
#ifndef ARDUINO_USB_MODE
#error This ESP32 SoC has no Native USB interface
#elif ARDUINO_USB_MODE == 1
#warning This sketch should be used when USB is in OTG mode. Wrong USB mode is in use, check settings in platformio.ini
#endif
USB.onEvent(usbEventCallback);
USBSerial.onEvent(usbEventCallback);
USBSerial.setTimeout(1000);
cmdSerial.onEvent(usbEventCallback);
cmdSerial.setTimeout(1000);
USB.productName("OpenEpaperLink-flasher");
USB.begin();
USBSerial.begin();
struct flasherCommand* cmd;
cmdSerial.begin();
Serial.println("Task started");
uint32_t notificationValue;
struct flasherCommand* cmd = nullptr;
while (true) {
while (serialPassthroughState) {
tagDebugPassthrough();
vTaskDelay(1 / portTICK_PERIOD_MS);
}
BaseType_t queuereceive = xQueueReceive(flasherCmdQueue, &cmd, 1000 / portTICK_PERIOD_MS); // timeout every second to make sure the timeout gets triggered after a while
if (queuereceive == pdTRUE) {
processFlasherCommand(cmd);
processFlasherCommand(cmd, TRANSPORT_USB);
lastCmdTimeStamp = millis();
if (cmd->data != nullptr) {
free(cmd->data);
cmd->data = nullptr;
}
delete cmd;
cmd = nullptr;
} else {
if (lastCmdTimeStamp) {
if (millis() - lastCmdTimeStamp > USBFLASHER_CONNECTION_TIMEOUT)
flasherCommandTimeout();
flasherCommandTimeout();
}
}
vTaskDelay(50 / portTICK_PERIOD_MS);
}
}
}
#endif

View File

@@ -9,6 +9,8 @@
#include <Preferences.h>
#include <WiFi.h>
#include <algorithm>
#include "AsyncJson.h"
#include "LittleFS.h"
#include "SPIFFSEditor.h"
@@ -25,6 +27,10 @@
#include "udp.h"
#include "wifimanager.h"
#ifdef HAS_EXT_FLASHER
#include "webflasher.h"
#endif
extern uint8_t data_to_send[];
AsyncWebServer server(80);
@@ -125,17 +131,18 @@ void wsSendSysteminfo() {
uint32_t tagcount = getTagCount(timeoutcount);
char result[40];
if (timeoutcount > 0) {
#ifdef HAS_RGB_LED
if (apInfo.state == AP_STATE_ONLINE && apInfo.isOnline == true) rgbIdleColor = CRGB::DarkBlue;
#endif
snprintf(result, sizeof(result), "%lu / %lu, %lu timed out", tagcount, tagDB.size(), timeoutcount);
snprintf(result, sizeof(result), "%lu/%lu, %lu timeout", tagcount, tagDB.size(), timeoutcount);
} else {
#ifdef HAS_RGB_LED
if (apInfo.state == AP_STATE_ONLINE && apInfo.isOnline == true) rgbIdleColor = CRGB::Green;
#endif
snprintf(result, sizeof(result), "%lu / %lu", tagcount, tagDB.size());
}
setVarDB("ap_tagcount", result);
#ifdef HAS_RGB_LED
if (timeoutcount > 0) {
if (apInfo.state == AP_STATE_ONLINE && apInfo.isOnline == true) rgbIdleColor = CRGB::DarkBlue;
} else {
if (apInfo.state == AP_STATE_ONLINE && apInfo.isOnline == true) rgbIdleColor = CRGB::Green;
}
#endif
tagcounttimer = millis();
}
@@ -201,8 +208,13 @@ void wsSendAPitem(struct APlist *apitem) {
}
void wsSerial(const String &text) {
wsSerial(text, String(""));
}
void wsSerial(const String &text, const String &color) {
StaticJsonDocument<250> doc;
doc["console"] = text;
if (!color.isEmpty()) doc["color"] = color;
Serial.println(text);
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
ws.textAll(doc.as<String>());
@@ -274,17 +286,38 @@ void init_web() {
if (hex2mac(dst, mac)) {
tagRecord *taginfo = tagRecord::findByMAC(mac);
if (taginfo != nullptr) {
if (taginfo->data == nullptr) {
fs::File file = contentFS->open(taginfo->filename);
if (!file) {
request->send(404, "text/plain", "File not found");
if (request->hasParam("md5")) {
uint8_t md5[8];
if (hex2mac(request->getParam("md5")->value(), md5)) {
PendingItem *queueItem = getQueueItem(mac, *reinterpret_cast<uint64_t *>(md5));
if (queueItem->data == nullptr) {
fs::File file = contentFS->open(queueItem->filename);
if (file) {
queueItem->data = getDataForFile(file);
Serial.println("Reading file " + String(queueItem->filename));
file.close();
} else {
request->send(404, "text/plain", "File not found");
return;
}
}
request->send_P(200, "application/octet-stream", queueItem->data, queueItem->len);
return;
}
taginfo->data = getDataForFile(file);
file.close();
} else {
// older version without queue
if (taginfo->data == nullptr) {
fs::File file = contentFS->open(taginfo->filename);
if (!file) {
request->send(404, "text/plain", "File not found");
return;
}
taginfo->data = getDataForFile(file);
file.close();
}
request->send_P(200, "application/octet-stream", taginfo->data, taginfo->len);
return;
}
request->send_P(200, "application/octet-stream", taginfo->data, taginfo->len);
return;
}
}
}
@@ -444,30 +477,38 @@ void init_web() {
udpsync.getAPList();
AsyncResponseStream *response = request->beginResponseStream("application/json");
File configFile = contentFS->open("/current/apconfig.json", "r");
if (!configFile) {
request->send(500, "text/plain", "Error opening apconfig.json file");
return;
}
response->print("{");
#if defined YELLOW_IPS_AP || defined C6_OTA_FLASHING
#ifdef C6_OTA_FLASHING
response->print("\"C6\": \"1\", ");
#else
response->print("\"C6\": \"1\", ");
#endif
#if defined SAVE_SPACE
#ifdef SAVE_SPACE
response->print("\"savespace\": \"1\", ");
#else
response->print("\"savespace\": \"0\", ");
#endif
configFile.seek(1);
const size_t bufferSize = 64;
uint8_t buffer[bufferSize];
while (configFile.available()) {
size_t bytesRead = configFile.read(buffer, bufferSize);
response->write(buffer, bytesRead);
#ifdef HAS_EXT_FLASHER
response->print("\"hasFlasher\": \"1\", ");
#else
response->print("\"hasFlasher\": \"0\", ");
#endif
response->print("\"apstate\": \"" + String(apInfo.state) + "\", ");
File configFile = contentFS->open("/current/apconfig.json", "r");
if (configFile) {
configFile.seek(1);
const size_t bufferSize = 64;
uint8_t buffer[bufferSize];
while (configFile.available()) {
size_t bytesRead = configFile.read(buffer, bufferSize);
response->write(buffer, bytesRead);
}
configFile.close();
} else {
response->print("}");
}
configFile.close();
request->send(response);
});
@@ -479,15 +520,17 @@ void init_web() {
aliasValue.toCharArray(config.alias, aliasLength + 1);
config.alias[aliasLength] = '\0';
}
if (request->hasParam("channel", true)) {
config.channel = static_cast<uint8_t>(request->getParam("channel", true)->value().toInt());
}
if (request->hasParam("led", true)) {
config.led = static_cast<int16_t>(request->getParam("led", true)->value().toInt());
config.led = static_cast<uint8_t>(request->getParam("led", true)->value().toInt());
updateBrightnessFromConfig();
}
if (request->hasParam("tft", true)) {
config.tft = static_cast<int16_t>(request->getParam("tft", true)->value().toInt());
config.tft = static_cast<uint8_t>(request->getParam("tft", true)->value().toInt());
updateBrightnessFromConfig();
}
if (request->hasParam("language", true)) {
@@ -663,6 +706,8 @@ void init_web() {
},
dotagDBUpload);
// OTA related calls
server.on("/sysinfo", HTTP_GET, handleSysinfoRequest);
server.on("/check_file", HTTP_GET, handleCheckFile);
server.on("/rollback", HTTP_POST, handleRollback);
@@ -677,6 +722,15 @@ void init_web() {
},
handleLittleFSUpload);
#ifdef HAS_EXT_FLASHER
// Flasher related calls
ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
if (type == WS_EVT_DATA) handleWSdata(data, len, client);
});
#endif
server.onNotFound([](AsyncWebServerRequest *request) {
if (request->url() == "/" || request->url() == "index.htm") {
request->send(200, "text/html", "index.html not found. Did you forget to upload the littlefs partition?");

View File

@@ -0,0 +1,506 @@
#include "webflasher.h"
#ifdef HAS_EXT_FLASHER
#include <ArduinoJson.h>
#include <AsyncTCP.h>
// #include <freertos/FreeRTOS.h>
// #include <freertos/task.h>
#include "flasher.h"
#include "ips_display.h"
#include "settings.h"
// #include "storage.h"
#include "powermgt.h"
#include "swd.h"
#include "usbflasher.h"
#include "util.h"
#include "web.h"
#include "zbs_interface.h"
#define FLASHMODE_OFF 0
#define FLASHMODE_AUTO_BACKGROUND 1
#define FLASHMODE_AUTO_FOCUS 2
#define AUTOFLASH_STEP_IDLE 0
#define AUTOFLASH_STEP_CONNECT 1
#define AUTOFLASH_STEP_STARTUP 2
#define AUTOFLASH_STEP_WAITDISCONNECT 3
#define AUTOFLASH_STEP_FINISHED 4
#define AUTOFLASH_STEP_ABORT 5
#define AUTOFLASH_STEP_COUNTDOWN 6
#define AUTOFLASH_STEP_COUNTING 8
#define AUTOFLASH_STEP_WRITE 7
#define AUTOFLASH_START_USBFLASHER 9
#define AUTOFLASH_USBFLASHER_RUNNING 10
#define AUTOFLASH_END_USBFLASHER 11
#define Seriallog logger
uint8_t webFlashMode = FLASHMODE_OFF;
uint8_t autoFlashStep = AUTOFLASH_STEP_IDLE;
// TaskHandle_t usbFlasherTaskHandle;
AsyncServer TCPserver(243);
AsyncClient* connectedClient = NULL;
Logger::Logger() {
}
Logger::~Logger() {
}
size_t Logger::write(uint8_t c) {
wsSerial(String((char)c));
return 1;
}
size_t Logger::write(const uint8_t* buffer, size_t size) {
wsSerial(String((const char*)buffer, size));
return size;
}
size_t Logger::printf(const char* format, ...) {
char buffer[256];
va_list args;
va_start(args, format);
size_t len = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
wsSerial(String(buffer));
#ifdef HAS_TFT
if (len >= 6 && strncmp(buffer, "\r[", 2) == 0) {
char content[50];
strncpy(content, buffer + 2, len - 7);
content[len - 7] = '\0';
tft2.fillRect(0, 50, tft2.width(), 40, TFT_BLUE);
tft2.setCursor(10, 57, 2);
tft2.setTextColor(TFT_YELLOW);
tft2.print(String(content));
}
#endif
return len;
}
size_t Logger::println(const char* text) {
size_t len = print(text);
print('\n');
return len + 1;
}
Logger logger;
util::Timer tagConnectTimer(seconds(2));
extern flasher* zbsflasherp;
uint8_t errors = 0;
void infoDisplay(String actionName, uint8_t icon) {
wsSerial(" " + actionName, "white");
#ifdef HAS_TFT
tftOverride = true;
tft2.fillScreen(TFT_PURPLE);
tft2.setCursor(0, 0, 4);
tft2.setTextColor(TFT_WHITE);
tft2.print("Progress:");
tft2.setCursor(18, 30, 2);
tft2.setTextColor(TFT_WHITE);
tft2.print(actionName);
#endif
}
bool report(bool result) {
if (result) {
wsSerial("<✔", "green");
#ifdef HAS_TFT
tft2.setTextColor(TFT_GREEN);
tft2.setCursor(5, 30, 2);
tft2.print("v");
tft2.setCursor(6, 30, 2);
tft2.print("v");
vTaskDelay(100 / portTICK_PERIOD_MS);
#endif
} else {
errors++;
autoFlashStep = AUTOFLASH_STEP_ABORT;
wsSerial("<✘", "red");
#ifdef HAS_TFT
tft2.setTextColor(TFT_RED);
tft2.setCursor(5, 30, 2);
tft2.print("x");
tft2.setCursor(6, 30, 2);
tft2.print("x");
vTaskDelay(500 / portTICK_PERIOD_MS);
#endif
}
return result;
}
void onDataReceived(void* arg, AsyncClient* client, void* data, size_t len) {
flasherDataHandler((uint8_t*)data, len, TRANSPORT_TCP);
}
void onClientConnect(void* arg, AsyncClient* client) {
Serial.println("New client connected");
if (autoFlashStep == AUTOFLASH_USBFLASHER_RUNNING) {
#ifdef HAS_TFT
if (tftOverride == true) {
tft2.fillRect(0, 62, tft2.width(), 18, TFT_BLUE);
tft2.setCursor(10, 63, 2);
tft2.setTextColor(TFT_GREEN);
tft2.print("TCP connected");
}
#endif
} else {
autoFlashStep = AUTOFLASH_START_USBFLASHER;
}
webFlashMode = FLASHMODE_OFF;
if (connectedClient == NULL) {
connectedClient = client;
client->onData(&onDataReceived, NULL);
client->onDisconnect([](void* arg, AsyncClient* c) {
Serial.println("Client disconnected");
if (zbsflasherp != nullptr) {
zbsflasherp->zbs->reset();
delete zbsflasherp;
zbsflasherp = nullptr;
}
#ifdef HAS_TFT
if (autoFlashStep == AUTOFLASH_USBFLASHER_RUNNING && tftOverride == true) {
tft2.fillRect(0, 62, tft2.width(), 18, TFT_BLUE);
tft2.setCursor(10, 63, 2);
tft2.setTextColor(TFT_YELLOW);
tft2.print("TCP disconnected");
}
#endif
connectedClient = NULL;
});
} else {
client->close(true);
}
}
void sendDataToClient(const uint8_t* data, size_t len) {
if (connectedClient != NULL) {
connectedClient->write(reinterpret_cast<const char*>(data), len);
} else {
Serial.println("No client connected");
}
}
void webFlasherTask(void* parameter) {
TCPserver.begin();
TCPserver.onClient(&onClientConnect, NULL);
static int32_t countDownTimer = 0;
while (1) {
switch (autoFlashStep) {
case AUTOFLASH_STEP_IDLE: {
if (webFlashMode != FLASHMODE_OFF && (tagConnectTimer.doRun() || webFlashMode == FLASHMODE_AUTO_FOCUS)) {
Serial.println("check pins");
if (extTagConnected()) autoFlashStep = AUTOFLASH_STEP_CONNECT;
}
break;
}
case AUTOFLASH_STEP_CONNECT: {
wsSerial("-", "clear");
infoDisplay("Connecting", 0);
vTaskDelay(2000 / portTICK_PERIOD_MS);
pinMode(FLASHER_EXT_TEST, OUTPUT);
digitalWrite(FLASHER_EXT_TEST, LOW);
zbsflasherp = new flasher();
errors = 0;
if (!report(zbsflasherp->connectTag(FLASHER_EXT_PORT))) {
Seriallog.printf("Sorry, failed to connect to this tag...\n");
break;
} else {
infoDisplay("Get firmware md5", 0);
report(zbsflasherp->getFirmwareMD5());
infoDisplay("Find tag", 0);
if (zbsflasherp->findTagByMD5()) {
// this tag currently contains original firmware, found its fingerprint
Seriallog.printf("Found original firmware tag (fingerprint %s)\n", zbsflasherp->md5char);
infoDisplay("Read info block", 0);
report(zbsflasherp->readInfoBlock());
if (errors) break;
infoDisplay("Get firmware mac", 0);
report(zbsflasherp->getFirmwareMac());
if (errors) break;
infoDisplay("Prepare info block", 0);
report(zbsflasherp->prepareInfoBlock());
if (errors) break;
zbsflasherp->includeInfoBlock = true;
autoFlashStep = AUTOFLASH_STEP_COUNTDOWN;
break;
} else if (report(zbsflasherp->getInfoBlockMD5())) {
infoDisplay("Find by md5", 0);
// did find an infoblock MD5 that looks valid
if (report(zbsflasherp->findTagByMD5())) {
// did find the md5 in the database
infoDisplay("Found tag", 0);
Seriallog.printf("Found an already-flashed tag (fingerprint %s)\n", zbsflasherp->md5char);
infoDisplay("Get infoblock mac", 0);
report(zbsflasherp->getInfoBlockMac());
if (errors) break;
infoDisplay("Get infoblock type", 0);
report(zbsflasherp->getInfoBlockType());
if (errors) break;
infoDisplay("Read infoblock", 0);
report(zbsflasherp->readInfoBlock());
if (errors) break;
zbsflasherp->includeInfoBlock = false;
autoFlashStep = AUTOFLASH_STEP_COUNTDOWN;
break;
} else {
infoDisplay("Unknown fingerprint", 0);
report(false);
// couldn't find the md5 from the infoblock
Seriallog.printf("Found an already-flashed tag, but we couldn't find its fingerprint (%s) in the database\n", zbsflasherp->md5char);
break;
}
} else {
// We couldn't recognize the tag from its fingerprint...
Seriallog.printf("Found a tag but didn't recognize its fingerprint (%s)\n", zbsflasherp->md5char);
infoDisplay("Backup firmware", 0);
report(zbsflasherp->backupFlash());
Seriallog.printf("Saved this MD5 binary to filesystem\n");
infoDisplay("Unknown tag", 0);
report(false);
break;
}
}
zbsflasherp->zbs->reset(false);
delete zbsflasherp;
zbsflasherp = nullptr;
autoFlashStep = AUTOFLASH_STEP_FINISHED;
break;
}
case AUTOFLASH_STEP_COUNTDOWN: {
infoDisplay("Final Countdown", 0);
countDownTimer = millis() + 3000;
autoFlashStep = AUTOFLASH_STEP_COUNTING;
break;
}
case AUTOFLASH_STEP_COUNTING: {
uint32_t timenow = millis();
if (timenow >= countDownTimer) {
autoFlashStep = AUTOFLASH_STEP_WRITE;
timenow = countDownTimer;
}
float timeDifference = countDownTimer - timenow;
char timeString[10];
snprintf(timeString, sizeof(timeString), "%.1fs", timeDifference / 1000.0);
#ifdef HAS_TFT
tftOverride = true;
tft2.fillRect(0, 50, tft2.width(), 40, TFT_BLUE);
tft2.setCursor(10, 55, 4);
tft2.setTextColor(TFT_YELLOW);
tft2.print(timeString);
#endif
wsSerial("\r" + String(timeString), "yellow");
break;
}
case AUTOFLASH_STEP_WRITE: {
if (zbsflasherp->includeInfoBlock) {
infoDisplay("Write info block", 0);
report(zbsflasherp->writeInfoBlock());
if (errors) break;
}
infoDisplay("Write flash", 0);
report(zbsflasherp->writeFlashFromPack("/Tag_FW_Pack.bin", zbsflasherp->tagtype));
if (errors) break;
zbsflasherp->zbs->reset(false);
delete zbsflasherp;
zbsflasherp = nullptr;
infoDisplay("Write successful", 0);
report(true);
wsSerial("Flashing succeeded", "green");
#ifdef HAS_TFT
tftOverride = true;
tft2.fillRect(0, 50, tft2.width(), 40, TFT_DARKGREEN);
tft2.setCursor(0, 55, 4);
tft2.setTextColor(TFT_WHITE);
tft2.print("OK, Done!");
vTaskDelay(500 / portTICK_PERIOD_MS);
#endif
autoFlashStep = AUTOFLASH_STEP_FINISHED;
break;
}
case AUTOFLASH_STEP_ABORT: {
wsSerial("Flashing failed", "red");
#ifdef HAS_TFT
tftOverride = true;
tft2.fillRect(0, 50, tft2.width(), 40, TFT_RED);
tft2.setCursor(0, 55, 4);
tft2.setTextColor(TFT_WHITE);
tft2.print("Flash failed");
vTaskDelay(500 / portTICK_PERIOD_MS);
#endif
zbsflasherp->zbs->reset(false);
delete zbsflasherp;
zbsflasherp = nullptr;
vTaskDelay(100 / portTICK_PERIOD_MS);
pinMode(FLASHER_EXT_TEST, INPUT_PULLDOWN);
vTaskDelay(100 / portTICK_PERIOD_MS);
autoFlashStep = AUTOFLASH_STEP_WAITDISCONNECT;
break;
}
case AUTOFLASH_STEP_FINISHED: {
Seriallog.printf("You can safely disconnect the tag");
vTaskDelay(100 / portTICK_PERIOD_MS);
pinMode(FLASHER_EXT_TEST, INPUT_PULLDOWN);
vTaskDelay(100 / portTICK_PERIOD_MS);
autoFlashStep = AUTOFLASH_STEP_WAITDISCONNECT;
break;
}
case AUTOFLASH_STEP_WAITDISCONNECT: {
if (!extTagConnected()) autoFlashStep = AUTOFLASH_STEP_STARTUP;
vTaskDelay(500 / portTICK_PERIOD_MS);
break;
}
case AUTOFLASH_STEP_STARTUP: {
int8_t powerPins2[] = FLASHER_EXT_POWER;
uint8_t numPowerPins = sizeof(powerPins2);
powerControl(false, (uint8_t*)powerPins2, numPowerPins);
#ifdef HAS_TFT
tftOverride = true;
tft2.fillScreen(TFT_PURPLE);
tft2.setCursor(0, 0, 4);
tft2.setTextColor(TFT_WHITE);
tft2.print("Flasher mode");
tft2.setCursor(0, 25, 2);
tft2.setTextColor(TFT_WHITE);
tft2.print("Ready to connect a tag.");
tft2.print("When recognized, it will be flashed automatically");
if (webFlashMode == FLASHMODE_AUTO_BACKGROUND) tftOverride = false;
#endif
wsSerial("Ready to connect a tag. It will be flashed automatically", "silver");
autoFlashStep = AUTOFLASH_STEP_IDLE;
vTaskDelay(100 / portTICK_PERIOD_MS);
break;
}
case AUTOFLASH_START_USBFLASHER: {
#ifdef HAS_TFT
tftOverride = true;
tft2.fillScreen(TFT_PURPLE);
tft2.setCursor(0, 0, 4);
tft2.setTextColor(TFT_WHITE);
tft2.print("CMD mode");
tft2.setCursor(0, 25, 2);
tft2.setTextColor(TFT_WHITE);
tft2.print("Use OEPL-flasher.py to flash a tag");
if (webFlashMode == FLASHMODE_AUTO_BACKGROUND) tftOverride = false;
#endif
autoFlashStep = AUTOFLASH_USBFLASHER_RUNNING;
break;
}
case AUTOFLASH_USBFLASHER_RUNNING: {
vTaskDelay(500 / portTICK_PERIOD_MS);
break;
}
case AUTOFLASH_END_USBFLASHER: {
autoFlashStep = AUTOFLASH_STEP_FINISHED;
}
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void handleWSdata(uint8_t* data, size_t len, AsyncWebSocketClient* client) {
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, (const char*)data);
StaticJsonDocument<250> response;
response["flashstatus"] = 1;
if (error) {
wsSerial("Failed to parse JSON");
return;
}
if (doc.containsKey("flashcmd")) {
uint16_t flashcmd = doc["flashcmd"].as<int>();
switch (flashcmd) {
case WEBFLASH_ENABLE_AUTOFLASH:
wsSerial("Switching to autoflash", "yellow");
webFlashMode = FLASHMODE_AUTO_FOCUS;
autoFlashStep = AUTOFLASH_STEP_STARTUP;
break;
case WEBFLASH_ENABLE_USBFLASHER:
wsSerial("Switching to usbflasher", "yellow");
wsSerial("You can now use OEPL-flasher.py to flash your tags", "silver");
autoFlashStep = AUTOFLASH_START_USBFLASHER;
webFlashMode = FLASHMODE_OFF;
break;
case WEBFLASH_FOCUS:
if (webFlashMode == FLASHMODE_AUTO_BACKGROUND) webFlashMode = FLASHMODE_AUTO_FOCUS;
if (webFlashMode == FLASHMODE_OFF) {
autoFlashStep = AUTOFLASH_START_USBFLASHER;
} else {
autoFlashStep = AUTOFLASH_STEP_STARTUP;
}
#ifdef HAS_TFT
tftOverride = true;
#endif
break;
case WEBFLASH_BLUR:
if (webFlashMode == FLASHMODE_AUTO_FOCUS) webFlashMode = FLASHMODE_AUTO_BACKGROUND;
#ifdef HAS_TFT
tftOverride = false;
sendAvail(0xFC);
#endif
break;
}
}
String jsonResponse;
serializeJson(response, jsonResponse);
client->text(jsonResponse);
}
#endif

View File

@@ -37,7 +37,7 @@ WifiManager::WifiManager() {
void WifiManager::terminalLog(String text) {
Serial.println(text);
#ifdef YELLOW_IPS_AP
#ifdef HAS_TFT
TFTLog(text);
#endif
}
@@ -67,6 +67,8 @@ void WifiManager::poll() {
}
}
#ifndef HAS_USB
// ap_and_flasher has gpio0 in use as FLASHER_AP_POWER
if (digitalRead(0) == LOW) {
Serial.println("GPIO0 LOW");
long starttime = millis();
@@ -97,6 +99,7 @@ void WifiManager::poll() {
ESP.restart();
}
}
#endif
pollSerial();
}
@@ -107,12 +110,12 @@ bool WifiManager::connectToWifi() {
_ssid = preferences.getString("ssid", WiFi_SSID());
_pass = preferences.getString("pw", WiFi_psk());
if (_ssid == "") {
terminalLog("No connection information saved");
terminalLog("No connection info saved");
logLine("No connection information saved");
startManagementServer();
return false;
}
terminalLog("Stored ssid: " + String(_ssid));
terminalLog("ssid: " + String(_ssid));
String ip = preferences.getString("ip", "");
String mask = preferences.getString("mask", "");
@@ -166,7 +169,7 @@ bool WifiManager::connectToWifi(String ssid, String pass, bool savewhensuccessfu
WiFi.setSleep(WIFI_PS_MIN_MODEM);
terminalLog("Connecting to WiFi...");
logLine("Connecting to WiFi...");
// logLine("Connecting to WiFi...");
WiFi.persistent(savewhensuccessfull);
WiFi.begin(_ssid.c_str(), _pass.c_str());
_connected = waitForConnection();
@@ -199,7 +202,7 @@ bool WifiManager::waitForConnection() {
WiFi.persistent(true);
IPAddress IP = WiFi.localIP();
terminalLog("Connected!");
logLine("Connected!");
// logLine("Connected!");
_nextReconnectCheck = millis() + _reconnectIntervalCheck;
wifiStatus = CONNECTED;
return true;
@@ -207,7 +210,7 @@ bool WifiManager::waitForConnection() {
void WifiManager::startManagementServer() {
if (!_APstarted) {
terminalLog("Starting configuration AP, ssid: OpenEPaperLink");
terminalLog("Starting config AP, ssid: OpenEPaperLink");
logLine("Starting configuration AP, ssid OpenEPaperLink");
WiFi.disconnect(true, true);
delay(100);

View File

@@ -57,7 +57,7 @@ void ZBS_interface::setSpeed(uint32_t speed) {
}
ZBS_interface::~ZBS_interface() {
if (spi) delete spi;
// if (spi) delete spi;
}
void ZBS_interface::set_power(uint8_t state) {
powerControl(state, _POWER_PIN, _POWER_PINS);
@@ -91,12 +91,14 @@ void ZBS_interface::enable_debug() {
delay(100);
}
void ZBS_interface::reset() {
void ZBS_interface::reset(bool leavepower) {
/*
if (spi) {
spi->end();
delete spi;
spi = nullptr;
}
*/
pinMode(_SS_PIN, INPUT);
pinMode(_CLK_PIN, INPUT);
pinMode(_MOSI_PIN, INPUT);
@@ -105,7 +107,7 @@ void ZBS_interface::reset() {
set_power(ZBS_OFF);
delay(500);
digitalWrite(_RESET_PIN, HIGH);
set_power(ZBS_ON);
if (leavepower) set_power(ZBS_ON);
pinMode(_RESET_PIN, INPUT);
}

View File

@@ -281,6 +281,60 @@
}
]
},
{
"id": 26,
"name": "Time Stamp",
"desc": "Displays the last date and time when a button press took place. For example, to keep track of the last time somebody let the dog out, or when you last watered the plants, or change the bed sheets.",
"properties": [ "savespace" ],
"param": [
{
"key": "title",
"name": "Title",
"desc": "Displayed title",
"type": "text"
},
{
"key": "button1",
"name": "Name for Button 1",
"desc": "Text displayed for button 1 (for example, a name or activity)",
"type": "text"
},
{
"key": "button2",
"name": "Name for Button 2",
"desc": "Text displayed for button 2 (for example, a name or activity)",
"type": "text"
},
{
"key": "mode",
"name": "Working mode",
"desc": "With 'keep one time', both buttons reset the same timestamp, recording which button is pressed last. The option for seperate date/time, records two different timestamps, one for each button.",
"type": "select",
"options": {
"0": "keep one timestamp",
"1": "seperate timestamp per button"
}
},
{
"key": "nextaction",
"name": "Frequency (days)",
"desc": "When is the next action expected to take place (in days after a timestamp). Leave 0 to disable.",
"type": "text"
},
{
"key": "last1",
"name": "Timestamp 1",
"desc": "The date/time the last event took place",
"type": "ro"
},
{
"key": "last2",
"name": "Name, or timestamp 2",
"desc": "The name of the button that was pressed, or in case of seperate timestamp, the date/time of the second button press",
"type": "ro"
}
]
},
{
"id": 27,
"name": "Dayahead prices",

View File

@@ -0,0 +1,139 @@
const $ = document.querySelector.bind(document);
let running = false;
let buttonState = false;
const WEBFLASH_ENABLE_AUTOFLASH = 1
const WEBFLASH_ENABLE_USBFLASHER = 2
const WEBFLASH_FOCUS = 3
export const WEBFLASH_BLUR = 4
export async function init() {
wsCmd(WEBFLASH_FOCUS);
checkTagFW();
}
export function wsCmd(command) {
const dataToSend = {
flashcmd: command,
};
const jsonData = JSON.stringify(dataToSend);
socket.send(jsonData);
}
$('#doAutoflash').onclick = function () {
if (running) return;
disableButtons(true);
running = true;
wsCmd(WEBFLASH_ENABLE_AUTOFLASH);
running = false;
disableButtons(false);
}
$('#doUSBflash').onclick = function () {
if (running) return;
disableButtons(true);
running = true;
wsCmd(WEBFLASH_ENABLE_USBFLASHER);
running = false;
disableButtons(false);
}
export function print(line, color = "white") {
const consoleDiv = document.getElementById('flashconsole');
if (consoleDiv) {
if (color == "clear") {
consoleDiv.innerHTML = "";
return;
}
const isScrolledToBottom = consoleDiv.scrollHeight - consoleDiv.clientHeight <= consoleDiv.scrollTop;
const newLine = document.createElement('div');
newLine.style.color = color;
if (line.startsWith("<")) {
const existingLines = consoleDiv.getElementsByTagName('div');
let lastLine;
for (let i = existingLines.length - 1; i >= 0; i--) {
const lineText = existingLines[i].textContent;
if (lineText.startsWith(" ")) {
lastLine = existingLines[i];
break;
}
}
if (lastLine) {
lastLine.innerHTML = line.substring(1) + lastLine.innerHTML.substring(line.length - 1);
lastLine.style.color = color;
}
} else if (line.startsWith("\r")) {
const existingLines = consoleDiv.getElementsByTagName('div');
if (existingLines.length > 0) {
const lastLine = existingLines[existingLines.length - 1];
lastLine.innerHTML = line.substring(1);
}
} else {
newLine.textContent = line;
consoleDiv.appendChild(newLine);
}
if (isScrolledToBottom) {
consoleDiv.scrollTop = consoleDiv.scrollHeight;
}
}
}
function disableButtons(active) {
$("#flashtab").querySelectorAll('button').forEach(button => {
button.disabled = active;
});
buttonState = active;
}
const fetchAndPost = async (url, name, path) => {
try {
print("updating " + path);
const response = await fetch(url);
const fileContent = await response.blob();
const formData = new FormData();
formData.append('path', path);
formData.append('file', fileContent, name);
const uploadResponse = await fetch('/littlefs_put', {
method: 'POST',
body: formData
});
if (!uploadResponse.ok) {
print(`${response.status} ${response.body}`, "red");
errors++;
} else {
print(`Firmware file downloaded`, "green");
}
} catch (error) {
print('error: ' + error, "red");
errors++;
}
};
async function checkTagFW() {
const fwfile = "/Tag_FW_Pack.bin";
const url = "/check_file?path=" + encodeURIComponent(fwfile);
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
if (data.filesize > 0) {
print(`File ${fwfile} found`, "green");
} else {
print(`File ${fwfile} not found. Downloading...`, "red");
await fetchAndPost("https://raw.githubusercontent.com/jjwbruijn/OpenEPaperLink/master/binaries/Tag/Tag_FW_Pack.bin", "Tag_FW_Pack.bin", fwfile);
}
} else {
print(`error checking file ${file.path}: ${response.status}`, "red");
errors++;
}
}

View File

@@ -26,6 +26,7 @@
<div class="tablinks material-symbols-outlined" data-target="aptab" title="Access Points">cell_tower</div>
<!--<div class="tablinks material-symbols-outlined" data-target="templatetab" title="Templates">browse
</div>-->
<div class="tablinks material-symbols-outlined" data-target="flashtab" title="Tag flasher" style="display:none;">flash_on</div>
<div class="tablinks material-symbols-outlined" data-target="configtab" title="Settings">settings</div>
<div class="tablinks material-symbols-outlined" data-target="logtab" title="Logging">text_snippet</div>
</div>
@@ -226,6 +227,48 @@
Work in progress...
</div>
<div id="flashtab" class="tabcontent">
<h3>Tag flasher</h3>
<div>
<div class="flashCol1">
<h4>Mode</h4>
<div style="max-width:400px;">
<button class="button" id="doAutoflash">Automatic flash</button><br><br>
With automatic flash, a tag is flashed to the latest firmware as soon as you connect it.
It sets the mac automatically, tries to recognize the type, and starts flashing. Currently, Solum M2 tags only.
<br><br>
<button class="button" id="doUSBflash">Command line</button><br><br>
Using <a href="https://github.com/jjwbruijn/OpenEPaperLink/raw/master/Tag_Flasher/OEPL-Flasher.py" target="_blank">OEPL-Flasher.py</a>, you have full control over the flashing of the tag.<br>
Use the --ip argument to connect to the flasher.<br>
<br>
Usage:<br>
<pre style="text-wrap: wrap;">
OEPL-Flasher.py [-h] [-t IP] [-f] [-i] [-eep] [-n] [-z]
[--pt] [--exit] [{read,write,autoflash,debug}] [filename]
{read,write,autoflash,debug} Command to execute
filename Filename for read/write commands
options:
-h, --help show this help message and exit
-t IP, --ip IP IP Address to use
-f, --flash Write to the flash
-i, --infopage Write to the infopage/UICR
-eep, --eeprom EEPROM operations
-n, --nrf82511 nRF82511 programming
-z, --zbs243 ZBS243 programming
--pt, --passthrough Enters serial passthrough for debug output after flashing
--exit Exit eeprom loader after sending data
</pre>
</div>
<!--<h4>Other actions</h4>-->
<div>
</div>
</div>
<div class="console" id="flashconsole"></div>
</div>
</div>
<div id="configtab" class="tabcontent">
<h3>Access Point config</h3>
<p>
@@ -247,7 +290,7 @@
<p>
<label for="apcfgledbrightness">RGB LED brightness</label>
<select id="apcfgledbrightness">
<option value="-1">off</option>
<option value="0">off</option>
<option value="15">10%</option>
<option value="31">25%</option>
<option value="127" selected>50%</option>
@@ -443,7 +486,7 @@
</div>
</p>
<p>
<span id="c6Option">
<span id="c6Option" style="display:none;">
<div id="updateC6Option">
<button type="button" id="updateC6Btn">Update ESP32-C6</button>
<input type="checkbox" value="1" checked id="c6download">download latest version

View File

@@ -198,7 +198,8 @@ label {
#aptab,
#configtab,
#updatetab {
#updatetab,
#flashtab {
padding: 10px;
& p {
@@ -206,7 +207,7 @@ label {
}
}
#updatetab {
#updatetab, #flashtab {
&>div {
display: flex;
gap: 2em;
@@ -858,7 +859,7 @@ h4 {
display: none;
}
.updateCol1 {
.updateCol1, .flashCol1 {
flex-grow: 1;
}
@@ -873,6 +874,7 @@ h4 {
overflow-y: scroll;
white-space: break-spaces;
flex-grow: 1;
max-width: 600px;
}
.console div {

View File

@@ -22,11 +22,12 @@ const apstate = [
{ state: "wait for reset", color: "blue" },
{ state: "requires power cycle", color: "purple" },
{ state: "failed", color: "red" },
{ state: "coming online", color: "yellow" }
{ state: "coming online", color: "yellow" },
{ state: "AP without radio", color: "green" }
];
const runstate = [
{ state: "⏹︎ stopped" },
{ state: "⏸pause" },
{ state: "⏸ pause" },
{ state: "" }, // hide running
{ state: "⏳︎ init" }
];
@@ -36,7 +37,7 @@ let isProcessing = false;
let servertimediff = 0;
let paintLoaded = false, paintShow = false;
let cardconfig;
let otamodule;
let otamodule, flashmodule;
let socket;
let finishedInitialLoading = false;
let getTagtypeBusy = false;
@@ -53,6 +54,19 @@ window.addEventListener("loadConfig", function () {
this.document.title = data.alias;
}
if (data.C6) {
var optionToRemove = $("#apcfgchid").querySelector('option[value="27"]');
if (optionToRemove) $("#apcfgchid").removeChild(optionToRemove);
$('#c6Option').style.display = 'block';
}
if (data.hasFlasher) {
$('[data-target="flashtab"]').style.display = 'block';
}
if (data.savespace) {
}
if (data.apstate) {
$("#apstatecolor").style.color = apstate[data.apstate].color;
$("#apstate").innerHTML = apstate[data.apstate].state;
$('#dashboardStatus').innerHTML = apstate[data.apstate].state;
}
});
});
@@ -86,11 +100,12 @@ window.addEventListener("load", function () {
faviconLink.rel = 'icon';
faviconLink.href = 'favicon.ico';
document.head.appendChild(faviconLink);
});
});
});
/* tabs */
let activeTab = '';
let activeTab = '', previousTab = '';
function initTabs() {
const tabLinks = document.querySelectorAll(".tablinks");
const tabContents = document.querySelectorAll(".tabcontent");
@@ -204,7 +219,13 @@ function connect() {
populateAPCard(msg.apitem);
}
if (msg.console) {
if (otamodule && typeof (otamodule.print) === "function") {
if (activeTab == 'flashtab' && flashmodule && typeof (flashmodule.print) === "function") {
let color = (msg.color ? msg.color : "#c0c0c0");
if (msg.console.startsWith("Fail") || msg.console.startsWith("Err")) {
color = "red";
}
flashmodule.print(msg.console, color);
} else if (otamodule && typeof (otamodule.print) === "function") {
let color = "#c0c0c0";
if (msg.console.startsWith("Fail") || msg.console.startsWith("Err")) {
color = "red";
@@ -426,7 +447,7 @@ function updatecards() {
let nextcheckin = item.dataset.nextcheckin - ((Date.now() / 1000) - servertimediff);
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "<span>expected checkin</span>" + displayTime(Math.floor(nextcheckin));
} else {
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "";
// $('#tag' + tagmac + ' .nextcheckin').innerHTML = "";
}
})
@@ -723,7 +744,15 @@ document.addEventListener("loadTab", function (event) {
$('#updateconsole').innerHTML = '';
loadOTA();
break;
case 'flashtab':
$('#flashconsole').innerHTML = '';
loadFlash();
break;
}
if (previousTab == 'flashtab' && activeTab != 'flashtab' && flashmodule && typeof (flashmodule.wsCmd) === "function") {
flashmodule.wsCmd(flashmodule.WEBFLASH_BLUR);
}
previousTab = activeTab;
});
$('#apcfgsave').onclick = function () {
@@ -771,16 +800,13 @@ $('#uploadButton').onclick = function () {
return response.text();
})
.then(data => {
console.log('File uploaded successfully: ', data);
alert('TagDB restored. Webpage will reload.');
location.reload();
})
.catch(error => {
console.error('Error uploading file:', error);
alert('Error uploading file: ' + error);
});
} else {
console.error('No file selected.');
alert('No file selected');
}
}
@@ -812,16 +838,14 @@ $('#restoreFromLocal').onclick = function () {
return response.text();
})
.then(data => {
console.log('File uploaded successfully: ', data);
alert('TagDB restored. Webpage will reload.');
location.reload();
})
.catch(error => {
console.error('Error uploading file:', error);
alert('Error uploading file: ' + error);
});
} else {
console.log('No data found in localStorage');
alert('No data found in localStorage');
}
}
@@ -830,6 +854,11 @@ async function loadOTA() {
otamodule.initUpdate();
}
async function loadFlash() {
flashmodule = await import('./flash.js?v=' + Date.now());
flashmodule.init();
}
$('#paintbutton').onclick = function () {
if (paintShow) {
paintShow = false;
@@ -1154,7 +1183,7 @@ function processZlib(data) {
return inflatedBuffer.subarray(headerSize);
} catch (err) {
console.log('zlib: ' + err);
}
}
}
function displayTime(seconds) {
@@ -1656,7 +1685,6 @@ function selectLocation(location) {
document.getElementById('opt#lon').value = location.longitude;
if (document.getElementById('opt#tz')) document.getElementById('opt#tz').value = location.timezone;
$('#georesults').innerHTML = '';
console.log('Selected location:', location);
}
function debounce(func, delay) {

View File

@@ -4,6 +4,8 @@ import serial
import time
from intelhex import IntelHex
import os.path
import socket
import sys
CMD_GET_VERSION = 1
CMD_RESET_ESP = 2
@@ -33,6 +35,9 @@ CMD_WRITE_FLASH = 83
CMD_AUTOFLASH = 87
CMD_COMPLETE = 88
TRANSPORT_SER = 0
TRANSPORT_TCP = 1
def read_binary_file(file_path):
with open(file_path, 'rb') as file:
binary_data = file.read()
@@ -55,12 +60,20 @@ def send_cmd(cmd, data):
for x in return_data:
crc_val += x
return_data = b"AT" + return_data + to_byte(crc_val & 0xffff, 2)
ser.write(return_data)
if transport == TRANSPORT_TCP:
tcp_socket.send(return_data)
else:
ser.write(return_data)
def wait_for_command():
if transport == TRANSPORT_TCP:
return wait_for_command_tcp()
else:
return wait_for_command_ser()
def wait_for_command_ser():
start_time = time.time()
ser.timeout = 50 # Set the timeout to 1 second
ser.timeout = 3 # Set the timeout to 3 seconds
while True:
if ser.in_waiting > 0:
command = ser.read(2) # Read the "AT" prefix
@@ -86,23 +99,50 @@ def wait_for_command():
print("timeout waiting for reply")
return None, None
def wait_for_command_tcp():
start_time = time.time()
tcp_socket.settimeout(5)
while True:
try:
if tcp_socket.recv(2) == b"AT":
# Read the command byte
cmd = int.from_bytes(tcp_socket.recv(1), byteorder='big')
data_length = int.from_bytes(
tcp_socket.recv(4), byteorder='big') # Read the data length
data = tcp_socket.recv(data_length) # Read the data
# Read the CRC value
crc = int.from_bytes(tcp_socket.recv(2), byteorder='big')
# Verify CRC
crc_val = 0xAB34
for x in to_byte(cmd, 1) + to_byte(data_length, 4) + data:
crc_val += x
if crc_val & 0xffff == crc:
return cmd, data
else:
print("Invalid CRC. Discarding command. Got " +
str(crc_val) + " but was expecting " + str(crc))
print("data was:" + str(data))
except socket.timeout:
if time.time() - start_time > 1:
print("timeout waiting for reply")
return None, None
def list_available_com_ports():
ports = serial.tools.list_ports.comports()
available_ports = [port.device for port in ports]
print("Specify a serial port to use with -p <PORT>")
print("available COM ports:")
for port in available_ports:
print(port)
available_ports = [port.device for port in serial.tools.list_ports.comports()]
print("Available COM ports:", ', '.join(available_ports))
def validate_arguments(args):
if not (args.nrf82511 or args.zbs243):
print("Either -nrf82511 or -zbs243 option is required.")
if not (args.port or args.ip):
print("Either --port or --ip option is required.")
list_available_com_ports()
return False
if args.command:
if not (args.nrf82511 or args.zbs243):
print("Either -nrf82511 or -zbs243 option is required.")
return False
if not (args.internalap or args.external or args.altradio):
print("One of -internalap, -external, or -altradio options is required.")
return False
print("Using external port")
args.external = True
if args.command in ["read", "write"] and not (args.flash or args.infopage or args.eeprom):
print("One of --flash, --infopage or --eeprom arguments is required for read and write commands.")
return False
@@ -121,7 +161,7 @@ def validate_arguments(args):
return True
def read_from_serial(port, filename, args):
def read_from_serial(filename, args):
if args.flash:
print(
f"Reading flash data and saving to file: {filename}.")
@@ -158,7 +198,7 @@ def read_from_serial(port, filename, args):
print("Failed reading block, timeout?")
def write_to_serial(port, filename, args):
def write_to_serial(filename, args):
if (args.flash):
print(f"\nErasing flash... ")
send_cmd(CMD_ERASE_FLASH, bytearray([]))
@@ -204,7 +244,7 @@ def write_to_serial(port, filename, args):
print(f'\rSent {i} bytes', end='', flush=True)
elif (cmd == CMD_COMPLETE):
print(
'\Tried to write more bytes than we have room for! \n', end='', flush=True)
'Tried to write more bytes than we have room for! \n', end='', flush=True)
return
else:
print("Some other error, dunno\n")
@@ -212,23 +252,30 @@ def write_to_serial(port, filename, args):
print('\rAll done writing! ', end='', flush=True)
def short_passthough(period_time):
def short_passthrough(period_time):
start_time = time.time()
while time.time() - start_time < period_time:
data = ser.read()
if data:
print(data.decode('utf-8', errors='ignore'), end='')
if chr(0x04) in data.decode('utf-8', errors='ignore'):
break
if transport == TRANSPORT_TCP:
try:
data = tcp_socket.recv(1)
except socket.timeout:
pass
else:
try:
data = ser.read(1)
except UnicodeDecodeError:
pass
print(data, end='')
if chr(0x04) in data:
break
def main():
try:
parser = argparse.ArgumentParser(
description="OpenEPaperLink Flasher for AP/Flasher board")
parser.add_argument("-p", "--port", help="COM port to use")
parser.add_argument("command", choices=[
"read", "write", "autoflash", "debug"], help="Command to execute")
parser.add_argument("-t", "--ip", help="IP Address to use")
parser.add_argument("command", nargs="?", choices=["read", "write", "autoflash", "debug"], help="Command to execute")
parser.add_argument("filename", nargs="?",
help="Filename for read/write commands")
parser.add_argument("-f", "--flash", action="store_true",
@@ -244,7 +291,7 @@ def main():
parser.add_argument("--internalap", action="store_true",
help="Selects the internal accesspoint port")
parser.add_argument("-e", "--external", action="store_true",
help="Selects the external(side) port")
help="Selects the external(side) port (default)")
parser.add_argument("--altradio", action="store_true",
help="Selects the alternate radio port")
parser.add_argument("--pt", "--passthrough", action="store_true",
@@ -254,22 +301,43 @@ def main():
args = parser.parse_args()
if not validate_arguments(args):
program_name = os.path.basename(sys.argv[0])
print(f"Usage: {program_name} --help")
return
if not args.port:
list_available_com_ports()
return
global ser
ser = serial.Serial(args.port, baudrate=115200)
time.sleep(0.1) # Flush serial data
while (ser.inWaiting() > 0):
data_str = ser.read(ser.inWaiting())
global transport
if (args.ip):
global tcp_socket
ip_address = args.ip
port = 243
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
transport = TRANSPORT_TCP
try:
tcp_socket.connect((ip_address, port))
print(f"Connected to {ip_address}:{port}")
except Exception as e:
print(f"Error: {e}")
return
else:
global ser
try:
ser = serial.Serial(args.port, baudrate=115200)
print(f"Successfully opened serial port {args.port}")
except serial.SerialException as e:
print(f"Error: {e}")
print("Please check if the specified COM port exists or is accessible.")
list_available_com_ports()
return
transport = TRANSPORT_SER
time.sleep(0.1) # Flush serial data
while (ser.inWaiting() > 0):
data_str = ser.read(ser.inWaiting())
send_cmd(CMD_GET_VERSION, bytearray([]))
cmd, answer = wait_for_command()
if (cmd == CMD_GET_VERSION):
print("AP/Flasher version: " +
print("Connection with the flasher established. Version: " +
str(answer[0] << 24 | answer[1] << 16 | answer[2] << 8 | answer[3]))
else:
print(
@@ -308,18 +376,19 @@ def main():
print("Connection established to microcontroller")
else:
print("Failed to establish a connection to the microcontroller")
send_cmd(CMD_SET_POWER, bytearray([0]))
exit(0)
if args.command == "read":
read_from_serial(ser, args.filename, args)
read_from_serial(args.filename, args)
elif args.command == "write":
write_to_serial(ser, args.filename, args)
write_to_serial(args.filename, args)
elif args.command == "autoflash":
print("Starting automatic tag flash")
send_cmd(CMD_AUTOFLASH, bytearray([]))
short_passthough(30)
short_passthrough(30)
else:
print("Invalid command!")
print("No command")
if(args.eeprom):
send_cmd(CMD_SET_TESTP, bytearray([1]))
@@ -340,21 +409,28 @@ def main():
print(
"---------------------------------------------------------------------------------")
while True:
try:
data = ser.read()
if data:
print(data.decode('utf-8', errors='ignore'), end='')
except UnicodeDecodeError:
print(" ")
data = ser.read()
if data:
print(data.decode('utf-8', errors='ignore'), end='')
if chr(0x04) in data.decode('utf-8', errors='ignore'):
break
if transport == TRANSPORT_TCP:
try:
data = tcp_socket.recv(1)
except socket.timeout:
data = ""
pass
else:
try:
data = ser.read(1)
except UnicodeDecodeError:
data = ""
pass
print(data, end='')
if chr(0x04) in data:
break
except KeyboardInterrupt:
print("\nBye!")
ser.close()
if transport == TRANSPORT_TCP:
tcp_socket.close()
else:
ser.close()
exit(1)

Binary file not shown.

View File

@@ -1,14 +1,12 @@
import os
import json
version = "0022" # You can set your desired version here.
version = "26" # You can set your desired version here.
types = {
0x00: "SOLUM_154_SSD1619-tag-00-" + version + ".bin",
0x01: "SOLUM_29_SSD1619-tag-01-" + version + ".bin",
0xF0: "Tag_FW_Segmented_UK.bin",
0x02: "SOLUM_42_SSD1619-tag-02-" + version + ".bin",
0x11: "SOLUM_29_UC8151-tag-11-" + version + ".bin",
0x00: "SOL_M2_154_SSD_" + version + ".bin",
0x01: "SOL_M2_29_SSD_" + version + ".bin",
0x02: "SOL_M2_42_SSD_" + version + ".bin"
}
binpath = "../binaries/Tag"