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 - name: Install intelhex
run: pip install --upgrade intelhex run: pip install --upgrade intelhex
- name: Build NRF firmware # - name: Build NRF firmware
run: | # run: |
cd ARM_Tag_FW/Newton_M3_nRF52811 # cd ARM_Tag_FW/Newton_M3_nRF52811
pio run --environment Newton_M3_22_BWR # pio run --environment Newton_M3_22_BWR
pio run --environment Newton_M3_29_BWR # pio run --environment Newton_M3_29_BWR
pio run --environment Newton_M3_75_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-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_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-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_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-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 # cp Newton_M3_75_BWR-full-flash.bin /home/runner/work/OpenEPaperLink/OpenEPaperLink/binaries/Newton_M3_75_BWR-full-flash.bin
- name: Install esptool - name: Install esptool
run: pip install esptool run: pip install esptool
@@ -199,14 +199,14 @@ jobs:
file_glob: true file_glob: true
overwrite: true overwrite: true
- name: Add tag bins to release # - name: Add tag bins to release
uses: svenstaro/upload-release-action@v2 # uses: svenstaro/upload-release-action@v2
with: # with:
repo_token: ${{ secrets.GITHUB_TOKEN }} # repo_token: ${{ secrets.GITHUB_TOKEN }}
file: binaries/* # file: binaries/*
tag: ${{ github.ref }} # tag: ${{ github.ref }}
file_glob: true # file_glob: true
overwrite: 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) # 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 *.axf
# Allow # Allow
!*.bin !*.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; goto SCPfailed;
SCPchannelFound: SCPchannelFound:
pr("ACK>"); pr("ACK>");
if (curChannel != scp->channel) { if (curChannel != scp->channel) {
radioSetChannel(scp->channel); radioSetChannel(scp->channel);
curChannel = scp->channel; curChannel = scp->channel;
} }
curPower = scp->power; curPower = scp->power;
radioSetTxPower(scp->power); radioSetTxPower(scp->power);
ESP_LOGI(TAG, "Set channel: %d power: %d", curChannel, curPower); ESP_LOGI(TAG, "Set channel: %d power: %d", curChannel, curPower);
} else { } else {
SCPfailed: SCPfailed:
pr("NOK>"); pr("NOK>");
} }
@@ -412,27 +412,27 @@ void espNotifyAPInfo() {
} }
void espNotifyTagReturnData(uint8_t *src, uint8_t len) { 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 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 espTagReturnData *etrd = (struct espTagReturnData *)radiotxbuffer;
if (memcmp((void *) & trd->dataVer, lastTagReturn, 8) == 0) { if (memcmp((void *) & trd->dataVer, lastTagReturn, 8) == 0) {
return; return;
} else { } else {
memcpy(lastTagReturn, &trd->dataVer, 8); memcpy(lastTagReturn, &trd->dataVer, 8);
} }
memcpy(etrd->src, src, 8); memcpy(etrd->src, src, 8);
etrd->len = len; etrd->len = len;
memcpy(&etrd->returnData, trd, len); memcpy(&etrd->returnData, trd, len);
addCRC(etrd, len + 10); addCRC(etrd, len + 10);
uartTx('T'); uartTx('T');
uartTx('R'); uartTx('R');
uartTx('D'); uartTx('D');
uartTx('>'); uartTx('>');
for (uint8_t c = 0; c < len + 10; c++) { for (uint8_t c = 0; c < len + 10; c++) {
uartTx(((uint8_t *)etrd)[c]); uartTx(((uint8_t *)etrd)[c]);
} }
} }
// process data from tag // process data from tag
@@ -492,7 +492,7 @@ void processBlockRequest(const uint8_t *buffer, uint8_t forceBlockDownload) {
if (blockStartTimer == 0) { if (blockStartTimer == 0) {
if (requestDataDownload) { if (requestDataDownload) {
if (highspeedSerial == true) { if (highspeedSerial == true) {
blockRequestAck->pleaseWaitMs = 220; blockRequestAck->pleaseWaitMs = 140;
} else { } else {
blockRequestAck->pleaseWaitMs = 550; blockRequestAck->pleaseWaitMs = 550;
} }
@@ -583,23 +583,23 @@ void processXferComplete(uint8_t *buffer) {
} }
void processTagReturnData(uint8_t *buffer, uint8_t len) { void processTagReturnData(uint8_t *buffer, uint8_t len) {
struct MacFrameBcast *rxframe = (struct MacFrameBcast *)buffer; struct MacFrameBcast *rxframe = (struct MacFrameBcast *)buffer;
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *)(radiotxbuffer + 1); struct MacFrameNormal *frameHeader = (struct MacFrameNormal *)(radiotxbuffer + 1);
if (!checkCRC((buffer + sizeof(struct MacFrameBcast) + 1), len - (sizeof(struct MacFrameBcast) + 1))) { if (!checkCRC((buffer + sizeof(struct MacFrameBcast) + 1), len - (sizeof(struct MacFrameBcast) + 1))) {
return; return;
} }
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_TAG_RETURN_DATA_ACK; radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_TAG_RETURN_DATA_ACK;
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING; radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING;
memcpy(frameHeader->src, mSelfMac, 8); memcpy(frameHeader->src, mSelfMac, 8);
memcpy(frameHeader->dst, rxframe->src, 8); memcpy(frameHeader->dst, rxframe->src, 8);
radiotxbuffer[1] = 0x41; // fast way to set the appropriate bits radiotxbuffer[1] = 0x41; // fast way to set the appropriate bits
radiotxbuffer[2] = 0xCC; // normal frame radiotxbuffer[2] = 0xCC; // normal frame
frameHeader->seq = seq++; frameHeader->seq = seq++;
frameHeader->pan = rxframe->srcPan; frameHeader->pan = rxframe->srcPan;
radioTx(radiotxbuffer); radioTx(radiotxbuffer);
espNotifyTagReturnData(rxframe->src, len - (sizeof(struct MacFrameBcast) + 1)); espNotifyTagReturnData(rxframe->src, len - (sizeof(struct MacFrameBcast) + 1));
} }
// send block data to the tag // send block data to the tag
@@ -628,6 +628,18 @@ void sendBlockData() {
pr("Invalid block request received, 0 parts..\n"); pr("Invalid block request received, 0 parts..\n");
requestedData.requestedParts[0] |= 0x01; 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; uint8_t partNo = 0;
while (partNo < BLOCK_MAX_PARTS) { while (partNo < BLOCK_MAX_PARTS) {
for (uint8_t c = 0; (c < BLOCK_MAX_PARTS) && (partNo < BLOCK_MAX_PARTS); c++) { 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) { void app_main(void) {
esp_event_loop_create_default(); esp_event_loop_create_default();
init_nvs(); init_nvs();
init_led(); init_led();
init_second_uart(); init_second_uart();
requestedData.blockId = 0xFF; requestedData.blockId = 0xFF;
// clear the array with pending information // clear the array with pending information
memset(pendingDataArr, 0, sizeof(pendingDataArr)); memset(pendingDataArr, 0, sizeof(pendingDataArr));
radio_init(curChannel); radio_init(curChannel);
radioSetTxPower(10); radioSetTxPower(10);
pr("RES>"); pr("RES>");
pr("RDY>"); pr("RDY>");
ESP_LOGI(TAG, "C6 ready!"); ESP_LOGI(TAG, "C6 ready!");
housekeepingTimer = getMillis(); housekeepingTimer = getMillis();
while (1) { while (1) {
while ((getMillis() - housekeepingTimer) < ((1000 * HOUSEKEEPING_INTERVAL) - 100)) { while ((getMillis() - housekeepingTimer) < ((1000 * HOUSEKEEPING_INTERVAL) - 100)) {
int8_t ret = commsRxUnencrypted(radiorxbuffer); int8_t ret = commsRxUnencrypted(radiorxbuffer);
@@ -741,11 +753,11 @@ void app_main(void) {
processAvailDataReq(radiorxbuffer); processAvailDataReq(radiorxbuffer);
} }
break; break;
case PKT_TAG_RETURN_DATA: case PKT_TAG_RETURN_DATA:
processTagReturnData(radiorxbuffer, ret); processTagReturnData(radiorxbuffer, ret);
break; break;
default: default:
ESP_LOGI(TAG, "t=%02X" , getPacketType(radiorxbuffer)); ESP_LOGI(TAG, "t=%02X" , getPacketType(radiorxbuffer));
break; break;
} }
} else if (blockStartTimer == 0) { } else if (blockStartTimer == 0) {
@@ -763,8 +775,8 @@ void app_main(void) {
} }
} }
memset(&lastTagReturn, 0, 8); memset(&lastTagReturn, 0, 8);
for (uint8_t cCount = 0; cCount < MAX_PENDING_MACS; cCount++) { for (uint8_t cCount = 0; cCount < MAX_PENDING_MACS; cCount++) {
if (pendingDataArr[cCount].attemptsLeft == 1) { if (pendingDataArr[cCount].attemptsLeft == 1) {
if (pendingDataArr[cCount].availdatainfo.dataType != DATATYPE_NOUPDATE) { if (pendingDataArr[cCount].availdatainfo.dataType != DATATYPE_NOUPDATE) {
espNotifyTimeOut(pendingDataArr[cCount].targetMac); espNotifyTimeOut(pendingDataArr[cCount].targetMac);

View File

@@ -14,6 +14,8 @@
#include "main.h" #include "main.h"
#include "proto.h" #include "proto.h"
#include "sdkconfig.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/lp_uart_reg.h"
#include "soc/uart_struct.h" #include "soc/uart_struct.h"
#include "utils.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) { 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 // this will trigger a "IEEE802154 MAC sleep init failed" when called a second time, but it works
esp_ieee802154_enable(); esp_ieee802154_enable();
esp_ieee802154_set_channel(ch); esp_ieee802154_set_channel(ch);
// esp_ieee802154_set_txpower(int8_t power); // esp_ieee802154_set_txpower(int8_t power);
esp_ieee802154_set_panid(PROTO_PAN_ID); esp_ieee802154_set_panid(PROTO_PAN_ID);
esp_ieee802154_set_promiscuous(false); esp_ieee802154_set_promiscuous(false);
esp_ieee802154_set_coordinator(false); esp_ieee802154_set_coordinator(false);
esp_ieee802154_set_pending_mode(ESP_IEEE802154_AUTO_PENDING_ZIGBEE); esp_ieee802154_set_pending_mode(ESP_IEEE802154_AUTO_PENDING_ZIGBEE);
// esp_ieee802154_set_extended_address needs the MAC in reversed byte order // esp_ieee802154_set_extended_address needs the MAC in reversed byte order
esp_read_mac(mSelfMac, ESP_MAC_IEEE802154); esp_read_mac(mSelfMac, ESP_MAC_IEEE802154);
uint8_t eui64_rev[8] = {0}; uint8_t eui64_rev[8] = {0};
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
eui64_rev[7 - i] = mSelfMac[i]; eui64_rev[7 - i] = mSelfMac[i];
} }
esp_ieee802154_set_extended_address(eui64_rev); esp_ieee802154_set_extended_address(eui64_rev);
esp_ieee802154_get_extended_address(mSelfMac); 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_set_rx_when_idle(true);
esp_ieee802154_receive(); esp_ieee802154_receive();
led_flash(1); led_flash(1);
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(0); led_flash(0);
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(1); led_flash(1);
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(0); 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_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(), esp_ieee802154_get_panid(), esp_ieee802154_get_channel(),
mSelfMac[0], mSelfMac[1], mSelfMac[2], mSelfMac[3], mSelfMac[0], mSelfMac[1], mSelfMac[2], mSelfMac[3],
mSelfMac[4], mSelfMac[5], mSelfMac[6], mSelfMac[7], mSelfMac[4], mSelfMac[5], mSelfMac[6], mSelfMac[7],
esp_ieee802154_get_short_address()); esp_ieee802154_get_short_address());
} }
uint32_t lastZbTx = 0; // uint32_t lastZbTx = 0;
bool radioTx(uint8_t *packet) { bool radioTx(uint8_t *packet) {
static uint8_t txPKT[130]; static uint8_t txPKT[130];
while (isInTransmit) {
}
while (getMillis() - lastZbTx < 6) {
}
led_flash(1); led_flash(1);
memcpy(txPKT, packet, packet[0]); while (isInTransmit) {
isInTransmit = 1; }
lastZbTx = getMillis(); // while (getMillis() - lastZbTx < 6) {
esp_ieee802154_transmit(txPKT, false); // }
return true; // lastZbTx = getMillis();
memcpy(txPKT, packet, packet[0]);
isInTransmit = 1;
esp_ieee802154_transmit(txPKT, false);
return true;
} }
void radioSetChannel(uint8_t ch) { void radioSetChannel(uint8_t ch) {
radio_init(ch); radio_init(ch);
} }
void radioSetTxPower(uint8_t power) {} void radioSetTxPower(uint8_t power) {}

View File

@@ -20,6 +20,7 @@
#include "sdkconfig.h" #include "sdkconfig.h"
#include "soc/uart_struct.h" #include "soc/uart_struct.h"
#include "soc/lp_uart_reg.h" #include "soc/lp_uart_reg.h"
#include "second_uart.h"
static const char *TAG = "SECOND_UART"; static const char *TAG = "SECOND_UART";
@@ -32,9 +33,6 @@ volatile int curr_buff_pos = 0;
volatile int worked_buff_pos = 0; volatile int worked_buff_pos = 0;
volatile uint8_t buff_pos[MAX_BUFF_POS + 5]; 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); static void uart_event_task(void *pvParameters);
void init_second_uart() { void init_second_uart() {
uart_config_t uart_config = { 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_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_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); xTaskCreate(uart_event_task, "uart_event_task", 16384, NULL, 12, NULL);
} }

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <inttypes.h>
void init_second_uart(); void init_second_uart();
void uart_switch_speed(int baudrate); void uart_switch_speed(int baudrate);
@@ -9,3 +11,15 @@ bool getRxCharSecond(uint8_t *newChar);
void uart_printf(const char *format, ...); void uart_printf(const char *format, ...);
#define pr uart_printf #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", "stdexcept": "cpp",
"streambuf": "cpp", "streambuf": "cpp",
"cinttypes": "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 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 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 bool isRequestHandlerTrivial() override final {return false;}
virtual String listFilesRecursively(String path, bool recursive = false);
}; };
#endif #endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@
class tagRecord { class tagRecord {
public: 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]; uint8_t mac[8];
String alias; String alias;
@@ -49,6 +49,7 @@ class tagRecord {
uint8_t lut; uint8_t lut;
uint16_t tagSoftwareVersion; uint16_t tagSoftwareVersion;
uint8_t currentChannel; uint8_t currentChannel;
uint8_t invert;
uint8_t dataType; uint8_t dataType;
String filename; String filename;
@@ -62,6 +63,7 @@ struct Config {
uint8_t channel; uint8_t channel;
char alias[32]; char alias[32];
int16_t led; int16_t led;
uint8_t tft;
uint8_t language; uint8_t language;
uint8_t maxsleep; uint8_t maxsleep;
uint8_t stopsleep; uint8_t stopsleep;
@@ -71,6 +73,8 @@ struct Config {
char timeZone[52]; char timeZone[52];
uint8_t sleepTime1; uint8_t sleepTime1;
uint8_t sleepTime2; uint8_t sleepTime2;
String repo;
String env;
}; };
struct HwType { struct HwType {
@@ -90,7 +94,6 @@ extern Config config;
extern std::vector<tagRecord*> tagDB; extern std::vector<tagRecord*> tagDB;
extern std::unordered_map<int, HwType> hwtype; extern std::unordered_map<int, HwType> hwtype;
extern std::unordered_map<std::string, varStruct> varDB; 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 String tagDBtoJson(const uint8_t mac[8] = nullptr, uint8_t startPos = 0);
extern bool deleteRecord(const uint8_t mac[8]); extern bool deleteRecord(const uint8_t mac[8]);
extern void fillNode(JsonObject& tag, const tagRecord* taginfo); extern void fillNode(JsonObject& tag, const tagRecord* taginfo);
@@ -105,7 +108,14 @@ extern void clearPending(tagRecord* taginfo);
extern void initAPconfig(); extern void initAPconfig();
extern void saveAPconfig(); extern void saveAPconfig();
extern HwType getHwType(const uint8_t id); 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(); extern void cleanupCurrent();
#pragma pack(pop) #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 <ArduinoJson.h>
#include <HTTPClient.h> #include <HTTPClient.h>
#include "system.h"
#include "web.h" #include "web.h"
/// @brief Different utility functions
namespace util { namespace util {
/// @brief Can be used to wrap a stream and see what's going on /// @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) 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; HTTPClient http;
logLine("http httpGetJson " + url);
http.begin(url); http.begin(url);
http.setTimeout(timeout); http.setTimeout(timeout);
// http.setFollowRedirects(redirects); // http.setFollowRedirects(redirects);
@@ -100,7 +103,7 @@ static bool httpGetJson(String &url, JsonDocument &json, const uint16_t timeout,
/// ///
/// @param str String to check /// @param str String to check
/// @return True if empty or null, false if not /// @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"; 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 { class Timer {
public: 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) { /// @brief Change the interval
interval_ = interval; /// @param interval New interval in ms
void setInterval(const unsigned long interval) {
m_interval = interval;
} }
bool doRun() { /// @brief Check if interval is met
unsigned long currentMillis = millis(); /// @param currentMillis Optionally provide the current time in millis
if (currentMillis - previousMillis_ >= interval_) { /// @return True if interval is met, false if not
previousMillis_ = currentMillis; bool doRun(const unsigned long currentMillis = millis()) {
if (currentMillis >= m_nextMillis) {
m_nextMillis = currentMillis + m_interval;
return true; return true;
} }
return false; return false;
} }
private: private:
unsigned long interval_; /// @brief Timer interval in ms
unsigned long previousMillis_; 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 } // 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 init_web();
void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void doJsonUpload(AsyncWebServerRequest *request); void doJsonUpload(AsyncWebServerRequest *request);
void wsLog(String text); void wsLog(const String &text);
void wsErr(String text); void wsErr(const String &text);
void wsSendTaginfo(const uint8_t *mac, uint8_t syncMode); void wsSendTaginfo(const uint8_t *mac, uint8_t syncMode);
void wsSendSysteminfo(); void wsSendSysteminfo();
void wsSendAPitem(struct APlist *apitem); void wsSendAPitem(struct APlist *apitem);
void wsSerial(String text); void wsSerial(const String &text);
uint8_t wsClientCount(); uint8_t wsClientCount();
extern AsyncWebSocket ws; extern AsyncWebSocket ws;

View File

@@ -26,7 +26,10 @@ board_build.filesystem = littlefs
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
monitor_speed = 115200 monitor_speed = 115200
board_build.f_cpu = 240000000L board_build.f_cpu = 240000000L
build_unflags =
-std=gnu++11
build_flags = build_flags =
-std=gnu++17
-D BUILD_ENV_NAME=$PIOENV -D BUILD_ENV_NAME=$PIOENV
-D BUILD_TIME=$UNIX_TIME -D BUILD_TIME=$UNIX_TIME
-D USER_SETUP_LOADED -D USER_SETUP_LOADED
@@ -42,8 +45,10 @@ platform = https://github.com/platformio/platform-espressif32.git
board=lolin_s2_mini board=lolin_s2_mini
board_build.partitions = default.csv board_build.partitions = default.csv
build_unflags = build_unflags =
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y -std=gnu++11
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags = build_flags =
-std=gnu++17
${env.build_flags} ${env.build_flags}
-D OPENEPAPERLINK_MINI_AP_PCB -D OPENEPAPERLINK_MINI_AP_PCB
-D ARDUINO_USB_MODE=0 -D ARDUINO_USB_MODE=0
@@ -64,7 +69,7 @@ build_flags =
-D FLASHER_LED=15 -D FLASHER_LED=15
-D FLASHER_RGB_LED=33 -D FLASHER_RGB_LED=33
build_src_filter = build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp> +<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.psram_type=qspi_opi board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304 board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680 board_upload.maximum_ram_size = 327680
@@ -79,8 +84,10 @@ platform = https://github.com/platformio/platform-espressif32.git
board=lolin_s2_mini board=lolin_s2_mini
board_build.partitions = default.csv board_build.partitions = default.csv
build_unflags = build_unflags =
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y -std=gnu++11
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags = build_flags =
-std=gnu++17
${env.build_flags} ${env.build_flags}
-D OPENEPAPERLINK_NANO_AP_PCB -D OPENEPAPERLINK_NANO_AP_PCB
-D ARDUINO_USB_MODE=0 -D ARDUINO_USB_MODE=0
@@ -99,7 +106,7 @@ build_flags =
-D FLASHER_LED=15 -D FLASHER_LED=15
-D FLASHER_RGB_LED=-1 -D FLASHER_RGB_LED=-1
build_src_filter = build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp> +<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.psram_type=qspi_opi board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304 board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680 board_upload.maximum_ram_size = 327680
@@ -114,9 +121,11 @@ platform = https://github.com/platformio/platform-espressif32.git
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1
board_build.partitions = default_16MB.csv board_build.partitions = default_16MB.csv
build_unflags = build_unflags =
-D ARDUINO_USB_MODE=1 -std=gnu++11
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y -D ARDUINO_USB_MODE=1
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags = build_flags =
-std=gnu++17
${env.build_flags} ${env.build_flags}
-D OPENEPAPERLINK_PCB -D OPENEPAPERLINK_PCB
-D ARDUINO_USB_MODE=0 -D ARDUINO_USB_MODE=0
@@ -158,7 +167,7 @@ build_flags =
-D FLASHER_LED=21 -D FLASHER_LED=21
-D FLASHER_RGB_LED=48 -D FLASHER_RGB_LED=48
build_src_filter = build_src_filter =
+<*>-<espflasher.cpp> +<*>-<espflasher.cpp>
board_build.flash_mode=qio board_build.flash_mode=qio
board_build.arduino.memory_type = qio_opi board_build.arduino.memory_type = qio_opi
board_build.psram_type=qspi_opi board_build.psram_type=qspi_opi
@@ -173,7 +182,10 @@ board_upload.flash_size = 16MB
[env:Simple_AP] [env:Simple_AP]
board = esp32dev board = esp32dev
board_build.partitions = default.csv board_build.partitions = default.csv
build_unflags =
-std=gnu++11
build_flags = build_flags =
-std=gnu++17
${env.build_flags} ${env.build_flags}
-D CORE_DEBUG_LEVEL=0 -D CORE_DEBUG_LEVEL=0
-D SIMPLE_AP -D SIMPLE_AP
@@ -188,7 +200,7 @@ build_flags =
-D FLASHER_AP_RXD=16 -D FLASHER_AP_RXD=16
-D FLASHER_LED=22 -D FLASHER_LED=22
build_src_filter = build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp> +<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
; ---------------------------------------------------------------------------------------- ; ----------------------------------------------------------------------------------------
; !!! this configuration expects an wemos_d1_mini32 ; !!! this configuration expects an wemos_d1_mini32
@@ -197,7 +209,10 @@ build_src_filter =
[env:Wemos_d1_mini32_AP] [env:Wemos_d1_mini32_AP]
board = wemos_d1_mini32 board = wemos_d1_mini32
board_build.partitions = default.csv board_build.partitions = default.csv
build_unflags =
-std=gnu++11
build_flags = build_flags =
-std=gnu++17
${env.build_flags} ${env.build_flags}
-D CORE_DEBUG_LEVEL=0 -D CORE_DEBUG_LEVEL=0
@@ -214,7 +229,7 @@ build_flags =
-D FLASHER_AP_RXD=17 -D FLASHER_AP_RXD=17
-D FLASHER_LED=22 -D FLASHER_LED=22
build_src_filter = build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp> +<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
; ---------------------------------------------------------------------------------------- ; ----------------------------------------------------------------------------------------
; !!! this configuration expects an m5stack esp32 ; !!! this configuration expects an m5stack esp32
@@ -224,7 +239,10 @@ build_src_filter =
platform = espressif32 platform = espressif32
board = m5stack-core-esp32 board = m5stack-core-esp32
board_build.partitions = esp32_sdcard.csv board_build.partitions = esp32_sdcard.csv
build_unflags =
-std=gnu++11
build_flags = build_flags =
-std=gnu++17
${env.build_flags} ${env.build_flags}
-D CORE_DEBUG_LEVEL=0 -D CORE_DEBUG_LEVEL=0
@@ -251,7 +269,7 @@ build_flags =
-D ILI9341_DRIVER -D ILI9341_DRIVER
-D SMOOTH_FONT -D SMOOTH_FONT
build_src_filter = 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 ; !!! this configuration expects an ESP32-S3 16MB Flash 8MB RAM
; ;
@@ -260,12 +278,14 @@ build_src_filter =
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1
board_build.partitions = large_spiffs_16MB.csv board_build.partitions = large_spiffs_16MB.csv
build_unflags = build_unflags =
-std=gnu++11
-D ARDUINO_USB_MODE=1 -D ARDUINO_USB_MODE=1
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y -D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
-D ILI9341_DRIVER -D ILI9341_DRIVER
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
build_flags = build_flags =
-std=gnu++17
${env.build_flags} ${env.build_flags}
-D YELLOW_IPS_AP -D YELLOW_IPS_AP
-D CORE_DEBUG_LEVEL=0 -D CORE_DEBUG_LEVEL=0
@@ -307,7 +327,7 @@ build_flags =
-D SERIAL_FLASHER_BOOT_HOLD_TIME_MS=50 -D SERIAL_FLASHER_BOOT_HOLD_TIME_MS=50
-D SERIAL_FLASHER_RESET_HOLD_TIME_MS=100 -D SERIAL_FLASHER_RESET_HOLD_TIME_MS=100
build_src_filter = build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp> +<*>-<usbflasher.cpp>-<swd.cpp>
board_build.flash_mode=qio board_build.flash_mode=qio
board_build.arduino.memory_type = qio_opi board_build.arduino.memory_type = qio_opi
board_build.psram_type=qspi_opi board_build.psram_type=qspi_opi
@@ -321,7 +341,10 @@ board_upload.flash_size = 16MB
[env:Sonoff_zb_bridge_P_AP] [env:Sonoff_zb_bridge_P_AP]
board = esp32dev board = esp32dev
board_build.partitions = default.csv board_build.partitions = default.csv
build_unflags =
-std=gnu++11
build_flags = build_flags =
-std=gnu++17
${env.build_flags} ${env.build_flags}
-D CORE_DEBUG_LEVEL=0 -D CORE_DEBUG_LEVEL=0
@@ -340,7 +363,7 @@ build_flags =
-D FLASHER_AP_RXD=23 -D FLASHER_AP_RXD=23
-D FLASHER_LED=2 -D FLASHER_LED=2
build_src_filter = build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp> +<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.psram_type=qspi_opi board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304 board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680 board_upload.maximum_ram_size = 327680
@@ -354,8 +377,10 @@ platform = https://github.com/platformio/platform-espressif32.git
board=lolin_s2_mini board=lolin_s2_mini
board_build.partitions = default.csv board_build.partitions = default.csv
build_unflags = build_unflags =
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y -std=gnu++11
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags = build_flags =
-std=gnu++17
${env.build_flags} ${env.build_flags}
-D OPENEPAPERLINK_MINI_AP_PCB -D OPENEPAPERLINK_MINI_AP_PCB
-D ARDUINO_USB_MODE=0 -D ARDUINO_USB_MODE=0
@@ -375,7 +400,7 @@ build_flags =
-D FLASHER_LED=2 -D FLASHER_LED=2
-D FLASHER_RGB_LED=-1 -D FLASHER_RGB_LED=-1
build_src_filter = build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp> +<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.psram_type=qspi_opi board_build.psram_type=qspi_opi
board_upload.maximum_size = 4194304 board_upload.maximum_size = 4194304
board_upload.maximum_ram_size = 327680 board_upload.maximum_ram_size = 327680
@@ -389,9 +414,11 @@ board_upload.flash_size = 4MB
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1
board_build.partitions = 32MB_partition table.csv board_build.partitions = 32MB_partition table.csv
build_unflags = build_unflags =
-D ARDUINO_USB_MODE=1 -std=gnu++11
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y -D ARDUINO_USB_MODE=1
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
build_flags = build_flags =
-std=gnu++17
${env.build_flags} ${env.build_flags}
-D OutdoorAP -D OutdoorAP
-D HAS_RGB_LED -D HAS_RGB_LED
@@ -414,7 +441,7 @@ build_flags =
-D FLASHER_LED=21 -D FLASHER_LED=21
-D FLASHER_RGB_LED=38 -D FLASHER_RGB_LED=38
build_src_filter = build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp> +<*>-<usbflasher.cpp>-<swd.cpp>-<espflasher.cpp>
board_build.flash_mode=opi board_build.flash_mode=opi
board_build.arduino.memory_type = opi_opi board_build.arduino.memory_type = opi_opi
board_build.psram_type=qspi_opi board_build.psram_type=qspi_opi
@@ -422,3 +449,53 @@ board_upload.maximum_size = 16777216
board_upload.maximum_ram_size = 327680 board_upload.maximum_ram_size = 327680
board_upload.flash_size = 32MB board_upload.flash_size = 32MB
#upload_flags = --no-stub #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; 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) { void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request) {
if (_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str())) { if (_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str())) {
return request->requestAuthentication(); return request->requestAuthentication();
@@ -51,23 +82,7 @@ void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request) {
if (request->method() == HTTP_GET) { if (request->method() == HTTP_GET) {
if (request->hasParam("list")) { if (request->hasParam("list")) {
const String path = request->getParam("list")->value(); const String path = request->getParam("list")->value();
String output = "[" + listFilesRecursively(path, request->hasParam("recursive")).substring(1) + "]";
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 += "]";
request->send(200, "application/json", output); request->send(200, "application/json", output);
} else if (request->hasParam("edit") || request->hasParam("download")) { } else if (request->hasParam("edit") || request->hasParam("download")) {
request->send(request->_tempFile, request->_tempFile.name(), String(), request->hasParam("download")); request->send(request->_tempFile, request->_tempFile.name(), String(), request->hasParam("download"));

View File

@@ -28,6 +28,7 @@
#endif #endif
#include "language.h" #include "language.h"
#include "settings.h" #include "settings.h"
#include "system.h"
#include "tag_db.h" #include "tag_db.h"
#include "truetype.h" #include "truetype.h"
#include "util.h" #include "util.h"
@@ -177,12 +178,6 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
} }
#endif #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); DynamicJsonDocument doc(500);
deserializeJson(doc, taginfo->modeConfigJson); deserializeJson(doc, taginfo->modeConfigJson);
JsonObject cfgobj = doc.as<JsonObject>(); 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; taginfo->nextupdate = now + 60;
imgParam imageParams; imgParam imageParams;
imageParams.width = hwdata.width; imageParams.width = hwdata.width;
imageParams.height = hwdata.height; imageParams.height = hwdata.height;
imageParams.bpp = hwdata.bpp; imageParams.bpp = hwdata.bpp;
@@ -203,7 +197,7 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
imageParams.dither = false; imageParams.dither = false;
if (taginfo->hasCustomLUT && taginfo->lut != 1) imageParams.grayLut = true; if (taginfo->hasCustomLUT && taginfo->lut != 1) imageParams.grayLut = true;
imageParams.invert = false; imageParams.invert = taginfo->invert;
imageParams.symbols = 0; imageParams.symbols = 0;
imageParams.rotate = taginfo->rotate; imageParams.rotate = taginfo->rotate;
@@ -230,12 +224,20 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
} }
if (contentFS->exists(configFilename)) { if (contentFS->exists(configFilename)) {
imageParams.dither = cfgobj["dither"] && cfgobj["dither"] == "1"; 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); jpg2buffer(configFilename, filename, imageParams);
} else { } else {
filename = "/current/" + String(hexmac) + ".raw"; filename = "/current/" + String(hexmac) + ".pending";
if (!contentFS->exists(filename)) {
filename = "/current/" + String(hexmac) + ".raw";
}
if (contentFS->exists(filename)) { if (contentFS->exists(filename)) {
prepareDataAvail(filename, imageParams.dataType, imageParams.lut, mac, cfgobj["timetolive"].as<int>(), true); 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 { } else {
wsErr("File " + configFilename + " not found"); 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; imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
if (imageParams.lut = EPD_LUT_NO_REPEATS && imageParams.shortlut == SHORTLUT_ONLY_BLACK) imageParams.lut = EPD_LUT_DEFAULT; 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") { if (cfgobj["delete"].as<String>() == "1") {
contentFS->remove("/" + configFilename); contentFS->remove("/" + configFilename);
} }
@@ -260,12 +273,12 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
case 1: // Today case 1: // Today
drawDate(filename, taginfo, imageParams); drawDate(filename, taginfo, imageParams);
taginfo->nextupdate = midnight; taginfo->nextupdate = util::getMidnightTime();
updateTagImage(filename, mac, (midnight - now) / 60 - 10, taginfo, imageParams); updateTagImage(filename, mac, (taginfo->nextupdate - now) / 60 - 10, taginfo, imageParams);
break; break;
case 2: // CountDays case 2: // CountDays
drawCounter(mac, buttonPressed, taginfo, cfgobj, filename, imageParams, midnight, 15); drawCounter(mac, buttonPressed, taginfo, cfgobj, filename, imageParams, util::getMidnightTime(), 15);
break; break;
case 3: // CountHours 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) { bool updateTagImage(String &filename, const uint8_t *dst, uint16_t nextCheckin, tagRecord *&taginfo, imgParam &imageParams) {
if (taginfo->hwType == SOLUM_SEG_UK) { 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 { } else {
if (imageParams.hasRed) { if (imageParams.hasRed) {
imageParams.dataType = DATATYPE_IMG_RAW_2BPP; imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
@@ -489,11 +502,19 @@ void replaceVariables(String &format) {
size_t startIndex = 0; size_t startIndex = 0;
size_t openBraceIndex, closeBraceIndex; 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 && while ((openBraceIndex = format.indexOf('{', startIndex)) != -1 &&
(closeBraceIndex = format.indexOf('}', openBraceIndex + 1)) != -1) { (closeBraceIndex = format.indexOf('}', openBraceIndex + 1)) != -1) {
const std::string variableName = format.substring(openBraceIndex + 1, closeBraceIndex).c_str(); const std::string variableName = format.substring(openBraceIndex + 1, closeBraceIndex).c_str();
const std::string varKey = "{" + variableName + "}"; const std::string varKey = "{" + variableName + "}";
auto var = varDB.find(variableName); const auto var = varDB.find(variableName);
if (var != varDB.end()) { if (var != varDB.end()) {
format.replace(varKey.c_str(), var->second.value); 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", "\uf01b", "", "\uf01b", "", "\uf01b", "", "\uf076", "", "", "\uf01a",
"\uf01a", "\uf01a", "", "", "\uf064", "\uf064", "", "", "", "", "\uf01a", "\uf01a", "", "", "\uf064", "\uf064", "", "", "", "",
"", "", "", "", "\uf01e", "\uf01d", "", "", "\uf01e"}; "", "", "", "", "\uf01e", "\uf01d", "", "", "\uf01e"};
if (isNight && id <= 3) { if (isNight && id <= 2) {
const String nightIcons[] = {"\uf02e", "\uf083", "\uf086"}; const String nightIcons[] = {"\uf02e", "\uf083", "\uf086"};
return nightIcons[id]; return nightIcons[id];
} }
@@ -851,6 +872,7 @@ int getImgURL(String &filename, String URL, time_t fetched, imgParam &imageParam
Storage.begin(); Storage.begin();
HTTPClient http; HTTPClient http;
logLine("http getImgURL " + URL);
http.begin(URL); http.begin(URL);
http.addHeader("If-Modified-Since", formatHttpDate(fetched)); http.addHeader("If-Modified-Since", formatHttpDate(fetched));
http.addHeader("X-ESL-MAC", MAC); 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); strftime(dateString, sizeof(dateString), "%d.%m.%Y", &timeinfo);
HTTPClient http; HTTPClient http;
logLine("http getCalFeed " + URL);
http.begin(URL); http.begin(URL);
http.setTimeout(10000); http.setTimeout(10000);
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
@@ -1058,6 +1081,7 @@ uint8_t drawBuienradar(String &filename, JsonObject &cfgobj, tagRecord *&taginfo
String lat = cfgobj["#lat"]; String lat = cfgobj["#lat"];
String lon = cfgobj["#lon"]; String lon = cfgobj["#lon"];
logLine("http drawBuienradar");
http.begin("https://gps.buienradar.nl/getrr.php?lat=" + lat + "&lon=" + lon); http.begin("https://gps.buienradar.nl/getrr.php?lat=" + lat + "&lon=" + lon);
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.setTimeout(5000); 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) { int getJsonTemplateUrl(String &filename, String URL, time_t fetched, String MAC, tagRecord *&taginfo, imgParam &imageParams) {
HTTPClient http; HTTPClient http;
http.useHTTP10(true); http.useHTTP10(true);
logLine("http getJsonTemplateUrl " + URL);
http.begin(URL); http.begin(URL);
http.addHeader("If-Modified-Since", formatHttpDate(fetched)); http.addHeader("If-Modified-Since", formatHttpDate(fetched));
http.addHeader("X-ESL-MAC", MAC); 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) { void drawJsonStream(Stream &stream, String &filename, tagRecord *&taginfo, imgParam &imageParams) {
TFT_eSprite spr = TFT_eSprite(&tft); TFT_eSprite spr = TFT_eSprite(&tft);
initSprite(spr, imageParams.width, imageParams.height, imageParams); initSprite(spr, imageParams.width, imageParams.height, imageParams);
DynamicJsonDocument doc(300); DynamicJsonDocument doc(500);
if (stream.find("[")) { if (stream.find("[")) {
do { do {
DeserializationError error = deserializeJson(doc, stream); DeserializationError error = deserializeJson(doc, stream);

View File

@@ -7,6 +7,7 @@
#include "esp32_port.h" #include "esp32_port.h"
#include "esp_littlefs.h" #include "esp_littlefs.h"
#include "storage.h" #include "storage.h"
#include "tag_db.h"
#include "web.h" #include "web.h"
esp_loader_error_t connect_to_target(uint32_t higher_transmission_rate) { 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(); int binaryResponseCode = binaryHttp.GET();
Serial.println(binaryResponseCode); Serial.println(binaryResponseCode);
if (binaryResponseCode == HTTP_CODE_OK) { if (binaryResponseCode == HTTP_CODE_OK) {
int contentLength = binaryHttp.getSize();
Serial.println(contentLength);
xSemaphoreTake(fsMutex, portMAX_DELAY); xSemaphoreTake(fsMutex, portMAX_DELAY);
File file = contentFS->open(filename, "wb"); File file = contentFS->open(filename, "wb");
if (file) { if (file) {
wsSerial("downloading " + String(filename)); wsSerial("downloading " + String(filename));
WiFiClient *stream = binaryHttp.getStreamPtr(); WiFiClient *stream = binaryHttp.getStreamPtr();
uint8_t buffer[256]; uint8_t buffer[1024];
size_t totalBytesRead = 0; 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)); size_t bytesRead = stream->readBytes(buffer, sizeof(buffer));
file.write(buffer, bytesRead); file.write(buffer, bytesRead);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
vTaskDelay(1 / portTICK_PERIOD_MS); if (totalBytesRead == contentLength) break;
} }
file.close(); file.close();
xSemaphoreGive(fsMutex); xSemaphoreGive(fsMutex);
@@ -144,7 +149,7 @@ bool downloadAndWriteBinary(String &filename, const char *url) {
file = contentFS->open(filename, "r"); file = contentFS->open(filename, "r");
if (file) { if (file) {
if (totalBytesRead == file.size() && file.size() > 0) { if (totalBytesRead == contentLength || (contentLength == 0 && file.size() > 0)) {
file.close(); file.close();
return true; return true;
} }
@@ -163,8 +168,7 @@ bool downloadAndWriteBinary(String &filename, const char *url) {
} }
bool doC6flash(uint8_t doDownload) { 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; HTTPClient http;
Serial.println(githubUrl); Serial.println(githubUrl);
http.begin(githubUrl); http.begin(githubUrl);
@@ -181,7 +185,8 @@ bool doC6flash(uint8_t doDownload) {
JsonArray jsonArray = jsonDoc.as<JsonArray>(); JsonArray jsonArray = jsonDoc.as<JsonArray>();
for (JsonObject obj : jsonArray) { for (JsonObject obj : jsonArray) {
String filename = "/" + obj["filename"].as<String>(); 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++) { for (int retry = 0; retry < 10; retry++) {
if (downloadAndWriteBinary(filename, binaryUrl.c_str())) { if (downloadAndWriteBinary(filename, binaryUrl.c_str())) {
break; break;

View File

@@ -176,7 +176,6 @@ bool flasher::getInfoBlockType() {
bool flasher::findTagByMD5() { bool flasher::findTagByMD5() {
DynamicJsonDocument doc(3000); DynamicJsonDocument doc(3000);
DynamicJsonDocument APconfig(600);
fs::File readfile = contentFS->open("/tag_md5_db.json", "r"); fs::File readfile = contentFS->open("/tag_md5_db.json", "r");
DeserializationError err = deserializeJson(doc, readfile); DeserializationError err = deserializeJson(doc, readfile);
if (!err) { if (!err) {
@@ -206,7 +205,6 @@ bool flasher::findTagByMD5() {
bool flasher::findTagByType(uint8_t type) { bool flasher::findTagByType(uint8_t type) {
DynamicJsonDocument doc(3000); DynamicJsonDocument doc(3000);
DynamicJsonDocument APconfig(600);
fs::File readfile = contentFS->open("/tag_md5_db.json", "r"); fs::File readfile = contentFS->open("/tag_md5_db.json", "r");
DeserializationError err = deserializeJson(doc, readfile); DeserializationError err = deserializeJson(doc, readfile);
if (!err) { if (!err) {
@@ -445,8 +443,7 @@ bool flasher::writeFlashFromPackOffset(fs::File *file, uint16_t length) {
} }
bool flasher::writeFlashFromPack(String filename, uint8_t type) { bool flasher::writeFlashFromPack(String filename, uint8_t type) {
StaticJsonDocument<512> doc; DynamicJsonDocument doc(1024);
DynamicJsonDocument APconfig(512);
fs::File readfile = contentFS->open(filename, "r"); fs::File readfile = contentFS->open(filename, "r");
DeserializationError err = deserializeJson(doc, readfile); DeserializationError err = deserializeJson(doc, readfile);
if (!err) { 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"); Serial.print("Failed to find this tag's type in the FW pack database.\n");
} else { } else {
Serial.println(err.c_str());
Serial.print("Failed to read json header from FW pack\n"); Serial.print("Failed to read json header from FW pack\n");
} }
readfile.close(); 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) { uint16_t getAPUpdateVersion(uint8_t type) {
StaticJsonDocument<512> doc; StaticJsonDocument<512> doc;
DynamicJsonDocument APconfig(512);
fs::File readfile = contentFS->open("/AP_FW_Pack.bin", "r"); fs::File readfile = contentFS->open("/AP_FW_Pack.bin", "r");
DeserializationError err = deserializeJson(doc, readfile); DeserializationError err = deserializeJson(doc, readfile);
if (!err) { if (!err) {

View File

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

View File

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

View File

@@ -10,6 +10,7 @@
#include "storage.h" #include "storage.h"
#include "system.h" #include "system.h"
#include "tag_db.h" #include "tag_db.h"
#include "tagdata.h"
#include "wifimanager.h" #include "wifimanager.h"
#ifdef HAS_USB #ifdef HAS_USB
@@ -22,10 +23,15 @@
#include "util.h" #include "util.h"
#include "web.h" #include "web.h"
util::Timer intervalSysinfo(3000); util::Timer intervalContentRunner(seconds(1));
util::Timer intervalVars(10000); util::Timer intervalSysinfo(seconds(3));
util::Timer intervalSaveDB(300000); util::Timer intervalVars(seconds(10));
util::Timer intervalContentRunner(1000); 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); SET_LOOP_TASK_STACK_SIZE(16 * 1024);
@@ -52,11 +58,6 @@ void setup() {
xTaskCreate(ledTask, "ledhandler", 2000, NULL, 2, NULL); xTaskCreate(ledTask, "ledhandler", 2000, NULL, 2, NULL);
vTaskDelay(10 / portTICK_PERIOD_MS); 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) #if defined(OPENEPAPERLINK_MINI_AP_PCB) || defined(OPENEPAPERLINK_NANO_AP_PCB)
APEnterEarlyReset(); 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 // 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(); initAPconfig();
xTaskCreate(initTime, "init time", 5000, NULL, 2, NULL);
updateLanguageFromConfig(); updateLanguageFromConfig();
updateBrightnessFromConfig(); updateBrightnessFromConfig();
@@ -126,6 +128,7 @@ void setup() {
#ifdef HAS_RGB_LED #ifdef HAS_RGB_LED
rgbIdle(); rgbIdle();
#endif #endif
TagData::loadParsers("/parsers.json");
loadDB("/current/tagDB.json"); loadDB("/current/tagDB.json");
cleanupCurrent(); cleanupCurrent();
xTaskCreate(APTask, "AP Process", 6000, NULL, 2, NULL); xTaskCreate(APTask, "AP Process", 6000, NULL, 2, NULL);
@@ -138,7 +141,6 @@ void setup() {
config.runStatus = RUNSTATUS_PAUSE; config.runStatus = RUNSTATUS_PAUSE;
} }
xTaskCreate(initTime, "init time", 5000, NULL, 2, NULL);
xTaskCreate(delayedStart, "delaystart", 2000, NULL, 2, NULL); xTaskCreate(delayedStart, "delaystart", 2000, NULL, 2, NULL);
wsSendSysteminfo(); wsSendSysteminfo();
@@ -161,6 +163,21 @@ void loop() {
if (intervalContentRunner.doRun() && apInfo.state == AP_STATE_ONLINE) { if (intervalContentRunner.doRun() && apInfo.state == AP_STATE_ONLINE) {
contentRunner(); 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 #ifdef YELLOW_IPS_AP
extern void yellow_ap_display_loop(void); extern void yellow_ap_display_loop(void);
@@ -168,21 +185,17 @@ void loop() {
#endif #endif
#ifdef OPENEPAPERLINK_PCB #ifdef OPENEPAPERLINK_PCB
time_t tagConnectTimer = 0; if (tagConnectTimer.doRun() && extTagConnected()) {
if (millis() - tagConnectTimer > 1000) { flashCountDown(3);
tagConnectTimer = millis();
if (extTagConnected()) {
flashCountDown(3);
pinMode(FLASHER_EXT_TEST, OUTPUT); pinMode(FLASHER_EXT_TEST, OUTPUT);
digitalWrite(FLASHER_EXT_TEST, LOW); digitalWrite(FLASHER_EXT_TEST, LOW);
doTagFlash(); doTagFlash();
vTaskDelay(10000 / portTICK_PERIOD_MS); vTaskDelay(10000 / portTICK_PERIOD_MS);
pinMode(FLASHER_EXT_TEST, INPUT); pinMode(FLASHER_EXT_TEST, INPUT);
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
}
} }
#endif #endif

View File

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

View File

@@ -12,6 +12,7 @@
#include "storage.h" #include "storage.h"
#include "system.h" #include "system.h"
#include "tag_db.h" #include "tag_db.h"
#include "tagdata.h"
#include "udp.h" #include "udp.h"
#include "util.h" #include "util.h"
#include "web.h" #include "web.h"
@@ -238,6 +239,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
String imageUrl = "http://" + remoteIP.toString() + filename; String imageUrl = "http://" + remoteIP.toString() + filename;
wsLog("GET " + imageUrl); wsLog("GET " + imageUrl);
HTTPClient http; HTTPClient http;
logLine("http prepareExternalDataAvail " + imageUrl);
http.begin(imageUrl); http.begin(imageUrl);
int httpCode = http.GET(); int httpCode = http.GET();
if (httpCode == 200) { if (httpCode == 200) {
@@ -249,6 +251,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
} else if (httpCode == 404) { } else if (httpCode == 404) {
imageUrl = "http://" + remoteIP.toString() + "/current/" + String(hexmac) + ".raw"; imageUrl = "http://" + remoteIP.toString() + "/current/" + String(hexmac) + ".raw";
http.end(); http.end();
logLine("http prepareExternalDataAvail " + imageUrl);
http.begin(imageUrl); http.begin(imageUrl);
httpCode = http.GET(); httpCode = http.GET();
if (httpCode == 200) { if (httpCode == 200) {
@@ -295,6 +298,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
String dataUrl = "http://" + remoteIP.toString() + "/getdata?mac=" + String(hexmac); String dataUrl = "http://" + remoteIP.toString() + "/getdata?mac=" + String(hexmac);
wsLog("GET " + dataUrl); wsLog("GET " + dataUrl);
HTTPClient http; HTTPClient http;
logLine("http DATATYPE_CUSTOM_LUT_OTA " + dataUrl);
http.begin(dataUrl); http.begin(dataUrl);
int httpCode = http.GET(); int httpCode = http.GET();
if (httpCode == 200) { if (httpCode == 200) {
@@ -326,7 +330,9 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
} }
void processBlockRequest(struct espBlockRequest* br) { void processBlockRequest(struct espBlockRequest* br) {
if (config.runStatus == RUNSTATUS_STOP) return; if (config.runStatus == RUNSTATUS_STOP) {
return;
}
if (!checkCRC(br, sizeof(struct espBlockRequest))) { if (!checkCRC(br, sizeof(struct espBlockRequest))) {
Serial.print("Failed CRC on a blockrequest received by the AP"); Serial.print("Failed CRC on a blockrequest received by the AP");
return; return;
@@ -368,7 +374,9 @@ void processBlockRequest(struct espBlockRequest* br) {
} }
void processXferComplete(struct espXferComplete* xfc, bool local) { void processXferComplete(struct espXferComplete* xfc, bool local) {
if (config.runStatus == RUNSTATUS_STOP) return; if (config.runStatus == RUNSTATUS_STOP) {
return;
}
char buffer[64]; 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]); 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); wsLog((String)buffer);
@@ -412,7 +420,9 @@ void processXferComplete(struct espXferComplete* xfc, bool local) {
} }
void processXferTimeout(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]; 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]); 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); wsErr((String)buffer);
@@ -436,7 +446,9 @@ void processXferTimeout(struct espXferComplete* xfc, bool local) {
} }
void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP) { void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP) {
if (config.runStatus == RUNSTATUS_STOP) return; if (config.runStatus == RUNSTATUS_STOP) {
return;
}
char buffer[64]; char buffer[64];
tagRecord* taginfo = tagRecord::findByMAC(eadr->src); tagRecord* taginfo = tagRecord::findByMAC(eadr->src);
@@ -527,16 +539,17 @@ void processTagReturnData(struct espTagReturnData* trd, uint8_t len, bool local)
if (!checkCRC(trd, len)) { if (!checkCRC(trd, len)) {
return; 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! // 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]); 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); 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); wsLog((String)buffer);
uint8_t actualPayloadLength = trd->len - 11; TagData::parse(trd->src, trd->returnData.dataType, trd->returnData.data, payloadLength);
uint8_t* actualPayload = (uint8_t*)calloc(actualPayloadLength, 1);
memcpy(actualPayload, trd->returnData.data, actualPayloadLength);
} }
void refreshAllPending() { void refreshAllPending() {

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,9 @@
#include "storage.h" #include "storage.h"
#include "util.h" #include "util.h"
#define STR_IMPL(x) #x
#define STR(x) STR_IMPL(x)
std::vector<tagRecord*> tagDB; std::vector<tagRecord*> tagDB;
std::unordered_map<std::string, varStruct> varDB; std::unordered_map<std::string, varStruct> varDB;
std::unordered_map<int, HwType> hwdata = { std::unordered_map<int, HwType> hwdata = {
@@ -118,6 +121,7 @@ void fillNode(JsonObject& tag, const tagRecord* taginfo) {
tag["apip"] = taginfo->apIp.toString(); tag["apip"] = taginfo->apIp.toString();
tag["rotate"] = taginfo->rotate; tag["rotate"] = taginfo->rotate;
tag["lut"] = taginfo->lut; tag["lut"] = taginfo->lut;
tag["invert"] = taginfo->invert;
tag["ch"] = taginfo->currentChannel; tag["ch"] = taginfo->currentChannel;
tag["ver"] = taginfo->tagSoftwareVersion; tag["ver"] = taginfo->tagSoftwareVersion;
} }
@@ -213,6 +217,7 @@ void loadDB(const String& filename) {
taginfo->apIp.fromString(tag["apip"].as<String>()); taginfo->apIp.fromString(tag["apip"].as<String>());
taginfo->rotate = tag["rotate"] | 0; taginfo->rotate = tag["rotate"] | 0;
taginfo->lut = tag["lut"] | 0; taginfo->lut = tag["lut"] | 0;
taginfo->invert = tag["invert"] | 0;
taginfo->currentChannel = tag["ch"] | 0; taginfo->currentChannel = tag["ch"] | 0;
taginfo->tagSoftwareVersion = tag["ver"] | 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 // not initialised, timeout if not seen last 10 minutes
if (timeout > 600) timeoutcount++; if (timeout > 600) timeoutcount++;
} else if (now - taginfo->expectedNextCheckin > 600) { } 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++; if (timeout > 600) timeoutcount++;
} }
} }
@@ -300,6 +305,7 @@ void initAPconfig() {
config.channel = APconfig["channel"] | 0; config.channel = APconfig["channel"] | 0;
if (APconfig["alias"]) strlcpy(config.alias, APconfig["alias"], sizeof(config.alias)); if (APconfig["alias"]) strlcpy(config.alias, APconfig["alias"], sizeof(config.alias));
config.led = APconfig["led"] | 255; config.led = APconfig["led"] | 255;
config.tft = APconfig["tft"] | 255;
config.language = APconfig["language"] | getDefaultLanguage(); config.language = APconfig["language"] | getDefaultLanguage();
config.maxsleep = APconfig["maxsleep"] | 10; config.maxsleep = APconfig["maxsleep"] | 10;
config.stopsleep = APconfig["stopsleep"] | 1; config.stopsleep = APconfig["stopsleep"] | 1;
@@ -309,6 +315,8 @@ void initAPconfig() {
// default wifi power 8.5 dbM // default wifi power 8.5 dbM
// see https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiGeneric.h#L111 // see https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiGeneric.h#L111
config.wifiPower = APconfig["wifipower"] | 34; config.wifiPower = APconfig["wifipower"] | 34;
config.repo = APconfig["repo"] | "jjwbruijn/OpenEPaperLink";
config.env = APconfig["env"] | STR(BUILD_ENV_NAME);
if (APconfig["timezone"]) { if (APconfig["timezone"]) {
strlcpy(config.timeZone, APconfig["timezone"], sizeof(config.timeZone)); strlcpy(config.timeZone, APconfig["timezone"], sizeof(config.timeZone));
} else { } else {
@@ -323,6 +331,7 @@ void saveAPconfig() {
APconfig["channel"] = config.channel; APconfig["channel"] = config.channel;
APconfig["alias"] = config.alias; APconfig["alias"] = config.alias;
APconfig["led"] = config.led; APconfig["led"] = config.led;
APconfig["tft"] = config.tft;
APconfig["language"] = config.language; APconfig["language"] = config.language;
APconfig["maxsleep"] = config.maxsleep; APconfig["maxsleep"] = config.maxsleep;
APconfig["stopsleep"] = config.stopsleep; APconfig["stopsleep"] = config.stopsleep;
@@ -331,6 +340,8 @@ void saveAPconfig() {
APconfig["timezone"] = config.timeZone; APconfig["timezone"] = config.timeZone;
APconfig["sleeptime1"] = config.sleepTime1; APconfig["sleeptime1"] = config.sleepTime1;
APconfig["sleeptime2"] = config.sleepTime2; APconfig["sleeptime2"] = config.sleepTime2;
APconfig["repo"] = config.repo;
APconfig["env"] = config.env;
serializeJsonPretty(APconfig, configFile); serializeJsonPretty(APconfig, configFile);
configFile.close(); configFile.close();
xSemaphoreGive(fsMutex); 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); auto it = varDB.find(key);
if (it == varDB.end()) { if (it == varDB.end()) {
varStruct newVar; varStruct newVar;
newVar.value = value; newVar.value = value;
newVar.changed = true; newVar.changed = notify;
varDB[key] = newVar; varDB[key] = newVar;
return true; return true;
} }
if (it->second.value != value) { if (it->second.value != value) {
it->second.value = value; it->second.value = value;
it->second.changed = true; it->second.changed = notify;
return true; return true;
} else { } else {
return false; 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 <Arduino.h>
#include <WiFi.h> #include <WiFi.h>
#include "AsyncUDP.h" #include "AsyncUDP.h"
#include "commstructs.h" #include "commstructs.h"
#include "newproto.h" #include "newproto.h"
#include "serialap.h"
#include "tag_db.h" #include "tag_db.h"
#include "web.h" #include "web.h"
#include "serialap.h"
#include "udp.h"
#define UDPIP IPAddress(239, 10, 0, 1) #define UDPIP IPAddress(239, 10, 0, 1)
#define UDPPORT 16033 #define UDPPORT 16033
@@ -41,8 +42,9 @@ void UDPcomm::init() {
} }
void UDPcomm::processPacket(AsyncUDPPacket packet) { void UDPcomm::processPacket(AsyncUDPPacket packet) {
if (config.runStatus == RUNSTATUS_STOP) {
if (config.runStatus == RUNSTATUS_STOP) return; return;
}
IPAddress senderIP = packet.remoteIP(); IPAddress senderIP = packet.remoteIP();
switch (packet.data()[0]) { switch (packet.data()[0]) {
@@ -127,7 +129,7 @@ void autoselect(void* pvParameters) {
} }
if (curChannel.channel == 0) { if (curChannel.channel == 0) {
curChannel.channel = 11; curChannel.channel = 11;
} }
config.channel = curChannel.channel; config.channel = curChannel.channel;
do { do {
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);

View File

@@ -20,14 +20,13 @@
#include "serialap.h" #include "serialap.h"
#include "settings.h" #include "settings.h"
#include "storage.h" #include "storage.h"
#include "system.h"
#include "tag_db.h" #include "tag_db.h"
#include "udp.h" #include "udp.h"
#include "wifimanager.h" #include "wifimanager.h"
extern uint8_t data_to_send[]; extern uint8_t data_to_send[];
// const char *http_username = "admin";
// const char *http_password = "admin";
AsyncWebServer server(80); AsyncWebServer server(80);
AsyncWebSocket ws("/ws"); AsyncWebSocket ws("/ws");
WifiManager wm; WifiManager wm;
@@ -35,7 +34,7 @@ WifiManager wm;
SemaphoreHandle_t wsMutex; SemaphoreHandle_t wsMutex;
uint32_t lastssidscan = 0; uint32_t lastssidscan = 0;
void wsLog(String text) { void wsLog(const String &text) {
StaticJsonDocument<250> doc; StaticJsonDocument<250> doc;
doc["logMsg"] = text; doc["logMsg"] = text;
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY); if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
@@ -43,7 +42,7 @@ void wsLog(String text) {
if (wsMutex) xSemaphoreGive(wsMutex); if (wsMutex) xSemaphoreGive(wsMutex);
} }
void wsErr(String text) { void wsErr(const String &text) {
StaticJsonDocument<250> doc; StaticJsonDocument<250> doc;
doc["errMsg"] = text; doc["errMsg"] = text;
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY); if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
@@ -69,7 +68,7 @@ void wsSendSysteminfo() {
time(&now); time(&now);
static int freeSpaceLastRun = 0; static int freeSpaceLastRun = 0;
static size_t tagDBsize = 0; static size_t tagDBsize = 0;
static size_t freeSpace = Storage.freeSpace(); static uint64_t freeSpace = Storage.freeSpace();
sys["currtime"] = now; sys["currtime"] = now;
sys["heap"] = ESP.getFreeHeap(); sys["heap"] = ESP.getFreeHeap();
sys["recordcount"] = tagDBsize; sys["recordcount"] = tagDBsize;
@@ -80,6 +79,11 @@ void wsSendSysteminfo() {
freeSpaceLastRun = millis(); freeSpaceLastRun = millis();
} }
sys["littlefsfree"] = freeSpace; sys["littlefsfree"] = freeSpace;
#if BOARD_HAS_PSRAM
sys["psfree"] = ESP.getFreePsram();
#endif
sys["apstate"] = apInfo.state; sys["apstate"] = apInfo.state;
sys["runstate"] = config.runStatus; sys["runstate"] = config.runStatus;
#if !defined(CONFIG_IDF_TARGET_ESP32) #if !defined(CONFIG_IDF_TARGET_ESP32)
@@ -98,8 +102,14 @@ void wsSendSysteminfo() {
uint32_t tagcount = getTagCount(timeoutcount); uint32_t tagcount = getTagCount(timeoutcount);
char result[40]; char result[40];
if (timeoutcount > 0) { if (timeoutcount > 0) {
#ifdef HAS_RGB_LED
if (apInfo.state == AP_STATE_ONLINE && apInfo.isOnline == true) rgbIdleColor = CRGB::DarkBlue;
#endif
snprintf(result, sizeof(result), "%lu / %lu, %lu timed out", tagcount, tagDB.size(), timeoutcount); snprintf(result, sizeof(result), "%lu / %lu, %lu timed out", tagcount, tagDB.size(), timeoutcount);
} else { } 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()); snprintf(result, sizeof(result), "%lu / %lu", tagcount, tagDB.size());
} }
setVarDB("ap_tagcount", result); setVarDB("ap_tagcount", result);
@@ -167,7 +177,7 @@ void wsSendAPitem(struct APlist *apitem) {
if (wsMutex) xSemaphoreGive(wsMutex); if (wsMutex) xSemaphoreGive(wsMutex);
} }
void wsSerial(String text) { void wsSerial(const String &text) {
StaticJsonDocument<250> doc; StaticJsonDocument<250> doc;
doc["console"] = text; doc["console"] = text;
Serial.println(text); Serial.println(text);
@@ -184,12 +194,10 @@ void init_web() {
wsMutex = xSemaphoreCreateMutex(); wsMutex = xSemaphoreCreateMutex();
Storage.begin(); Storage.begin();
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
WiFi.setTxPower(static_cast<wifi_power_t>(config.wifiPower)); WiFi.setTxPower(static_cast<wifi_power_t>(config.wifiPower));
wm.connectToWifi(); wm.connectToWifi();
// server.addHandler(new SPIFFSEditor(*contentFS, http_username, http_password));
server.addHandler(new SPIFFSEditor(*contentFS)); server.addHandler(new SPIFFSEditor(*contentFS));
server.addHandler(&ws); server.addHandler(&ws);
@@ -270,6 +278,9 @@ void init_web() {
if (request->hasParam("lut", true)) { if (request->hasParam("lut", true)) {
taginfo->lut = atoi(request->getParam("lut", true)->value().c_str()); 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->md5, 0, 16 * sizeof(uint8_t));
// memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); // memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
wsSendTaginfo(mac, SYNC_USERCFG); wsSendTaginfo(mac, SYNC_USERCFG);
@@ -424,48 +435,59 @@ void init_web() {
}); });
server.on("/save_apcfg", HTTP_POST, [](AsyncWebServerRequest *request) { 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(); String aliasValue = request->getParam("alias", true)->value();
size_t aliasLength = aliasValue.length(); size_t aliasLength = aliasValue.length();
if (aliasLength > 31) aliasLength = 31; if (aliasLength > 31) aliasLength = 31;
aliasValue.toCharArray(config.alias, aliasLength + 1); aliasValue.toCharArray(config.alias, aliasLength + 1);
config.alias[aliasLength] = '\0'; 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"); request->send(200, "text/plain", "Ok, saved");
}); });
@@ -563,8 +585,26 @@ void init_web() {
request->send(200, "text/plain", "Ok, saved"); request->send(200, "text/plain", "Ok, saved");
ws.enable(false); 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(); ws.closeAll();
delay(100); delay(100);
ESP.restart(); 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) { void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (config.runStatus != RUNSTATUS_RUN) { static bool imageUploadBusy = false;
request->send(409, "text/plain", "come back later");
return;
}
if (!index) { if (!index) {
if (config.runStatus != RUNSTATUS_RUN || imageUploadBusy) {
request->send(409, "text/plain", "Come back later");
return;
}
if (request->hasParam("mac", true)) { if (request->hasParam("mac", true)) {
filename = request->getParam("mac", true)->value() + ".jpg"; filename = request->getParam("mac", true)->value() + ".jpg";
} else { } else {
filename = "unknown.jpg"; filename = "unknown.jpg";
} }
imageUploadBusy = true;
logLine("http imageUpload " + filename);
xSemaphoreTake(fsMutex, portMAX_DELAY); xSemaphoreTake(fsMutex, portMAX_DELAY);
request->_tempFile = contentFS->open("/" + filename, "w"); request->_tempFile = contentFS->open("/" + filename, "w");
} }
@@ -652,6 +695,7 @@ void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index
} else { } else {
request->send(400, "text/plain", "parameters incomplete"); request->send(400, "text/plain", "parameters incomplete");
} }
imageUploadBusy = false;
} }
} }

View File

@@ -2,7 +2,7 @@
{ {
"id": 0, "id": 0,
"name": "Static image", "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": [ "hwtype": [
0, 0,
1, 1,
@@ -17,7 +17,7 @@
"key": "filename", "key": "filename",
"name": "Filename", "name": "Filename",
"desc": "Local filename on the littlefs drive", "desc": "Local filename on the littlefs drive",
"type": "text" "type": "jpgfile"
}, },
{ {
"key": "timetolive", "key": "timetolive",
@@ -34,6 +34,47 @@
"0": "off", "0": "off",
"1": "on" "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", "key": "filename",
"name": "Filename", "name": "Filename",
"desc": "Filename of the json template. See OpenEpaperLink wiki for the right json format. Specify a url OR a 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", "key": "interval",
@@ -406,7 +447,7 @@
{ {
"id": 5, "id": 5,
"name": "Firmware update", "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": [ "hwtype": [
0, 0,
1, 1,
@@ -422,7 +463,7 @@
"key": "filename", "key": "filename",
"name": "Filename", "name": "Filename",
"desc": "Local file on littlefs partition", "desc": "Local file on littlefs partition",
"type": "text" "type": "binfile"
} }
] ]
}, },
@@ -649,6 +690,9 @@
"id": 21, "id": 21,
"name": "Access point info", "name": "Access point info",
"desc": "Displays information about the currently connected access point", "desc": "Displays information about the currently connected access point",
"hwtype": [0, 1] "hwtype": [
0,
1
]
} }
] ]

View File

@@ -39,376 +39,420 @@
</nav> </nav>
</header> </header>
<form> <div class="container">
<div class="container">
<div class="window"> <div class="window">
<div id="hometab" class="tabcontent"> <div id="hometab" class="tabcontent">
<table> <table>
<tr onclick="setFilterAndShow('')"> <tr onclick="setFilterAndShow('')">
<td class="material-symbols-outlined" style="color:#239f26" id="dashboardStatusIcon"> <td class="material-symbols-outlined" style="color:#239f26" id="dashboardStatusIcon">
check_circle check_circle
</td> </td>
<td id="dashboardStatus" style="color:#239f26" colspan="2"> <td id="dashboardStatus" style="color:#239f26" colspan="2">
initialising... initialising...
</td> </td>
</tr> </tr>
<tr onclick="setFilterAndShow('')"> <tr onclick="setFilterAndShow('')">
<td class="material-symbols-outlined" style="color:#77239e"> <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 sell
</td> </span>
<td> <span class="aptagcount">13</span>
tags <span class="material-symbols-outlined space">
</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 cell_tower
</td> </span>
<td> <span class="apchannel">25</span>
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>
<p class="apswversion">
fetching software version...
</p>
</div> </div>
</div> </div>
</div>
<div id="logtab" class="tabcontent"> <div id="templatetab" class="tabcontent">
<div class="tabheader"> Work in progress...
<div><img id="clearlog" src="data:image/gif;base64,R0lGODlhEAAQAPMAANXV1e3t7d/f39HR0dvb2/Hx8dTU1OLi4urq6mZmZpmZmf///wAAAAAAAAAAAAAAACH5BAEAAAwALAAAAAAQABAAAARBkMlJq71Yrp3ZXkr4WWCYnOZSgQVyEMYwJCq1nHhe20qgCAoA7QLyAYU7njE4JPV+zOSkCEUSFbmTVPPpbjvgTAQAOw== </div>
"></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"> <div id="configtab" class="tabcontent">
<h3>Active access points</h3> <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="updatetab" class="tabcontent">
<div id="apcard" class="apcard"> <h3>Firmware Updates</h3>
<p class="apip">194.109.6.66</p> <div>
<p class="apalias">AP kitchen</p> <div class="updateCol1">
<div> <div id="easyupdate"></div>
<span class="material-symbols-outlined"> <h4>Repository</h4>
sell <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>
<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> </p>
</div> </div>
</div> </div>
<div class="console" id="updateconsole"></div>
</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>
</div> </div>
</form>
</div>
<footer class="logbox"> <footer class="logbox">
<p> <p>
@@ -447,6 +491,13 @@
<option value="0">auto</option> <option value="0">auto</option>
</select> </select>
</p> </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"> <p class="tagbuttons">
<button id="cfgrefresh">force refresh</button> <button id="cfgrefresh">force refresh</button>
<button id="cfgclrpending">clear pending</button> <button id="cfgclrpending">clear pending</button>
@@ -464,27 +515,6 @@
</p> </p>
</dialog> </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" <ul id="context-menu"
style="display: none; position: absolute; background: white; border: 1px solid gray; padding: 0; list-style: none;"> style="display: none; position: absolute; background: white; border: 1px solid gray; padding: 0; list-style: none;">
</ul> </ul>

View File

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

View File

@@ -83,7 +83,8 @@ function initTabs() {
const tabContents = document.querySelectorAll(".tabcontent"); const tabContents = document.querySelectorAll(".tabcontent");
tabLinks.forEach(tabLink => { tabLinks.forEach(tabLink => {
tabLink.addEventListener("click", function () { tabLink.addEventListener("click", function (event) {
event.preventDefault();
const targetId = this.getAttribute("data-target"); const targetId = this.getAttribute("data-target");
const loadTabEvent = new CustomEvent('loadTab', { detail: targetId }); const loadTabEvent = new CustomEvent('loadTab', { detail: targetId });
document.dispatchEvent(loadTabEvent); document.dispatchEvent(loadTabEvent);
@@ -134,11 +135,24 @@ function connect() {
processTags(msg.tags); processTags(msg.tags);
} }
if (msg.sys) { if (msg.sys) {
let filesystem = 'filesystem free: ' + convertSize(msg.sys.littlefsfree); let str = "";
if (msg.sys.littlefsfree < 31000) { str += `free heap: ${convertSize(msg.sys.heap)} &#x2507; `;
filesystem = 'filesystem <span class="blink-red" title="Generating content is paused">FULL! ' + convertSize(msg.sys.littlefsfree) + '</span>'; if (msg.sys.psfree) {
} str += `free PSRAM: ${convertSize(msg.sys.psfree)} &#x2507; `;
$('#sysinfo').innerHTML = 'free heap: ' + convertSize(msg.sys.heap) + ' &#x2507; db size: ' + convertSize(msg.sys.dbsize) + ' &#x2507; db record count: ' + msg.sys.recordcount + ' &#x2507; ' + filesystem; }
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) { if (msg.sys.apstate) {
$("#apstatecolor").style.color = apstate[msg.sys.apstate].color; $("#apstatecolor").style.color = apstate[msg.sys.apstate].color;
$("#apstate").innerHTML = apstate[msg.sys.apstate].state; $("#apstate").innerHTML = apstate[msg.sys.apstate].state;
@@ -208,7 +222,6 @@ function processTags(tagArray) {
if (!alias) alias = tagmac.replace(/^0{1,4}/, ''); if (!alias) alias = tagmac.replace(/^0{1,4}/, '');
if ($('#tag' + tagmac + ' .alias').innerHTML != alias) { if ($('#tag' + tagmac + ' .alias').innerHTML != alias) {
$('#tag' + tagmac + ' .alias').innerHTML = alias; $('#tag' + tagmac + ' .alias').innerHTML = alias;
//GroupSortFilter();
} }
let contentDefObj = getContentDefById(element.contentMode); let contentDefObj = getContentDefById(element.contentMode);
@@ -302,6 +315,7 @@ function processTags(tagArray) {
break; break;
case WAKEUP_REASON_NFC: case WAKEUP_REASON_NFC:
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "NFC wakeup" $('#tag' + tagmac + ' .nextcheckin').innerHTML = "NFC wakeup"
$('#tag' + tagmac).style.background = "#c8f1bb";
break; break;
case WAKEUP_REASON_NETWORK_SCAN: case WAKEUP_REASON_NETWORK_SCAN:
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "<font color=yellow>Network scan</font>" $('#tag' + tagmac + ' .nextcheckin').innerHTML = "<font color=yellow>Network scan</font>"
@@ -332,7 +346,6 @@ function updatecards() {
$('#taglist').querySelectorAll('[data-mac]').forEach(item => { $('#taglist').querySelectorAll('[data-mac]').forEach(item => {
let tagmac = item.dataset.mac; let tagmac = item.dataset.mac;
tagcount++; tagcount++;
if (tagDB[tagmac].pending) pendingcount++;
if (tagDB[tagmac].batteryMv < 2400 && tagDB[tagmac].batteryMv != 0 && tagDB[tagmac].batteryMv != 1337) lowbattcount++; 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) { if (item.dataset.lastseen && item.dataset.lastseen > (Date.now() / 1000) - servertimediff - 30 * 24 * 3600 * 60) {
let idletime = (Date.now() / 1000) - servertimediff - item.dataset.lastseen; let idletime = (Date.now() / 1000) - servertimediff - item.dataset.lastseen;
@@ -342,6 +355,8 @@ function updatecards() {
$('#tag' + tagmac).classList.remove("tagpending") $('#tag' + tagmac).classList.remove("tagpending")
$('#tag' + tagmac).style.background = '#e0e0a0'; $('#tag' + tagmac).style.background = '#e0e0a0';
timeoutcount++; timeoutcount++;
} else {
if (tagDB[tagmac].pending) pendingcount++;
} }
if (idletime > 24 * 3600) { if (idletime > 24 * 3600) {
$('#tag' + tagmac).style.opacity = '.5'; $('#tag' + tagmac).style.opacity = '.5';
@@ -424,6 +439,7 @@ function loadContentCard(mac) {
} }
$('#cfgrotate').value = tagdata.rotate; $('#cfgrotate').value = tagdata.rotate;
$('#cfglut').value = tagdata.lut; $('#cfglut').value = tagdata.lut;
$('#cfginvert').value = tagdata.invert;
$('#cfgmore').innerHTML = '&#x25BC;'; $('#cfgmore').innerHTML = '&#x25BC;';
$('#configbox').showModal(); $('#configbox').showModal();
}) })
@@ -477,6 +493,7 @@ $('#cfgsave').onclick = function () {
formData.append("rotate", $('#cfgrotate').value); formData.append("rotate", $('#cfgrotate').value);
formData.append("lut", $('#cfglut').value); formData.append("lut", $('#cfglut').value);
formData.append("invert", $('#cfginvert').value);
fetch("/save_cfg", { fetch("/save_cfg", {
method: "POST", method: "POST",
@@ -539,11 +556,13 @@ $('#cfgreset').onclick = function () {
$('#rebootbutton').onclick = function (event) { $('#rebootbutton').onclick = function (event) {
event.preventDefault(); event.preventDefault();
showMessage("rebooting AP....", true); if (!confirm('Reboot AP now?')) return;
socket.close();
fetch("/reboot", { fetch("/reboot", {
method: "POST" method: "POST"
}); });
socket.close(); alert('Rebooted. Webpage will reload.');
location.reload()
} }
$('#configbox').addEventListener('click', (event) => { $('#configbox').addEventListener('click', (event) => {
@@ -564,6 +583,7 @@ document.addEventListener("loadTab", function (event) {
$('#apcfgalias').value = data.alias; $('#apcfgalias').value = data.alias;
$('#apcfgchid').value = data.channel; $('#apcfgchid').value = data.channel;
$("#apcfgledbrightness").value = data.led; $("#apcfgledbrightness").value = data.led;
$("#apcfgtftbrightness").value = data.tft;
$("#apcfglanguage").value = data.language; $("#apcfglanguage").value = data.language;
$("#apclatency").value = data.maxsleep; $("#apclatency").value = data.maxsleep;
$("#apcpreventsleep").value = data.stopsleep; $("#apcpreventsleep").value = data.stopsleep;
@@ -575,6 +595,10 @@ document.addEventListener("loadTab", function (event) {
}) })
$('#apcfgmsg').innerHTML = ''; $('#apcfgmsg').innerHTML = '';
break; break;
case 'updatetab':
$('#updateconsole').innerHTML = '';
loadOTA();
break;
} }
}); });
@@ -583,6 +607,7 @@ $('#apcfgsave').onclick = function () {
formData.append("alias", $('#apcfgalias').value); formData.append("alias", $('#apcfgalias').value);
formData.append("channel", $('#apcfgchid').value); formData.append("channel", $('#apcfgchid').value);
formData.append('led', $('#apcfgledbrightness').value); formData.append('led', $('#apcfgledbrightness').value);
formData.append('tft', $('#apcfgtftbrightness').value);
formData.append('language', $('#apcfglanguage').value); formData.append('language', $('#apcfglanguage').value);
formData.append('maxsleep', $('#apclatency').value); formData.append('maxsleep', $('#apclatency').value);
formData.append('stopsleep', $('#apcpreventsleep').value); formData.append('stopsleep', $('#apcpreventsleep').value);
@@ -605,12 +630,6 @@ $('#apcfgsave').onclick = function () {
.catch(error => showMessage('Error: ' + error)); .catch(error => showMessage('Error: ' + error));
} }
$('#updatebutton').onclick = function (event) {
event.preventDefault();
$('#apupdatebox').style.display = 'block';
loadOTA();
}
async function loadOTA() { async function loadOTA() {
otamodule = await import('./ota.js?v=' + Date.now()); otamodule = await import('./ota.js?v=' + Date.now());
otamodule.initUpdate(); otamodule.initUpdate();
@@ -684,6 +703,31 @@ function contentselected() {
input.type = "text"; input.type = "text";
input.disabled = true; input.disabled = true;
break; 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': case 'select':
input = document.createElement("select"); input = document.createElement("select");
for (const key in element.options) { for (const key in element.options) {
@@ -978,10 +1022,18 @@ $('#activefilter').addEventListener('click', (event) => {
}); });
async function getTagtype(hwtype) { async function getTagtype(hwtype) {
if (tagTypes[hwtype]) { if (tagTypes[hwtype] && tagTypes[hwtype].busy == false) {
return tagTypes[hwtype]; 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) { if (getTagtypeBusy) {
await new Promise(resolve => { await new Promise(resolve => {
const checkBusy = setInterval(() => { const checkBusy = setInterval(() => {
@@ -1000,7 +1052,7 @@ async function getTagtype(hwtype) {
clearInterval(checkBusy); clearInterval(checkBusy);
resolve(); resolve();
} }
}, 10); }, 50);
}); });
} }
@@ -1009,8 +1061,8 @@ async function getTagtype(hwtype) {
} }
try { try {
tagTypes[hwtype] = { busy: true };
getTagtypeBusy = true; getTagtypeBusy = true;
tagTypes[hwtype] = { busy: true };
const response = await fetch('/tagtypes/' + hwtype.toString(16).padStart(2, '0').toUpperCase() + '.json'); const response = await fetch('/tagtypes/' + hwtype.toString(16).padStart(2, '0').toUpperCase() + '.json');
if (!response.ok) { if (!response.ok) {
let data = { name: 'unknown id ' + hwtype, width: 0, height: 0, bpp: 0, rotatebuffer: 0, colortable: [], busy: false }; 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); const $ = document.querySelector.bind(document);
@@ -8,19 +9,28 @@ let env = '', currentVer = '', currentBuildtime = 0;
let buttonState = false; let buttonState = false;
export async function initUpdate() { 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"); const response = await fetch("/version.txt");
let filesystemversion = await response.text(); let filesystemversion = await response.text();
if (!filesystemversion) filesystemversion = "unknown"; 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 => { .then(response => {
if (response.status != 200) { if (response.status != 200) {
print("Error fetching sysinfo: " + response.status, "red"); print("Error fetching sysinfo: " + response.status, "red");
@@ -34,33 +44,36 @@ export async function initUpdate() {
return response.json(); 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 => { .catch(error => {
print('Error fetching sysinfo: ' + error, "red"); print('Error fetching sysinfo: ' + error, "red");
}); });
fetch(repoUrl) const repoPromise = fetch(repoUrl)
.then(response => response.json()) .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 assets = release.assets;
const filesJsonAsset = assets.find(asset => asset.name === 'filesystem.json'); const filesJsonAsset = assets.find(asset => asset.name === 'filesystem.json');
const binariesJsonAsset = assets.find(asset => asset.name === 'binaries.json'); const binariesJsonAsset = assets.find(asset => asset.name === 'binaries.json');
@@ -76,7 +89,7 @@ export async function initUpdate() {
} }
}; };
}); });
const easyupdate = $('#easyupdate'); const easyupdate = $('#easyupdate');
if (releaseDetails.length === 0) { if (releaseDetails.length === 0) {
easyupdate.innerHTML = ("No releases found."); 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 table = document.createElement('table');
const tableHeader = document.createElement('tr'); const tableHeader = document.createElement('tr');
@@ -101,9 +113,9 @@ export async function initUpdate() {
let rowCounter = 0; let rowCounter = 0;
releaseDetails.forEach(release => { releaseDetails.forEach(release => {
if (rowCounter < 3 && release?.html_url) { if (rowCounter < 4 && release?.html_url) {
const tableRow = document.createElement('tr'); 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) { if (release.tag_name == currentVer) {
tablerow += "<td>current version</td>"; tablerow += "<td>current version</td>";
} else if (release.date < formatEpoch(currentBuildtime)) { } else if (release.date < formatEpoch(currentBuildtime)) {
@@ -362,6 +374,90 @@ $('#updateC6Btn').onclick = function () {
disableButtons(false); 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") { export function print(line, color = "white") {
const consoleDiv = document.getElementById('updateconsole'); const consoleDiv = document.getElementById('updateconsole');
if (consoleDiv) { if (consoleDiv) {
@@ -464,7 +560,7 @@ const writeVersion = async (content, name, path) => {
}; };
function disableButtons(active) { function disableButtons(active) {
$("#apupdatebox").querySelectorAll('button').forEach(button => { $("#configtab").querySelectorAll('button').forEach(button => {
button.disabled = active; button.disabled = active;
}); });
buttonState = active; buttonState = active;

View File

@@ -104,7 +104,7 @@ function startPainter(mac, width, height) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', '/imgupload'); xhr.open('POST', '/imgupload');
xhr.send(formData); xhr.send(formData);
$('#configbox').style.display = 'none'; $('#configbox').close();
}); });
$("#buttonbar").appendChild(blackButton); $("#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 ## 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'. 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 ## 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] tag = sys.argv[1]
binaries = generate_file_hashes2(rp + "/espbinaries",tag) 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 = 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","/",tag))
files1.extend(generate_file_hashes(rp + "/ESP32_AP-Flasher/data/fonts","/fonts/",tag)) files1.extend(generate_file_hashes(rp + "/ESP32_AP-Flasher/data/fonts","/fonts/",tag))
@@ -65,7 +65,7 @@ jsonarray = {
"builddate": dt_string, "builddate": dt_string,
"binaries": binaries, "binaries": binaries,
"files": files1, "files": files1,
"tagota": tagota, # "tagota": tagota,
} }
with open("jsonfiles/binaries.json", "w") as json_file: 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: with open("jsonfiles/files.json", "w") as json_file:
json.dump(jsonarray, json_file, indent=4) json.dump(jsonarray, json_file, indent=4)
with open("jsonfiles/tagota.json", "w") as json_file: #with open("jsonfiles/tagota.json", "w") as json_file:
json.dump(tagota, json_file, indent=4) # json.dump(tagota, json_file, indent=4)
with open("jsonfiles/filesystem.json", "w") as json_file: with open("jsonfiles/filesystem.json", "w") as json_file:
json.dump(files1, json_file, indent=4) json.dump(files1, json_file, indent=4)

View File

@@ -57,18 +57,48 @@
#define CMD_DO_RESET_SETTINGS 2 #define CMD_DO_RESET_SETTINGS 2
#define CMD_DO_DEEPSLEEP 3 #define CMD_DO_DEEPSLEEP 3
#define CMD_DO_LEDFLASH 4 #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_TIMED 0
#define WAKEUP_REASON_GPIO 2 #define WAKEUP_REASON_GPIO 2
#define WAKEUP_REASON_NFC 3 #define WAKEUP_REASON_NFC 3
#define WAKEUP_REASON_BUTTON1 4 #define WAKEUP_REASON_BUTTON1 4
#define WAKEUP_REASON_BUTTON2 5 #define WAKEUP_REASON_BUTTON2 5
#define WAKEUP_REASON_RF 0x0F
#define WAKEUP_REASON_FIRSTBOOT 0xFC #define WAKEUP_REASON_FIRSTBOOT 0xFC
#define WAKEUP_REASON_NETWORK_SCAN 0xFD #define WAKEUP_REASON_NETWORK_SCAN 0xFD
#define WAKEUP_REASON_WDT_RESET 0xFE #define WAKEUP_REASON_WDT_RESET 0xFE
#define EPD_LUT_DEFAULT 0 #define EPD_LUT_DEFAULT 0
#define EPD_LUT_NO_REPEATS 1 #define EPD_LUT_NO_REPEATS 1
#define EPD_LUT_FAST_NO_REDS 2 #define EPD_LUT_FAST_NO_REDS 2
#define EPD_LUT_FAST 3 #define EPD_LUT_FAST 3
#define EPD_LUT_OTA 0x10 #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 repeats;
uint8_t spare; uint8_t spare;
} __packed; } __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-definitions.h"
#include "../oepl-proto.h" #include "../oepl-proto.h"
// #define DEBUG_MODE // #define DEBUG_MODE
static const uint64_t __code __at(0x008b) mVersionRom = 0x1000011300000000ull; static const uint64_t __code __at(0x008b) mVersionRom = 0x1000011300000000ull;
#define TAG_MODE_CHANSEARCH 0 #define TAG_MODE_CHANSEARCH 0
#define TAG_MODE_ASSOCIATED 1 #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 currentTagMode = TAG_MODE_CHANSEARCH;
uint8_t __xdata slideShowCurrentImg = 0;
uint8_t __xdata slideShowRefreshCount = 1;
void displayLoop() { void displayLoop() {
powerUp(INIT_BASE | INIT_UART); powerUp(INIT_BASE | INIT_UART);
@@ -46,22 +54,6 @@ void displayLoop() {
showApplyUpdate(); showApplyUpdate();
timerDelay(TIMER_TICKS_PER_SECOND * 4); 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(); wdt30s();
pr("AP Found\n"); pr("AP Found\n");
@@ -99,43 +91,16 @@ void displayLoop() {
wdtDeviceReset(); wdtDeviceReset();
} }
uint8_t showChannelSelect() { // returns 0 if no accesspoints were found uint8_t channelSelect(uint8_t rounds) { // 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
powerUp(INIT_RADIO); powerUp(INIT_RADIO);
uint8_t __xdata result[16]; uint8_t __xdata result[16];
memset(result, 0, sizeof(result)); 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++) { for (uint8_t c = 0; c < sizeof(channelList); c++) {
if (detectAP(channelList[c])) { if (detectAP(channelList[c])) {
if (mLastLqi > result[c]) result[c] = mLastLqi; 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() { void TagAssociated() {
// associated // associated
bool fastNextCheckin = false;
struct AvailDataInfo *__xdata avail; struct AvailDataInfo *__xdata avail;
// Is there any reason why we should do a long (full) get data request (including reason, status)? // 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) { if ((longDataReqCounter > LONG_DATAREQ_INTERVAL) || wakeUpReason != WAKEUP_REASON_TIMED) {
@@ -234,7 +200,8 @@ void TagAssociated() {
if (curImgSlot != 0xFF) { if (curImgSlot != 0xFF) {
powerUp(INIT_EEPROM | INIT_EPD); powerUp(INIT_EEPROM | INIT_EPD);
wdt60s(); wdt60s();
drawImageFromEeprom(curImgSlot); uint8_t lut = getEepromImageDataArgument(curImgSlot) & 0x03;
drawImageFromEeprom(curImgSlot, lut);
powerDown(INIT_EEPROM | INIT_EPD); powerDown(INIT_EEPROM | INIT_EPD);
} else { } else {
powerUp(INIT_EPD); powerUp(INIT_EPD);
@@ -247,6 +214,29 @@ void TagAssociated() {
avail = getAvailDataInfo(); avail = getAvailDataInfo();
powerDown(INIT_RADIO); 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) { if (avail != NULL) {
// we got some data! // we got some data!
longDataReqCounter = 0; longDataReqCounter = 0;
@@ -254,14 +244,12 @@ void TagAssociated() {
wakeUpReason = WAKEUP_REASON_TIMED; wakeUpReason = WAKEUP_REASON_TIMED;
} }
if (tagSettings.enableTagRoaming) { if (tagSettings.enableTagRoaming) {
uint8_t roamChannel = channelSelect(); uint8_t roamChannel = channelSelect(1);
if (roamChannel) currentChannel = roamChannel; if (roamChannel) currentChannel = roamChannel;
} }
} else { } else {
powerUp(INIT_RADIO); powerUp(INIT_RADIO);
#ifdef ENABLE_RETURN_DATA #ifdef ENABLE_RETURN_DATA
// example code to send data back to the AP. Up to 90 bytes can be sent in one packet // 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}; 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 (fastNextCheckin) {
if (nextCheckInFromAP) { // do a fast check-in next
doSleep(nextCheckInFromAP * 60000UL); fastNextCheckin = false;
doSleep(100UL);
} else { } 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 // try to find a working channel
currentChannel = channelSelect(); currentChannel = channelSelect(2);
// Check if we should redraw the screen with icons, info screen or screensaver // Check if we should redraw the screen with icons, info screen or screensaver
if ((!currentChannel && !noAPShown && tagSettings.enableNoRFSymbol) || if ((!currentChannel && !noAPShown && tagSettings.enableNoRFSymbol) ||
@@ -328,9 +327,12 @@ void TagChanSearch() {
powerUp(INIT_EPD); powerUp(INIT_EPD);
wdt60s(); wdt60s();
if (curImgSlot != 0xFF) { if (curImgSlot != 0xFF) {
powerUp(INIT_EEPROM); if (!displayCustomImage(CUSTOM_IMAGE_LOST_CONNECTION)) {
drawImageFromEeprom(curImgSlot); powerUp(INIT_EEPROM);
powerDown(INIT_EEPROM); uint8_t lut = getEepromImageDataArgument(curImgSlot) & 0x03;
drawImageFromEeprom(curImgSlot, lut);
powerDown(INIT_EEPROM);
}
} else if ((scanAttempts >= (INTERVAL_1_ATTEMPTS + INTERVAL_2_ATTEMPTS - 1))) { } else if ((scanAttempts >= (INTERVAL_1_ATTEMPTS + INTERVAL_2_ATTEMPTS - 1))) {
showLongTermSleep(); showLongTermSleep();
} else { } 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) { void executeCommand(uint8_t cmd) {
switch (cmd) { switch (cmd) {
case CMD_DO_REBOOT: case CMD_DO_REBOOT:
@@ -364,7 +432,7 @@ void executeCommand(uint8_t cmd) {
writeSettings(); writeSettings();
break; break;
case CMD_DO_SCAN: case CMD_DO_SCAN:
currentChannel = channelSelect(); currentChannel = channelSelect(4);
break; break;
case CMD_DO_DEEPSLEEP: case CMD_DO_DEEPSLEEP:
powerUp(INIT_EPD); powerUp(INIT_EPD);
@@ -374,6 +442,61 @@ void executeCommand(uint8_t cmd) {
doSleep(-1); doSleep(-1);
} }
break; 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 // get the highest slot number, number of slots
initializeProto(); 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) { if (tagSettings.enableFastBoot) {
// Fastboot // Fastboot
pr("Doing fast boot\n"); pr("Doing fast boot\n");
@@ -406,7 +540,7 @@ void main() {
if (tagSettings.fixedChannel) { if (tagSettings.fixedChannel) {
currentChannel = tagSettings.fixedChannel; currentChannel = tagSettings.fixedChannel;
} else { } else {
currentChannel = channelSelect(); currentChannel = channelSelect(2);
} }
} else { } else {
// Normal boot/startup // Normal boot/startup
@@ -426,9 +560,9 @@ void main() {
detectButtonOrJig(); detectButtonOrJig();
// show the splashscreen // show the splashscreen
pr("EPD: First powerup\n"); currentChannel = 11;
powerUp(INIT_EPD);
showSplashScreen(); showSplashScreen();
currentChannel = 0;
// we've now displayed something on the screen; for the SSD1619, we are now aware of the lut-size // we've now displayed something on the screen; for the SSD1619, we are now aware of the lut-size
#ifdef EPD_SSD1619 #ifdef EPD_SSD1619
@@ -443,28 +577,25 @@ void main() {
writeSettings(); writeSettings();
// scan for channels // scan for channels
powerUp(INIT_EPD);
wdt30s(); wdt30s();
if (tagSettings.fixedChannel) { if (tagSettings.fixedChannel) {
currentChannel = tagSettings.fixedChannel; currentChannel = tagSettings.fixedChannel;
} else { } else {
currentChannel = showChannelSelect(); currentChannel = channelSelect(4);
} }
} }
// end of the fastboot option split // end of the fastboot option split
wdt10s(); wdt10s();
powerUp(INIT_EPD); powerUp(INIT_EPD);
if (currentChannel) { if (currentChannel) {
showAPFound(); showAPFound();
initPowerSaving(INTERVAL_BASE); initPowerSaving(INTERVAL_BASE);
powerDown(INIT_EPD | INIT_UART);
currentTagMode = TAG_MODE_ASSOCIATED; currentTagMode = TAG_MODE_ASSOCIATED;
doSleep(5000UL); doSleep(5000UL);
} else { } else {
showNoAP(); showNoAP();
initPowerSaving(INTERVAL_AT_MAX_ATTEMPTS); initPowerSaving(INTERVAL_AT_MAX_ATTEMPTS);
powerDown(INIT_EPD | INIT_UART);
currentTagMode = TAG_MODE_CHANSEARCH; currentTagMode = TAG_MODE_CHANSEARCH;
doSleep(120000UL); doSleep(120000UL);
} }

View File

@@ -1,12 +1,12 @@
<?php <?php
$types[0x00] = "Tag_FW_1.54.bin"; $types[0x00] = "SOLUM_154_SSD1619-tag-00-0022.bin";
$types[0x01] = "Tag_FW_2.9.bin"; $types[0x01] = "SOLUM_29_SSD1619-tag-01-0022";
$types[0xF0] = "Tag_FW_Segmented_UK.bin"; $types[0xF0] = "Tag_FW_Segmented_UK.bin";
$types[0x02] = "Tag_FW_4.2.bin"; $types[0x02] = "SOLUM_42_SSD1619-tag-02-0022.bin";
$types[0x11] = "Tag_FW_2.9-uc8151.bin"; $types[0x11] = "SOLUM_29_UC8151-tag-11-0022.bin";
$binpath = "../binaries/"; $binpath = "../binaries/Tag";
$tocmaxsize = 512; $tocmaxsize = 512;
$toc = array(); $toc = array();
@@ -21,6 +21,7 @@ $version = hexdec($version);
*/ */
$version = 0; $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); exec("ls -1 $binpath | grep 'Tag_FW' | grep -v battery | grep -v Pack | grep -v M3", $binaries);
foreach($binaries as $file){ foreach($binaries as $file){
$file = trim($file); $file = trim($file);
@@ -30,7 +31,7 @@ foreach($binaries as $file){
$type = $typeid; $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); $binary = file_get_contents($binpath.$file);
$length = strlen($binary); $length = strlen($binary);
$offset = strlen($output); $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; uartActive = false;
eepromActive = false; eepromActive = false;
capabilities |= CAPABILITY_HAS_WAKE_BUTTON;
if (capabilities & CAPABILITY_HAS_WAKE_BUTTON) { if (capabilities & CAPABILITY_HAS_WAKE_BUTTON) {
// Button setup on TEST pin 1.0 (input pullup) // Button setup on TEST pin 1.0 (input pullup)
P1FUNC &= ~(1 << 0); P1FUNC &= ~(1 << 0);
P1DIR |= (1 << 0); P1DIR |= (1 << 0);
P1PULL |= (1 << 0); P1PULL |= (1 << 0);
P1LVLSEL |= (1 << 0); P1LVLSEL |= (1 << 0);
P1INTEN = (1 << 0); P1INTEN |= (1 << 0);
P1CHSTA &= ~(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) { if (capabilities & CAPABILITY_NFC_WAKE) {
P1FUNC &= ~(1 << 3); P1FUNC &= ~(1 << 3);
P1DIR |= (1 << 3); P1DIR |= (1 << 3);
P1PULL |= (1 << 3); P1PULL |= (1 << 3);
P1LVLSEL |= (1 << 3); P1LVLSEL |= (1 << 3);
P1INTEN = (1 << 3); P1INTEN |= (1 << 3);
P1CHSTA &= ~(1 << 3); P1CHSTA &= ~(1 << 3);
} }
if (tagSettings.enableRFWake) { if (tagSettings.enableRFWake) {
// enabled RF wake, adds a little extra energy draw! // enabled RF wake, adds a little extra energy draw!
RADIO_RadioPowerCtl &= 0xFB; RADIO_RadioPowerCtl &= 0xFB;
} }
// sleepy time // sleepy time
sleepForMsec(t); sleepForMsec(t);
P1INTEN = 0; P1INTEN = 0;
if ((P1CHSTA & (1 << 0)) && (capabilities & CAPABILITY_HAS_WAKE_BUTTON)) { P0INTEN = 0;
wakeUpReason = WAKEUP_REASON_GPIO;
P1CHSTA &= ~(1 << 0);
}
if ((P1CHSTA & (1 << 3)) && (capabilities & CAPABILITY_NFC_WAKE)) { switch (RADIO_Wake_Reason) {
wakeUpReason = WAKEUP_REASON_NFC; case RADIO_WAKE_REASON_TIMER:
P1CHSTA &= ~(1 << 3); 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_2_ATTEMPTS 12 // for 12 attempts (an additional day)
#define INTERVAL_3_TIME 86400UL // Finally, try every 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 uint8_t checkButtonOrJig();
extern void setupPortsInitial(); extern void setupPortsInitial();

View File

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

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