Pending queue (#210)

Pretty complex change here: pending images/commands are now queued.

A command (like LED flasher) will not overwrite a pending image anymore. Also, sending multiple preloaded images is possible.
Also works (at least, as far as I could test) in combination with Multi AP and mirroring tags ('display a copy' content type).

It you want to test this: don't forget to upload the changed files in /www (the pending icon is now displaying the amount of queued messages). Timing improvements will follow later (only one message can be transmitted every checkin. If multiple messages are queued, at this moment, you have to wait until the next checkin which takes 40-60 sec).

This comes also with the advantage of better stability if you upload multiple images to the same tag in succession. Before queuing, if was possible to replace the image between sending the pending message and the image transfer to the tag, causing md5 mismatches and instability.

Solves #47
This commit is contained in:
Nic Limper
2024-01-29 00:49:52 +01:00
committed by GitHub
parent 8c4627007f
commit 6292e73135
26 changed files with 357 additions and 215 deletions

View File

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

Binary file not shown.

View File

@@ -1,4 +1,7 @@
#include <Arduino.h>
#ifndef NEWPROTO_H
#define NEWPROTO_H
#include<Arduino.h>
#pragma pack(push, 1)
#include "../../oepl-definitions.h"
@@ -25,7 +28,7 @@ struct APlist {
#define SYNC_USERCFG 1
#define SYNC_TAGSTATUS 2
#define SYNC_DELETE 3
#define SYNC_VERSION 0xAA00
#define SYNC_VERSION 0xAA01
struct TagInfo {
uint16_t structVersion = SYNC_VERSION;
@@ -34,14 +37,16 @@ struct TagInfo {
char alias[32];
uint32_t lastseen;
uint32_t nextupdate;
bool pending;
uint16_t pendingCount;
uint32_t expectedNextCheckin;
uint8_t hwType;
uint8_t wakeupReason;
uint8_t capabilities;
uint16_t pendingIdle;
uint8_t contentMode;
uint8_t reserved[8];
} __packed;
#pragma pack(pop)
#pragma pack(pop)
#endif // NEWPROTO_H

View File

@@ -1,4 +1,14 @@
#include <Arduino.h>
#include <FS.h>
#include "commstructs.h"
struct PendingItem {
struct pendingData pendingdata;
char filename[50];
uint8_t* data;
uint32_t len;
};
extern void addCRC(void* p, uint8_t len);
extern bool checkCRC(void* p, uint8_t len);
@@ -23,3 +33,13 @@ bool checkMirror(struct tagRecord* taginfo, struct pendingData* pending);
void refreshAllPending();
void updateContent(const uint8_t* dst);
void setAPchannel();
void enqueueItem(const PendingItem& item);
bool dequeueItem(const uint8_t* targetMac);
bool dequeueItem(const uint8_t* targetMac, const uint64_t dataVer);
uint16_t countQueueItem(const uint8_t* targetMac);
extern PendingItem* getQueueItem(const uint8_t* targetMac);
extern PendingItem* getQueueItem(const uint8_t* targetMac, const uint64_t dataVer);
void checkQueue(const uint8_t* targetMac);
bool queueDataAvail(struct pendingData* pending);
uint8_t* getDataForFile(fs::File& file);

View File

@@ -18,7 +18,7 @@ struct APInfoS {
uint8_t channel;
uint8_t mac[8];
uint8_t power;
uint8_t pending;
uint8_t pendingBuffer;
uint8_t nop;
};
@@ -34,4 +34,4 @@ bool sendChannelPower(struct espSetChannelPower* scp);
void rxSerialTask2(void* parameter);
void APTagReset();
bool bringAPOnline();
void setAPstate(bool isOnline, uint8_t state);
void setAPstate(bool isOnline, uint8_t state);

View File

@@ -14,7 +14,7 @@
class tagRecord {
public:
tagRecord() : mac{0}, version(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) {}
tagRecord() : mac{0}, version(0), alias(""), lastseen(0), nextupdate(0), contentMode(0), pendingCount(0), md5{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 version;
@@ -22,9 +22,8 @@ class tagRecord {
uint32_t lastseen;
uint32_t nextupdate;
uint8_t contentMode;
bool pending;
uint16_t pendingCount;
uint8_t md5[16];
uint8_t md5pending[16];
uint32_t expectedNextCheckin;
String modeConfigJson;
uint8_t LQI;

View File

@@ -3,6 +3,8 @@
/// @brief Custom tag data parser and helpers
#pragma once
#ifndef SAVE_SPACE
#include <Arduino.h>
#include <ArduinoJson.h>
@@ -137,4 +139,6 @@ template <typename T, std::enable_if_t<std::is_same_v<T, std::string>, bool> = t
inline T bytesTo(const uint8_t *data, int length) {
return T(data, data + length);
}
} // namespace TagData
} // namespace TagData
#endif

View File

@@ -57,6 +57,7 @@ build_flags =
-D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
-D HAS_RGB_LED
-D BOARD_HAS_PSRAM
-D SAVE_SPACE
-D POWER_NO_SOFT_POWER
-D FLASHER_AP_SS=11
-D FLASHER_AP_CLK=9
@@ -93,6 +94,7 @@ build_flags =
-D CONFIG_SPIRAM_USE_MALLOC=1
-D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
-D BOARD_HAS_PSRAM
-D SAVE_SPACE
-D FLASHER_AP_SS=38
-D FLASHER_AP_CLK=40
-D FLASHER_AP_MOSI=39
@@ -184,6 +186,7 @@ build_flags =
${env.build_flags}
-D CORE_DEBUG_LEVEL=0
-D SIMPLE_AP
-D SAVE_SPACE
-D FLASHER_AP_SS=5
-D FLASHER_AP_CLK=18
-D FLASHER_AP_MOSI=23

View File

@@ -51,7 +51,7 @@ void contentRunner() {
// taginfo->wakeupReason = 0;
}
if (taginfo->expectedNextCheckin > now - 10 && taginfo->expectedNextCheckin < now + 30 && taginfo->pendingIdle == 0 && taginfo->pending == false) {
if (taginfo->expectedNextCheckin > now - 10 && taginfo->expectedNextCheckin < now + 30 && taginfo->pendingIdle == 0 && taginfo->pendingCount == 0) {
int16_t minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60;
if (minutesUntilNextUpdate > config.maxsleep) {
minutesUntilNextUpdate = config.maxsleep;
@@ -240,10 +240,7 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) {
jpg2buffer(configFilename, filename, imageParams);
} else {
filename = "/current/" + String(hexmac) + ".pending";
if (!contentFS->exists(filename)) {
filename = "/current/" + String(hexmac) + ".raw";
}
filename = "/current/" + String(hexmac) + ".raw";
if (contentFS->exists(filename)) {
prepareDataAvail(filename, imageParams.dataType, imageParams.lut, mac, cfgobj["timetolive"].as<int>(), true);
wsLog("Resending image " + filename);
@@ -944,8 +941,6 @@ void drawForecast(String &filename, JsonObject &cfgobj, const tagRecord *taginfo
int getImgURL(String &filename, String URL, time_t fetched, imgParam &imageParams, String MAC) {
// https://images.klari.net/kat-bw29.jpg
Storage.begin();
HTTPClient http;
logLine("http getImgURL " + URL);
http.begin(URL);
@@ -1363,7 +1358,6 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa
void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginfo, imgParam &imageParams) {
#ifdef CONTENT_QR
TFT_eSprite spr = TFT_eSprite(&tft);
Storage.begin();
const char *text = qrcontent.c_str();
QRCode qrcode;

View File

@@ -64,11 +64,11 @@ uint8_t pinsExt[] = {FLASHER_EXT_CLK, FLASHER_EXT_MISO, FLASHER_EXT_MOSI, FLASHE
flasher::flasher() {
zbs = new ZBS_interface;
Storage.end();
// Storage.end();
}
flasher::~flasher() {
delete zbs;
Storage.begin();
// Storage.begin();
}
static uint8_t validatePowerPinCount(int8_t *powerPin, uint8_t pinCount) {

View File

@@ -112,7 +112,7 @@ void yellow_ap_display_loop(void) {
}
if (millis() - last_update >= 1000) {
tagRecord* tag = tagDB.at(tftid);
if (tag->pending) {
if (tag->pendingCount > 0) {
String filename = tag->filename;
fs::File file = contentFS->open(filename);
if (!file) {

View File

@@ -127,7 +127,11 @@ void setup() {
#ifdef HAS_RGB_LED
rgbIdle();
#endif
#ifndef SAVE_SPACE
TagData::loadParsers("/parsers.json");
#endif
if (!loadDB("/current/tagDB.json")) {
Serial.println("unable to load tagDB, reverting to backup");
loadDB("/current/tagDB.json.bak");

View File

@@ -18,7 +18,6 @@ bool spr_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap)
}
void jpg2buffer(String filein, String fileout, imgParam &imageParams) {
Storage.begin();
TJpgDec.setSwapBytes(true);
TJpgDec.setJpgScale(1);
TJpgDec.setCallback(spr_output);
@@ -227,7 +226,6 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
long t = millis();
Storage.begin();
#ifdef YELLOW_IPS_AP
extern uint8_t YellowSense;

View File

@@ -6,7 +6,11 @@
#include <MD5Builder.h>
#include <time.h>
#include "commstructs.h"
#include <algorithm>
#include <cstring>
#include <mutex>
#include <vector>
#include "serialap.h"
#include "settings.h"
#include "storage.h"
@@ -19,6 +23,8 @@
extern uint16_t sendBlock(const void* data, const uint16_t len);
extern UDPcomm udpsync;
std::vector<PendingItem> pendingQueue;
std::mutex queueMutex;
void addCRC(void* p, uint8_t len) {
uint8_t total = 0;
@@ -44,6 +50,7 @@ uint8_t* getDataForFile(fs::File& file) {
file.readBytes((char*)ret, fileSize);
} else {
Serial.printf("malloc failed for file with size %d\n", fileSize);
wsErr("malloc failed while reading file");
util::printHeap();
}
return ret;
@@ -61,7 +68,9 @@ void prepareCancelPending(const uint8_t dst[8]) {
return;
}
clearPending(taginfo);
while (dequeueItem(dst)) {
};
taginfo->pendingCount = countQueueItem(dst);
wsSendTaginfo(dst, SYNC_TAGSTATUS);
}
@@ -74,7 +83,7 @@ void prepareIdleReq(const uint8_t* dst, uint16_t nextCheckin) {
pending.attemptsLeft = 10 + config.maxsleep;
Serial.printf(">SDA %02X%02X%02X%02X%02X%02X%02X%02X NOP\n", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
sendDataAvail(&pending);
queueDataAvail(&pending);
}
}
@@ -92,26 +101,35 @@ void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, const uint8
wsErr("no memory allocation for data");
return;
}
uint8_t md5bytes[16];
{
MD5Builder md5;
md5.begin();
md5.add(data, len);
md5.calculate();
md5.getBytes(md5bytes);
}
memcpy(taginfo->data, data, len);
taginfo->pending = true;
taginfo->pendingCount++;
taginfo->len = len;
taginfo->pendingIdle = 0;
taginfo->filename = String();
taginfo->dataType = dataType;
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
pending.availdatainfo.dataSize = len;
pending.availdatainfo.dataType = dataType;
pending.availdatainfo.nextCheckIn = 0;
pending.availdatainfo.dataVer = millis();
pending.availdatainfo.dataVer = *((uint64_t*)md5bytes);
pending.attemptsLeft = 10;
if (taginfo->isExternal) {
udpsync.netSendDataAvail(&pending);
} else {
sendDataAvail(&pending);
queueDataAvail(&pending);
}
wsSendTaginfo(dst, SYNC_TAGSTATUS);
@@ -137,7 +155,6 @@ bool prepareDataAvail(String& filename, uint8_t dataType, uint8_t dataTypeArgume
}
filename = "/" + filename;
Storage.begin();
if (!contentFS->exists(filename)) {
wsErr("File not found. " + filename);
@@ -164,49 +181,30 @@ bool prepareDataAvail(String& filename, uint8_t dataType, uint8_t dataTypeArgume
file.close();
uint16_t attempts = 60 * 24;
if (memcmp(md5bytes, taginfo->md5pending, 16) == 0) {
wsLog("new image is the same as current or already pending image. not updating tag.");
wsSendTaginfo(dst, SYNC_TAGSTATUS);
if (contentFS->exists(filename) && resend == false) {
contentFS->remove(filename);
}
return true;
}
if (dataType != DATATYPE_FW_UPDATE) {
char dst_path[64];
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X_%lu.pending\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0], millis());
if (contentFS->exists(dst_path)) {
contentFS->remove(dst_path);
}
if (resend == false) {
contentFS->rename(filename, dst_path);
filename = String(dst_path);
wsLog("new image: " + String(dst_path));
wsLog("new image: " + filename);
}
time_t now;
time(&now);
taginfo->pendingIdle = nextCheckin * 60 + 60;
clearPending(taginfo);
taginfo->filename = filename;
taginfo->len = filesize;
taginfo->dataType = dataType;
taginfo->pending = true;
memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes));
} else {
char dst_path[64];
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.raw\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
contentFS->remove(dst_path);
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
contentFS->remove(dst_path);
wsLog("firmware upload pending");
clearPending(taginfo);
taginfo->filename = filename;
taginfo->len = filesize;
taginfo->dataType = dataType;
taginfo->pending = true;
}
taginfo->filename = filename;
taginfo->len = filesize;
taginfo->dataType = dataType;
taginfo->pendingCount++;
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
@@ -219,7 +217,7 @@ bool prepareDataAvail(String& filename, uint8_t dataType, uint8_t dataTypeArgume
checkMirror(taginfo, &pending);
if (taginfo->isExternal == false) {
Serial.printf(">SDA %02X%02X%02X%02X%02X%02X%02X%02X TYPE 0x%02X\n", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0], pending.availdatainfo.dataType);
sendDataAvail(&pending);
queueDataAvail(&pending);
} else {
udpsync.netSendDataAvail(&pending);
}
@@ -238,13 +236,11 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
case DATATYPE_IMG_DIFF:
case DATATYPE_IMG_RAW_1BPP:
case DATATYPE_IMG_RAW_2BPP: {
Storage.begin();
char hexmac[17];
mac2hex(pending->targetMac, hexmac);
String filename = "/current/" + String(hexmac) + ".pending";
String imageUrl = "http://" + remoteIP.toString() + filename;
wsLog("GET " + imageUrl);
String filename = "/current/" + String(hexmac) + "_" + String(millis()) + ".pending";
String imageUrl = "http://" + remoteIP.toString() + "/getdata?mac=" + String(hexmac);
wsLog("prepareExternalDataAvail GET " + imageUrl);
HTTPClient http;
logLine("http prepareExternalDataAvail " + imageUrl);
http.begin(imageUrl);
@@ -293,8 +289,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
taginfo->filename = filename;
taginfo->len = filesize;
taginfo->dataType = pending->availdatainfo.dataType;
taginfo->pending = true;
memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes));
taginfo->pendingCount++;
break;
}
case DATATYPE_NFC_RAW_CONTENT:
@@ -316,7 +311,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
WiFiClient* stream = http.getStreamPtr();
stream->readBytes(taginfo->data, len);
taginfo->dataType = pending->availdatainfo.dataType;
taginfo->pending = true;
taginfo->pendingCount++;
taginfo->len = len;
}
}
@@ -327,16 +322,15 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
return;
}
}
// taginfo->contentMode = 12;
// taginfo->nextupdate = 3216153600;
checkMirror(taginfo, pending);
sendDataAvail(pending);
queueDataAvail(pending);
wsSendTaginfo(pending->targetMac, SYNC_NOSYNC);
}
}
void processBlockRequest(struct espBlockRequest* br) {
uint32_t t = millis();
if (config.runStatus == RUNSTATUS_STOP) {
return;
}
@@ -345,40 +339,38 @@ void processBlockRequest(struct espBlockRequest* br) {
return;
}
tagRecord* taginfo = tagRecord::findByMAC(br->src);
if (taginfo == nullptr) {
if (config.lock) return;
PendingItem* queueItem = getQueueItem(br->src, br->ver);
if (queueItem == nullptr) {
prepareCancelPending(br->src);
Serial.printf("blockrequest: couldn't find taginfo %02X%02X%02X%02X%02X%02X%02X%02X\n", br->src[7], br->src[6], br->src[5], br->src[4], br->src[3], br->src[2], br->src[1], br->src[0]);
return;
}
if (taginfo->data == nullptr) {
// not cached. open file, cache the data
fs::File file = contentFS->open(taginfo->filename);
if (queueItem->data == nullptr) {
fs::File file = contentFS->open(queueItem->filename);
if (!file) {
Serial.print("No current file. Canceling request\n");
prepareCancelPending(br->src);
return;
}
taginfo->data = getDataForFile(file);
queueItem->data = getDataForFile(file);
Serial.println("Reading file " + String(queueItem->filename) + " in " + String(millis() - t) + "ms");
file.close();
}
// check if we're not exceeding max blocks (to prevent sendBlock from exceeding its boundary)
uint8_t totalblocks = (taginfo->len / BLOCK_DATA_SIZE);
if (taginfo->len % BLOCK_DATA_SIZE) totalblocks++;
uint8_t totalblocks = (queueItem->len / BLOCK_DATA_SIZE);
if (queueItem->len % BLOCK_DATA_SIZE) totalblocks++;
if (br->blockId >= totalblocks) {
br->blockId = totalblocks - 1;
}
uint32_t len = taginfo->len - (BLOCK_DATA_SIZE * br->blockId);
uint32_t len = queueItem->len - (BLOCK_DATA_SIZE * br->blockId);
if (len > BLOCK_DATA_SIZE) len = BLOCK_DATA_SIZE;
uint16_t checksum = sendBlock(taginfo->data + (br->blockId * BLOCK_DATA_SIZE), len);
uint16_t checksum = sendBlock(queueItem->data + (br->blockId * BLOCK_DATA_SIZE), len);
char buffer[150];
sprintf(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X block request %s block %d, len %d checksum %u\0", br->src[7], br->src[6], br->src[5], br->src[4], br->src[3], br->src[2], br->src[1], br->src[0], taginfo->filename.c_str(), br->blockId, len, checksum);
sprintf(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X block request %s block %d, len %d checksum %u\0", br->src[7], br->src[6], br->src[5], br->src[4], br->src[3], br->src[2], br->src[1], br->src[0], queueItem->filename, br->blockId, len, checksum);
wsLog((String)buffer);
Serial.printf("<RQB file %s block %d, len %d checksum %u\n\0", taginfo->filename.c_str(), br->blockId, len, checksum);
Serial.printf("<RQB file %s block %d, len %d checksum %u\n\0", queueItem->filename, br->blockId, len, checksum);
}
void processXferComplete(struct espXferComplete* xfc, bool local) {
@@ -395,27 +387,34 @@ void processXferComplete(struct espXferComplete* xfc, bool local) {
Serial.printf("<REMOTE XFC %02X%02X%02X%02X%02X%02X%02X%02X\n", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]);
}
char src_path[64];
char dst_path[64];
sprintf(src_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\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(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.raw\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]);
if (contentFS->exists(dst_path) && contentFS->exists(src_path)) {
contentFS->remove(dst_path);
}
if (contentFS->exists(src_path)) {
if (config.preview) {
contentFS->rename(src_path, dst_path);
} else {
contentFS->remove(src_path);
}
}
time_t now;
time(&now);
char dst_path[64];
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.raw\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]);
uint8_t md5bytes[16];
PendingItem* queueItem = getQueueItem(xfc->src);
if (queueItem != nullptr) {
if (contentFS->exists(dst_path) && contentFS->exists(queueItem->filename)) {
contentFS->remove(dst_path);
}
if (contentFS->exists(queueItem->filename)) {
if (config.preview && (queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_2BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_1BPP)) {
contentFS->rename(queueItem->filename, String(dst_path));
} else {
contentFS->remove(queueItem->filename);
}
}
memcpy(md5bytes, &queueItem->pendingdata.availdatainfo.dataVer, sizeof(uint64_t));
dequeueItem(xfc->src);
}
tagRecord* taginfo = tagRecord::findByMAC(xfc->src);
if (taginfo != nullptr) {
memcpy(taginfo->md5, taginfo->md5pending, sizeof(taginfo->md5pending));
clearPending(taginfo);
memcpy(taginfo->md5, md5bytes, sizeof(md5bytes));
taginfo->pendingCount = countQueueItem(xfc->src);
taginfo->wakeupReason = 0;
if (taginfo->contentMode == 12 && local == false) {
if (contentFS->exists(dst_path)) {
@@ -428,6 +427,10 @@ void processXferComplete(struct espXferComplete* xfc, bool local) {
taginfo->nextupdate = now;
}
}
// more in the queue?
checkQueue(xfc->src);
wsSendTaginfo(xfc->src, SYNC_TAGSTATUS);
if (local) udpsync.netProcessXferComplete(xfc);
}
@@ -451,9 +454,13 @@ void processXferTimeout(struct espXferComplete* xfc, bool local) {
tagRecord* taginfo = tagRecord::findByMAC(xfc->src);
if (taginfo != nullptr) {
taginfo->pendingIdle = 60;
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
clearPending(taginfo);
while (dequeueItem(xfc->src)) {
};
}
checkQueue(xfc->src);
wsSendTaginfo(xfc->src, SYNC_TAGSTATUS);
if (local) udpsync.netProcessXferTimeout(xfc);
}
@@ -469,7 +476,7 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP
if (config.lock == 1 || (config.lock == 2 && eadr->adr.wakeupReason != WAKEUP_REASON_FIRSTBOOT)) return;
taginfo = new tagRecord;
memcpy(taginfo->mac, eadr->src, sizeof(taginfo->mac));
taginfo->pending = false;
taginfo->pendingCount = 0;
tagDB.push_back(taginfo);
}
time_t now;
@@ -505,9 +512,7 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP
if (eadr->adr.lastPacketRSSI != 0) {
if (eadr->adr.wakeupReason >= 0xE0) {
if (!taginfo->pending) taginfo->nextupdate = 0;
memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
if (taginfo->pendingCount == 0) taginfo->nextupdate = 0;
if (local) {
const char* reason = "";
@@ -523,13 +528,6 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP
logLine(buffer);
}
}
/*
if (local && taginfo->batteryMv != eadr->adr.batteryMv) {
sprintf(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X battery went from %.2fV to %.2fV", eadr->src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0], static_cast<float>(taginfo->batteryMv) / 1000.0, static_cast<float>(eadr->adr.batteryMv) / 1000.0);
logLine(buffer);
}
*/
taginfo->LQI = eadr->adr.lastPacketLQI;
taginfo->hwType = eadr->adr.hwType;
@@ -545,8 +543,6 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP
if (local) {
sprintf(buffer, "<ADR %02X%02X%02X%02X%02X%02X%02X%02X\n\0", eadr->src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]);
Serial.print(buffer);
} else {
// sprintf(buffer, "<REMOTE ADR %02X%02X%02X%02X%02X%02X%02X%02X\n\0", eadr->src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]);
}
if (local) {
@@ -571,17 +567,17 @@ void processTagReturnData(struct espTagReturnData* trd, uint8_t len, bool local)
sprintf(buffer, "TRD Data: len=%d, type=%d, ver=0x%08X\n", payloadLength, trd->returnData.dataType, trd->returnData.dataVer);
wsLog((String)buffer);
#ifndef SAVE_SPACE
TagData::parse(trd->src, trd->returnData.dataType, trd->returnData.data, payloadLength);
#endif
}
void refreshAllPending() {
for (int16_t c = 0; c < tagDB.size(); c++) {
tagRecord* taginfo = tagDB.at(c);
if (taginfo->pending && taginfo->version == 0) {
if (taginfo->pendingCount > 0 && taginfo->version == 0) {
clearPending(taginfo);
taginfo->nextupdate = 0;
memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
wsSendTaginfo(taginfo->mac, SYNC_TAGSTATUS);
}
}
@@ -592,8 +588,6 @@ void updateContent(const uint8_t* dst) {
if (taginfo != nullptr) {
clearPending(taginfo);
taginfo->nextupdate = 0;
memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
wsSendTaginfo(taginfo->mac, SYNC_TAGSTATUS);
}
}
@@ -622,7 +616,7 @@ bool sendAPSegmentedData(const uint8_t* dst, String data, uint16_t icons, bool i
pending.attemptsLeft = 120;
Serial.printf(">AP Segmented Data %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
if (local) {
return sendDataAvail(&pending);
return queueDataAvail(&pending);
} else {
udpsync.netSendDataAvail(&pending);
return true;
@@ -640,7 +634,7 @@ bool showAPSegmentedInfo(const uint8_t* dst, bool local) {
pending.attemptsLeft = 120;
Serial.printf(">SDA %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
if (local) {
return sendDataAvail(&pending);
return queueDataAvail(&pending);
} else {
udpsync.netSendDataAvail(&pending);
return true;
@@ -659,8 +653,15 @@ bool sendTagCommand(const uint8_t* dst, uint8_t cmd, bool local, const uint8_t*
}
pending.attemptsLeft = 120;
Serial.printf(">Tag CMD %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
tagRecord* taginfo = tagRecord::findByMAC(dst);
if (taginfo != nullptr) {
taginfo->pendingCount++;
wsSendTaginfo(taginfo->mac, SYNC_TAGSTATUS);
}
if (local) {
return sendDataAvail(&pending);
return queueDataAvail(&pending);
} else {
udpsync.netSendDataAvail(&pending);
return true;
@@ -674,7 +675,7 @@ void updateTaginfoitem(struct TagInfo* taginfoitem, IPAddress remoteIP) {
if (config.lock) return;
taginfo = new tagRecord;
memcpy(taginfo->mac, taginfoitem->mac, sizeof(taginfo->mac));
taginfo->pending = false;
taginfo->pendingCount = 0;
tagDB.push_back(taginfo);
}
tagRecord initialTagInfo = *taginfo;
@@ -687,7 +688,7 @@ void updateTaginfoitem(struct TagInfo* taginfoitem, IPAddress remoteIP) {
case SYNC_TAGSTATUS:
taginfo->lastseen = taginfoitem->lastseen;
taginfo->nextupdate = taginfoitem->nextupdate;
taginfo->pending = taginfoitem->pending;
taginfo->pendingCount = taginfoitem->pendingCount;
taginfo->expectedNextCheckin = taginfoitem->expectedNextCheckin;
taginfo->hwType = taginfoitem->hwType;
taginfo->wakeupReason = taginfoitem->wakeupReason;
@@ -739,24 +740,23 @@ bool checkMirror(struct tagRecord* taginfo, struct pendingData* pending) {
taginfo2->len = taginfo->len;
taginfo2->data = taginfo->data; // copy buffer pointer
taginfo2->dataType = taginfo->dataType;
taginfo2->pending = true;
taginfo2->pendingCount++;
taginfo2->nextupdate = 3216153600;
memcpy(taginfo2->md5pending, taginfo->md5pending, sizeof(taginfo->md5pending));
struct pendingData pending2 = {0};
memcpy(pending2.targetMac, taginfo2->mac, 8);
pending2.availdatainfo.dataType = taginfo2->dataType;
pending2.availdatainfo.dataVer = *((uint64_t*)taginfo2->md5pending);
pending2.availdatainfo.dataVer = pending->availdatainfo.dataVer;
pending2.availdatainfo.dataSize = taginfo2->len;
pending2.availdatainfo.dataTypeArgument = pending->availdatainfo.dataTypeArgument;
pending2.availdatainfo.nextCheckIn = pending->availdatainfo.nextCheckIn;
pending2.attemptsLeft = pending->attemptsLeft;
if (taginfo2->isExternal == false) {
sendDataAvail(&pending2);
queueDataAvail(&pending2);
} else {
char dst_path[64];
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", taginfo2->mac[7], taginfo2->mac[6], taginfo2->mac[5], taginfo2->mac[4], taginfo2->mac[3], taginfo2->mac[2], taginfo2->mac[1], taginfo2->mac[0]);
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X_%lu.pending", taginfo2->mac[7], taginfo2->mac[6], taginfo2->mac[5], taginfo2->mac[4], taginfo2->mac[3], taginfo2->mac[2], taginfo2->mac[1], taginfo2->mac[0], millis());
xSemaphoreTake(fsMutex, portMAX_DELAY);
File file = contentFS->open(dst_path, "w");
if (file) {
@@ -775,3 +775,123 @@ bool checkMirror(struct tagRecord* taginfo, struct pendingData* pending) {
}
return false;
}
void enqueueItem(struct PendingItem& item) {
std::lock_guard<std::mutex> lock(queueMutex);
pendingQueue.push_back(item);
}
bool dequeueItem(const uint8_t* targetMac) {
return dequeueItem(targetMac, 0);
}
bool dequeueItem(const uint8_t* targetMac, const uint64_t dataVer) {
std::lock_guard<std::mutex> lock(queueMutex);
auto it = std::find_if(pendingQueue.begin(), pendingQueue.end(),
[targetMac, dataVer](const PendingItem& item) {
bool macMatches = memcmp(item.pendingdata.targetMac, targetMac, sizeof(item.pendingdata.targetMac)) == 0;
bool dataVerMatches = (dataVer == 0) || (dataVer == item.pendingdata.availdatainfo.dataVer);
return macMatches && dataVerMatches;
});
if (it != pendingQueue.end()) {
if (it->data != nullptr) {
int datacount = 0;
for (const PendingItem& item : pendingQueue) {
if (item.data == it->data) {
datacount++;
}
}
if (datacount == 1) {
free(it->data);
}
it->data = nullptr;
}
pendingQueue.erase(it);
return true;
}
return false;
}
uint16_t countQueueItem(const uint8_t* targetMac) {
std::unique_lock<std::mutex> lock(queueMutex);
int count = std::count_if(pendingQueue.begin(), pendingQueue.end(),
[targetMac](const PendingItem& item) {
return memcmp(item.pendingdata.targetMac, targetMac, sizeof(item.pendingdata.targetMac)) == 0;
});
return count;
}
PendingItem* getQueueItem(const uint8_t* targetMac) {
return getQueueItem(targetMac, 0);
}
PendingItem* getQueueItem(const uint8_t* targetMac, const uint64_t dataVer) {
auto it = std::find_if(pendingQueue.begin(), pendingQueue.end(),
[targetMac, dataVer](const PendingItem& item) {
bool macMatches = memcmp(item.pendingdata.targetMac, targetMac, sizeof(item.pendingdata.targetMac)) == 0;
bool dataVerMatches = (dataVer == 0) || (dataVer == item.pendingdata.availdatainfo.dataVer);
return macMatches && dataVerMatches;
});
if (it != pendingQueue.end()) {
return &(*it);
} else {
return nullptr;
}
}
void checkQueue(const uint8_t* targetMac) {
uint16_t queueCount;
queueCount = countQueueItem(targetMac);
if (queueCount > 0) {
Serial.printf("more from queue: total %d elements\n", pendingQueue.size());
PendingItem* queueItem = getQueueItem(targetMac);
if (queueItem == nullptr) {
return;
}
if (queueCount > 1) queueItem->pendingdata.availdatainfo.nextCheckIn = 0;
sendDataAvail(&queueItem->pendingdata);
}
}
bool queueDataAvail(struct pendingData* pending) {
PendingItem newPending;
newPending.pendingdata.availdatainfo = pending->availdatainfo;
newPending.pendingdata.attemptsLeft = pending->attemptsLeft;
std::copy(pending->targetMac, pending->targetMac + sizeof(pending->targetMac), newPending.pendingdata.targetMac);
tagRecord* taginfo = tagRecord::findByMAC(pending->targetMac);
if (taginfo == nullptr) {
return false;
}
std::strcpy(newPending.filename, taginfo->filename.c_str());
if (taginfo->data != nullptr) {
// move data pointer
newPending.data = taginfo->data;
taginfo->data = nullptr;
} else {
newPending.data = nullptr;
// optional: read data early, don't wait for block request.
fs::File file = contentFS->open(newPending.filename);
if (file) {
newPending.data = getDataForFile(file);
Serial.println("Reading file " + String(newPending.filename));
file.close();
}
}
newPending.len = taginfo->len;
if (countQueueItem(pending->targetMac) == 0) {
enqueueItem(newPending);
// first in line, send to tag
Serial.printf("queue item added, first in line, total %d elements\n", pendingQueue.size());
sendDataAvail(pending);
} else {
enqueueItem(newPending);
Serial.printf("queue item added, total %d elements\n", pendingQueue.size());
}
return true;
}

View File

@@ -1,8 +1,8 @@
#include "serialap.h"
#include <Arduino.h>
#include <HardwareSerial.h>
#include "serialap.h"
#include "commstructs.h"
#include "contentmanager.h"
#include "flasher.h"
@@ -15,7 +15,6 @@
#include "zbs_interface.h"
QueueHandle_t rxCmdQueue;
SemaphoreHandle_t txActive;
// If a command is sent, it will wait for a reply here
@@ -28,7 +27,7 @@ volatile uint8_t cmdReplyValue = CMD_REPLY_WAIT;
#define AP_SERIAL_PORT Serial1
volatile bool rxSerialStopTask2 = false;
uint8_t channelList[6];
uint8_t channelList[6];
struct espSetChannelPower curChannel = {0, 11, 10};
#define RX_CMD_RQB 0x01
@@ -43,8 +42,6 @@ struct espSetChannelPower curChannel = {0, 11, 10};
volatile uint32_t lastAPActivity = 0;
struct APInfoS apInfo;
extern uint8_t* getDataForFile(File& file);
struct rxCmd {
uint8_t* data;
uint8_t len;
@@ -98,7 +95,7 @@ bool waitCmdReply() {
break;
case CMD_REPLY_ACK:
lastAPActivity = millis();
if(apInfo.isOnline == false)
if (apInfo.isOnline == false)
setAPstate(true, AP_STATE_ONLINE);
return true;
break;
@@ -150,8 +147,7 @@ void setAPstate(bool isOnline, uint8_t state) {
CRGB::Yellow,
CRGB::Aqua,
CRGB::Red,
CRGB::YellowGreen
};
CRGB::YellowGreen};
rgbIdleColor = colorMap[state];
rgbIdlePeriod = (isOnline ? 767 : 255);
#endif
@@ -222,6 +218,7 @@ blksend:
txEnd();
return bd->checksum;
}
bool sendDataAvail(struct pendingData* pending) {
if (!apInfo.isOnline) return false;
if (!txStart()) return false;
@@ -462,7 +459,7 @@ void rxSerialTask(void* parameter) {
packetp = (uint8_t*)calloc(sizeof(struct espBlockRequest) + 8, 1);
memset(cmdbuffer, 0x00, 4);
lastAPActivity = millis();
if(apInfo.isOnline == false)
if (apInfo.isOnline == false)
setAPstate(true, AP_STATE_ONLINE);
}
if (strncmp(cmdbuffer, "ADR>", 4) == 0) {
@@ -472,7 +469,7 @@ void rxSerialTask(void* parameter) {
packetp = (uint8_t*)calloc(sizeof(struct espAvailDataReq) + 8, 1);
memset(cmdbuffer, 0x00, 4);
lastAPActivity = millis();
if(apInfo.isOnline == false)
if (apInfo.isOnline == false)
setAPstate(true, AP_STATE_ONLINE);
}
if (strncmp(cmdbuffer, "XFC>", 4) == 0) {
@@ -496,7 +493,7 @@ void rxSerialTask(void* parameter) {
packetp = (uint8_t*)calloc(sizeof(struct espTagReturnData) + 8, 1);
memset(cmdbuffer, 0x00, 4);
lastAPActivity = millis();
if(apInfo.isOnline == false)
if (apInfo.isOnline == false)
setAPstate(true, AP_STATE_ONLINE);
}
break;
@@ -535,7 +532,7 @@ void rxSerialTask(void* parameter) {
case ZBS_RX_WAIT_TAG_RETURN_DATA: {
packetp[pktindex] = lastchar;
pktindex++;
if ((pktindex > 10) && (pktindex >= (packetp[9]+10))) {
if ((pktindex > 10) && (pktindex >= (packetp[9] + 10))) {
addRXQueue(packetp, pktindex, RX_CMD_TRD);
RXState = ZBS_RX_WAIT_HEADER;
}
@@ -582,7 +579,7 @@ void rxSerialTask(void* parameter) {
charindex++;
if (charindex == 2) {
RXState = ZBS_RX_WAIT_HEADER;
apInfo.pending = (uint8_t)strtoul(cmdbuffer, NULL, 16);
apInfo.pendingBuffer = (uint8_t)strtoul(cmdbuffer, NULL, 16);
}
break;
case ZBS_RX_WAIT_NOP:
@@ -772,7 +769,6 @@ void APTask(void* parameter) {
updateContent(apInfo.mac);
}
uint16_t fsversion;
if (FLASHER_AP_MOSI != -1) {
fsversion = getAPUpdateVersion(apInfo.type);
@@ -901,7 +897,7 @@ void APTask(void* parameter) {
if (!reply) {
attempts++;
} else {
if(apInfo.isOnline == false)
if (apInfo.isOnline == false)
setAPstate(true, AP_STATE_ONLINE);
attempts = 0;
}
@@ -913,7 +909,7 @@ void APTask(void* parameter) {
#ifdef HAS_RGB_LED
showColorPattern(CRGB::Yellow, CRGB::Yellow, CRGB::Red);
#endif
lastAPActivity = millis();// we set this to retrigger a recovery in AP_ACTIVITY_MAX_INTERVAL seconds
lastAPActivity = millis(); // we set this to retrigger a recovery in AP_ACTIVITY_MAX_INTERVAL seconds
} else {
setAPstate(true, AP_STATE_ONLINE);
attempts = 0;

View File

@@ -176,7 +176,6 @@ void DynStorage::end() {
}
void listDir(fs::FS& fs, const char* dirname, uint8_t levels) {
Storage.begin();
Serial.printf(" \n ");
Serial.printf("Listing directory: %s\n", dirname);

View File

@@ -103,7 +103,7 @@ void fillNode(JsonObject& tag, const tagRecord* taginfo) {
tag["lastseen"] = taginfo->lastseen;
tag["nextupdate"] = taginfo->nextupdate;
tag["nextcheckin"] = taginfo->expectedNextCheckin;
tag["pending"] = taginfo->pending;
tag["pending"] = taginfo->pendingCount;
tag["alias"] = taginfo->alias;
tag["contentMode"] = taginfo->contentMode;
tag["LQI"] = taginfo->LQI;
@@ -128,7 +128,6 @@ void saveDB(const String& filename) {
const long t = millis();
Storage.begin();
xSemaphoreTake(fsMutex, portMAX_DELAY);
fs::File existingFile = contentFS->open(filename, "r");
@@ -174,7 +173,6 @@ bool loadDB(const String& filename) {
Serial.println("reading DB from " + String(filename));
const long t = millis();
Storage.begin();
fs::File readfile = contentFS->open(filename, "r");
if (!readfile) {
Serial.println("loadDB: Failed to open file");
@@ -206,14 +204,13 @@ bool loadDB(const String& filename) {
taginfo->md5[i] = strtoul(md5.substring(i * 2, i * 2 + 2).c_str(), NULL, 16);
}
}
memcpy(taginfo->md5pending, taginfo->md5, sizeof(taginfo->md5));
taginfo->lastseen = (uint32_t)tag["lastseen"];
taginfo->nextupdate = (uint32_t)tag["nextupdate"];
taginfo->expectedNextCheckin = (uint32_t)tag["nextcheckin"];
if (taginfo->expectedNextCheckin < now) {
taginfo->expectedNextCheckin = now + 1800;
}
taginfo->pending = false;
taginfo->pendingCount = 0;
taginfo->alias = tag["alias"].as<String>();
taginfo->contentMode = tag["contentMode"];
taginfo->LQI = tag["LQI"];
@@ -303,11 +300,9 @@ void clearPending(tagRecord* taginfo) {
}
taginfo->data = nullptr;
}
taginfo->pending = false;
}
void initAPconfig() {
Storage.begin();
DynamicJsonDocument APconfig(500);
File configFile = contentFS->open("/current/apconfig.json", "r");
if (configFile) {

View File

@@ -1,5 +1,7 @@
#include "tagdata.h"
#ifndef SAVE_SPACE
#include "tag_db.h"
#include "util.h"
@@ -8,7 +10,6 @@ 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;
@@ -143,3 +144,5 @@ void TagData::parse(const uint8_t src[8], const size_t id, const uint8_t* data,
Serial.printf("Set %s to %s\n", varName.c_str(), value.c_str());
}
}
#endif

View File

@@ -169,7 +169,7 @@ void wsSendTaginfo(const uint8_t *mac, uint8_t syncMode) {
if (syncMode == SYNC_TAGSTATUS) {
taginfoitem.lastseen = taginfo->lastseen;
taginfoitem.nextupdate = taginfo->nextupdate;
taginfoitem.pending = taginfo->pending;
taginfoitem.pendingCount = taginfo->pendingCount;
taginfoitem.expectedNextCheckin = taginfo->expectedNextCheckin;
taginfoitem.hwType = taginfo->hwType;
taginfoitem.wakeupReason = taginfo->wakeupReason;
@@ -215,7 +215,6 @@ uint8_t wsClientCount() {
void init_web() {
wsMutex = xSemaphoreCreateMutex();
Storage.begin();
WiFi.mode(WIFI_STA);
WiFi.setTxPower(static_cast<wifi_power_t>(config.wifiPower));
@@ -240,7 +239,6 @@ void init_web() {
server.serveStatic("/current", *contentFS, "/current/").setCacheControl("max-age=604800");
server.serveStatic("/tagtypes", *contentFS, "/tagtypes/").setCacheControl("max-age=600");
server.serveStatic("/", *contentFS, "/www/").setDefaultFile("index.html");
server.on(
"/imgupload", HTTP_POST, [](AsyncWebServerRequest *request) {
@@ -274,12 +272,19 @@ void init_web() {
String dst = request->getParam("mac")->value();
uint8_t mac[8];
if (hex2mac(dst, mac)) {
const tagRecord *taginfo = tagRecord::findByMAC(mac);
tagRecord *taginfo = tagRecord::findByMAC(mac);
if (taginfo != nullptr) {
if (taginfo->pending == true) {
request->send_P(200, "application/octet-stream", taginfo->data, taginfo->len);
return;
if (taginfo->data == nullptr) {
fs::File file = contentFS->open(taginfo->filename);
if (!file) {
request->send(404, "text/plain", "File not found");
return;
}
taginfo->data = getDataForFile(file);
file.close();
}
request->send_P(200, "application/octet-stream", taginfo->data, taginfo->len);
return;
}
}
}
@@ -311,8 +316,6 @@ void init_web() {
if (request->hasParam("invert", true)) {
taginfo->invert = atoi(request->getParam("invert", true)->value().c_str());
}
// memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
// memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
wsSendTaginfo(mac, SYNC_USERCFG);
// saveDB("/current/tagDB.json");
request->send(200, "text/plain", "Ok, saved");
@@ -337,7 +340,9 @@ void init_web() {
}
if (strcmp(cmdValue, "clear") == 0) {
clearPending(taginfo);
memcpy(taginfo->md5pending, taginfo->md5, sizeof(taginfo->md5pending));
while (dequeueItem(mac)) {
};
taginfo->pendingCount = countQueueItem(mac);
wsSendTaginfo(mac, SYNC_TAGSTATUS);
}
if (strcmp(cmdValue, "refresh") == 0) {
@@ -660,6 +665,8 @@ void init_web() {
request->send(404);
});
server.serveStatic("/", *contentFS, "/www/").setDefaultFile("index.html");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "content-type");

View File

@@ -410,7 +410,7 @@
if (ext == "raw" || ext == "pending") {
let storedTagTypes = localStorage.getItem("tagTypes");
let tagTypes = JSON.parse(storedTagTypes);
let mac = name.substring(name.lastIndexOf('/') + 1);
let mac = name.substring(name.lastIndexOf('/') + 1, name.lastIndexOf('/') + 17);
let targetDiv = null;
if (window.opener && !window.opener.closed) {
targetDiv = window.opener.document.querySelector(`div[data-mac="${mac}"]`);

View File

@@ -6,8 +6,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>Open EPaper Link Access Point</title>
<script src="main.js" defer></script>
<link rel="stylesheet" href="main.css" type="text/css" />
<link rel="icon" type="image/vnd.icon" href="favicon.ico">
<!--<link rel="icon" type="image/vnd.icon" href="favicon.ico">-->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0" />
</head>
@@ -525,8 +526,6 @@
style="display: none; position: absolute; background: white; border: 1px solid gray; padding: 0; list-style: none;">
</ul>
<script src="main.js"></script>
</body>
</html>

View File

@@ -626,12 +626,13 @@ select {
.pendingicon {
width: 20px;
height: 20px;
background-color: rgb(7, 174, 230);
background-color: rgb(1, 154, 201);
font-size: 1.2em;
text-align: center;
font-weight: bold;
display: none;
vertical-align: top;
color: white;
}
.warningicon {

View File

@@ -40,7 +40,6 @@ let otamodule;
let socket;
let finishedInitialLoading = false;
let getTagtypeBusy = false;
let webVersion = "0";
const loadConfig = new Event("loadConfig");
window.addEventListener("loadConfig", function () {
@@ -57,14 +56,18 @@ window.addEventListener("loadConfig", function () {
});
window.addEventListener("load", function () {
initVersionInfo();
window.dispatchEvent(loadConfig);
initTabs();
fetch('/content_cards.json')
.then(response => response.json())
.then(data => {
cardconfig = data;
loadTags(0);
connect();
loadTags(0)
.then(() => {
finishedInitialLoading = true;
connect();
})
.catch(error => showMessage('loadTags error: ' + error));
setInterval(updatecards, 1000);
})
.catch(error => {
@@ -72,26 +75,17 @@ window.addEventListener("load", function () {
alert("I can't load /www/content_cards.json.\r\nHave you upload it to the data partition?");
});
window.dispatchEvent(loadConfig);
dropUpload();
populateTimes($('#apcnight1'));
populateTimes($('#apcnight2'));
});
function initVersionInfo() {
fetch('/version.txt')
.then(response => {
return response.text();
})
.then(data => {
webVersion = data;
console.log(webVersion);
})
.catch(error => {
console.error('Fetch error:', error);
});
}
document.addEventListener('DOMContentLoaded', function () {
var faviconLink = document.createElement('link');
faviconLink.rel = 'icon';
faviconLink.href = 'favicon.ico';
document.head.appendChild(faviconLink);
});
});
/* tabs */
let activeTab = '';
@@ -119,14 +113,14 @@ function initTabs() {
};
function loadTags(pos) {
fetch("/get_db?pos=" + pos)
return fetch("/get_db?pos=" + pos)
.then(response => response.json())
.then(data => {
processTags(data.tags);
if (data.continu && data.continu > pos) loadTags(data.continu);
finishedInitialLoading = true;
})
//.catch(error => showMessage('loadTags error: ' + error));
if (data.continu && data.continu > pos) {
return loadTags(data.continu);
}
});
}
function formatUptime(seconds) {
@@ -280,7 +274,24 @@ function processTags(tagArray) {
div.dataset.ver = element.ver;
$('#tag' + localTagmac + ' .resolution').innerHTML += ` fw:${element.ver} 0x${element.ver.toString(16)}`;
}
if (!apConfig.preview || element.contentMode == 20) {
$('#tag' + tagmac + ' .tagimg').style.display = 'none'
} else if (div.dataset.hash != element.hash && div.dataset.hwtype > -1) {
let cachetag = element.hash;
if (element.hash != '00000000000000000000000000000000') {
if (element.isexternal && element.contentMode == 12) {
loadImage(tagmac, 'http://' + tagDB[tagmac].apip + '/current/' + tagmac + '.raw?' + cachetag);
} else {
loadImage(tagmac, '/current/' + tagmac + '.raw?' + cachetag);
}
} else {
$('#tag' + tagmac + ' .tagimg').style.display = 'none'
}
div.dataset.hash = element.hash;
}
})();
let statusline = "";
if (element.RSSI != 100) {
if (element.ch > 0) statusline += `CH ${element.ch}, `;
@@ -293,29 +304,13 @@ function processTags(tagArray) {
}
$('#tag' + tagmac + ' .received').innerHTML = statusline;
$('#tag' + tagmac + ' .received').style.opacity = "1";
} else {
$('#tag' + tagmac + ' .model').innerHTML = "waiting for hardware type";
$('#tag' + tagmac + ' .received').style.opacity = "0";
$('#tag' + tagmac + ' .resolution').innerHTML = "";
}
if (!apConfig.preview || element.contentMode == 20) {
$('#tag' + tagmac + ' .tagimg').style.display = 'none'
} else if (div.dataset.hash != element.hash && div.dataset.hwtype > -1) {
let cachetag = element.hash;
if (element.hash != '00000000000000000000000000000000') {
//cachetag = Math.random();
if (element.isexternal && element.contentMode == 12) {
loadImage(tagmac, 'http://' + tagDB[tagmac].apip + '/current/' + tagmac + '.raw?' + cachetag);
} else {
loadImage(tagmac, '/current/' + tagmac + '.raw?' + cachetag);
}
} else {
$('#tag' + tagmac + ' .tagimg').style.display = 'none'
}
div.dataset.hash = element.hash;
}
if (element.nextupdate > 1672531200 && element.nextupdate != 3216153600) {
const date = new Date(element.nextupdate * 1000);
const options = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
@@ -379,6 +374,7 @@ function processTags(tagArray) {
break;
}
$('#tag' + tagmac + ' .pendingicon').style.display = (element.pending ? 'inline-block' : 'none');
$('#tag' + tagmac + ' .pendingicon').innerHTML = element.pending;
div.classList.add("tagflash");
(function (tagmac) {
setTimeout(function () { $('#tag' + tagmac).classList.remove("tagflash"); }, 1400);
@@ -438,9 +434,9 @@ function updatecards() {
$('#dashboardTimeout').innerHTML = timeoutcount;
}
$('#clearlog').onclick = function () {
$('#clearlog').addEventListener("click", (event) => {
$('#messages').innerHTML = '';
}
});
document.querySelectorAll('.closebtn').forEach(button => {
button.addEventListener('click', (event) => {
@@ -1301,7 +1297,7 @@ async function getTagtype(hwtype) {
try {
getTagtypeBusy = true;
tagTypes[hwtype] = { busy: true };
const response = await fetch('/tagtypes/' + hwtype.toString(16).padStart(2, '0').toUpperCase() + '.json?' + webVersion);
const response = await fetch('/tagtypes/' + hwtype.toString(16).padStart(2, '0').toUpperCase() + '.json');
if (!response.ok) {
let data = { name: 'unknown id ' + hwtype.toString(16), width: 0, height: 0, bpp: 0, rotatebuffer: 0, colortable: [], busy: false };
tagTypes[hwtype] = data;
@@ -1539,7 +1535,6 @@ function populateAPCard(msg) {
}
});
// $('#ap' + apid + ' .apversion').innerHTML = msg.version;
if (activeTab == 'aptab') {
populateAPInfo(apip);
}