22 Commits
2.00 ... 2.01b

Author SHA1 Message Date
Nic Limper
9c06cdf2d7 small fix for fw compile test 2023-10-08 15:44:02 +02:00
Nic Limper
7a0ca319e7 timing fix in getting version info for ota 2023-10-07 22:21:54 +02:00
Nic Limper
c095f4c881 created Python port for packagebinaries.php to create Tag_FW_Pack.bin 2023-10-06 22:30:09 +02:00
Nic Limper
c586c9f541 added negative sign to fonts; small improvements 2023-10-06 13:14:22 +02:00
Moritz Wirger
6c4f8ef35b Add ap_date and ap_time vars (#142)
* Add ap_date and ap_time vars
* Add convenience creation function for Timer
* Optimize timer
* Document timer
2023-10-04 21:43:28 +02:00
Nic Limper
c403c06b09 fix AP not responding + add more logs to investigate crashes 2023-10-04 15:33:22 +02:00
Nic Limper
ddd043f44f update esp32-C6 binaries 2023-10-02 15:09:29 +02:00
Nic Limper
81cc5ccc9a tiny oops 2023-10-02 13:51:11 +02:00
Nic Limper
be325b0e62 forgot to add gzipped files with previous commit 2023-10-02 13:45:03 +02:00
Nic Limper
3621c84cc4 various small fixes
- neopixel patterns optimized. The 'breathing' led state now is green colored if everything is okay, and blue if there are one or more tags timed out.
- time zone is now set before wifi connect to show correct time zone in the logs during startup
- concurrent image upload POST is now blocked. If an upload is in progress while you do a second http POST, http status 409 Conflict is returned.
- small synchronisation bug fix in web interface on loading tag type
- dialog window close bugfix in painter
- image upload is now logged in /log.txt
2023-10-02 13:43:53 +02:00
Nic Limper
ed82795e5f tweak timings 2023-10-02 11:54:36 +02:00
Marcel
5b9f8b324e New hardware profile: PoE AP (#141)
* New hardware profile: PoE AP

- added harware profiles for C6 firmware in menuconfig
- added free PSRAM stat in webinterface

* fix(fsfree): fixed var type of freesize of FS
2023-09-29 02:46:11 +02:00
Nic Limper
db80d23b52 several small improvements
- neopixel idle color now represents AP status
- option to invert colors (advanced options at tag card)
- filename dropdown in tag card when a filename is expected
- redrawing pending instead of raw, if previews are off and tag reboots
- tft brightness setting independent from neopixel brightness
2023-09-29 00:11:44 +02:00
jjwbruijn
125922f8e7 M2 2.2 - RFW for added RF Wake 2023-09-28 17:29:00 +02:00
Nic Limper
aa484575b8 update update screen to new design; ability to choose repo source for OTA 2023-09-28 11:40:29 +02:00
jjwbruijn
fa97daef3c M2 FW 2.2 - Preload and buttons 2023-09-28 01:03:29 +02:00
Jonas Niesner
0c591660bc Fix wrong domain
fixes #140
2023-09-27 20:33:54 +02:00
Jelmer
c8fb0ca4de ESP: Added feature to preload images 2023-09-27 14:04:48 +02:00
jjwbruijn
87ce823776 added new image type arguments for M3 2023-09-27 12:24:08 +02:00
Jonas Niesner
7fe4a1e6ad Rename bin files to the new standard (#139)
* Update release.yml
* Update genfilelist.py
* Rename files
* Create .gitignore
2023-09-27 08:33:32 +02:00
Nic Limper
29b8c9bc21 small fix in data parser 2023-09-26 22:53:59 +02:00
Moritz Wirger
2e44889b19 Add custom tag data parser (#132)
* Add formatString convenience function

* Use String& for wsLog, wsErr and wsSerial

* Add tag data parser and parse tag data

* Make logLine use String&

* Fix issue with formatString

* Reuse payloadLength in processTagReturnData

* Fix parsing of unsigned/signed inetegers and cleanup

* Use c++17 standard

* Cleanup logging
2023-09-26 22:51:57 +02:00
107 changed files with 2368 additions and 2964 deletions

View File

@@ -39,18 +39,18 @@ jobs:
- name: Install intelhex
run: pip install --upgrade intelhex
- name: Build NRF firmware
run: |
cd ARM_Tag_FW/Newton_M3_nRF52811
pio run --environment Newton_M3_22_BWR
pio run --environment Newton_M3_29_BWR
pio run --environment Newton_M3_75_BWR
cp Newton_M3_22_BWR-ota.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_22_BWR-ota.bin
cp Newton_M3_22_BWR-full-flash.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_22_BWR-full-flash.bin
cp Newton_M3_29_BWR-ota.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_29_BWR-ota.bin
cp Newton_M3_29_BWR-full-flash.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_29_BWR-full-flash.bin
cp Newton_M3_75_BWR-ota.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_75_BWR-ota.bin
cp Newton_M3_75_BWR-full-flash.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_75_BWR-full-flash.bin
# - name: Build NRF firmware
# run: |
# cd ARM_Tag_FW/Newton_M3_nRF52811
# pio run --environment Newton_M3_22_BWR
# pio run --environment Newton_M3_29_BWR
# pio run --environment Newton_M3_75_BWR
# cp Newton_M3_22_BWR-ota.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_22_BWR-ota.bin
# cp Newton_M3_22_BWR-full-flash.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_22_BWR-full-flash.bin
# cp Newton_M3_29_BWR-ota.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_29_BWR-ota.bin
# cp Newton_M3_29_BWR-full-flash.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_29_BWR-full-flash.bin
# cp Newton_M3_75_BWR-ota.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_75_BWR-ota.bin
# cp Newton_M3_75_BWR-full-flash.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_75_BWR-full-flash.bin
- name: Install esptool
run: pip install esptool
@@ -199,14 +199,14 @@ jobs:
file_glob: true
overwrite: true
- name: Add tag bins to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: binaries/*
tag: ${{ github.ref }}
file_glob: true
overwrite: true
# - name: Add tag bins to release
# uses: svenstaro/upload-release-action@v2
# with:
# repo_token: ${{ secrets.GITHUB_TOKEN }}
# file: binaries/*
# tag: ${{ github.ref }}
# file_glob: true
# overwrite: true
# this is down here intentionally to be able to modify the binary folder before adding it to the Tag_Flasher later (ota binaries can be removed)

View File

@@ -2,3 +2,7 @@ build
*.axf
# Allow
!*.bin
.vscode
sdkconfig
sdkconfig.old

View File

@@ -0,0 +1,28 @@
menu "OEPL Hardware config"
choice OEPL_HARDWARE_PROFILE
prompt "Hardware profile"
default OEPL_HARDWARE_PROFILE_DEFAULT
config OEPL_HARDWARE_PROFILE_DEFAULT
bool "Default"
config OEPL_HARDWARE_PROFILE_POE_AP
bool "PoE-AP"
config OEPL_HARDWARE_PROFILE_CUSTOM
bool "Custom"
endchoice
config OEPL_HARDWARE_UART_TX
depends on OEPL_HARDWARE_PROFILE_CUSTOM
int "GPIO - UART TX"
default 3
config OEPL_HARDWARE_UART_RX
depends on OEPL_HARDWARE_PROFILE_CUSTOM
int "GPIO - UART RX"
default 2
endmenu

View File

@@ -326,15 +326,15 @@ void processSerial(uint8_t lastchar) {
}
goto SCPfailed;
SCPchannelFound:
pr("ACK>");
if (curChannel != scp->channel) {
radioSetChannel(scp->channel);
curChannel = scp->channel;
}
curPower = scp->power;
pr("ACK>");
if (curChannel != scp->channel) {
radioSetChannel(scp->channel);
curChannel = scp->channel;
}
curPower = scp->power;
radioSetTxPower(scp->power);
ESP_LOGI(TAG, "Set channel: %d power: %d", curChannel, curPower);
} else {
} else {
SCPfailed:
pr("NOK>");
}
@@ -412,27 +412,27 @@ void espNotifyAPInfo() {
}
void espNotifyTagReturnData(uint8_t *src, uint8_t len) {
struct tagReturnData *trd = (struct tagReturnData *)(radiorxbuffer + sizeof(struct MacFrameBcast) + 1); // oh how I'd love to pass this as an argument, but sdcc won't let me
struct espTagReturnData *etrd = (struct espTagReturnData *)radiotxbuffer;
struct tagReturnData *trd = (struct tagReturnData *)(radiorxbuffer + sizeof(struct MacFrameBcast) + 1); // oh how I'd love to pass this as an argument, but sdcc won't let me
struct espTagReturnData *etrd = (struct espTagReturnData *)radiotxbuffer;
if (memcmp((void *) & trd->dataVer, lastTagReturn, 8) == 0) {
return;
} else {
memcpy(lastTagReturn, &trd->dataVer, 8);
}
if (memcmp((void *) & trd->dataVer, lastTagReturn, 8) == 0) {
return;
} else {
memcpy(lastTagReturn, &trd->dataVer, 8);
}
memcpy(etrd->src, src, 8);
etrd->len = len;
memcpy(&etrd->returnData, trd, len);
addCRC(etrd, len + 10);
memcpy(etrd->src, src, 8);
etrd->len = len;
memcpy(&etrd->returnData, trd, len);
addCRC(etrd, len + 10);
uartTx('T');
uartTx('R');
uartTx('D');
uartTx('>');
for (uint8_t c = 0; c < len + 10; c++) {
uartTx(((uint8_t *)etrd)[c]);
}
uartTx('T');
uartTx('R');
uartTx('D');
uartTx('>');
for (uint8_t c = 0; c < len + 10; c++) {
uartTx(((uint8_t *)etrd)[c]);
}
}
// process data from tag
@@ -492,7 +492,7 @@ void processBlockRequest(const uint8_t *buffer, uint8_t forceBlockDownload) {
if (blockStartTimer == 0) {
if (requestDataDownload) {
if (highspeedSerial == true) {
blockRequestAck->pleaseWaitMs = 220;
blockRequestAck->pleaseWaitMs = 140;
} else {
blockRequestAck->pleaseWaitMs = 550;
}
@@ -583,23 +583,23 @@ void processXferComplete(uint8_t *buffer) {
}
void processTagReturnData(uint8_t *buffer, uint8_t len) {
struct MacFrameBcast *rxframe = (struct MacFrameBcast *)buffer;
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *)(radiotxbuffer + 1);
struct MacFrameBcast *rxframe = (struct MacFrameBcast *)buffer;
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *)(radiotxbuffer + 1);
if (!checkCRC((buffer + sizeof(struct MacFrameBcast) + 1), len - (sizeof(struct MacFrameBcast) + 1))) {
return;
}
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_TAG_RETURN_DATA_ACK;
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING;
memcpy(frameHeader->src, mSelfMac, 8);
memcpy(frameHeader->dst, rxframe->src, 8);
radiotxbuffer[1] = 0x41; // fast way to set the appropriate bits
radiotxbuffer[2] = 0xCC; // normal frame
frameHeader->seq = seq++;
frameHeader->pan = rxframe->srcPan;
radioTx(radiotxbuffer);
if (!checkCRC((buffer + sizeof(struct MacFrameBcast) + 1), len - (sizeof(struct MacFrameBcast) + 1))) {
return;
}
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_TAG_RETURN_DATA_ACK;
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING;
memcpy(frameHeader->src, mSelfMac, 8);
memcpy(frameHeader->dst, rxframe->src, 8);
radiotxbuffer[1] = 0x41; // fast way to set the appropriate bits
radiotxbuffer[2] = 0xCC; // normal frame
frameHeader->seq = seq++;
frameHeader->pan = rxframe->srcPan;
radioTx(radiotxbuffer);
espNotifyTagReturnData(rxframe->src, len - (sizeof(struct MacFrameBcast) + 1));
espNotifyTagReturnData(rxframe->src, len - (sizeof(struct MacFrameBcast) + 1));
}
// send block data to the tag
@@ -628,6 +628,18 @@ void sendBlockData() {
pr("Invalid block request received, 0 parts..\n");
requestedData.requestedParts[0] |= 0x01;
}
pr("Sending parts:");
for (uint8_t c = 0; (c < BLOCK_MAX_PARTS); c++) {
if (c % 10 == 0) pr(" ");
if (requestedData.requestedParts[c / 8] & (1 << (c % 8))) {
pr("X");
} else {
pr(".");
}
}
pr("\n");
uint8_t partNo = 0;
while (partNo < BLOCK_MAX_PARTS) {
for (uint8_t c = 0; (c < BLOCK_MAX_PARTS) && (partNo < BLOCK_MAX_PARTS); c++) {
@@ -684,24 +696,24 @@ void sendPong(void *buf) {
}
void app_main(void) {
esp_event_loop_create_default();
esp_event_loop_create_default();
init_nvs();
init_led();
init_led();
init_second_uart();
requestedData.blockId = 0xFF;
// clear the array with pending information
memset(pendingDataArr, 0, sizeof(pendingDataArr));
radio_init(curChannel);
radio_init(curChannel);
radioSetTxPower(10);
pr("RES>");
pr("RDY>");
ESP_LOGI(TAG, "C6 ready!");
ESP_LOGI(TAG, "C6 ready!");
housekeepingTimer = getMillis();
housekeepingTimer = getMillis();
while (1) {
while ((getMillis() - housekeepingTimer) < ((1000 * HOUSEKEEPING_INTERVAL) - 100)) {
int8_t ret = commsRxUnencrypted(radiorxbuffer);
@@ -741,11 +753,11 @@ void app_main(void) {
processAvailDataReq(radiorxbuffer);
}
break;
case PKT_TAG_RETURN_DATA:
processTagReturnData(radiorxbuffer, ret);
break;
default:
ESP_LOGI(TAG, "t=%02X" , getPacketType(radiorxbuffer));
case PKT_TAG_RETURN_DATA:
processTagReturnData(radiorxbuffer, ret);
break;
default:
ESP_LOGI(TAG, "t=%02X" , getPacketType(radiorxbuffer));
break;
}
} else if (blockStartTimer == 0) {
@@ -763,8 +775,8 @@ void app_main(void) {
}
}
memset(&lastTagReturn, 0, 8);
for (uint8_t cCount = 0; cCount < MAX_PENDING_MACS; cCount++) {
memset(&lastTagReturn, 0, 8);
for (uint8_t cCount = 0; cCount < MAX_PENDING_MACS; cCount++) {
if (pendingDataArr[cCount].attemptsLeft == 1) {
if (pendingDataArr[cCount].availdatainfo.dataType != DATATYPE_NOUPDATE) {
espNotifyTimeOut(pendingDataArr[cCount].targetMac);

View File

@@ -14,6 +14,8 @@
#include "main.h"
#include "proto.h"
#include "sdkconfig.h"
// if you get an error about soc/lp_uart_reg.h not being found,
// you didn't choose the right build target. :-)
#include "soc/lp_uart_reg.h"
#include "soc/uart_struct.h"
#include "utils.h"
@@ -48,62 +50,62 @@ void esp_ieee802154_transmit_done(const uint8_t *frame, const uint8_t *ack, esp_
}
void radio_init(uint8_t ch) {
if (packet_buffer == NULL) packet_buffer = xQueueCreate(32, 130);
if (packet_buffer == NULL) packet_buffer = xQueueCreate(32, 130);
// this will trigger a "IEEE802154 MAC sleep init failed" when called a second time, but it works
esp_ieee802154_enable();
esp_ieee802154_set_channel(ch);
// esp_ieee802154_set_txpower(int8_t power);
esp_ieee802154_set_panid(PROTO_PAN_ID);
// this will trigger a "IEEE802154 MAC sleep init failed" when called a second time, but it works
esp_ieee802154_enable();
esp_ieee802154_set_channel(ch);
// esp_ieee802154_set_txpower(int8_t power);
esp_ieee802154_set_panid(PROTO_PAN_ID);
esp_ieee802154_set_promiscuous(false);
esp_ieee802154_set_coordinator(false);
esp_ieee802154_set_pending_mode(ESP_IEEE802154_AUTO_PENDING_ZIGBEE);
// esp_ieee802154_set_extended_address needs the MAC in reversed byte order
esp_read_mac(mSelfMac, ESP_MAC_IEEE802154);
uint8_t eui64_rev[8] = {0};
for (int i = 0; i < 8; i++) {
eui64_rev[7 - i] = mSelfMac[i];
}
esp_ieee802154_set_extended_address(eui64_rev);
esp_ieee802154_get_extended_address(mSelfMac);
// esp_ieee802154_set_extended_address needs the MAC in reversed byte order
esp_read_mac(mSelfMac, ESP_MAC_IEEE802154);
uint8_t eui64_rev[8] = {0};
for (int i = 0; i < 8; i++) {
eui64_rev[7 - i] = mSelfMac[i];
}
esp_ieee802154_set_extended_address(eui64_rev);
esp_ieee802154_get_extended_address(mSelfMac);
esp_ieee802154_set_short_address(0xFFFE);
esp_ieee802154_set_short_address(0xFFFE);
esp_ieee802154_set_rx_when_idle(true);
esp_ieee802154_receive();
led_flash(1);
vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(0);
vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(1);
vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(0);
led_flash(1);
vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(0);
vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(1);
vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(0);
ESP_LOGI(TAG, "Receiver ready, panId=0x%04x, channel=%d, long=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, short=%04x",
esp_ieee802154_get_panid(), esp_ieee802154_get_channel(),
mSelfMac[0], mSelfMac[1], mSelfMac[2], mSelfMac[3],
mSelfMac[4], mSelfMac[5], mSelfMac[6], mSelfMac[7],
esp_ieee802154_get_short_address());
ESP_LOGI(TAG, "Receiver ready, panId=0x%04x, channel=%d, long=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, short=%04x",
esp_ieee802154_get_panid(), esp_ieee802154_get_channel(),
mSelfMac[0], mSelfMac[1], mSelfMac[2], mSelfMac[3],
mSelfMac[4], mSelfMac[5], mSelfMac[6], mSelfMac[7],
esp_ieee802154_get_short_address());
}
uint32_t lastZbTx = 0;
// uint32_t lastZbTx = 0;
bool radioTx(uint8_t *packet) {
static uint8_t txPKT[130];
while (isInTransmit) {
}
while (getMillis() - lastZbTx < 6) {
}
led_flash(1);
memcpy(txPKT, packet, packet[0]);
isInTransmit = 1;
lastZbTx = getMillis();
esp_ieee802154_transmit(txPKT, false);
return true;
while (isInTransmit) {
}
// while (getMillis() - lastZbTx < 6) {
// }
// lastZbTx = getMillis();
memcpy(txPKT, packet, packet[0]);
isInTransmit = 1;
esp_ieee802154_transmit(txPKT, false);
return true;
}
void radioSetChannel(uint8_t ch) {
radio_init(ch);
radio_init(ch);
}
void radioSetTxPower(uint8_t power) {}

View File

@@ -20,6 +20,7 @@
#include "sdkconfig.h"
#include "soc/uart_struct.h"
#include "soc/lp_uart_reg.h"
#include "second_uart.h"
static const char *TAG = "SECOND_UART";
@@ -32,9 +33,6 @@ volatile int curr_buff_pos = 0;
volatile int worked_buff_pos = 0;
volatile uint8_t buff_pos[MAX_BUFF_POS + 5];
#define S3_TX_PIN 3
#define S3_RX_PIN 2
static void uart_event_task(void *pvParameters);
void init_second_uart() {
uart_config_t uart_config = {
@@ -47,7 +45,7 @@ void init_second_uart() {
};
ESP_ERROR_CHECK(uart_driver_install(1, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0));
ESP_ERROR_CHECK(uart_param_config(1, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(1, S3_TX_PIN, S3_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
ESP_ERROR_CHECK(uart_set_pin(1, CONFIG_OEPL_HARDWARE_UART_TX, CONFIG_OEPL_HARDWARE_UART_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
xTaskCreate(uart_event_task, "uart_event_task", 16384, NULL, 12, NULL);
}

View File

@@ -1,5 +1,7 @@
#pragma once
#include <inttypes.h>
void init_second_uart();
void uart_switch_speed(int baudrate);
@@ -9,3 +11,15 @@ bool getRxCharSecond(uint8_t *newChar);
void uart_printf(const char *format, ...);
#define pr uart_printf
#if defined(CONFIG_OEPL_HARDWARE_PROFILE_DEFAULT)
#define CONFIG_OEPL_HARDWARE_UART_TX 3
#define CONFIG_OEPL_HARDWARE_UART_RX 2
#elif defined(CONFIG_OEPL_HARDWARE_PROFILE_POE_AP)
#define CONFIG_OEPL_HARDWARE_UART_TX 5
#define CONFIG_OEPL_HARDWARE_UART_RX 18
#elif defined(CONFIG_OEPL_HARDWARE_PROFILE_CUSTOM)
#if !defined(CONFIG_OEPL_HARDWARE_UART_TX) || !defined(CONFIG_OEPL_HARDWARE_UART_RX)
#error "No UART TX / RX pins defined. Please check menuconfig"
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32c6"
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y
CONFIG_PARTITION_TABLE_CUSTOM=y

View File

@@ -50,6 +50,8 @@
"stdexcept": "cpp",
"streambuf": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp"
"typeinfo": "cpp",
"chrono": "cpp",
"ratio": "cpp"
}
}

View File

@@ -0,0 +1,8 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000
otadata, data, ota, 0xD000, 0x2000
phy_init, data, phy, 0xF000, 0x1000
app0, app, ota_0, 0x10000, 0x200000
app1, app, ota_1, 0x210000, 0x200000
spiffs, data, spiffs, 0x410000, 0xBE0000
coredump, data, coredump, 0xFF0000, 0x10000
1 # Name, Type, SubType, Offset, Size, Flags
2 nvs, data, nvs, 0x9000, 0x4000
3 otadata, data, ota, 0xD000, 0x2000
4 phy_init, data, phy, 0xF000, 0x1000
5 app0, app, ota_0, 0x10000, 0x200000
6 app1, app, ota_1, 0x210000, 0x200000
7 spiffs, data, spiffs, 0x410000, 0xBE0000
8 coredump, data, coredump, 0xFF0000, 0x10000

Binary file not shown.

Binary file not shown.

View File

@@ -19,6 +19,7 @@ class SPIFFSEditor: public AsyncWebHandler {
virtual void handleRequest(AsyncWebServerRequest *request) override final;
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final;
virtual bool isRequestHandlerTrivial() override final {return false;}
virtual String listFilesRecursively(String path, bool recursive = false);
};
#endif

View File

@@ -28,6 +28,8 @@ void setBrightness(int brightness);
void updateBrightnessFromConfig();
#ifdef HAS_RGB_LED
extern CRGB rgbIdleColor;
extern uint16_t rgbIdlePeriod;
void shortBlink(CRGB cname);
void showColorPattern(CRGB colorone, CRGB colortwo, CRGB colorthree);
void rgbIdle();

View File

@@ -24,10 +24,14 @@ struct imgParam {
char segments[12];
uint16_t symbols;
bool invert;
uint8_t invert;
uint8_t lut;
uint8_t shortlut;
bool preload;
uint8_t preloadtype;
uint8_t preloadlut;
};
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams);

View File

@@ -33,4 +33,5 @@ void APEnterEarlyReset();
bool sendChannelPower(struct espSetChannelPower* scp);
void rxSerialTask2(void* parameter);
void APTagReset();
bool bringAPOnline();
bool bringAPOnline();
void setAPstate(bool isOnline, uint8_t state);

View File

@@ -27,7 +27,7 @@ class DynStorage {
void begin();
void end();
void listFiles();
size_t freeSpace();
uint64_t freeSpace();
private:
bool isInited;

View File

@@ -12,5 +12,5 @@
void initTime(void* parameter);
void logLine(const char* buffer);
void logLine(String text);
void logLine(const String& text);
void logStartUp();

View File

@@ -21,7 +21,7 @@
class tagRecord {
public:
tagRecord() : mac{0}, alias(""), lastseen(0), nextupdate(0), contentMode(0), pending(false), md5{0}, md5pending{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0), isExternal(false), apIp(IPAddress(0, 0, 0, 0)), pendingIdle(0), hasCustomLUT(false), rotate(0), lut(0), tagSoftwareVersion(0), currentChannel(0), dataType(0), filename(""), data(nullptr), len(0) {}
tagRecord() : mac{0}, alias(""), lastseen(0), nextupdate(0), contentMode(0), pending(false), md5{0}, md5pending{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0), isExternal(false), apIp(IPAddress(0, 0, 0, 0)), pendingIdle(0), hasCustomLUT(false), rotate(0), lut(0), tagSoftwareVersion(0), currentChannel(0), dataType(0), filename(""), data(nullptr), len(0), invert(0) {}
uint8_t mac[8];
String alias;
@@ -49,6 +49,7 @@ class tagRecord {
uint8_t lut;
uint16_t tagSoftwareVersion;
uint8_t currentChannel;
uint8_t invert;
uint8_t dataType;
String filename;
@@ -62,6 +63,7 @@ struct Config {
uint8_t channel;
char alias[32];
int16_t led;
uint8_t tft;
uint8_t language;
uint8_t maxsleep;
uint8_t stopsleep;
@@ -71,6 +73,8 @@ struct Config {
char timeZone[52];
uint8_t sleepTime1;
uint8_t sleepTime2;
String repo;
String env;
};
struct HwType {
@@ -90,7 +94,6 @@ extern Config config;
extern std::vector<tagRecord*> tagDB;
extern std::unordered_map<int, HwType> hwtype;
extern std::unordered_map<std::string, varStruct> varDB;
extern DynamicJsonDocument APconfig;
extern String tagDBtoJson(const uint8_t mac[8] = nullptr, uint8_t startPos = 0);
extern bool deleteRecord(const uint8_t mac[8]);
extern void fillNode(JsonObject& tag, const tagRecord* taginfo);
@@ -105,7 +108,14 @@ extern void clearPending(tagRecord* taginfo);
extern void initAPconfig();
extern void saveAPconfig();
extern HwType getHwType(const uint8_t id);
extern bool setVarDB(const std::string& key, const String& value);
/// @brief Update a variable with the given key and value
///
/// @param key Variable key
/// @param value Variable value
/// @param notify Should the change be notified (true, default) or not (false)
/// @return true If variable was created/updated
/// @return false If not
extern bool setVarDB(const std::string& key, const String& value, const bool notify = true);
extern void cleanupCurrent();
#pragma pack(pop)

View File

@@ -0,0 +1,140 @@
/// @file tagdata.h
/// @author Moritz Wirger (contact@wirmo.de)
/// @brief Custom tag data parser and helpers
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include <optional>
#include <unordered_map>
#include <vector>
#include "storage.h"
#include "system.h"
#include "web.h"
/// @brief Functions for custom tag data parser
namespace TagData {
/// @brief All available data types
enum class Type {
/// @brief Signed integer type
INT,
/// @brief Unsigned integer type
UINT,
/// @brief Float type
FLOAT,
/// @brief String type
STRING,
/// @brief Not a type, just a helper to determine max type
MAX,
};
/// @brief Field that can be parsed
struct Field {
/// @brief Field name
String name;
/// @brief Field type
Type type;
/// @brief Field byte length
uint8_t length;
/// @brief Number of decimals numeric types
uint8_t decimals;
/// @brief Optional multiplication
std::optional<double> mult;
Field(const String &name, const Type type, const uint8_t length, uint8_t decimals = 0, std::optional<double> mult = std::nullopt)
: name(name), type(type), length(length), decimals(decimals), mult(mult) {}
};
/// @brief Parser for parsing custom tag data
struct Parser {
/// @brief Parser name
String name;
/// @brief Parsed fields
std::vector<Field> fields = {};
};
/// @brief Maps parser id to parser
extern std::unordered_map<size_t, Parser> parsers;
/// @brief Load all parsers from the given json file
/// @param filename File name
extern void loadParsers(const String &filename);
/// @brief Parse the incoming custom message
/// @param src Source mac address
/// @param id Message identifier
/// @param data Payload
/// @param len Payload length
extern void parse(const uint8_t src[8], const size_t id, const uint8_t *data, const uint8_t len);
/// @brief Convert the given byte array @ref data with given @ref length to an unsigned integer
///
/// Will also convert non standard integer sizes (e.g. 3, 5, 6, and 7 bytes)
/// @tparam T Unsigned integer type
/// @param data Byte array
/// @param length Length of byte array
/// @return Unsigned integer
template <typename T, std::enable_if_t<std::is_unsigned_v<T> && std::is_integral_v<T>, bool> = true>
inline T bytesTo(const uint8_t *data, const uint8_t length) {
T value = 0;
for (int i = 0; i < length; i++) {
value |= (data[i] & 0xFF) << (8 * i);
}
return value;
}
/// @brief Convert the given byte array @ref data with given @ref length to a signed integer
///
/// Will also convert non standard integer sizes (e.g. 3, 5, 6, and 7 bytes)
/// @tparam T Signed integer type
/// @param data Byte array
/// @param length Length of byte array
/// @return Signed integer
template <typename T, std::enable_if_t<std::is_signed_v<T> && std::is_integral_v<T>, bool> = true>
inline T bytesTo(const uint8_t *data, const uint8_t length) {
T value = 0;
for (int i = 0; i < length; ++i) {
value |= (data[i] & 0xFF) << (8 * i);
}
// If data is smaller than T and last byte is negative set all upper bytes negative
if (length < sizeof(T) && (data[length - 1] & 0x80) != 0) {
value |= ~((1 << (length * 8)) - 1);
}
return value;
}
/// @brief Convert the given byte array to a float/double
/// @param data Byte array, should be at least 4/8 bytes long
/// @param length Length of byte array
/// @return float/double
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
inline T bytesTo(const uint8_t *data, const uint8_t length) {
const size_t len = sizeof(T) < length ? sizeof(T) : length;
T value;
memcpy(&value, data, len);
return value;
}
/// @brief Convert the given byte array to a string
/// @param data Byte array representing a string
/// @param length Length of byte array
/// @return String
template <typename T, std::enable_if_t<std::is_same_v<T, String>, bool> = true>
inline T bytesTo(const uint8_t *data, int length) {
return T(data, length);
}
/// @brief Convert the given byte array to a string
/// @param data Byte array representing a string
/// @param length Length of byte array
/// @return std::string
template <typename T, std::enable_if_t<std::is_same_v<T, std::string>, bool> = true>
inline T bytesTo(const uint8_t *data, int length) {
return T(data, data + length);
}
} // namespace TagData

View File

@@ -4,8 +4,10 @@
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include "system.h"
#include "web.h"
/// @brief Different utility functions
namespace util {
/// @brief Can be used to wrap a stream and see what's going on
@@ -72,6 +74,7 @@ static void printLargestFreeBlock() {
static bool httpGetJson(String &url, JsonDocument &json, const uint16_t timeout, JsonDocument *filter = nullptr) //, const followRedirects_t redirects = followRedirects_t::HTTPC_DISABLE_FOLLOW_REDIRECTS)
{
HTTPClient http;
logLine("http httpGetJson " + url);
http.begin(url);
http.setTimeout(timeout);
// http.setFollowRedirects(redirects);
@@ -100,7 +103,7 @@ static bool httpGetJson(String &url, JsonDocument &json, const uint16_t timeout,
///
/// @param str String to check
/// @return True if empty or null, false if not
static inline bool isEmptyOrNull(const String &str) {
inline bool isEmptyOrNull(const String &str) {
return str.isEmpty() || str == "null";
}
@@ -123,26 +126,68 @@ static bool isSleeping(int sleeptime1, int sleeptime2) {
}
}
/// @brief Get the time_t for midnight
/// @return time_t for midnight
inline time_t getMidnightTime() {
struct tm time_info;
getLocalTime(&time_info);
time_info.tm_hour = time_info.tm_min = time_info.tm_sec = 0;
time_info.tm_mday++;
return mktime(&time_info);
}
/// @brief Timer for kind of scheduling things
class Timer {
public:
Timer(unsigned long interval) : interval_(interval), previousMillis_(0) {}
/// @brief Construct a timer
/// @param interval Interval in ms at which @ref doRun() returns true
/// @param delay Delay in ms until first execution to defer start
Timer(const unsigned long interval, const unsigned long delay = 0) : m_interval(interval), m_nextMillis(millis() + delay) {}
void setInterval(unsigned long interval) {
interval_ = interval;
/// @brief Change the interval
/// @param interval New interval in ms
void setInterval(const unsigned long interval) {
m_interval = interval;
}
bool doRun() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis_ >= interval_) {
previousMillis_ = currentMillis;
/// @brief Check if interval is met
/// @param currentMillis Optionally provide the current time in millis
/// @return True if interval is met, false if not
bool doRun(const unsigned long currentMillis = millis()) {
if (currentMillis >= m_nextMillis) {
m_nextMillis = currentMillis + m_interval;
return true;
}
return false;
}
private:
unsigned long interval_;
unsigned long previousMillis_;
/// @brief Timer interval in ms
unsigned long m_interval;
/// @brief Next timeer interval in ms
unsigned long m_nextMillis;
};
/// @brief Create a String from format
/// @param buffer Buffer to use for sprintf
/// @param format String format
/// @return String
template <size_t bufSize>
inline String formatString(char buffer[bufSize], const char *format, ...) {
va_list args;
va_start(args, format);
const size_t size = vsnprintf(buffer, bufSize, format, args);
va_end(args);
return String(buffer, size);
}
} // namespace util
/// @brief Converts seconds to milliseconds
#define seconds(s) s * 1000
/// @brief Converts minutes to milliseconds
#define minutes(m) seconds(m * 60)
/// @brief Converts hours to milliseconds
#define hours(m) minutes(m * 60)

View File

@@ -6,12 +6,12 @@
void init_web();
void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void doJsonUpload(AsyncWebServerRequest *request);
void wsLog(String text);
void wsErr(String text);
void wsLog(const String &text);
void wsErr(const String &text);
void wsSendTaginfo(const uint8_t *mac, uint8_t syncMode);
void wsSendSysteminfo();
void wsSendAPitem(struct APlist *apitem);
void wsSerial(String text);
void wsSerial(const String &text);
uint8_t wsClientCount();
extern AsyncWebSocket ws;

View File

@@ -26,7 +26,10 @@ board_build.filesystem = littlefs
monitor_filters = esp32_exception_decoder
monitor_speed = 115200
board_build.f_cpu = 240000000L
build_unflags =
-std=gnu++11
build_flags =
-std=gnu++17
-D BUILD_ENV_NAME=$PIOENV
-D BUILD_TIME=$UNIX_TIME
-D USER_SETUP_LOADED
@@ -42,8 +45,10 @@ platform = https://github.com/platformio/platform-espressif32.git
board=lolin_s2_mini
board_build.partitions = default.csv
build_unflags =
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
-std=gnu++11
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags =
-std=gnu++17
${env.build_flags}
-D OPENEPAPERLINK_MINI_AP_PCB
-D ARDUINO_USB_MODE=0
@@ -64,7 +69,7 @@ build_flags =
-D FLASHER_LED=15
-D FLASHER_RGB_LED=33
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680
@@ -79,8 +84,10 @@ platform = https://github.com/platformio/platform-espressif32.git
board=lolin_s2_mini
board_build.partitions = default.csv
build_unflags =
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
-std=gnu++11
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags =
-std=gnu++17
${env.build_flags}
-D OPENEPAPERLINK_NANO_AP_PCB
-D ARDUINO_USB_MODE=0
@@ -99,7 +106,7 @@ build_flags =
-D FLASHER_LED=15
-D FLASHER_RGB_LED=-1
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680
@@ -114,9 +121,11 @@ platform = https://github.com/platformio/platform-espressif32.git
board = esp32-s3-devkitc-1
board_build.partitions = default_16MB.csv
build_unflags =
-D ARDUINO_USB_MODE=1
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
-std=gnu++11
-D ARDUINO_USB_MODE=1
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags =
-std=gnu++17
${env.build_flags}
-D OPENEPAPERLINK_PCB
-D ARDUINO_USB_MODE=0
@@ -158,7 +167,7 @@ build_flags =
-D FLASHER_LED=21
-D FLASHER_RGB_LED=48
build_src_filter =
+<*>-<espflasher.cpp>
+<*>-<espflasher.cpp>
board_build.flash_mode=qio
board_build.arduino.memory_type = qio_opi
board_build.psram_type=qspi_opi
@@ -173,7 +182,10 @@ board_upload.flash_size = 16MB
[env:Simple_AP]
board = esp32dev
board_build.partitions = default.csv
build_unflags =
-std=gnu++11
build_flags =
-std=gnu++17
${env.build_flags}
-D CORE_DEBUG_LEVEL=0
-D SIMPLE_AP
@@ -188,7 +200,7 @@ build_flags =
-D FLASHER_AP_RXD=16
-D FLASHER_LED=22
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an wemos_d1_mini32
@@ -197,7 +209,10 @@ build_src_filter =
[env:Wemos_d1_mini32_AP]
board = wemos_d1_mini32
board_build.partitions = default.csv
build_unflags =
-std=gnu++11
build_flags =
-std=gnu++17
${env.build_flags}
-D CORE_DEBUG_LEVEL=0
@@ -214,7 +229,7 @@ build_flags =
-D FLASHER_AP_RXD=17
-D FLASHER_LED=22
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an m5stack esp32
@@ -224,7 +239,10 @@ build_src_filter =
platform = espressif32
board = m5stack-core-esp32
board_build.partitions = esp32_sdcard.csv
build_unflags =
-std=gnu++11
build_flags =
-std=gnu++17
${env.build_flags}
-D CORE_DEBUG_LEVEL=0
@@ -251,7 +269,7 @@ build_flags =
-D ILI9341_DRIVER
-D SMOOTH_FONT
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an ESP32-S3 16MB Flash 8MB RAM
;
@@ -260,12 +278,14 @@ build_src_filter =
board = esp32-s3-devkitc-1
board_build.partitions = large_spiffs_16MB.csv
build_unflags =
-std=gnu++11
-D ARDUINO_USB_MODE=1
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
-D ILI9341_DRIVER
lib_deps =
${env.lib_deps}
build_flags =
-std=gnu++17
${env.build_flags}
-D YELLOW_IPS_AP
-D CORE_DEBUG_LEVEL=0
@@ -307,7 +327,7 @@ build_flags =
-D SERIAL_FLASHER_BOOT_HOLD_TIME_MS=50
-D SERIAL_FLASHER_RESET_HOLD_TIME_MS=100
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>
board_build.flash_mode=qio
board_build.arduino.memory_type = qio_opi
board_build.psram_type=qspi_opi
@@ -321,7 +341,10 @@ board_upload.flash_size = 16MB
[env:Sonoff_zb_bridge_P_AP]
board = esp32dev
board_build.partitions = default.csv
build_unflags =
-std=gnu++11
build_flags =
-std=gnu++17
${env.build_flags}
-D CORE_DEBUG_LEVEL=0
@@ -340,7 +363,7 @@ build_flags =
-D FLASHER_AP_RXD=23
-D FLASHER_LED=2
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680
@@ -354,8 +377,10 @@ platform = https://github.com/platformio/platform-espressif32.git
board=lolin_s2_mini
board_build.partitions = default.csv
build_unflags =
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
-std=gnu++11
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags =
-std=gnu++17
${env.build_flags}
-D OPENEPAPERLINK_MINI_AP_PCB
-D ARDUINO_USB_MODE=0
@@ -375,7 +400,7 @@ build_flags =
-D FLASHER_LED=2
-D FLASHER_RGB_LED=-1
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680
@@ -389,9 +414,11 @@ board_upload.flash_size = 4MB
board = esp32-s3-devkitc-1
board_build.partitions = 32MB_partition table.csv
build_unflags =
-D ARDUINO_USB_MODE=1
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
-std=gnu++11
-D ARDUINO_USB_MODE=1
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags =
-std=gnu++17
${env.build_flags}
-D OutdoorAP
-D HAS_RGB_LED
@@ -414,7 +441,7 @@ build_flags =
-D FLASHER_LED=21
-D FLASHER_RGB_LED=38
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.flash_mode=opi
board_build.arduino.memory_type = opi_opi
board_build.psram_type=qspi_opi
@@ -422,3 +449,53 @@ board_upload.maximum_size = 16777216
board_upload.maximum_ram_size = 327680
board_upload.flash_size = 32MB
#upload_flags = --no-stub
; ----------------------------------------------------------------------------------------
; !!! this configuration expects the PoE-AP and is work in progress right now !!!
; ----------------------------------------------------------------------------------------
[env:OpenEPaperLink_PoE_AP]
platform = https://github.com/platformio/platform-espressif32.git
board=esp32dev
board_build.partitions = 16MB_partition table.csv
build_unflags =
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
-std=gnu++11
lib_deps =
${env.lib_deps}
build_flags =
-std=gnu++17
${env.build_flags}
; -D CORE_DEBUG_LEVEL=5
-D OPENEPAPERLINK_POE_AP_PCB
-D CONFIG_SPIRAM_USE_MALLOC=1
-D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
-D HAS_RGB_LED
-D BOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
-D HAS_SDCARD
-D POWER_NO_SOFT_POWER
-D FLASHER_AP_SS=-1
-D FLASHER_AP_CLK=-1
-D FLASHER_AP_MOSI=-1
-D FLASHER_AP_MISO=-1
-D FLASHER_AP_RESET=-1
-D FLASHER_AP_POWER={-1} ;this board has no soft power control
-D FLASHER_AP_TXD=15
-D FLASHER_AP_RXD=4
-D FLASHER_AP_TEST=-1
-D FLASHER_LED=-1
-D FLASHER_RGB_LED=5
-D SD_CARD_CLK=13
-D SD_CARD_MISO=36
-D SD_CARD_MOSI=14
-D SD_CARD_SS=12
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.flash_mode=qio
board_upload.maximum_size = 16777216
board_upload.maximum_ram_size = 327680
board_upload.flash_size = 16MB

View File

@@ -43,6 +43,37 @@ bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request) {
return false;
}
String SPIFFSEditor::listFilesRecursively(String path, bool recursive) {
if (recursive && (path == "//www" || path == "//tagtypes" || path == "//current")) return "";
File dir = _fs.open(path);
String output = "";
File file = dir.openNextFile();
bool isFirstFile = true;
while (file) {
if (file.isDirectory()) {
if (recursive) {
String subDirPath = String(path + "/" + file.name());
String subDirOutput = listFilesRecursively(subDirPath, true);
output += subDirOutput;
} else {
output += ",{\"type\":\"dir\",\"name\":\"" + String(file.name()) + "\"}";
}
} else {
if (recursive) {
output += ",{\"type\":\"file\",\"name\":\"" + path.substring(2) + "/" + String(file.name()) + "\",\"size\":" + file.size() + "}";
} else {
output += ",{\"type\":\"file\",\"name\":\"" + String(file.name()) + "\",\"size\":" + file.size() + "}";
}
}
file = dir.openNextFile();
}
dir.close();
return output;
}
void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request) {
if (_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str())) {
return request->requestAuthentication();
@@ -51,23 +82,7 @@ void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request) {
if (request->method() == HTTP_GET) {
if (request->hasParam("list")) {
const String path = request->getParam("list")->value();
File dir = _fs.open(path);
String output = "[";
File file = dir.openNextFile();
while (file) {
if (output != "[") {
output += ',';
}
if (file.isDirectory()) {
output += "{\"type\":\"dir\",\"name\":\"" + String(file.name()) + "\",\"size\":" + file.size() + "}";
} else {
output += "{\"type\":\"file\",\"name\":\"" + String(file.name()) + "\",\"size\":" + file.size() + "}";
}
file = dir.openNextFile();
}
dir.close();
output += "]";
String output = "[" + listFilesRecursively(path, request->hasParam("recursive")).substring(1) + "]";
request->send(200, "application/json", output);
} else if (request->hasParam("edit") || request->hasParam("download")) {
request->send(request->_tempFile, request->_tempFile.name(), String(), request->hasParam("download"));

View File

@@ -28,6 +28,7 @@
#endif
#include "language.h"
#include "settings.h"
#include "system.h"
#include "tag_db.h"
#include "truetype.h"
#include "util.h"
@@ -177,12 +178,6 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
}
#endif
struct tm time_info;
getLocalTime(&time_info);
time_info.tm_hour = time_info.tm_min = time_info.tm_sec = 0;
time_info.tm_mday++;
const time_t midnight = mktime(&time_info);
DynamicJsonDocument doc(500);
deserializeJson(doc, taginfo->modeConfigJson);
JsonObject cfgobj = doc.as<JsonObject>();
@@ -192,7 +187,6 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
taginfo->nextupdate = now + 60;
imgParam imageParams;
imageParams.width = hwdata.width;
imageParams.height = hwdata.height;
imageParams.bpp = hwdata.bpp;
@@ -203,7 +197,7 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
imageParams.dither = false;
if (taginfo->hasCustomLUT && taginfo->lut != 1) imageParams.grayLut = true;
imageParams.invert = false;
imageParams.invert = taginfo->invert;
imageParams.symbols = 0;
imageParams.rotate = taginfo->rotate;
@@ -230,12 +224,20 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
}
if (contentFS->exists(configFilename)) {
imageParams.dither = cfgobj["dither"] && cfgobj["dither"] == "1";
imageParams.preload = cfgobj["preload"] && cfgobj["preload"] == "1";
imageParams.preloadlut = cfgobj["preload_lut"];
imageParams.preloadtype = cfgobj["preload_type"];
jpg2buffer(configFilename, filename, imageParams);
} else {
filename = "/current/" + String(hexmac) + ".raw";
filename = "/current/" + String(hexmac) + ".pending";
if (!contentFS->exists(filename)) {
filename = "/current/" + String(hexmac) + ".raw";
}
if (contentFS->exists(filename)) {
prepareDataAvail(filename, imageParams.dataType, imageParams.lut, mac, cfgobj["timetolive"].as<int>(), true);
wsLog("File " + configFilename + " not found, resending image " + filename);
wsLog("Resending image " + filename);
} else {
wsErr("File " + configFilename + " not found");
}
@@ -246,7 +248,18 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
if (imageParams.lut = EPD_LUT_NO_REPEATS && imageParams.shortlut == SHORTLUT_ONLY_BLACK) imageParams.lut = EPD_LUT_DEFAULT;
}
if (prepareDataAvail(filename, imageParams.dataType, imageParams.lut, mac, cfgobj["timetolive"].as<int>())) {
struct imageDataTypeArgStruct arg = {0};
// load parameters in case we do need to preload an image
if (imageParams.preload) {
arg.preloadImage = 1;
arg.specialType = imageParams.preloadtype;
arg.lut = imageParams.preloadlut;
} else {
arg.lut = imageParams.lut & 0x03;
}
if (prepareDataAvail(filename, imageParams.dataType, *((uint8_t *)&arg), mac, cfgobj["timetolive"].as<int>())) {
if (cfgobj["delete"].as<String>() == "1") {
contentFS->remove("/" + configFilename);
}
@@ -260,12 +273,12 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
case 1: // Today
drawDate(filename, taginfo, imageParams);
taginfo->nextupdate = midnight;
updateTagImage(filename, mac, (midnight - now) / 60 - 10, taginfo, imageParams);
taginfo->nextupdate = util::getMidnightTime();
updateTagImage(filename, mac, (taginfo->nextupdate - now) / 60 - 10, taginfo, imageParams);
break;
case 2: // CountDays
drawCounter(mac, buttonPressed, taginfo, cfgobj, filename, imageParams, midnight, 15);
drawCounter(mac, buttonPressed, taginfo, cfgobj, filename, imageParams, util::getMidnightTime(), 15);
break;
case 3: // CountHours
@@ -462,7 +475,7 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
bool updateTagImage(String &filename, const uint8_t *dst, uint16_t nextCheckin, tagRecord *&taginfo, imgParam &imageParams) {
if (taginfo->hwType == SOLUM_SEG_UK) {
sendAPSegmentedData(dst, (String)imageParams.segments, imageParams.symbols, imageParams.invert, (taginfo->isExternal == false));
sendAPSegmentedData(dst, (String)imageParams.segments, imageParams.symbols, (imageParams.invert == 1), (taginfo->isExternal == false));
} else {
if (imageParams.hasRed) {
imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
@@ -489,11 +502,19 @@ void replaceVariables(String &format) {
size_t startIndex = 0;
size_t openBraceIndex, closeBraceIndex;
time_t now;
time(&now);
struct tm timedef;
localtime_r(&now, &timedef);
char timeBuffer[80];
strftime(timeBuffer, sizeof(timeBuffer), "%H:%M:%S", &timedef);
setVarDB("ap_time", timeBuffer, false);
while ((openBraceIndex = format.indexOf('{', startIndex)) != -1 &&
(closeBraceIndex = format.indexOf('}', openBraceIndex + 1)) != -1) {
const std::string variableName = format.substring(openBraceIndex + 1, closeBraceIndex).c_str();
const std::string varKey = "{" + variableName + "}";
auto var = varDB.find(variableName);
const auto var = varDB.find(variableName);
if (var != varDB.end()) {
format.replace(varKey.c_str(), var->second.value);
}
@@ -671,7 +692,7 @@ const String getWeatherIcon(const uint8_t id, const bool isNight = false) {
"\uf01b", "", "\uf01b", "", "\uf01b", "", "\uf076", "", "", "\uf01a",
"\uf01a", "\uf01a", "", "", "\uf064", "\uf064", "", "", "", "",
"", "", "", "", "\uf01e", "\uf01d", "", "", "\uf01e"};
if (isNight && id <= 3) {
if (isNight && id <= 2) {
const String nightIcons[] = {"\uf02e", "\uf083", "\uf086"};
return nightIcons[id];
}
@@ -851,6 +872,7 @@ int getImgURL(String &filename, String URL, time_t fetched, imgParam &imageParam
Storage.begin();
HTTPClient http;
logLine("http getImgURL " + URL);
http.begin(URL);
http.addHeader("If-Modified-Since", formatHttpDate(fetched));
http.addHeader("X-ESL-MAC", MAC);
@@ -956,6 +978,7 @@ bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo,
strftime(dateString, sizeof(dateString), "%d.%m.%Y", &timeinfo);
HTTPClient http;
logLine("http getCalFeed " + URL);
http.begin(URL);
http.setTimeout(10000);
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
@@ -1058,6 +1081,7 @@ uint8_t drawBuienradar(String &filename, JsonObject &cfgobj, tagRecord *&taginfo
String lat = cfgobj["#lat"];
String lon = cfgobj["#lon"];
logLine("http drawBuienradar");
http.begin("https://gps.buienradar.nl/getrr.php?lat=" + lat + "&lon=" + lon);
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.setTimeout(5000);
@@ -1319,6 +1343,7 @@ bool getJsonTemplateFileExtractVariables(String &filename, String jsonfile, Json
int getJsonTemplateUrl(String &filename, String URL, time_t fetched, String MAC, tagRecord *&taginfo, imgParam &imageParams) {
HTTPClient http;
http.useHTTP10(true);
logLine("http getJsonTemplateUrl " + URL);
http.begin(URL);
http.addHeader("If-Modified-Since", formatHttpDate(fetched));
http.addHeader("X-ESL-MAC", MAC);
@@ -1339,7 +1364,7 @@ int getJsonTemplateUrl(String &filename, String URL, time_t fetched, String MAC,
void drawJsonStream(Stream &stream, String &filename, tagRecord *&taginfo, imgParam &imageParams) {
TFT_eSprite spr = TFT_eSprite(&tft);
initSprite(spr, imageParams.width, imageParams.height, imageParams);
DynamicJsonDocument doc(300);
DynamicJsonDocument doc(500);
if (stream.find("[")) {
do {
DeserializationError error = deserializeJson(doc, stream);

View File

@@ -7,6 +7,7 @@
#include "esp32_port.h"
#include "esp_littlefs.h"
#include "storage.h"
#include "tag_db.h"
#include "web.h"
esp_loader_error_t connect_to_target(uint32_t higher_transmission_rate) {
@@ -125,18 +126,22 @@ bool downloadAndWriteBinary(String &filename, const char *url) {
int binaryResponseCode = binaryHttp.GET();
Serial.println(binaryResponseCode);
if (binaryResponseCode == HTTP_CODE_OK) {
int contentLength = binaryHttp.getSize();
Serial.println(contentLength);
xSemaphoreTake(fsMutex, portMAX_DELAY);
File file = contentFS->open(filename, "wb");
if (file) {
wsSerial("downloading " + String(filename));
WiFiClient *stream = binaryHttp.getStreamPtr();
uint8_t buffer[256];
uint8_t buffer[1024];
size_t totalBytesRead = 0;
while (stream->available()) {
time_t timeOut = millis() + 5000;
// while (stream->available()) {
while (millis() < timeOut) {
size_t bytesRead = stream->readBytes(buffer, sizeof(buffer));
file.write(buffer, bytesRead);
totalBytesRead += bytesRead;
vTaskDelay(1 / portTICK_PERIOD_MS);
if (totalBytesRead == contentLength) break;
}
file.close();
xSemaphoreGive(fsMutex);
@@ -144,7 +149,7 @@ bool downloadAndWriteBinary(String &filename, const char *url) {
file = contentFS->open(filename, "r");
if (file) {
if (totalBytesRead == file.size() && file.size() > 0) {
if (totalBytesRead == contentLength || (contentLength == 0 && file.size() > 0)) {
file.close();
return true;
}
@@ -163,8 +168,7 @@ bool downloadAndWriteBinary(String &filename, const char *url) {
}
bool doC6flash(uint8_t doDownload) {
const char *githubUrl = "https://raw.githubusercontent.com/jjwbruijn/OpenEPaperLink/master/binaries/ESP32-C6/firmware.json";
const String githubUrl = "https://raw.githubusercontent.com/" + config.repo + "/master/binaries/ESP32-C6/firmware.json";
HTTPClient http;
Serial.println(githubUrl);
http.begin(githubUrl);
@@ -181,7 +185,8 @@ bool doC6flash(uint8_t doDownload) {
JsonArray jsonArray = jsonDoc.as<JsonArray>();
for (JsonObject obj : jsonArray) {
String filename = "/" + obj["filename"].as<String>();
String binaryUrl = "https://raw.githubusercontent.com/jjwbruijn/OpenEPaperLink/master/binaries/ESP32-C6" + String(filename);
// String binaryUrl = "https://raw.githubusercontent.com/" + config.repo + "/master/binaries/ESP32-C6" + String(filename);
String binaryUrl = "http://www.openepaperlink.eu/binaries/ESP32-C6" + String(filename);
for (int retry = 0; retry < 10; retry++) {
if (downloadAndWriteBinary(filename, binaryUrl.c_str())) {
break;

View File

@@ -176,7 +176,6 @@ bool flasher::getInfoBlockType() {
bool flasher::findTagByMD5() {
DynamicJsonDocument doc(3000);
DynamicJsonDocument APconfig(600);
fs::File readfile = contentFS->open("/tag_md5_db.json", "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {
@@ -206,7 +205,6 @@ bool flasher::findTagByMD5() {
bool flasher::findTagByType(uint8_t type) {
DynamicJsonDocument doc(3000);
DynamicJsonDocument APconfig(600);
fs::File readfile = contentFS->open("/tag_md5_db.json", "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {
@@ -445,8 +443,7 @@ bool flasher::writeFlashFromPackOffset(fs::File *file, uint16_t length) {
}
bool flasher::writeFlashFromPack(String filename, uint8_t type) {
StaticJsonDocument<512> doc;
DynamicJsonDocument APconfig(512);
DynamicJsonDocument doc(1024);
fs::File readfile = contentFS->open(filename, "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {
@@ -469,6 +466,7 @@ bool flasher::writeFlashFromPack(String filename, uint8_t type) {
}
Serial.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");
}
readfile.close();
@@ -505,7 +503,6 @@ bool flasher::writeBlock(uint16_t offset, uint8_t *data, uint16_t len, bool info
uint16_t getAPUpdateVersion(uint8_t type) {
StaticJsonDocument<512> doc;
DynamicJsonDocument APconfig(512);
fs::File readfile = contentFS->open("/AP_FW_Pack.bin", "r");
DeserializationError err = deserializeJson(doc, readfile);
if (!err) {

View File

@@ -84,7 +84,7 @@ void yellow_ap_display_init(void) {
ledcSetup(6, 5000, 8);
ledcAttachPin(TFT_BACKLIGHT, 6);
ledcWrite(6, 255); // config.led
ledcWrite(6, config.tft);
tft2.init();
tft2.setRotation(YellowSense == 1 ? 1 : 3);

View File

@@ -14,6 +14,8 @@ int maxledbrightness = 255;
#ifdef HAS_RGB_LED
QueueHandle_t rgbLedQueue;
CRGB rgbIdleColor = CRGB::Green;
uint16_t rgbIdlePeriod = 511;
struct ledInstructionRGB {
CRGB ledColor;
@@ -56,6 +58,7 @@ 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;
@@ -72,6 +75,7 @@ void shortBlink(CRGB cname) {
rgb->fadeTime = 0;
rgb->length = 3;
addToRGBQueue(rgb, false);
#endif
}
void flushRGBQueue() {
@@ -119,9 +123,6 @@ void showRGB() {
FastLED.show();
}
volatile CRGB rgbIdleColor = CRGB::Green;
volatile uint16_t rgbIdlePeriod = 800;
void rgbIdleStep() {
static bool dirUp = true;
static uint16_t step = 0;
@@ -129,7 +130,7 @@ void rgbIdleStep() {
if (dirUp) {
// up
step++;
if (step == rgbIdlePeriod) {
if (step >= rgbIdlePeriod) {
dirUp = false;
}
} else {
@@ -139,7 +140,7 @@ void rgbIdleStep() {
dirUp = true;
}
}
CRGB newvalue = blend(CRGB::Black, (const CRGB&)rgbIdleColor, gamma8[map(step, 0, rgbIdlePeriod, 0, 255)]);
CRGB newvalue = blend(CRGB::Black, (const CRGB&)rgbIdleColor, map(step, 0, rgbIdlePeriod, 0, 255));
if (newvalue != leds[0]) {
leds[0] = newvalue;
showRGB();
@@ -150,7 +151,7 @@ void rgbIdleStep() {
void setBrightness(int brightness) {
maxledbrightness = brightness;
#ifdef YELLOW_IPS_AP
// ledcWrite(6, config.led);
ledcWrite(6, config.tft);
#endif
#ifdef HAS_RGB_LED
FastLED.setBrightness(maxledbrightness);
@@ -165,6 +166,7 @@ void updateBrightnessFromConfig() {
setBrightness(newbrightness);
}
}
ledcWrite(6, config.tft);
}
void addToMonoQueue(struct ledInstruction* mono) {
@@ -193,7 +195,11 @@ 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
mono->value = 255;
#else
mono->value = maxledbrightness;
#endif
mono->fadeTime = 120 / repeat;
mono->length = 0;
addToMonoQueue(mono);
@@ -207,31 +213,6 @@ void quickBlink(uint8_t repeat) {
volatile uint16_t monoIdlePeriod = 900;
uint8_t monoValue = 0;
void monoIdleStep() {
static bool dirUp = true;
static uint16_t step = 0;
if (dirUp) {
// up
step++;
if (step == monoIdlePeriod) {
dirUp = false;
}
} else {
// down
step--;
if (step == 0) {
dirUp = true;
}
}
uint8_t newvalue = map(step, 0, monoIdlePeriod, 0, maxledbrightness);
if (newvalue != monoValue) {
monoValue = newvalue;
showMono(newvalue);
}
}
void ledTask(void* parameter) {
#ifdef HAS_RGB_LED
FastLED.addLeds<WS2812B, FLASHER_RGB_LED, GRB>(leds, 1); // GRB ordering is typical
@@ -244,9 +225,6 @@ void ledTask(void* parameter) {
addFadeColor(CRGB::Red);
addFadeColor(CRGB::Green);
addFadeColor(CRGB::Blue);
addFadeColor(CRGB::Red);
addFadeColor(CRGB::Green);
addFadeColor(CRGB::Blue);
CRGB oldColor = CRGB::Black;
uint16_t rgbInstructionFadeTime = 0;
#endif
@@ -263,7 +241,11 @@ void ledTask(void* parameter) {
struct ledInstruction* monoled = nullptr;
addFadeMono(0);
#ifdef YELLOW_IPS_AP
addFadeMono(255);
#else
addFadeMono(maxledbrightness);
#endif
addFadeMono(0);
uint8_t oldBrightness = 0;
@@ -324,9 +306,7 @@ void ledTask(void* parameter) {
if (monoled->fadeTime <= 1) {
showMono(monoled->value);
}
} else {
// monoIdleStep();
}
}
} else {
if (monoled->fadeTime) {
monoled->fadeTime--;

View File

@@ -10,6 +10,7 @@
#include "storage.h"
#include "system.h"
#include "tag_db.h"
#include "tagdata.h"
#include "wifimanager.h"
#ifdef HAS_USB
@@ -22,10 +23,15 @@
#include "util.h"
#include "web.h"
util::Timer intervalSysinfo(3000);
util::Timer intervalVars(10000);
util::Timer intervalSaveDB(300000);
util::Timer intervalContentRunner(1000);
util::Timer intervalContentRunner(seconds(1));
util::Timer intervalSysinfo(seconds(3));
util::Timer intervalVars(seconds(10));
util::Timer intervalSaveDB(minutes(5));
util::Timer intervalCheckDate(minutes(5));
#ifdef OPENEPAPERLINK_PCB
util::Timer tagConnectTimer(seconds(1));
#endif
SET_LOOP_TASK_STACK_SIZE(16 * 1024);
@@ -52,11 +58,6 @@ void setup() {
xTaskCreate(ledTask, "ledhandler", 2000, NULL, 2, NULL);
vTaskDelay(10 / portTICK_PERIOD_MS);
#ifdef HAS_RGB_LED
// show a nice pattern to indicate the AP is booting / waiting for WiFi setup
showColorPattern(CRGB::Aqua, CRGB::Green, CRGB::Blue);
#endif
#if defined(OPENEPAPERLINK_MINI_AP_PCB) || defined(OPENEPAPERLINK_NANO_AP_PCB)
APEnterEarlyReset();
// this allows us to view the booting process. After the device showing up, you have 3 seconds to open a terminal on the COM port
@@ -118,6 +119,7 @@ void setup() {
initAPconfig();
xTaskCreate(initTime, "init time", 5000, NULL, 2, NULL);
updateLanguageFromConfig();
updateBrightnessFromConfig();
@@ -126,6 +128,7 @@ void setup() {
#ifdef HAS_RGB_LED
rgbIdle();
#endif
TagData::loadParsers("/parsers.json");
loadDB("/current/tagDB.json");
cleanupCurrent();
xTaskCreate(APTask, "AP Process", 6000, NULL, 2, NULL);
@@ -138,7 +141,6 @@ void setup() {
config.runStatus = RUNSTATUS_PAUSE;
}
xTaskCreate(initTime, "init time", 5000, NULL, 2, NULL);
xTaskCreate(delayedStart, "delaystart", 2000, NULL, 2, NULL);
wsSendSysteminfo();
@@ -161,6 +163,21 @@ void loop() {
if (intervalContentRunner.doRun() && apInfo.state == AP_STATE_ONLINE) {
contentRunner();
}
if (intervalCheckDate.doRun() && config.runStatus == RUNSTATUS_RUN) {
static uint8_t day = 0;
time_t now;
time(&now);
struct tm timedef;
localtime_r(&now, &timedef);
if (day != timedef.tm_mday) {
day = timedef.tm_mday;
char timeBuffer[80];
strftime(timeBuffer, sizeof(timeBuffer), "%d-%m-%Y", &timedef);
setVarDB("ap_date", timeBuffer);
}
}
#ifdef YELLOW_IPS_AP
extern void yellow_ap_display_loop(void);
@@ -168,21 +185,17 @@ void loop() {
#endif
#ifdef OPENEPAPERLINK_PCB
time_t tagConnectTimer = 0;
if (millis() - tagConnectTimer > 1000) {
tagConnectTimer = millis();
if (extTagConnected()) {
flashCountDown(3);
if (tagConnectTimer.doRun() && extTagConnected()) {
flashCountDown(3);
pinMode(FLASHER_EXT_TEST, OUTPUT);
digitalWrite(FLASHER_EXT_TEST, LOW);
pinMode(FLASHER_EXT_TEST, OUTPUT);
digitalWrite(FLASHER_EXT_TEST, LOW);
doTagFlash();
doTagFlash();
vTaskDelay(10000 / portTICK_PERIOD_MS);
pinMode(FLASHER_EXT_TEST, INPUT);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
vTaskDelay(10000 / portTICK_PERIOD_MS);
pinMode(FLASHER_EXT_TEST, INPUT);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
#endif

View File

@@ -97,6 +97,9 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t
{0, 0, 0}, // Black
{255, 0, 0} // Red
};
if (imageParams.invert == 1) {
std::swap(palette[0], palette[1]);
}
if (imageParams.grayLut) {
Color newColor = {160, 160, 160};
palette.push_back(newColor);

View File

@@ -12,6 +12,7 @@
#include "storage.h"
#include "system.h"
#include "tag_db.h"
#include "tagdata.h"
#include "udp.h"
#include "util.h"
#include "web.h"
@@ -238,6 +239,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
String imageUrl = "http://" + remoteIP.toString() + filename;
wsLog("GET " + imageUrl);
HTTPClient http;
logLine("http prepareExternalDataAvail " + imageUrl);
http.begin(imageUrl);
int httpCode = http.GET();
if (httpCode == 200) {
@@ -249,6 +251,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
} else if (httpCode == 404) {
imageUrl = "http://" + remoteIP.toString() + "/current/" + String(hexmac) + ".raw";
http.end();
logLine("http prepareExternalDataAvail " + imageUrl);
http.begin(imageUrl);
httpCode = http.GET();
if (httpCode == 200) {
@@ -295,6 +298,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
String dataUrl = "http://" + remoteIP.toString() + "/getdata?mac=" + String(hexmac);
wsLog("GET " + dataUrl);
HTTPClient http;
logLine("http DATATYPE_CUSTOM_LUT_OTA " + dataUrl);
http.begin(dataUrl);
int httpCode = http.GET();
if (httpCode == 200) {
@@ -326,7 +330,9 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
}
void processBlockRequest(struct espBlockRequest* br) {
if (config.runStatus == RUNSTATUS_STOP) return;
if (config.runStatus == RUNSTATUS_STOP) {
return;
}
if (!checkCRC(br, sizeof(struct espBlockRequest))) {
Serial.print("Failed CRC on a blockrequest received by the AP");
return;
@@ -368,7 +374,9 @@ void processBlockRequest(struct espBlockRequest* br) {
}
void processXferComplete(struct espXferComplete* xfc, bool local) {
if (config.runStatus == RUNSTATUS_STOP) return;
if (config.runStatus == RUNSTATUS_STOP) {
return;
}
char buffer[64];
sprintf(buffer, "< %02X%02X%02X%02X%02X%02X%02X%02X reports xfer complete\n\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
wsLog((String)buffer);
@@ -412,7 +420,9 @@ void processXferComplete(struct espXferComplete* xfc, bool local) {
}
void processXferTimeout(struct espXferComplete* xfc, bool local) {
if (config.runStatus == RUNSTATUS_STOP) return;
if (config.runStatus == RUNSTATUS_STOP) {
return;
}
char buffer[64];
sprintf(buffer, "< %02X%02X%02X%02X%02X%02X%02X%02X xfer timeout\n\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
wsErr((String)buffer);
@@ -436,7 +446,9 @@ void processXferTimeout(struct espXferComplete* xfc, bool local) {
}
void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP) {
if (config.runStatus == RUNSTATUS_STOP) return;
if (config.runStatus == RUNSTATUS_STOP) {
return;
}
char buffer[64];
tagRecord* taginfo = tagRecord::findByMAC(eadr->src);
@@ -527,16 +539,17 @@ void processTagReturnData(struct espTagReturnData* trd, uint8_t len, bool local)
if (!checkCRC(trd, len)) {
return;
}
char buffer[64];
const uint8_t payloadLength = trd->len - 11;
// Replace this stuff with something that handles the data coming from the tag. This is here for demo purposes!
char buffer[64];
sprintf(buffer, "<TRD %02X%02X%02X%02X%02X%02X%02X%02X\n", trd->src[7], trd->src[6], trd->src[5], trd->src[4], trd->src[3], trd->src[2], trd->src[1], trd->src[0]);
wsLog((String)buffer);
sprintf(buffer, "TRD Data: len=%d, type=%d, ver=0x%08X\n", trd->len - 11, trd->returnData.dataType, trd->returnData.dataVer);
sprintf(buffer, "TRD Data: len=%d, type=%d, ver=0x%08X\n", payloadLength, trd->returnData.dataType, trd->returnData.dataVer);
wsLog((String)buffer);
uint8_t actualPayloadLength = trd->len - 11;
uint8_t* actualPayload = (uint8_t*)calloc(actualPayloadLength, 1);
memcpy(actualPayload, trd->returnData.data, actualPayloadLength);
TagData::parse(trd->src, trd->returnData.dataType, trd->returnData.data, payloadLength);
}
void refreshAllPending() {

View File

@@ -8,6 +8,7 @@
#include <Update.h>
#include "espflasher.h"
#include "leds.h"
#include "serialap.h"
#include "storage.h"
#include "tag_db.h"
@@ -240,8 +241,7 @@ void C6firmwareUpdateTask(void* parameter) {
uint8_t doDownload = *((uint8_t*)parameter);
wsSerial("Stopping AP service");
apInfo.isOnline = false;
apInfo.state = AP_STATE_FLASHING;
setAPstate(false, AP_STATE_FLASHING);
config.runStatus = RUNSTATUS_STOP;
extern bool rxSerialStopTask2;
rxSerialStopTask2 = true;
@@ -255,8 +255,8 @@ void C6firmwareUpdateTask(void* parameter) {
wsSerial("C6 flash end");
if (result) {
apInfo.state = AP_STATE_OFFLINE;
setAPstate(false, AP_STATE_OFFLINE);
wsSerial("Finishing config...");
vTaskDelay(3000 / portTICK_PERIOD_MS);

View File

@@ -137,6 +137,24 @@ void APEnterEarlyReset() {
digitalWrite(AP_RESET_PIN, LOW);
}
void setAPstate(bool isOnline, uint8_t state) {
apInfo.isOnline = isOnline;
apInfo.state = state;
#ifdef HAS_RGB_LED
CRGB colorMap[7] = {
CRGB::Orange,
CRGB::Green,
CRGB::Blue,
CRGB::Yellow,
CRGB::Aqua,
CRGB::Red,
CRGB::YellowGreen
};
rgbIdleColor = colorMap[state];
rgbIdlePeriod = (isOnline ? 767 : 255);
#endif
}
// Reset the tag
void APTagReset() {
Serial.println("Resetting tag");
@@ -270,7 +288,7 @@ bool sendPing() {
Serial.print("ping");
int t = millis();
if (!txStart()) return false;
for (uint8_t attempt = 0; attempt < 5; attempt++) {
for (uint8_t attempt = 0; attempt < 3; attempt++) {
cmdReplyValue = CMD_REPLY_WAIT;
AP_SERIAL_PORT.print("RDY?");
if (waitCmdReply()) {
@@ -632,8 +650,7 @@ void notifySegmentedFlash() {
void checkWaitPowerCycle() {
// check if we should wait for a power cycle. If we do, try to inform the user the best we can, and hang.
#ifdef POWER_NO_SOFT_POWER
apInfo.isOnline = false;
apInfo.state = AP_STATE_REQUIRED_POWER_CYCLE;
setAPstate(false, AP_STATE_REQUIRED_POWER_CYCLE);
// If we have no soft power control, we'll now wait until the device is power-cycled
Serial.printf("Please power-cycle your AP/device\n");
#ifdef HAS_RGB_LED
@@ -660,16 +677,11 @@ void segmentedShowIp() {
bool bringAPOnline() {
if (apInfo.state == AP_STATE_FLASHING) return false;
apInfo.isOnline = false;
apInfo.state = AP_STATE_OFFLINE;
setAPstate(false, AP_STATE_OFFLINE);
// try without rebooting
AP_SERIAL_PORT.updateBaudRate(115200);
uint32_t bootTimeout = millis();
bool APrdy = false;
while ((!APrdy) && (millis() - bootTimeout < 3 * 1000)) {
APrdy = sendPing();
vTaskDelay(300 / portTICK_PERIOD_MS);
}
bool APrdy = sendPing();
if (!APrdy) {
if (apInfo.state == AP_STATE_FLASHING) return false;
APTagReset();
@@ -684,11 +696,11 @@ bool bringAPOnline() {
if (!APrdy) {
return false;
} else {
apInfo.state = AP_STATE_COMING_ONLINE;
setAPstate(false, AP_STATE_COMING_ONLINE);
sendChannelPower(&curChannel);
vTaskDelay(200 / portTICK_PERIOD_MS);
if (!sendGetInfo()) {
apInfo.state = AP_STATE_OFFLINE;
setAPstate(false, AP_STATE_OFFLINE);
return false;
}
if (apInfo.type == ESP32_C6) {
@@ -701,8 +713,7 @@ bool bringAPOnline() {
}
vTaskDelay(200 / portTICK_PERIOD_MS);
apInfo.isOnline = true;
apInfo.state = AP_STATE_ONLINE;
setAPstate(true, AP_STATE_ONLINE);
return true;
}
}
@@ -735,8 +746,7 @@ void APTask(void* parameter) {
Serial.printf("We're going to try to perform an 'AP forced flash' in\n");
flashCountDown(10);
Serial.printf("\nPerforming force flash of the AP\n");
apInfo.isOnline = false;
apInfo.state = AP_STATE_FLASHING;
setAPstate(false, AP_STATE_FLASHING);
doForcedAPFlash();
checkWaitPowerCycle();
bringAPOnline();
@@ -747,10 +757,10 @@ void APTask(void* parameter) {
ShowAPInfo();
if (apInfo.type == SOLUM_SEG_UK) {
apInfo.state = AP_STATE_COMING_ONLINE;
setAPstate(true, AP_STATE_COMING_ONLINE);
segmentedShowIp();
showAPSegmentedInfo(apInfo.mac, true);
apInfo.state = AP_STATE_ONLINE;
setAPstate(true, AP_STATE_ONLINE);
updateContent(apInfo.mac);
}
@@ -765,8 +775,7 @@ void APTask(void* parameter) {
flashCountDown(30);
Serial.printf("\n");
notifySegmentedFlash();
apInfo.isOnline = false;
apInfo.state = AP_STATE_FLASHING;
setAPstate(false, AP_STATE_FLASHING);
if (doAPUpdate(apInfo.type)) {
checkWaitPowerCycle();
Serial.printf("Flash completed, let's try to boot the AP!\n");
@@ -777,18 +786,18 @@ void APTask(void* parameter) {
} else {
Serial.printf("Failed to bring up the AP after flashing seemed successful... That's not supposed to happen!\n");
Serial.printf("This can be caused by a bad AP firmware, failed or failing hardware, or the inability to fully power-cycle the AP\n");
apInfo.state = AP_STATE_FAILED;
#ifdef HAS_RGB_LED
setAPstate(false, AP_STATE_FAILED);
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Red, CRGB::Yellow, CRGB::Red);
#endif
#endif
}
} else {
apInfo.state = AP_STATE_FAILED;
setAPstate(false, AP_STATE_FAILED);
checkWaitPowerCycle();
Serial.println("Failed to update version on the AP :(\n");
#ifdef HAS_RGB_LED
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Red, CRGB::Red, CRGB::Red);
#endif
#endif
}
}
}
@@ -804,12 +813,10 @@ void APTask(void* parameter) {
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Red, CRGB::Yellow, CRGB::Red);
#endif
apInfo.isOnline = false;
apInfo.state = AP_STATE_FAILED;
setAPstate(false, AP_STATE_FAILED);
} else {
// AP unavailable, maybe time to flash?
apInfo.isOnline = false;
apInfo.state = AP_STATE_OFFLINE;
setAPstate(false, AP_STATE_OFFLINE);
Serial.printf("I wasn't able to connect to a ZBS (AP) tag.\n");
Serial.printf("This could be the first time this AP is booted and the AP-tag may be unflashed.\n");
@@ -840,16 +847,14 @@ void APTask(void* parameter) {
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Red, CRGB::Yellow, CRGB::Red);
#endif
apInfo.isOnline = false;
apInfo.state = AP_STATE_FAILED;
setAPstate(false, AP_STATE_FAILED);
}
} else {
// failed to flash
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Red, CRGB::Red, CRGB::Red);
#endif
apInfo.isOnline = false;
apInfo.state = AP_STATE_FAILED;
setAPstate(false, AP_STATE_FAILED);
Serial.println("Failed to flash the AP :(");
Serial.println("Seems like you're running into some issues with the wiring, or (very small chance) the tag itself");
Serial.println("This ESP32-build expects the following pins connected to the ZBS243:");
@@ -890,17 +895,15 @@ void APTask(void* parameter) {
attempts = 0;
}
if (attempts > 5) {
apInfo.state = AP_STATE_WAIT_RESET;
apInfo.isOnline = false;
setAPstate(false, AP_STATE_WAIT_RESET);
if (!bringAPOnline()) {
// tried to reset the AP, but we failed... Maybe the AP-Tag died?
apInfo.state = AP_STATE_FAILED;
setAPstate(false, AP_STATE_FAILED);
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Yellow, CRGB::Yellow, CRGB::Red);
#endif
} else {
apInfo.state = AP_STATE_ONLINE;
apInfo.isOnline = true;
setAPstate(true, AP_STATE_ONLINE);
attempts = 0;
refreshAllPending();
}

View File

@@ -46,7 +46,7 @@ static void initSDCard() {
}
#endif
size_t DynStorage::freeSpace(){
uint64_t DynStorage::freeSpace(){
this->begin();
#ifdef HAS_SDCARD
return SD.totalBytes() - SD.usedBytes();

View File

@@ -16,6 +16,7 @@ void initTime(void* parameter) {
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");
logStartUp();
struct tm timeinfo;
while (millis() < 30000) {
if (!getLocalTime(&timeinfo)) {
@@ -25,8 +26,9 @@ void initTime(void* parameter) {
break;
}
}
logStartUp();
if (config.runStatus = RUNSTATUS_INIT) config.runStatus = RUNSTATUS_RUN;
if (config.runStatus == RUNSTATUS_INIT) {
config.runStatus = RUNSTATUS_RUN;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
@@ -35,7 +37,7 @@ void logLine(const char* buffer) {
logLine(String(buffer));
}
void logLine(String text) {
void logLine(const String& text) {
time_t now;
time(&now);
@@ -43,6 +45,7 @@ void logLine(String text) {
const char* format = (now < (time_t)1672531200) ? " %H:%M:%S " : "%Y-%m-%d %H:%M:%S ";
strftime(timeStr, sizeof(timeStr), format, localtime(&now));
xSemaphoreTake(fsMutex, portMAX_DELAY);
File logFile = contentFS->open("/log.txt", "a");
if (logFile) {
if (logFile.size() >= 10 * 1024) {
@@ -57,6 +60,7 @@ void logLine(String text) {
logFile.println(text);
logFile.close();
}
xSemaphoreGive(fsMutex);
}
void logStartUp() {

View File

@@ -11,6 +11,9 @@
#include "storage.h"
#include "util.h"
#define STR_IMPL(x) #x
#define STR(x) STR_IMPL(x)
std::vector<tagRecord*> tagDB;
std::unordered_map<std::string, varStruct> varDB;
std::unordered_map<int, HwType> hwdata = {
@@ -118,6 +121,7 @@ void fillNode(JsonObject& tag, const tagRecord* taginfo) {
tag["apip"] = taginfo->apIp.toString();
tag["rotate"] = taginfo->rotate;
tag["lut"] = taginfo->lut;
tag["invert"] = taginfo->invert;
tag["ch"] = taginfo->currentChannel;
tag["ver"] = taginfo->tagSoftwareVersion;
}
@@ -213,6 +217,7 @@ void loadDB(const String& filename) {
taginfo->apIp.fromString(tag["apip"].as<String>());
taginfo->rotate = tag["rotate"] | 0;
taginfo->lut = tag["lut"] | 0;
taginfo->invert = tag["invert"] | 0;
taginfo->currentChannel = tag["ch"] | 0;
taginfo->tagSoftwareVersion = tag["ver"] | 0;
}
@@ -259,7 +264,7 @@ uint32_t getTagCount(uint32_t& timeoutcount) {
// not initialised, timeout if not seen last 10 minutes
if (timeout > 600) timeoutcount++;
} else if (now - taginfo->expectedNextCheckin > 600) {
//expected checkin is behind, timeout if not seen last 10 minutes
// expected checkin is behind, timeout if not seen last 10 minutes
if (timeout > 600) timeoutcount++;
}
}
@@ -300,6 +305,7 @@ void initAPconfig() {
config.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"] | getDefaultLanguage();
config.maxsleep = APconfig["maxsleep"] | 10;
config.stopsleep = APconfig["stopsleep"] | 1;
@@ -309,6 +315,8 @@ void initAPconfig() {
// 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);
if (APconfig["timezone"]) {
strlcpy(config.timeZone, APconfig["timezone"], sizeof(config.timeZone));
} else {
@@ -323,6 +331,7 @@ void saveAPconfig() {
APconfig["channel"] = config.channel;
APconfig["alias"] = config.alias;
APconfig["led"] = config.led;
APconfig["tft"] = config.tft;
APconfig["language"] = config.language;
APconfig["maxsleep"] = config.maxsleep;
APconfig["stopsleep"] = config.stopsleep;
@@ -331,6 +340,8 @@ void saveAPconfig() {
APconfig["timezone"] = config.timeZone;
APconfig["sleeptime1"] = config.sleepTime1;
APconfig["sleeptime2"] = config.sleepTime2;
APconfig["repo"] = config.repo;
APconfig["env"] = config.env;
serializeJsonPretty(APconfig, configFile);
configFile.close();
xSemaphoreGive(fsMutex);
@@ -372,19 +383,19 @@ HwType getHwType(const uint8_t id) {
}
}
bool setVarDB(const std::string& key, const String& value) {
bool setVarDB(const std::string& key, const String& value, const bool notify) {
auto it = varDB.find(key);
if (it == varDB.end()) {
varStruct newVar;
newVar.value = value;
newVar.changed = true;
newVar.changed = notify;
varDB[key] = newVar;
return true;
}
if (it->second.value != value) {
it->second.value = value;
it->second.changed = true;
it->second.changed = notify;
return true;
} else {
return false;

View File

@@ -0,0 +1,145 @@
#include "tagdata.h"
#include "tag_db.h"
#include "util.h"
std::unordered_map<size_t, TagData::Parser> TagData::parsers = {};
void TagData::loadParsers(const String& filename) {
const long start = millis();
Storage.begin();
fs::File file = contentFS->open(filename, "r");
if (!file) {
return;
}
Serial.println("Reading parsers from file");
if (file.find("[")) {
DynamicJsonDocument doc(1000);
bool parsing = true;
while (parsing) {
DeserializationError err = deserializeJson(doc, file);
if (!err) {
const JsonObject parserDoc = doc[0];
const auto& id = parserDoc["id"];
const auto& name = parserDoc["name"];
if (!id || !name) {
Serial.printf("Error: Parser must have name and id\n");
continue;
}
Parser parser;
parser.name = name.as<String>();
for (const auto& parserField : parserDoc["parser"].as<JsonArray>()) {
const uint8_t type = parserField["type"].as<uint8_t>();
if (type >= (uint8_t)Type::MAX) {
Serial.printf("Error: Type %d is not a valid tag data parser data type\n", type);
continue;
}
const auto& mult = parserField["mult"];
const uint8_t decimals = parserField["decimals"].as<uint8_t>();
if (mult) {
parser.fields.emplace_back(parserField["name"].as<String>(),
static_cast<Type>(type),
parserField["length"].as<uint8_t>(),
decimals,
std::make_optional(mult.as<double>()));
} else {
parser.fields.emplace_back(parserField["name"].as<String>(),
static_cast<Type>(type),
parserField["length"].as<uint8_t>(),
decimals);
}
}
parsers.emplace(id.as<uint8_t>(), parser);
} else {
Serial.print(F("deserializeJson() failed: "));
Serial.println(err.c_str());
parsing = false;
}
parsing = parsing && file.find(",");
}
}
file.close();
Serial.printf("Loaded %d parsers in %d ms\n", parsers.size(), millis() - start);
}
void TagData::parse(const uint8_t src[8], const size_t id, const uint8_t* data, const uint8_t len) {
char buffer[64];
const auto it = parsers.find(id);
if (it == parsers.end()) {
const String log = util::formatString<64>(buffer, "Error: No parser with id %d found(%d)", id, parsers.size());
wsErr(log);
Serial.println(log);
return;
}
const String mac = util::formatString<64>(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X.", src[7], src[6], src[5], src[4], src[3], src[2], src[1], src[0]);
uint16_t offset = 0;
for (const Field& field : it->second.fields) {
const String& name = field.name;
const uint8_t length = field.length;
if (offset + length > len) {
const String log = util::formatString<64>(buffer, "Error: Not enough data for field %s", name.c_str());
wsErr(log);
Serial.println(log);
return;
}
const Type type = field.type;
const uint8_t* fieldData = data + offset;
offset += length;
String value = "";
switch (type) {
case Type::INT: {
const double mult = field.mult.value_or(1.0);
value = String(bytesTo<int64_t>(fieldData, length) * mult, (unsigned int)field.decimals);
} break;
case Type::UINT: {
const double mult = field.mult.value_or(1.0f);
value = String(bytesTo<uint64_t>(fieldData, length) * mult, (unsigned int)field.decimals);
} break;
case Type::FLOAT: {
const double mult = field.mult.value_or(1.0f);
if (length == 4) {
value = String(bytesTo<float>(fieldData, length) * mult, (unsigned int)field.decimals);
} else if (length == 8) {
value = String(bytesTo<double>(fieldData, length) * mult, (unsigned int)field.decimals);
} else {
const String log = "Error: Float can only be 4 or 8 bytes long";
wsErr(log);
Serial.println(log);
}
} break;
case Type::STRING: {
value = bytesTo<String>(fieldData, length);
} break;
default:
const String log = util::formatString<64>(buffer, "Error: Type %d not implemented", static_cast<uint8_t>(type));
wsErr(log);
Serial.println(log);
break;
}
if (value.isEmpty()) {
const String log = util::formatString<64>(buffer, "Error: Empty value for field %s", name.c_str());
wsErr(log);
Serial.println(log);
continue;
}
const std::string varName = (mac + name).c_str();
setVarDB(varName, value);
Serial.printf("Set %s to %s\n", varName.c_str(), value.c_str());
}
}

View File

@@ -1,13 +1,14 @@
#include "udp.h"
#include <Arduino.h>
#include <WiFi.h>
#include "AsyncUDP.h"
#include "commstructs.h"
#include "newproto.h"
#include "serialap.h"
#include "tag_db.h"
#include "web.h"
#include "serialap.h"
#include "udp.h"
#define UDPIP IPAddress(239, 10, 0, 1)
#define UDPPORT 16033
@@ -41,8 +42,9 @@ void UDPcomm::init() {
}
void UDPcomm::processPacket(AsyncUDPPacket packet) {
if (config.runStatus == RUNSTATUS_STOP) return;
if (config.runStatus == RUNSTATUS_STOP) {
return;
}
IPAddress senderIP = packet.remoteIP();
switch (packet.data()[0]) {
@@ -127,7 +129,7 @@ void autoselect(void* pvParameters) {
}
if (curChannel.channel == 0) {
curChannel.channel = 11;
}
}
config.channel = curChannel.channel;
do {
vTaskDelay(1000 / portTICK_PERIOD_MS);

View File

@@ -20,14 +20,13 @@
#include "serialap.h"
#include "settings.h"
#include "storage.h"
#include "system.h"
#include "tag_db.h"
#include "udp.h"
#include "wifimanager.h"
extern uint8_t data_to_send[];
// const char *http_username = "admin";
// const char *http_password = "admin";
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
WifiManager wm;
@@ -35,7 +34,7 @@ WifiManager wm;
SemaphoreHandle_t wsMutex;
uint32_t lastssidscan = 0;
void wsLog(String text) {
void wsLog(const String &text) {
StaticJsonDocument<250> doc;
doc["logMsg"] = text;
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
@@ -43,7 +42,7 @@ void wsLog(String text) {
if (wsMutex) xSemaphoreGive(wsMutex);
}
void wsErr(String text) {
void wsErr(const String &text) {
StaticJsonDocument<250> doc;
doc["errMsg"] = text;
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
@@ -69,7 +68,7 @@ void wsSendSysteminfo() {
time(&now);
static int freeSpaceLastRun = 0;
static size_t tagDBsize = 0;
static size_t freeSpace = Storage.freeSpace();
static uint64_t freeSpace = Storage.freeSpace();
sys["currtime"] = now;
sys["heap"] = ESP.getFreeHeap();
sys["recordcount"] = tagDBsize;
@@ -80,6 +79,11 @@ void wsSendSysteminfo() {
freeSpaceLastRun = millis();
}
sys["littlefsfree"] = freeSpace;
#if BOARD_HAS_PSRAM
sys["psfree"] = ESP.getFreePsram();
#endif
sys["apstate"] = apInfo.state;
sys["runstate"] = config.runStatus;
#if !defined(CONFIG_IDF_TARGET_ESP32)
@@ -98,8 +102,14 @@ 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);
} 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);
@@ -167,7 +177,7 @@ void wsSendAPitem(struct APlist *apitem) {
if (wsMutex) xSemaphoreGive(wsMutex);
}
void wsSerial(String text) {
void wsSerial(const String &text) {
StaticJsonDocument<250> doc;
doc["console"] = text;
Serial.println(text);
@@ -184,12 +194,10 @@ void init_web() {
wsMutex = xSemaphoreCreateMutex();
Storage.begin();
WiFi.mode(WIFI_STA);
WiFi.setTxPower(static_cast<wifi_power_t>(config.wifiPower));
wm.connectToWifi();
// server.addHandler(new SPIFFSEditor(*contentFS, http_username, http_password));
server.addHandler(new SPIFFSEditor(*contentFS));
server.addHandler(&ws);
@@ -270,6 +278,9 @@ void init_web() {
if (request->hasParam("lut", true)) {
taginfo->lut = atoi(request->getParam("lut", true)->value().c_str());
}
if (request->hasParam("invert", true)) {
taginfo->invert = atoi(request->getParam("invert", true)->value().c_str());
}
// memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
// memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
wsSendTaginfo(mac, SYNC_USERCFG);
@@ -424,48 +435,59 @@ void init_web() {
});
server.on("/save_apcfg", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("alias", true) && request->hasParam("channel", true)) {
if (request->hasParam("alias", true)) {
String aliasValue = request->getParam("alias", true)->value();
size_t aliasLength = aliasValue.length();
if (aliasLength > 31) aliasLength = 31;
aliasValue.toCharArray(config.alias, aliasLength + 1);
config.alias[aliasLength] = '\0';
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());
updateBrightnessFromConfig();
}
if (request->hasParam("language", true)) {
config.language = static_cast<uint8_t>(request->getParam("language", true)->value().toInt());
updateLanguageFromConfig();
}
if (request->hasParam("maxsleep", true)) {
config.maxsleep = static_cast<uint8_t>(request->getParam("maxsleep", true)->value().toInt());
}
if (request->hasParam("stopsleep", true)) {
config.stopsleep = static_cast<uint8_t>(request->getParam("stopsleep", true)->value().toInt());
}
if (request->hasParam("preview", true)) {
config.preview = static_cast<uint8_t>(request->getParam("preview", true)->value().toInt());
}
if (request->hasParam("sleeptime1", true)) {
config.sleepTime1 = static_cast<uint8_t>(request->getParam("sleeptime1", true)->value().toInt());
config.sleepTime2 = static_cast<uint8_t>(request->getParam("sleeptime2", true)->value().toInt());
}
if (request->hasParam("wifipower", true)) {
config.wifiPower = static_cast<uint8_t>(request->getParam("wifipower", true)->value().toInt());
WiFi.setTxPower(static_cast<wifi_power_t>(config.wifiPower));
}
if (request->hasParam("timezone", true)) {
strncpy(config.timeZone, request->getParam("timezone", true)->value().c_str(), sizeof(config.timeZone) - 1);
config.timeZone[sizeof(config.timeZone) - 1] = '\0';
setenv("TZ", config.timeZone, 1);
tzset();
}
saveAPconfig();
setAPchannel();
}
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());
updateBrightnessFromConfig();
}
if (request->hasParam("tft", true)) {
config.tft = static_cast<int16_t>(request->getParam("tft", true)->value().toInt());
updateBrightnessFromConfig();
}
if (request->hasParam("language", true)) {
config.language = static_cast<uint8_t>(request->getParam("language", true)->value().toInt());
updateLanguageFromConfig();
}
if (request->hasParam("maxsleep", true)) {
config.maxsleep = static_cast<uint8_t>(request->getParam("maxsleep", true)->value().toInt());
}
if (request->hasParam("stopsleep", true)) {
config.stopsleep = static_cast<uint8_t>(request->getParam("stopsleep", true)->value().toInt());
}
if (request->hasParam("preview", true)) {
config.preview = static_cast<uint8_t>(request->getParam("preview", true)->value().toInt());
}
if (request->hasParam("sleeptime1", true)) {
config.sleepTime1 = static_cast<uint8_t>(request->getParam("sleeptime1", true)->value().toInt());
config.sleepTime2 = static_cast<uint8_t>(request->getParam("sleeptime2", true)->value().toInt());
}
if (request->hasParam("wifipower", true)) {
config.wifiPower = static_cast<uint8_t>(request->getParam("wifipower", true)->value().toInt());
WiFi.setTxPower(static_cast<wifi_power_t>(config.wifiPower));
}
if (request->hasParam("timezone", true)) {
strncpy(config.timeZone, request->getParam("timezone", true)->value().c_str(), sizeof(config.timeZone) - 1);
config.timeZone[sizeof(config.timeZone) - 1] = '\0';
setenv("TZ", config.timeZone, 1);
tzset();
}
if (request->hasParam("repo", true)) {
config.repo = request->getParam("repo", true)->value();
}
if (request->hasParam("env", true)) {
config.env = request->getParam("env", true)->value();
}
saveAPconfig();
setAPchannel();
request->send(200, "text/plain", "Ok, saved");
});
@@ -563,8 +585,26 @@ void init_web() {
request->send(200, "text/plain", "Ok, saved");
ws.enable(false);
refreshAllPending();
saveDB("/current/tagDB.json");
if (jsonObj["ssid"].as<String>() == "factory") {
preferences.begin("wifi", false);
preferences.putString("ssid", "");
preferences.putString("pw", "");
preferences.end();
contentFS->remove("/AP_FW_Pack.bin");
contentFS->remove("/OpenEPaperLink_esp32_C6.bin");
contentFS->remove("/bootloader.bin");
contentFS->remove("/partition-table.bin");
contentFS->remove("/update_actions.json");
contentFS->remove("/log.txt");
contentFS->remove("/current/tagDB.json");
delay(100);
ESP.restart();
} else {
refreshAllPending();
saveDB("/current/tagDB.json");
}
ws.closeAll();
delay(100);
ESP.restart();
@@ -607,16 +647,19 @@ void init_web() {
}
void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (config.runStatus != RUNSTATUS_RUN) {
request->send(409, "text/plain", "come back later");
return;
}
static bool imageUploadBusy = false;
if (!index) {
if (config.runStatus != RUNSTATUS_RUN || imageUploadBusy) {
request->send(409, "text/plain", "Come back later");
return;
}
if (request->hasParam("mac", true)) {
filename = request->getParam("mac", true)->value() + ".jpg";
} else {
filename = "unknown.jpg";
}
imageUploadBusy = true;
logLine("http imageUpload " + filename);
xSemaphoreTake(fsMutex, portMAX_DELAY);
request->_tempFile = contentFS->open("/" + filename, "w");
}
@@ -652,6 +695,7 @@ void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index
} else {
request->send(400, "text/plain", "parameters incomplete");
}
imageUploadBusy = false;
}
}

View File

@@ -2,7 +2,7 @@
{
"id": 0,
"name": "Static image",
"desc": "Shows a static image, from file system, painter or external source",
"desc": "Shows a static image, from file system, painter or external source. Make sure to resize the image to the correct resolution.",
"hwtype": [
0,
1,
@@ -17,7 +17,7 @@
"key": "filename",
"name": "Filename",
"desc": "Local filename on the littlefs drive",
"type": "text"
"type": "jpgfile"
},
{
"key": "timetolive",
@@ -34,6 +34,47 @@
"0": "off",
"1": "on"
}
},
{
"key": "preload",
"name": "Display or Preload",
"desc": "Display now or preload for later use",
"type": "select",
"options": {
"0": "Display",
"1": "Preload"
}
},
{
"key": "preload_lut",
"name": "Preload LUT",
"desc": "Triggered preload images will display with this LUT",
"type": "select",
"options": {
"0": "Default",
"1": "No Repeats",
"2": "Fast No Reds",
"3": "Fast"
}
},
{
"key": "preload_type",
"name": "Preload Image type",
"desc": "Preload type to send to tag, for later recall, or special use",
"type": "select",
"options": {
"0": "Normal",
"1": "UI: Splash Screen",
"2": "UI: Lost connection",
"3": "UI: AP Found",
"4": "UI: No AP Found",
"5": "UI: Long Term Sleep",
"15": "Slideshow image",
"16": "Wake: Button 1",
"17": "Wake: Button 2",
"29": "Wake: GPIO",
"30": "Wake: NFC"
}
}
]
},
@@ -332,7 +373,7 @@
"key": "filename",
"name": "Filename",
"desc": "Filename of the json template. See OpenEpaperLink wiki for the right json format. Specify a url OR a filename",
"type": "text"
"type": "jsonfile"
},
{
"key": "interval",
@@ -406,7 +447,7 @@
{
"id": 5,
"name": "Firmware update",
"desc": "To update tag firmware",
"desc": "To update tag firmware. Make sure you send the right .bin file! You can brick your tag if you send a wrong file.",
"hwtype": [
0,
1,
@@ -422,7 +463,7 @@
"key": "filename",
"name": "Filename",
"desc": "Local file on littlefs partition",
"type": "text"
"type": "binfile"
}
]
},
@@ -649,6 +690,9 @@
"id": 21,
"name": "Access point info",
"desc": "Displays information about the currently connected access point",
"hwtype": [0, 1]
"hwtype": [
0,
1
]
}
]
]

View File

@@ -39,376 +39,420 @@
</nav>
</header>
<form>
<div class="container">
<div class="container">
<div class="window">
<div class="window">
<div id="hometab" class="tabcontent">
<table>
<tr onclick="setFilterAndShow('')">
<td class="material-symbols-outlined" style="color:#239f26" id="dashboardStatusIcon">
check_circle
</td>
<td id="dashboardStatus" style="color:#239f26" colspan="2">
initialising...
</td>
</tr>
<tr onclick="setFilterAndShow('')">
<td class="material-symbols-outlined" style="color:#77239e">
<div id="hometab" class="tabcontent">
<table>
<tr onclick="setFilterAndShow('')">
<td class="material-symbols-outlined" style="color:#239f26" id="dashboardStatusIcon">
check_circle
</td>
<td id="dashboardStatus" style="color:#239f26" colspan="2">
initialising...
</td>
</tr>
<tr onclick="setFilterAndShow('')">
<td class="material-symbols-outlined" style="color:#77239e">
sell
</td>
<td>
tags
</td>
<td id="dashboardTagCount" style="color:#77239e">
0
</td>
</tr>
<tr onclick="setFilterAndShow('pending')">
<td class="material-symbols-outlined" style="color:#235f9e">
hourglass_empty
</td>
<td>
pending data
</td>
<td id="dashboardPending" style="color:#235f9e">
0
</td>
</tr>
<tr onclick="setFilterAndShow('lowbatt')">
<td class="material-symbols-outlined" style="color:#9e9223">
battery_low
</td>
<td>
low battery
</td>
<td id="dashboardLowBatt" style="color:#9e9223">
0
</td>
</tr>
<tr onclick="setFilterAndShow('inactive')">
<td class="material-symbols-outlined" style="color:#9e2323">
signal_disconnected
</td>
<td>
timeout
</td>
<td id="dashboardTimeout" style="color:#9e2323">
0
</td>
</tr>
<!--
<tr onclick="$(`[data-target='aptab']`).click()">
<td class="material-symbols-outlined" style="color:#239f26">
cell_tower
</td>
<td>
access points
</td>
<td id="dashboardApCount" style="color:#239f26">
</td>
</tr>
-->
</table>
</div>
<div id="tagtab" class="tabcontent">
<div class="tagheader">
<h3>Currently active tags</h3>
<div id="activefilter"></div><button class="material-symbols-outlined"
id="toggleFilters">filter_alt</button>
</div>
<div id="filterOptions">
<div>
<div>group by</div>
<div>
<input type="radio" name="group" value="" id="rnone" checked><label
for="rnone">None</label>
</div>
<div>
<input type="radio" name="group" value="model" id="rtagtype"><label for="rtagtype">Tag
model</label>
</div>
<div>
<input type="radio" name="group" value="contentmode" id="rcontent"><label
for="rcontent">Content</label>
</div>
<div>
<input type="radio" name="group" value="data-channel" id="rchannel"><label
for="rchannel">Channel</label>
</div>
</div>
<div>
<div>sort by</div>
<div>
<input type="radio" name="sort" value="alias" id="ralias" checked><label
for="ralias">Alias</label>
</div>
<div>
<input type="radio" name="sort" value="mac" id="rmac"><label for="rmac">Mac</label>
</div>
<div>
<input type="radio" name="sort" value="data-lastseen" id="rlastseen"><label
for="rlastseen">Last seen</label>
</div>
<div>
<input type="radio" name="sort" value="data-nextupdate" id="rnext"><label
for="rnext">Next update</label>
</div>
</div>
<div>
<div>filter</div>
<div>
<input type="checkbox" name="filter" value="local" id="rlocal"><label
for="rlocal">local</label>
</div>
<div>
<input type="checkbox" name="filter" value="remote" id="rremote"><label
for="rremote">remote</label>
</div>
<div>
<input type="checkbox" name="filter" value="inactive" id="rinactive"><label
for="rinactive">timed out</label>
</div>
<div>
<input type="checkbox" name="filter" value="pending" id="rpending"><label
for="rpending">pending</label>
</div>
<div>
<input type="checkbox" name="filter" value="lowbatt" id="rlowbatt"><label
for="rlowbatt">low battery</label>
</div>
</div>
</div>
<div id="taglist" class="taglist">
<div class="tagcard" id="tagtemplate">
<div class="currimg"><canvas class="tagimg"></div>
<div class="mac"></div>
<div class="alias"></div>
<div class="model"></div>
<div class="received"></div>
<div class="contentmode"></div>
<div class="lastseen"></div>
<div class="nextcheckin"></div>
<div class="nextupdate"></div>
<div class="corner">
<div class="pendingicon" title="A new message is waiting for the tag to pick up">
&circlearrowright;</div>
<div class="warningicon" title="This tag has not been seen for a long time">&#9888;
</div>
</div>
</div>
</div>
</div>
<div id="logtab" class="tabcontent">
<div class="tabheader">
<div><img id="clearlog" src="data:image/gif;base64,R0lGODlhEAAQAPMAANXV1e3t7d/f39HR0dvb2/Hx8dTU1OLi4urq6mZmZpmZmf///wAAAAAAAAAAAAAAACH5BAEAAAwALAAAAAAQABAAAARBkMlJq71Yrp3ZXkr4WWCYnOZSgQVyEMYwJCq1nHhe20qgCAoA7QLyAYU7njE4JPV+zOSkCEUSFbmTVPPpbjvgTAQAOw==
"></div>
<div><input type="checkbox" id="showdebug" value="1"><label for="showdebug">Show all websocket
traffic</label></div>
</div>
<ul id="messages" class="messages">
</ul>
</div>
<div id="aptab" class="tabcontent">
<h3>Active access points</h3>
<div id="aplist">
<div id="apcard" class="apcard">
<p class="apip">194.109.6.66</p>
<p class="apalias">AP kitchen</p>
<div>
<span class="material-symbols-outlined">
sell
</td>
<td>
tags
</td>
<td id="dashboardTagCount" style="color:#77239e">
0
</td>
</tr>
<tr onclick="setFilterAndShow('pending')">
<td class="material-symbols-outlined" style="color:#235f9e">
hourglass_empty
</td>
<td>
pending data
</td>
<td id="dashboardPending" style="color:#235f9e">
0
</td>
</tr>
<tr onclick="setFilterAndShow('lowbatt')">
<td class="material-symbols-outlined" style="color:#9e9223">
battery_low
</td>
<td>
low battery
</td>
<td id="dashboardLowBatt" style="color:#9e9223">
0
</td>
</tr>
<tr onclick="setFilterAndShow('inactive')">
<td class="material-symbols-outlined" style="color:#9e2323">
signal_disconnected
</td>
<td>
timeout
</td>
<td id="dashboardTimeout" style="color:#9e2323">
0
</td>
</tr>
<!--
<tr onclick="$(`[data-target='aptab']`).click()">
<td class="material-symbols-outlined" style="color:#239f26">
</span>
<span class="aptagcount">13</span>
<span class="material-symbols-outlined space">
cell_tower
</td>
<td>
access points
</td>
<td id="dashboardApCount" style="color:#239f26">
</td>
</tr>
-->
</table>
</div>
<div id="tagtab" class="tabcontent">
<div class="tagheader">
<h3>Currently active tags</h3>
<div id="activefilter"></div><button class="material-symbols-outlined"
id="toggleFilters">filter_alt</button>
</div>
<div id="filterOptions">
<div>
<div>group by</div>
<div>
<input type="radio" name="group" value="" id="rnone" checked><label
for="rnone">None</label>
</div>
<div>
<input type="radio" name="group" value="model" id="rtagtype"><label for="rtagtype">Tag
model</label>
</div>
<div>
<input type="radio" name="group" value="contentmode" id="rcontent"><label
for="rcontent">Content</label>
</div>
<div>
<input type="radio" name="group" value="data-channel" id="rchannel"><label
for="rchannel">Channel</label>
</div>
</div>
<div>
<div>sort by</div>
<div>
<input type="radio" name="sort" value="alias" id="ralias" checked><label
for="ralias">Alias</label>
</div>
<div>
<input type="radio" name="sort" value="mac" id="rmac"><label for="rmac">Mac</label>
</div>
<div>
<input type="radio" name="sort" value="data-lastseen" id="rlastseen"><label
for="rlastseen">Last seen</label>
</div>
<div>
<input type="radio" name="sort" value="data-nextupdate" id="rnext"><label
for="rnext">Next update</label>
</div>
</div>
<div>
<div>filter</div>
<div>
<input type="checkbox" name="filter" value="local" id="rlocal"><label
for="rlocal">local</label>
</div>
<div>
<input type="checkbox" name="filter" value="remote" id="rremote"><label
for="rremote">remote</label>
</div>
<div>
<input type="checkbox" name="filter" value="inactive" id="rinactive"><label
for="rinactive">timed out</label>
</div>
<div>
<input type="checkbox" name="filter" value="pending" id="rpending"><label
for="rpending">pending</label>
</div>
<div>
<input type="checkbox" name="filter" value="lowbatt" id="rlowbatt"><label
for="rlowbatt">low battery</label>
</div>
</div>
</div>
<div id="taglist" class="taglist">
<div class="tagcard" id="tagtemplate">
<div class="currimg"><canvas class="tagimg"></div>
<div class="mac"></div>
<div class="alias"></div>
<div class="model"></div>
<div class="received"></div>
<div class="contentmode"></div>
<div class="lastseen"></div>
<div class="nextcheckin"></div>
<div class="nextupdate"></div>
<div class="corner">
<div class="pendingicon" title="A new message is waiting for the tag to pick up">
&circlearrowright;</div>
<div class="warningicon" title="This tag has not been seen for a long time">&#9888;
</div>
</div>
</span>
<span class="apchannel">25</span>
</div>
<p class="apswversion">
fetching software version...
</p>
</div>
</div>
</div>
<div id="logtab" class="tabcontent">
<div class="tabheader">
<div><img id="clearlog" src="data:image/gif;base64,R0lGODlhEAAQAPMAANXV1e3t7d/f39HR0dvb2/Hx8dTU1OLi4urq6mZmZpmZmf///wAAAAAAAAAAAAAAACH5BAEAAAwALAAAAAAQABAAAARBkMlJq71Yrp3ZXkr4WWCYnOZSgQVyEMYwJCq1nHhe20qgCAoA7QLyAYU7njE4JPV+zOSkCEUSFbmTVPPpbjvgTAQAOw==
"></div>
<div><input type="checkbox" id="showdebug" value="1"><label for="showdebug">Show all websocket
traffic</label></div>
</div>
<ul id="messages" class="messages">
</ul>
</div>
<div id="templatetab" class="tabcontent">
Work in progress...
</div>
<div id="aptab" class="tabcontent">
<h3>Active access points</h3>
<div id="configtab" class="tabcontent">
<h3>Access Point config</h3>
<p>
<label for="apcfgalias">Alias</label>
<input id="apcfgalias" type="text">
</p>
<p>
<label for="apcfgchid">IEEE 802.15.4 channel</label>
<select id="apcfgchid">
<option value="0" selected>auto</option>
<option value="11">11</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
</select>
</p>
<p>
<label for="apcfgledbrightness">RGB LED brightness</label>
<select id="apcfgledbrightness">
<option value="-1">off</option>
<option value="15">10%</option>
<option value="31">25%</option>
<option value="127" selected>50%</option>
<option value="191">75%</option>
<option value="255">100%</option>
</select>
</p>
<p>
<label for="apcfgtftbrightness">TFT brightness</label>
<select id="apcfgtftbrightness">
<option value="0">off</option>
<option value="20">10%</option>
<option value="64">25%</option>
<option value="128">50%</option>
<option value="192">75%</option>
<option value="255" selected>100%</option>
</select>
</p>
<p>
<label for="apcfglanguage">Content language</label>
<select id="apcfglanguage">
<option value="0" selected>EN English</option>
<option value="1">NL Nederlands</option>
<option value="2">DE Deutsch</option>
</select>
</p>
<p title="Depending on the content, a tag can sleep for
longer periods when no updates are expected
(like a date display). This setting specifies
the maximum sleep time.">
<label for="apclatency">Maximum sleep</label>
<select id="apclatency">
<option value="0" selected>shortest (40 sec)</option>
<option value="5">5 minutes</option>
<option value="10">10 minute</option>
<option value="30">30 minutes</option>
<option value="60">1 hour</option>
</select>
</p>
<p title="If connected to the website, don't sleep extra.
Latency will be around 40 seconds.">
<label for="apcpreventsleep">Shorten latency during config</label>
<select id="apcpreventsleep">
<option value="0">no</option>
<option value="1" selected>yes</option>
</select>
</p>
<p
title="Stops updates at night, and put the tags to sleep. During the configured night time, this overrides the maximum sleep time.">
<label for="apcnight1">No updates between</label>
<select id="apcnight1"></select>
<span style="align-self:center;">and</span>
<select id="apcnight2"></select>
</p>
<p title="Turn off preview images on the webpage if you want to manage many tags,
to save file system space">
<label for="apcpreview">Preview images</label>
<select id="apcpreview">
<option value="1" selected>yes</option>
<option value="0">no</option>
</select>
</p>
<p title="Wifi transmit power">
<label for="apcwifipower">Wifi power</label>
<select id="apcwifipower">
<option value="78">19.5 dBm</option>
<option value="76">19.0 dBm</option>
<option value="74">18.5 dBm</option>
<option value="68">17.0 dBm</option>
<option value="60">15.0 dBm</option>
<option value="52">13.0 dBm</option>
<option value="44">11.0 dBm</option>
<option value="34" selected>8.5 dBm</option>
<option value="28">7.0 dBm</option>
<option value="20">5.0 dBm</option>
<option value="8">2.0 dBm</option>
</select>
</p>
<p title="Your local time zone">
<label for="apctimezone">Local time zone</label>
<select id="apctimezone">
<optgroup label="Europe">
<option value="CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" selected>Central European
Time</option>
<option value="EET-2EEST-3,M3.5.0/03:00:00,M10.5.0/04:00:00">Athens, Greece</option>
<option value="GMT+0IST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">Dublin, Ireland</option>
<option value="EET-2EEST-3,M3.5.0/03:00:00,M10.5.0/04:00:00">Helsinki, Finland</option>
<option value="WET-0WEST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">Lisbon, Portugal</option>
<option value="GMT+0BST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">London, Great Britain
</option>
<option value="EET-2EEST,M3.5.0/3,M10.5.0/4">Kyiv, Ukraine</option>
</optgroup>
<optgroup label="USA / Canada">
<option value="HAW10">Hawaii Time</option>
<option value="AKST9AKDT">Alaska Time</option>
<option value="PST8PDT">Pacific Time</option>
<option value="MST7MDT">Mountain Time</option>
<option value="MST7">Arizona, no DST</option>
<option value="CST6CDT">Central Time</option>
<option value="EST5EDT">Eastern Time</option>
</optgroup>
<optgroup label="Australia / New Zealand">
<option value="EST-10EDT-11,M10.5.0/02:00:00,M3.5.0/03:00:00">Melbourne, Sydney</option>
<option value="WST-8">Perth</option>
<option value="EST-10">Brisbane</option>
<option value="CST-9:30CDT-10:30,M10.5.0/02:00:00,M3.5.0/03:00:00">Adelaide</option>
<option value="CST-9:30">Darwin</option>
<option value="EST-10EDT-11,M10.1.0/02:00:00,M3.5.0/03:00:00">Hobart</option>
<option value="NZST-12NZDT-13,M9.4.0/02:00:00,M4.1.0/03:00:00">New Zealand</option>
</optgroup>
<optgroup label="Asia">
<option value="JST-9">Tokyo</option>
<option value="WIB-7">Jakarta</option>
<option value="GMT+2">Jerusalem</option>
<option value="SGT-8">Singapore</option>
<option value="ULAT-8ULAST,M3.5.0/2,M9.5.0/2">Ulaanbaatar, Mongolia</option>
</optgroup>
<optgroup label="Central and South America">
<option value="BRST+3BRDT+2,M10.3.0,M2.3.0">Brazil, Sao Paulo</option>
<option value="UTC+3">Argentina</option>
<option value="CST+6">Central America</option>
</optgroup>
</select>
</p>
<p>
<input type="button" value="Save" id="apcfgsave"><span id="apcfgmsg"></span>
</p>
<h3>Manage</h3>
<p>
<button type="button" id="rebootbutton">Reboot AP</button> Saves the tagDB and instantly reboots the Access
Point
</p>
<p>
<a href="/backup_db" id="downloadDBbutton">Download tagDB</a>
</p>
<p>
<button type="button" id="updatebutton" class="tablinks" data-target="updatetab" title="Update">Update</button> Manage firmware of the ESP32
</p>
<p>
<a href="/setup" target="setup" class="wifibutton">WiFi config</a> Opens a new window with WiFi
config options
</p>
<p>
<br>
<a href="https://github.com/jjwbruijn/OpenEPaperLink" target="_new">Github
OpenEPaperLink</a><br>
<a href="https://github.com/jjwbruijn/OpenEPaperLink/wiki" target="_new">OpenEPaperLink
Wiki</a><br>
</p>
</div>
<div id="aplist">
<div id="apcard" class="apcard">
<p class="apip">194.109.6.66</p>
<p class="apalias">AP kitchen</p>
<div>
<span class="material-symbols-outlined">
sell
<div id="updatetab" class="tabcontent">
<h3>Firmware Updates</h3>
<div>
<div class="updateCol1">
<div id="easyupdate"></div>
<h4>Repository</h4>
<div>
<label for="repo">Repo</label><input type="text" id="repo" value="">
<button id="selectRepo">Change</button><br>
<p id="repoWarning" style="display:none" class="warning">
To change to this repository, select and confirm the build environment.
</p>
<label for="environment">Environment</label><input type="text" id="environment" readonly value="">
<button id="confirmSelectRepo">Confirm</button><button id="cancelSelectRepo">Cancel</button>
</div>
<h4>Releases</h4>
<div id="releasetable"></div>
<h4>Other actions</h4>
<div>
<p>
<div id="rollbackOption" style="display:none"><button type="button" id="rollbackBtn">Rollback to previous
firmware</button></div>
</p>
<p>
<span id="c6Option">
<div id="updateC6Option"><button type="button" id="updateC6Btn">Update ESP32-C6</button> <input type="checkbox"
value="1" checked id="c6download"> download latest version</div>
</span>
<span class="aptagcount">13</span>
<span class="material-symbols-outlined space">
cell_tower
</span>
<span class="apchannel">25</span>
</div>
<p class="apswversion">
fetching software version...
</p>
</div>
</div>
<div class="console" id="updateconsole"></div>
</div>
<div id="templatetab" class="tabcontent">
Work in progress...
</div>
<div id="configtab" class="tabcontent">
<h3>Access Point config</h3>
<p>
<label for="apcfgalias">Alias</label>
<input id="apcfgalias" type="text">
</p>
<p>
<label for="apcfgchid">Channel</label>
<select id="apcfgchid">
<option value="0" selected>auto</option>
<option value="11">11</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
</select>
</p>
<p>
<label for="apcfgledbrightness">LED brightness</label>
<select id="apcfgledbrightness">
<option value="-1">off</option>
<option value="20">10%</option>
<option value="64">25%</option>
<option value="128" selected>50%</option>
<option value="192">75%</option>
<option value="255">100%</option>
</select>
</p>
<p>
<label for="apcfglanguage">Content language</label>
<select id="apcfglanguage">
<option value="0" selected>EN English</option>
<option value="1">NL Nederlands</option>
<option value="2">DE Deutsch</option>
</select>
</p>
<p title="Depending on the content, a tag can sleep for
longer periods when no updates are expected
(like a date display). This setting specifies
the maximum sleep time.">
<label for="apclatency">Maximum sleep</label>
<select id="apclatency">
<option value="0" selected>shortest (40 sec)</option>
<option value="5">5 minutes</option>
<option value="10">10 minute</option>
<option value="30">30 minutes</option>
<option value="60">1 hour</option>
</select>
</p>
<p title="If connected to the website, don't sleep extra.
Latency will be around 40 seconds.">
<label for="apcpreventsleep">Shorten latency during config</label>
<select id="apcpreventsleep">
<option value="0">no</option>
<option value="1" selected>yes</option>
</select>
</p>
<p
title="Stops updates at night, and put the tags to sleep. During the configured night time, this overrides the maximum sleep time.">
<label for="apcnight1">No updates between</label>
<select id="apcnight1"></select>
<span style="align-self:center;">and</span>
<select id="apcnight2"></select>
</p>
<p title="Turn off preview images on the webpage if you want to manage many tags,
to save file system space">
<label for="apcpreview">Preview images</label>
<select id="apcpreview">
<option value="1" selected>yes</option>
<option value="0">no</option>
</select>
</p>
<p title="Wifi transmit power">
<label for="apcwifipower">Wifi power</label>
<select id="apcwifipower">
<option value="78">19.5 dBm</option>
<option value="76">19.0 dBm</option>
<option value="74">18.5 dBm</option>
<option value="68">17.0 dBm</option>
<option value="60">15.0 dBm</option>
<option value="52">13.0 dBm</option>
<option value="44">11.0 dBm</option>
<option value="34" selected>8.5 dBm</option>
<option value="28">7.0 dBm</option>
<option value="20">5.0 dBm</option>
<option value="8">2.0 dBm</option>
</select>
</p>
<p title="Your local time zone">
<label for="apctimezone">Local time zone</label>
<select id="apctimezone">
<optgroup label="Europe">
<option value="CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" selected>Central European
Time</option>
<option value="EET-2EEST-3,M3.5.0/03:00:00,M10.5.0/04:00:00">Athens, Greece</option>
<option value="GMT+0IST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">Dublin, Ireland</option>
<option value="EET-2EEST-3,M3.5.0/03:00:00,M10.5.0/04:00:00">Helsinki, Finland</option>
<option value="WET-0WEST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">Lisbon, Portugal</option>
<option value="GMT+0BST-1,M3.5.0/01:00:00,M10.5.0/02:00:00">London, Great Britain
</option>
<option value="EET-2EEST,M3.5.0/3,M10.5.0/4">Kyiv, Ukraine</option>
</optgroup>
<optgroup label="USA / Canada">
<option value="HAW10">Hawaii Time</option>
<option value="AKST9AKDT">Alaska Time</option>
<option value="PST8PDT">Pacific Time</option>
<option value="MST7MDT">Mountain Time</option>
<option value="MST7">Arizona, no DST</option>
<option value="CST6CDT">Central Time</option>
<option value="EST5EDT">Eastern Time</option>
</optgroup>
<optgroup label="Australia / New Zealand">
<option value="EST-10EDT-11,M10.5.0/02:00:00,M3.5.0/03:00:00">Melbourne, Sydney</option>
<option value="WST-8">Perth</option>
<option value="EST-10">Brisbane</option>
<option value="CST-9:30CDT-10:30,M10.5.0/02:00:00,M3.5.0/03:00:00">Adelaide</option>
<option value="CST-9:30">Darwin</option>
<option value="EST-10EDT-11,M10.1.0/02:00:00,M3.5.0/03:00:00">Hobart</option>
<option value="NZST-12NZDT-13,M9.4.0/02:00:00,M4.1.0/03:00:00">New Zealand</option>
</optgroup>
<optgroup label="Asia">
<option value="JST-9">Tokyo</option>
<option value="WIB-7">Jakarta</option>
<option value="GMT+2">Jerusalem</option>
<option value="SGT-8">Singapore</option>
<option value="ULAT-8ULAST,M3.5.0/2,M9.5.0/2">Ulaanbaatar, Mongolia</option>
</optgroup>
<optgroup label="Central and South America">
<option value="BRST+3BRDT+2,M10.3.0,M2.3.0">Brazil, Sao Paulo</option>
<option value="UTC+3">Argentina</option>
<option value="CST+6">Central America</option>
</optgroup>
</select>
</p>
<p>
<input type="button" value="Save" id="apcfgsave"><span id="apcfgmsg"></span>
</p>
<h3>Manage</h3>
<p>
<button id="rebootbutton">Reboot AP</button> Saves the tagDB and instantly reboots the Access
Point
</p>
<p>
<a href="/backup_db" id="downloadDBbutton">Download tagDB</a>
</p>
<p>
<button id="updatebutton">Update</button> Manage firmware of the ESP32
</p>
<p>
<a href="/setup" target="setup" class="wifibutton">WiFi config</a> Opens a new window with WiFi
config options
</p>
<p>
<br>
<a href="https://github.com/jjwbruijn/OpenEPaperLink" target="_new">Github
OpenEPaperLink</a><br>
<a href="https://github.com/jjwbruijn/OpenEPaperLink/wiki" target="_new">OpenEPaperLink
Wiki</a><br>
</p>
</div>
</div>
</div>
</form>
</div>
<footer class="logbox">
<p>
@@ -447,6 +491,13 @@
<option value="0">auto</option>
</select>
</p>
<p>
<label for="cfginvert">Invert colors</label>
<select id="cfginvert">
<option value="0">off</option>
<option value="1">on</option>
</select>
</p>
<p class="tagbuttons">
<button id="cfgrefresh">force refresh</button>
<button id="cfgclrpending">clear pending</button>
@@ -464,27 +515,6 @@
</p>
</dialog>
<div id="apupdatebox">
<div class="closebtn">&#10006;</div>
<h3>Update dashboard</h3>
<div id="easyupdate"></div>
<div id="advanceddiv">
<!--<div>
repo: <input type="text" name="repo"> <button id="switchRepo">Switch</button><br>
environment: <span id="environment">environment</span>
</div>-->
<div id="releasetable"></div>
<div>
<div id="rollbackOption" style="display:none"><button id="rollbackBtn">Rollback to previous
firmware</button></div>
<span id="c6Option">
<div id="updateC6Option"><button id="updateC6Btn">Update ESP32-C6</button> <input type="checkbox"
value="1" checked id="c6download"> download latest version</div>
</span>
</div>
</div>
</div>
<ul id="context-menu"
style="display: none; position: absolute; background: white; border: 1px solid gray; padding: 0; list-style: none;">
</ul>

View File

@@ -65,7 +65,7 @@ footer {
.logo {
margin: 0 auto;
height: 50px;
text-indent: 50px;
text-indent: 12px;
overflow: hidden;
font-size: 2.5em;
color: white;
@@ -202,7 +202,8 @@ label {
}
#aptab,
#configtab {
#configtab,
#updatetab {
padding: 10px;
& p {
@@ -210,6 +211,35 @@ label {
}
}
#updatetab {
&>div {
display: flex;
gap: 2em;
flex-flow: wrap;
}
& label {
width: 100px;
display: inline-block;
vertical-align: baseline;
padding: 7px 0px;
}
& input[type="text"] {
width: 200px;
}
& button {
margin: 0px 5px;
}
& input:read-only {
background-color: #ccc;
}
& .warning {
padding: 5px;
color: #f02000;
background-color: #ffffc0;
font-weight: bold;
}
}
#aplist {
display: flex;
gap: 1em;
@@ -356,6 +386,7 @@ select {
display: flex;
gap: 5px;
align-items: flex-start;
max-width: 400px;
}
#configbox h3,
@@ -719,7 +750,10 @@ ul.messages li.new {
/* updatescreens */
#easyupdate {
margin-top: 10px;
padding: 10px;
background-color: white;
width: 400px;
margin-bottom: 20px;
}
#easyupdate button {
@@ -733,13 +767,10 @@ ul.messages li.new {
text-decoration: underline;
}
#advanceddiv {
display: none;
}
#advanceddiv div:nth-child(2) {
display: flex;
gap: 10px;
h4 {
font-size: 1.25em;
margin: 10px 0px;
font-weight: bold;
}
#releasetable {
@@ -764,7 +795,7 @@ ul.messages li.new {
}
#releasetable td:nth-child(2) {
word-wrap: nowrap;
white-space: nowrap;
}
#releasetable button {
@@ -780,17 +811,21 @@ ul.messages li.new {
display: none;
}
.updateCol1 {
flex-grow: 1;
}
.console {
width: 100%;
width: 450px;
background-color: black;
font-family: 'lucida console', 'ui-monospace';
color: white;
padding: 5px 10px;
margin: 20px 0px;
padding-bottom: 25px;
height: 400px;
height: calc(100vh - 200px);
overflow-y: scroll;
white-space: break-spaces;
flex-grow: 1;
}
.console div {

View File

@@ -83,7 +83,8 @@ function initTabs() {
const tabContents = document.querySelectorAll(".tabcontent");
tabLinks.forEach(tabLink => {
tabLink.addEventListener("click", function () {
tabLink.addEventListener("click", function (event) {
event.preventDefault();
const targetId = this.getAttribute("data-target");
const loadTabEvent = new CustomEvent('loadTab', { detail: targetId });
document.dispatchEvent(loadTabEvent);
@@ -134,11 +135,24 @@ function connect() {
processTags(msg.tags);
}
if (msg.sys) {
let filesystem = 'filesystem free: ' + convertSize(msg.sys.littlefsfree);
if (msg.sys.littlefsfree < 31000) {
filesystem = 'filesystem <span class="blink-red" title="Generating content is paused">FULL! ' + convertSize(msg.sys.littlefsfree) + '</span>';
}
$('#sysinfo').innerHTML = 'free heap: ' + convertSize(msg.sys.heap) + ' &#x2507; db size: ' + convertSize(msg.sys.dbsize) + ' &#x2507; db record count: ' + msg.sys.recordcount + ' &#x2507; ' + filesystem;
let str = "";
str += `free heap: ${convertSize(msg.sys.heap)} &#x2507; `;
if (msg.sys.psfree) {
str += `free PSRAM: ${convertSize(msg.sys.psfree)} &#x2507; `;
}
str += `db size: ${convertSize(msg.sys.dbsize)} &#x2507; `;
str += `db record count: ${msg.sys.recordcount} &#x2507; `;
if (msg.sys.littlefsfree < 31000) {
str += `filesystem <span class="blink-red" title="Generating content is paused">FULL! ${convertSize(
msg.sys.littlefsfree
)} </span>`;
} else {
str += `filesystem free: ${convertSize(msg.sys.littlefsfree)}`;
}
$("#sysinfo").innerHTML = str;
if (msg.sys.apstate) {
$("#apstatecolor").style.color = apstate[msg.sys.apstate].color;
$("#apstate").innerHTML = apstate[msg.sys.apstate].state;
@@ -208,7 +222,6 @@ function processTags(tagArray) {
if (!alias) alias = tagmac.replace(/^0{1,4}/, '');
if ($('#tag' + tagmac + ' .alias').innerHTML != alias) {
$('#tag' + tagmac + ' .alias').innerHTML = alias;
//GroupSortFilter();
}
let contentDefObj = getContentDefById(element.contentMode);
@@ -302,6 +315,7 @@ function processTags(tagArray) {
break;
case WAKEUP_REASON_NFC:
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "NFC wakeup"
$('#tag' + tagmac).style.background = "#c8f1bb";
break;
case WAKEUP_REASON_NETWORK_SCAN:
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "<font color=yellow>Network scan</font>"
@@ -332,7 +346,6 @@ function updatecards() {
$('#taglist').querySelectorAll('[data-mac]').forEach(item => {
let tagmac = item.dataset.mac;
tagcount++;
if (tagDB[tagmac].pending) pendingcount++;
if (tagDB[tagmac].batteryMv < 2400 && tagDB[tagmac].batteryMv != 0 && tagDB[tagmac].batteryMv != 1337) lowbattcount++;
if (item.dataset.lastseen && item.dataset.lastseen > (Date.now() / 1000) - servertimediff - 30 * 24 * 3600 * 60) {
let idletime = (Date.now() / 1000) - servertimediff - item.dataset.lastseen;
@@ -342,6 +355,8 @@ function updatecards() {
$('#tag' + tagmac).classList.remove("tagpending")
$('#tag' + tagmac).style.background = '#e0e0a0';
timeoutcount++;
} else {
if (tagDB[tagmac].pending) pendingcount++;
}
if (idletime > 24 * 3600) {
$('#tag' + tagmac).style.opacity = '.5';
@@ -424,6 +439,7 @@ function loadContentCard(mac) {
}
$('#cfgrotate').value = tagdata.rotate;
$('#cfglut').value = tagdata.lut;
$('#cfginvert').value = tagdata.invert;
$('#cfgmore').innerHTML = '&#x25BC;';
$('#configbox').showModal();
})
@@ -477,6 +493,7 @@ $('#cfgsave').onclick = function () {
formData.append("rotate", $('#cfgrotate').value);
formData.append("lut", $('#cfglut').value);
formData.append("invert", $('#cfginvert').value);
fetch("/save_cfg", {
method: "POST",
@@ -539,11 +556,13 @@ $('#cfgreset').onclick = function () {
$('#rebootbutton').onclick = function (event) {
event.preventDefault();
showMessage("rebooting AP....", true);
if (!confirm('Reboot AP now?')) return;
socket.close();
fetch("/reboot", {
method: "POST"
});
socket.close();
alert('Rebooted. Webpage will reload.');
location.reload()
}
$('#configbox').addEventListener('click', (event) => {
@@ -564,6 +583,7 @@ document.addEventListener("loadTab", function (event) {
$('#apcfgalias').value = data.alias;
$('#apcfgchid').value = data.channel;
$("#apcfgledbrightness").value = data.led;
$("#apcfgtftbrightness").value = data.tft;
$("#apcfglanguage").value = data.language;
$("#apclatency").value = data.maxsleep;
$("#apcpreventsleep").value = data.stopsleep;
@@ -575,6 +595,10 @@ document.addEventListener("loadTab", function (event) {
})
$('#apcfgmsg').innerHTML = '';
break;
case 'updatetab':
$('#updateconsole').innerHTML = '';
loadOTA();
break;
}
});
@@ -583,6 +607,7 @@ $('#apcfgsave').onclick = function () {
formData.append("alias", $('#apcfgalias').value);
formData.append("channel", $('#apcfgchid').value);
formData.append('led', $('#apcfgledbrightness').value);
formData.append('tft', $('#apcfgtftbrightness').value);
formData.append('language', $('#apcfglanguage').value);
formData.append('maxsleep', $('#apclatency').value);
formData.append('stopsleep', $('#apcpreventsleep').value);
@@ -605,12 +630,6 @@ $('#apcfgsave').onclick = function () {
.catch(error => showMessage('Error: ' + error));
}
$('#updatebutton').onclick = function (event) {
event.preventDefault();
$('#apupdatebox').style.display = 'block';
loadOTA();
}
async function loadOTA() {
otamodule = await import('./ota.js?v=' + Date.now());
otamodule.initUpdate();
@@ -684,6 +703,31 @@ function contentselected() {
input.type = "text";
input.disabled = true;
break;
case 'jpgfile':
case 'binfile':
case 'jsonfile':
input = document.createElement("select");
fetch('/edit?list=%2F&recursive=1')
.then(response => response.json())
.then(data => {
let files = data.filter(item => item.type === "file" && item.name.endsWith(".jpg"));
if (element.type == 'binfile') files = data.filter(item => item.type === "file" && item.name.endsWith(".bin"));
if (element.type == 'jsonfile') files = data.filter(item => item.type === "file" && item.name.endsWith(".json"));
const optionElement = document.createElement("option");
optionElement.value = "";
optionElement.text = "";
input.appendChild(optionElement);
files.forEach(item => {
const optionElement = document.createElement("option");
optionElement.value = item.name;
optionElement.text = item.name;
input.appendChild(optionElement);
})
})
.catch(error => {
console.error("Error fetching JSON data:", error);
});
break;
case 'select':
input = document.createElement("select");
for (const key in element.options) {
@@ -978,10 +1022,18 @@ $('#activefilter').addEventListener('click', (event) => {
});
async function getTagtype(hwtype) {
if (tagTypes[hwtype]) {
if (tagTypes[hwtype] && tagTypes[hwtype].busy == false) {
return tagTypes[hwtype];
}
// nice, but no possibility to invalidate this cache yet.
/*
const storedData = JSON.parse(localStorage.getItem("tagTypes"));
if (storedData && storedData[hwtype]) {
return storedData[hwtype];
}
*/
if (getTagtypeBusy) {
await new Promise(resolve => {
const checkBusy = setInterval(() => {
@@ -1000,7 +1052,7 @@ async function getTagtype(hwtype) {
clearInterval(checkBusy);
resolve();
}
}, 10);
}, 50);
});
}
@@ -1009,8 +1061,8 @@ async function getTagtype(hwtype) {
}
try {
tagTypes[hwtype] = { busy: true };
getTagtypeBusy = true;
tagTypes[hwtype] = { busy: true };
const response = await fetch('/tagtypes/' + hwtype.toString(16).padStart(2, '0').toUpperCase() + '.json');
if (!response.ok) {
let data = { name: 'unknown id ' + hwtype, width: 0, height: 0, bpp: 0, rotatebuffer: 0, colortable: [], busy: false };

View File

@@ -1,4 +1,5 @@
const repoUrl = 'https://api.github.com/repos/jjwbruijn/OpenEPaperLink/releases';
var repo = apConfig.repo || 'jjwbruijn/OpenEPaperLink';
var repoUrl = 'https://api.github.com/repos/' + repo + '/releases';
const $ = document.querySelector.bind(document);
@@ -8,19 +9,28 @@ let env = '', currentVer = '', currentBuildtime = 0;
let buttonState = false;
export async function initUpdate() {
if (!$("#updateconsole")) {
const consoleDiv = document.createElement('div');
consoleDiv.classList.add('console');
consoleDiv.id = "updateconsole";
$('#apupdatebox').appendChild(consoleDiv);
}
$("#updateconsole").innerHTML = "";
const response = await fetch("/version.txt");
let filesystemversion = await response.text();
if (!filesystemversion) filesystemversion = "unknown";
$('#repo').value = repo;
fetch("/sysinfo")
const envBox = $('#environment');
if (envBox?.tagName === 'SELECT') {
const inputElement = document.createElement('input');
inputElement.type = 'text';
inputElement.id = 'environment';
envBox.parentNode.replaceChild(inputElement, envBox);
}
$('#environment').value = '';
$('#environment').setAttribute('readonly', true);
$('#repo').removeAttribute('readonly');
$('#confirmSelectRepo').style.display = 'none';
$('#cancelSelectRepo').style.display = 'none';
$('#selectRepo').style.display = 'inline-block';
$('#repoWarning').style.display = 'none';
const sysinfoPromise = fetch("/sysinfo")
.then(response => {
if (response.status != 200) {
print("Error fetching sysinfo: " + response.status, "red");
@@ -34,33 +44,36 @@ export async function initUpdate() {
return response.json();
}
})
.then(data => {
if (data.env) {
let matchtest = '';
if (data.buildversion != filesystemversion && filesystemversion != "custom" && data.buildversion != "custom") matchtest = " <- not matching!"
print(`env: ${data.env}`);
print(`build date: ${formatEpoch(data.buildtime)}`);
print(`esp32 version: ${data.buildversion}`);
print(`filesystem version: ${filesystemversion}` + matchtest);
print(`sha: ${data.sha}`);
print(`psram size: ${data.psramsize}`);
print(`flash size: ${data.flashsize}`);
print("--------------------------", "gray");
env = data.env;
currentVer = data.buildversion;
currentBuildtime = data.buildtime;
if (data.rollback) $("#rollbackOption").style.display = 'block';
if (data.env == 'ESP32_S3_16_8_YELLOW_AP') $("#c6Option").style.display = 'block';
}
})
.catch(error => {
print('Error fetching sysinfo: ' + error, "red");
});
fetch(repoUrl)
const repoPromise = fetch(repoUrl)
.then(response => response.json())
.then(data => {
const releaseDetails = data.map(release => {
Promise.all([sysinfoPromise, repoPromise])
.then(([sdata, rdata]) => {
if (sdata.env) {
let matchtest = '';
if (sdata.buildversion != filesystemversion && filesystemversion != "custom" && sdata.buildversion != "custom") matchtest = " <- not matching!"
print(`env: ${sdata.env}`);
print(`build date: ${formatEpoch(sdata.buildtime)}`);
print(`esp32 version: ${sdata.buildversion}`);
print(`filesystem version: ${filesystemversion}` + matchtest);
print(`psram size: ${sdata.psramsize}`);
print(`flash size: ${sdata.flashsize}`);
print("--------------------------", "gray");
env = sdata.env;
currentVer = sdata.buildversion;
currentBuildtime = sdata.buildtime;
if (sdata.rollback) $("#rollbackOption").style.display = 'block';
if (sdata.env == 'ESP32_S3_16_8_YELLOW_AP') $("#c6Option").style.display = 'block';
$('#environment').value = env;
}
const releaseDetails = rdata.map(release => {
const assets = release.assets;
const filesJsonAsset = assets.find(asset => asset.name === 'filesystem.json');
const binariesJsonAsset = assets.find(asset => asset.name === 'binaries.json');
@@ -76,7 +89,7 @@ export async function initUpdate() {
}
};
});
const easyupdate = $('#easyupdate');
if (releaseDetails.length === 0) {
easyupdate.innerHTML = ("No releases found.");
@@ -92,7 +105,6 @@ export async function initUpdate() {
}
}
}
easyupdate.innerHTML += "<br><a onclick=\"$('#advanceddiv').style.display='block'\">advanced options</a>"
const table = document.createElement('table');
const tableHeader = document.createElement('tr');
@@ -101,9 +113,9 @@ export async function initUpdate() {
let rowCounter = 0;
releaseDetails.forEach(release => {
if (rowCounter < 3 && release?.html_url) {
if (rowCounter < 4 && release?.html_url) {
const tableRow = document.createElement('tr');
let tablerow = `<td><a href="${release.html_url}" target="_new">${release.tag_name}</a></td><td>${release.date}</td><td>${release.name}</td><td><button onclick="otamodule.updateESP('${release.bin_url}', true)">ESP32</button></td><td><button onclick="otamodule.updateWebpage('${release.file_url}','${release.tag_name}', true)">Filesystem</button></td>`;
let tablerow = `<td><a href="${release.html_url}" target="_new">${release.tag_name}</a></td><td>${release.date}</td><td>${release.name}</td><td><button type="button" onclick="otamodule.updateWebpage('${release.file_url}','${release.tag_name}', true)">Filesystem</button></td><td><button type="button" onclick="otamodule.updateESP('${release.bin_url}', true)">ESP32</button></td>`;
if (release.tag_name == currentVer) {
tablerow += "<td>current version</td>";
} else if (release.date < formatEpoch(currentBuildtime)) {
@@ -362,6 +374,90 @@ $('#updateC6Btn').onclick = function () {
disableButtons(false);
}
$('#selectRepo').onclick = function (event) {
event.preventDefault();
$('#updateconsole').innerHTML = '';
let repoUrl = 'https://api.github.com/repos/' + $('#repo').value + '/releases';
fetch(repoUrl)
.then(response => response.json())
.then(data => {
if (Array.isArray(data) && data.length > 0) {
const release = data[0];
print("Repo found! Latest release: " + release.name + " created " + release.created_at);
const assets = release.assets;
const filesJsonAsset = assets.find(asset => asset.name === 'filesystem.json');
const binariesJsonAsset = assets.find(asset => asset.name === 'binaries.json');
if (filesJsonAsset && binariesJsonAsset) {
const updateUrl = "http://openepaperlink.eu/getupdate/?url=" + binariesJsonAsset.browser_download_url + "&env=" + $('#repo').value;
return fetch(updateUrl);
} else {
throw new Error("Json file binaries.json and/or filesystem.json not found in the release assets");
}
};
})
.then(updateResponse => {
if (!updateResponse.ok) {
throw new Error("Network response was not OK");
}
return updateResponse.text();
})
.then(responseBody => {
if (!responseBody.trim().startsWith("[")) {
throw new Error("Failed to fetch the release info file");
}
const updateData = JSON.parse(responseBody).filter(item => !item.name.endsWith('_full.bin'));
const inputParent = $('#environment').parentNode;
const selectElement = document.createElement('select');
selectElement.id = 'environment';
updateData.forEach(item => {
const option = document.createElement('option');
option.value = item.name.replace('.bin', '');
option.text = item.name.replace('.bin', '');
selectElement.appendChild(option);
});
inputParent.replaceChild(selectElement, $('#environment'));
$('#environment').value = env;
$('#confirmSelectRepo').style.display = 'inline-block';
$('#cancelSelectRepo').style.display = 'inline-block';
$('#selectRepo').style.display = 'none';
$('#repo').setAttribute('readonly', true);
$('#repoWarning').style.display = 'block';
})
.catch(error => {
print('Error fetching releases:' + error, "red");
});
}
$('#cancelSelectRepo').onclick = function (event) {
event.preventDefault();
$('#updateconsole').innerHTML = '';
initUpdate();
}
$('#confirmSelectRepo').onclick = function (event) {
event.preventDefault();
repo = $('#repo').value;
let formData = new FormData();
formData.append("repo", repo);
formData.append("env", $('#environment').value);
fetch("/save_apcfg", {
method: "POST",
body: formData
})
.then(response => response.text())
.then(data => {
window.dispatchEvent(loadConfig);
print('OK, Saved');
})
.catch(error => print('Error: ' + error));
$('#updateconsole').innerHTML = '';
repoUrl = 'https://api.github.com/repos/' + repo + '/releases';
initUpdate();
}
export function print(line, color = "white") {
const consoleDiv = document.getElementById('updateconsole');
if (consoleDiv) {
@@ -464,7 +560,7 @@ const writeVersion = async (content, name, path) => {
};
function disableButtons(active) {
$("#apupdatebox").querySelectorAll('button').forEach(button => {
$("#configtab").querySelectorAll('button').forEach(button => {
button.disabled = active;
});
buttonState = active;

View File

@@ -104,7 +104,7 @@ function startPainter(mac, width, height) {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/imgupload');
xhr.send(formData);
$('#configbox').style.display = 'none';
$('#configbox').close();
});
$("#buttonbar").appendChild(blackButton);

View File

@@ -34,7 +34,7 @@ Not all connections are required by all tags! If you want to solder fewer wires,
## Flashing the flasher
Clone the [Tag_Flasher repo](https://github.com/jjwbruijn/OpenEPaperLink/tree/master/Tag_Flasher/ESP32_Flasher) and open into PlatformIO. Choose the correct COM-port and hit 'Upload'.
Also, the precompiled binaries are part of any [release](https://github.com/jjwbruijn/OpenEPaperLink/releases), and to make it even easier, you can use the web flasher on https://install.openepaperlink.nl to even flash it without installing any extra software.
Also, the precompiled binaries are part of any [release](https://github.com/jjwbruijn/OpenEPaperLink/releases), and to make it even easier, you can use the web flasher on https://install.openepaperlink.de to even flash it without installing any extra software.
## OEPL-Flasher.py

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -52,7 +52,7 @@ dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
tag = sys.argv[1]
binaries = generate_file_hashes2(rp + "/espbinaries",tag)
tagota = generate_file_hashes2(rp + "/binaries",tag)
#tagota = generate_file_hashes2(rp + "/binaries",tag)
files1 = generate_file_hashes(rp + "/ESP32_AP-Flasher/data/www","/www/",tag)
files1.extend(generate_file_hashes(rp + "/ESP32_AP-Flasher/data","/",tag))
files1.extend(generate_file_hashes(rp + "/ESP32_AP-Flasher/data/fonts","/fonts/",tag))
@@ -65,7 +65,7 @@ jsonarray = {
"builddate": dt_string,
"binaries": binaries,
"files": files1,
"tagota": tagota,
# "tagota": tagota,
}
with open("jsonfiles/binaries.json", "w") as json_file:
@@ -74,8 +74,8 @@ with open("jsonfiles/binaries.json", "w") as json_file:
with open("jsonfiles/files.json", "w") as json_file:
json.dump(jsonarray, json_file, indent=4)
with open("jsonfiles/tagota.json", "w") as json_file:
json.dump(tagota, json_file, indent=4)
#with open("jsonfiles/tagota.json", "w") as json_file:
# json.dump(tagota, json_file, indent=4)
with open("jsonfiles/filesystem.json", "w") as json_file:
json.dump(files1, json_file, indent=4)

View File

@@ -57,18 +57,48 @@
#define CMD_DO_RESET_SETTINGS 2
#define CMD_DO_DEEPSLEEP 3
#define CMD_DO_LEDFLASH 4
#define CMD_ERASE_EEPROM_IMAGES 5
#define CMD_ENTER_SLIDESHOW_FAST 0x06
#define CMD_ENTER_SLIDESHOW_MEDIUM 0x07
#define CMD_ENTER_SLIDESHOW_SLOW 0x08
#define CMD_ENTER_SLIDESHOW_GLACIAL 0x09
#define CMD_ENTER_NORMAL_MODE 0x0F
#define WAKEUP_REASON_TIMED 0
#define WAKEUP_REASON_GPIO 2
#define WAKEUP_REASON_NFC 3
#define WAKEUP_REASON_BUTTON1 4
#define WAKEUP_REASON_BUTTON2 5
#define WAKEUP_REASON_RF 0x0F
#define WAKEUP_REASON_FIRSTBOOT 0xFC
#define WAKEUP_REASON_NETWORK_SCAN 0xFD
#define WAKEUP_REASON_WDT_RESET 0xFE
#define EPD_LUT_DEFAULT 0
#define EPD_LUT_NO_REPEATS 1
#define EPD_LUT_FAST_NO_REDS 2
#define EPD_LUT_FAST 3
#define EPD_LUT_OTA 0x10
// these are the 'custom image' arguments that will be sent in addition to their 'type'.
#define CUSTOM_IMAGE_NOCUSTOM 0x00 // regular image type
#define CUSTOM_IMAGE_SPLASHSCREEN 0x01 // will show at first boot/powerup
#define CUSTOM_IMAGE_LOST_CONNECTION 0x02 // this image will be shown (if it exists on the tag) if the tag looses its connection
#define CUSTOM_IMAGE_APFOUND 0x03 // this image will be shown during bootup if an AP was found
#define CUSTOM_IMAGE_NOAPFOUND 0x04 // shown if during bootup no AP was found
#define CUSTOM_IMAGE_LONGTERMSLEEP 0x05 // shown if long term sleep is entered via command
// UNUSED: 0x06-0x0F
#define CUSTOM_IMAGE_SLIDESHOW 0x0F // image is part of a slideshow
#define CUSTOM_IMAGE_BUTTON1 0x10
#define CUSTOM_IMAGE_BUTTON2 0x11
// UNUSED: 0x12 to 0x1B
#define CUSTOM_IMAGE_RF_WAKE 0x1C
#define CUSTOM_IMAGE_GPIO 0x1D
#define CUSTOM_IMAGE_NFC_WAKE 0x1E
#define TAG_CUSTOM_MODE_NONE 0x00
#define TAG_CUSTOM_SLIDESHOW_FAST 0x06
#define TAG_CUSTOM_SLIDESHOW_MEDIUM 0x07
#define TAG_CUSTOM_SLIDESHOW_SLOW 0x08
#define TAG_CUSTOM_SLIDESHOW_GLACIAL 0x09

View File

@@ -187,3 +187,9 @@ struct ledFlash {
uint8_t repeats;
uint8_t spare;
} __packed;
struct imageDataTypeArgStruct {
uint8_t lut : 2;
uint8_t preloadImage : 1; // set to 0 will draw image immediately
uint8_t specialType : 5;
} __packed;

View File

@@ -24,15 +24,23 @@
#include "../oepl-definitions.h"
#include "../oepl-proto.h"
// #define DEBUG_MODE
static const uint64_t __code __at(0x008b) mVersionRom = 0x1000011300000000ull;
#define TAG_MODE_CHANSEARCH 0
#define TAG_MODE_ASSOCIATED 1
#define DELAY_SLIDESHOW_FAST 30
#define DELAY_SLIDESHOW_MEDIUM 60
#define DELAY_SLIDESHOW_SLOW 300
#define DELAY_SLIDESHOW_GLACIAL 1800
uint8_t currentTagMode = TAG_MODE_CHANSEARCH;
uint8_t __xdata slideShowCurrentImg = 0;
uint8_t __xdata slideShowRefreshCount = 1;
void displayLoop() {
powerUp(INIT_BASE | INIT_UART);
@@ -46,22 +54,6 @@ void displayLoop() {
showApplyUpdate();
timerDelay(TIMER_TICKS_PER_SECOND * 4);
wdt60s();
pr("Scanning screen - ");
powerUp(INIT_EPD);
showScanningWindow();
timerDelay(TIMER_TICKS_PER_SECOND * 8);
for (uint8_t i = 0; i < 5; i++) {
for (uint8_t c = 0; c < 16; c++) {
addScanResult(11 + c, 2 * i + 60 + c);
}
pr("redraw... ");
draw();
}
pr("\n");
timerDelay(TIMER_TICKS_PER_SECOND * 4);
wdt30s();
pr("AP Found\n");
@@ -99,43 +91,16 @@ void displayLoop() {
wdtDeviceReset();
}
uint8_t showChannelSelect() { // returns 0 if no accesspoints were found
uint8_t __xdata result[sizeof(channelList)];
memset(result, 0, sizeof(result));
showScanningWindow();
drawNoWait();
powerUp(INIT_RADIO);
for (uint8_t i = 0; i < 4; i++) {
for (uint8_t c = 0; c < sizeof(channelList); c++) {
if (detectAP(channelList[c])) {
if (mLastLqi > result[c]) result[c] = mLastLqi;
pr("Channel: %d - LQI: %d RSSI %d\n", channelList[c], mLastLqi, mLastRSSI);
}
}
}
uint8_t __xdata highestLqi = 0;
uint8_t __xdata highestSlot = 0;
for (uint8_t c = 0; c < sizeof(result); c++) {
if (result[c] > highestLqi) {
highestSlot = channelList[c];
highestLqi = result[c];
}
}
powerDown(INIT_RADIO);
epdWaitRdy();
mLastLqi = highestLqi;
return highestSlot;
}
uint8_t channelSelect() { // returns 0 if no accesspoints were found
uint8_t channelSelect(uint8_t rounds) { // returns 0 if no accesspoints were found
powerUp(INIT_RADIO);
uint8_t __xdata result[16];
memset(result, 0, sizeof(result));
for (uint8_t i = 0; i < 2; i++) {
for (uint8_t i = 0; i < rounds; i++) {
for (uint8_t c = 0; c < sizeof(channelList); c++) {
if (detectAP(channelList[c])) {
if (mLastLqi > result[c]) result[c] = mLastLqi;
if (rounds > 2) pr("Channel: %d - LQI: %d RSSI %d\n", channelList[c], mLastLqi, mLastRSSI);
}
}
}
@@ -215,6 +180,7 @@ void detectButtonOrJig() {
void TagAssociated() {
// associated
bool fastNextCheckin = false;
struct AvailDataInfo *__xdata avail;
// Is there any reason why we should do a long (full) get data request (including reason, status)?
if ((longDataReqCounter > LONG_DATAREQ_INTERVAL) || wakeUpReason != WAKEUP_REASON_TIMED) {
@@ -234,7 +200,8 @@ void TagAssociated() {
if (curImgSlot != 0xFF) {
powerUp(INIT_EEPROM | INIT_EPD);
wdt60s();
drawImageFromEeprom(curImgSlot);
uint8_t lut = getEepromImageDataArgument(curImgSlot) & 0x03;
drawImageFromEeprom(curImgSlot, lut);
powerDown(INIT_EEPROM | INIT_EPD);
} else {
powerUp(INIT_EPD);
@@ -247,6 +214,29 @@ void TagAssociated() {
avail = getAvailDataInfo();
powerDown(INIT_RADIO);
switch (wakeUpReason) {
case WAKEUP_REASON_BUTTON1:
externalWakeHandler(CUSTOM_IMAGE_BUTTON1);
fastNextCheckin = true;
break;
case WAKEUP_REASON_BUTTON2:
externalWakeHandler(CUSTOM_IMAGE_BUTTON2);
fastNextCheckin = true;
break;
case WAKEUP_REASON_GPIO:
externalWakeHandler(CUSTOM_IMAGE_GPIO);
fastNextCheckin = true;
break;
case WAKEUP_REASON_RF:
externalWakeHandler(CUSTOM_IMAGE_RF_WAKE);
fastNextCheckin = true;
break;
case WAKEUP_REASON_NFC:
externalWakeHandler(CUSTOM_IMAGE_NFC_WAKE);
fastNextCheckin = true;
break;
}
if (avail != NULL) {
// we got some data!
longDataReqCounter = 0;
@@ -254,14 +244,12 @@ void TagAssociated() {
wakeUpReason = WAKEUP_REASON_TIMED;
}
if (tagSettings.enableTagRoaming) {
uint8_t roamChannel = channelSelect();
uint8_t roamChannel = channelSelect(1);
if (roamChannel) currentChannel = roamChannel;
}
} else {
powerUp(INIT_RADIO);
#ifdef ENABLE_RETURN_DATA
// example code to send data back to the AP. Up to 90 bytes can be sent in one packet
uint8_t __xdata blaat[2] = {0xAB, 0xBA};
@@ -304,11 +292,22 @@ void TagAssociated() {
}
}
// if the AP told us to sleep for a specific period, do so.
if (nextCheckInFromAP) {
doSleep(nextCheckInFromAP * 60000UL);
if (fastNextCheckin) {
// do a fast check-in next
fastNextCheckin = false;
doSleep(100UL);
} else {
doSleep(getNextSleep() * 1000UL);
if (nextCheckInFromAP) {
// if the AP told us to sleep for a specific period, do so.
if (nextCheckInFromAP & 0x8000) {
doSleep((nextCheckInFromAP & 0x7FFF) * 1000UL);
} else {
doSleep(nextCheckInFromAP * 60000UL);
}
} else {
// sleep determined by algorithm
doSleep(getNextSleep() * 1000UL);
}
}
}
@@ -319,7 +318,7 @@ void TagChanSearch() {
}
// try to find a working channel
currentChannel = channelSelect();
currentChannel = channelSelect(2);
// Check if we should redraw the screen with icons, info screen or screensaver
if ((!currentChannel && !noAPShown && tagSettings.enableNoRFSymbol) ||
@@ -328,9 +327,12 @@ void TagChanSearch() {
powerUp(INIT_EPD);
wdt60s();
if (curImgSlot != 0xFF) {
powerUp(INIT_EEPROM);
drawImageFromEeprom(curImgSlot);
powerDown(INIT_EEPROM);
if (!displayCustomImage(CUSTOM_IMAGE_LOST_CONNECTION)) {
powerUp(INIT_EEPROM);
uint8_t lut = getEepromImageDataArgument(curImgSlot) & 0x03;
drawImageFromEeprom(curImgSlot, lut);
powerDown(INIT_EEPROM);
}
} else if ((scanAttempts >= (INTERVAL_1_ATTEMPTS + INTERVAL_2_ATTEMPTS - 1))) {
showLongTermSleep();
} else {
@@ -354,6 +356,72 @@ void TagChanSearch() {
}
}
void TagSlideShow() {
currentChannel = 11; // suppress the no-rf image thing
displayCustomImage(CUSTOM_IMAGE_SPLASHSCREEN);
// do a short channel search
currentChannel = channelSelect(2);
pr("Slideshow mode ch: %d\n", currentChannel);
// if we did find an AP, check in once
if (currentChannel) {
doVoltageReading();
struct AvailDataInfo *__xdata avail;
powerUp(INIT_RADIO);
avail = getAvailDataInfo();
if (avail != NULL) {
processAvailDataInfo(avail);
}
}
powerDown(INIT_RADIO);
// suppress the no-rf image
currentChannel = 11;
while (1) {
powerUp(INIT_UART);
wdt60s();
powerUp(INIT_EEPROM);
uint8_t img = findNextSlideshowImage(slideShowCurrentImg);
if (img != slideShowCurrentImg) {
slideShowCurrentImg = img;
uint8_t lut = getEepromImageDataArgument(img) & 0x03;
powerUp(INIT_EPD);
if (SLIDESHOW_FORCE_FULL_REFRESH_EVERY) {
slideShowRefreshCount++;
}
if ((slideShowRefreshCount == SLIDESHOW_FORCE_FULL_REFRESH_EVERY) || (lut == 0)) {
slideShowRefreshCount = 1;
lut = 0;
}
drawImageFromEeprom(img, lut);
powerDown(INIT_EPD | INIT_EEPROM);
} else {
// same image, so don't update the screen; this only happens when there's exactly one slideshow image
powerDown(INIT_EEPROM);
}
switch (tagSettings.customMode) {
case TAG_CUSTOM_SLIDESHOW_FAST:
doSleep(1000UL * SLIDESHOW_INTERVAL_FAST);
break;
case TAG_CUSTOM_SLIDESHOW_MEDIUM:
doSleep(1000UL * SLIDESHOW_INTERVAL_MEDIUM);
break;
case TAG_CUSTOM_SLIDESHOW_SLOW:
doSleep(1000UL * SLIDESHOW_INTERVAL_SLOW);
break;
case TAG_CUSTOM_SLIDESHOW_GLACIAL:
doSleep(1000UL * SLIDESHOW_INTERVAL_GLACIAL);
break;
}
pr("wake...\n");
}
}
void executeCommand(uint8_t cmd) {
switch (cmd) {
case CMD_DO_REBOOT:
@@ -364,7 +432,7 @@ void executeCommand(uint8_t cmd) {
writeSettings();
break;
case CMD_DO_SCAN:
currentChannel = channelSelect();
currentChannel = channelSelect(4);
break;
case CMD_DO_DEEPSLEEP:
powerUp(INIT_EPD);
@@ -374,6 +442,61 @@ void executeCommand(uint8_t cmd) {
doSleep(-1);
}
break;
case CMD_ERASE_EEPROM_IMAGES:
powerUp(INIT_EEPROM);
eraseImageBlocks();
powerDown(INIT_EEPROM);
break;
case CMD_ENTER_SLIDESHOW_FAST:
powerUp(INIT_EEPROM);
if (findSlotDataTypeArg(CUSTOM_IMAGE_SLIDESHOW << 3) == 0xFF) {
powerDown(INIT_EEPROM);
return;
}
powerDown(INIT_EEPROM);
tagSettings.customMode = TAG_CUSTOM_SLIDESHOW_FAST;
writeSettings();
wdtDeviceReset();
break;
case CMD_ENTER_SLIDESHOW_MEDIUM:
powerUp(INIT_EEPROM);
if (findSlotDataTypeArg(CUSTOM_IMAGE_SLIDESHOW << 3) == 0xFF) {
powerDown(INIT_EEPROM);
return;
}
powerDown(INIT_EEPROM);
tagSettings.customMode = TAG_CUSTOM_SLIDESHOW_MEDIUM;
writeSettings();
wdtDeviceReset();
break;
case CMD_ENTER_SLIDESHOW_SLOW:
powerUp(INIT_EEPROM);
if (findSlotDataTypeArg(CUSTOM_IMAGE_SLIDESHOW << 3) == 0xFF) {
powerDown(INIT_EEPROM);
return;
}
powerDown(INIT_EEPROM);
tagSettings.customMode = TAG_CUSTOM_SLIDESHOW_SLOW;
writeSettings();
wdtDeviceReset();
break;
case CMD_ENTER_SLIDESHOW_GLACIAL:
powerUp(INIT_EEPROM);
if (findSlotDataTypeArg(CUSTOM_IMAGE_SLIDESHOW << 3) == 0xFF) {
powerDown(INIT_EEPROM);
return;
}
powerDown(INIT_EEPROM);
tagSettings.customMode = TAG_CUSTOM_SLIDESHOW_GLACIAL;
writeSettings();
wdtDeviceReset();
break;
case CMD_ENTER_NORMAL_MODE:
tagSettings.customMode = TAG_CUSTOM_MODE_NONE;
writeSettings();
wdtDeviceReset();
break;
}
}
@@ -399,6 +522,17 @@ void main() {
// get the highest slot number, number of slots
initializeProto();
switch (tagSettings.customMode) {
case TAG_CUSTOM_SLIDESHOW_FAST:
case TAG_CUSTOM_SLIDESHOW_MEDIUM:
case TAG_CUSTOM_SLIDESHOW_SLOW:
case TAG_CUSTOM_SLIDESHOW_GLACIAL:
TagSlideShow();
break;
default:
break;
}
if (tagSettings.enableFastBoot) {
// Fastboot
pr("Doing fast boot\n");
@@ -406,7 +540,7 @@ void main() {
if (tagSettings.fixedChannel) {
currentChannel = tagSettings.fixedChannel;
} else {
currentChannel = channelSelect();
currentChannel = channelSelect(2);
}
} else {
// Normal boot/startup
@@ -426,9 +560,9 @@ void main() {
detectButtonOrJig();
// show the splashscreen
pr("EPD: First powerup\n");
powerUp(INIT_EPD);
currentChannel = 11;
showSplashScreen();
currentChannel = 0;
// we've now displayed something on the screen; for the SSD1619, we are now aware of the lut-size
#ifdef EPD_SSD1619
@@ -443,28 +577,25 @@ void main() {
writeSettings();
// scan for channels
powerUp(INIT_EPD);
wdt30s();
if (tagSettings.fixedChannel) {
currentChannel = tagSettings.fixedChannel;
} else {
currentChannel = showChannelSelect();
currentChannel = channelSelect(4);
}
}
// end of the fastboot option split
wdt10s();
powerUp(INIT_EPD);
if (currentChannel) {
showAPFound();
initPowerSaving(INTERVAL_BASE);
powerDown(INIT_EPD | INIT_UART);
currentTagMode = TAG_MODE_ASSOCIATED;
doSleep(5000UL);
} else {
showNoAP();
initPowerSaving(INTERVAL_AT_MAX_ATTEMPTS);
powerDown(INIT_EPD | INIT_UART);
currentTagMode = TAG_MODE_CHANSEARCH;
doSleep(120000UL);
}

View File

@@ -1,12 +1,12 @@
<?php
$types[0x00] = "Tag_FW_1.54.bin";
$types[0x01] = "Tag_FW_2.9.bin";
$types[0x00] = "SOLUM_154_SSD1619-tag-00-0022.bin";
$types[0x01] = "SOLUM_29_SSD1619-tag-01-0022";
$types[0xF0] = "Tag_FW_Segmented_UK.bin";
$types[0x02] = "Tag_FW_4.2.bin";
$types[0x11] = "Tag_FW_2.9-uc8151.bin";
$types[0x02] = "SOLUM_42_SSD1619-tag-02-0022.bin";
$types[0x11] = "SOLUM_29_UC8151-tag-11-0022.bin";
$binpath = "../binaries/";
$binpath = "../binaries/Tag";
$tocmaxsize = 512;
$toc = array();
@@ -21,6 +21,7 @@ $version = hexdec($version);
*/
$version = 0;
// *** fixme: this should select only filenames containing the latest version. See python version of this script.
exec("ls -1 $binpath | grep 'Tag_FW' | grep -v battery | grep -v Pack | grep -v M3", $binaries);
foreach($binaries as $file){
$file = trim($file);
@@ -30,7 +31,7 @@ foreach($binaries as $file){
$type = $typeid;
}
}
if($type==-1)die("We don't recognize filetype <$file>, sorry...\n");
if($type!=-1)echo("Adding filetype <$file>\n");
$binary = file_get_contents($binpath.$file);
$length = strlen($binary);
$offset = strlen($output);

View File

@@ -0,0 +1,58 @@
import os
import json
version = "0022" # 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",
}
binpath = "../binaries/Tag"
tocmaxsize = 512
toc = []
output = b'\0' * tocmaxsize # Initialize as bytes
binaries = [file for file in os.listdir(binpath) if 'Pack' not in file and version in file]
for file in binaries:
file = file.strip()
type = -1
for typeid, typefile in types.items():
if typefile == file:
type = typeid
if type != -1:
print("Adding filetype <{}>".format(file))
with open(os.path.join(binpath, file), 'rb') as binary_file:
binary = binary_file.read()
length = len(binary)
offset = len(output)
subarr = {
'type': type,
'version': version,
'name': file,
'offset': offset,
'length': length,
}
toc.append(subarr)
output += binary
jtoc = json.dumps(toc)
jtoc = jtoc.replace("'", '"')
tocsize = len(jtoc)
if tocsize > tocmaxsize:
raise ValueError("TOC is too big! (" + str(tocsize) + "). Adjust size and try again")
# Encode jtoc as bytes
jtoc = jtoc.encode('utf-8')
# Concatenate bytes and write to the file
output = jtoc + output[len(jtoc):]
with open(os.path.join(binpath, "Tag_FW_Pack.bin"), 'wb') as output_file:
output_file.write(output)
print(toc)
print("All done.")

View File

@@ -286,41 +286,83 @@ void doSleep(const uint32_t __xdata t) {
uartActive = false;
eepromActive = false;
capabilities |= CAPABILITY_HAS_WAKE_BUTTON;
if (capabilities & CAPABILITY_HAS_WAKE_BUTTON) {
// Button setup on TEST pin 1.0 (input pullup)
P1FUNC &= ~(1 << 0);
P1DIR |= (1 << 0);
P1PULL |= (1 << 0);
P1LVLSEL |= (1 << 0);
P1INTEN = (1 << 0);
P1INTEN |= (1 << 0);
P1CHSTA &= ~(1 << 0);
// Button setup on RXD pin 0.7 (input pullup)
P0FUNC &= ~(1 << 7);
P0DIR |= (1 << 7);
P0PULL |= (1 << 7);
P0LVLSEL |= (1 << 7);
P0INTEN |= (1 << 7);
P0CHSTA &= ~(1 << 7);
}
#ifdef ENABLE_GPIO_WAKE
// enable wake on pin 0.2 (MISO)
P0FUNC &= ~(1 << 3);
P0DIR |= (1 << 3);
P0PULL |= (1 << 3);
P0LVLSEL |= (1 << 3);
P0INTEN |= (1 << 3);
P0CHSTA &= ~(1 << 3);
#endif
if (capabilities & CAPABILITY_NFC_WAKE) {
P1FUNC &= ~(1 << 3);
P1DIR |= (1 << 3);
P1PULL |= (1 << 3);
P1LVLSEL |= (1 << 3);
P1INTEN = (1 << 3);
P1INTEN |= (1 << 3);
P1CHSTA &= ~(1 << 3);
}
if (tagSettings.enableRFWake) {
// enabled RF wake, adds a little extra energy draw!
// enabled RF wake, adds a little extra energy draw!
RADIO_RadioPowerCtl &= 0xFB;
}
// sleepy time
sleepForMsec(t);
P1INTEN = 0;
if ((P1CHSTA & (1 << 0)) && (capabilities & CAPABILITY_HAS_WAKE_BUTTON)) {
wakeUpReason = WAKEUP_REASON_GPIO;
P1CHSTA &= ~(1 << 0);
}
P0INTEN = 0;
if ((P1CHSTA & (1 << 3)) && (capabilities & CAPABILITY_NFC_WAKE)) {
wakeUpReason = WAKEUP_REASON_NFC;
P1CHSTA &= ~(1 << 3);
switch (RADIO_Wake_Reason) {
case RADIO_WAKE_REASON_TIMER:
break;
case RADIO_WAKE_REASON_EXT:
if ((P1CHSTA & (1 << 0)) && (capabilities & CAPABILITY_HAS_WAKE_BUTTON)) {
wakeUpReason = WAKEUP_REASON_BUTTON1;
P1CHSTA &= ~(1 << 0);
}
if ((P0CHSTA & (1 << 7)) && (capabilities & CAPABILITY_HAS_WAKE_BUTTON)) {
wakeUpReason = WAKEUP_REASON_BUTTON2;
P0CHSTA &= ~(1 << 7);
}
if ((P1CHSTA & (1 << 3)) && (capabilities & CAPABILITY_NFC_WAKE)) {
wakeUpReason = WAKEUP_REASON_NFC;
P1CHSTA &= ~(1 << 3);
}
#ifdef ENABLE_GPIO_WAKE
if (P0CHSTA & (1 << 3)) {
wakeUpReason = WAKEUP_REASON_GPIO;
P0CHSTA &= ~(1 << 3);
}
#endif
break;
case RADIO_WAKE_REASON_RF:
wakeUpReason = WAKEUP_REASON_RF;
break;
}
}

View File

@@ -40,6 +40,13 @@
#define INTERVAL_2_ATTEMPTS 12 // for 12 attempts (an additional day)
#define INTERVAL_3_TIME 86400UL // Finally, try every day
// slideshow power settings
#define SLIDESHOW_FORCE_FULL_REFRESH_EVERY 16 // force a full refresh every X screen draws
#define SLIDESHOW_INTERVAL_FAST 15 // interval for 'fast'
#define SLIDESHOW_INTERVAL_MEDIUM 60
#define SLIDESHOW_INTERVAL_SLOW 300
#define SLIDESHOW_INTERVAL_GLACIAL 1800
extern uint8_t checkButtonOrJig();
extern void setupPortsInitial();

View File

@@ -3,11 +3,11 @@
#include <stdint.h>
#define FW_VERSION 21 // version number (max 2.5.5 :) )
#define FW_VERSION_SUFFIX "-RET" // suffix, like -RC1 or whatever.
#define FW_VERSION 22 // version number (max 2.5.5 :) )
#define FW_VERSION_SUFFIX "-RFW" // suffix, like -RC1 or whatever.
// #define DEBUGBLOCKS // uncomment to enable extra debug information on the block transfers
// #define PRINT_LUT // uncomment if you want the tag to print the LUT for the current temperature bracket
#define ENABLE_GPIO_WAKE // uncomment to enable GPIO wake
// #define ENABLE_RETURN_DATA // enables the tag to send blocks of data back. Enabling this costs about 4 IRAM bytes
#define SETTINGS_STRUCT_VERSION 0x01
@@ -42,4 +42,4 @@ void loadDefaultSettings();
void writeSettings();
void loadSettings();
void loadSettingsFromBuffer(uint8_t* p);
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More