From 2edbf2703386079d056757358f7af2175d581f41 Mon Sep 17 00:00:00 2001
From: Nic Limper
Date: Sun, 20 Aug 2023 20:16:34 +0200
Subject: [PATCH] various fixes / sleep at night / resend image at tag boot
- apconfig: configure night time, where the system is not updating tags, and the tags are sleeping
- fixed bug calculating expected checkin
- throttle down apinfo update to once per minute
- fixed too many concurrent requests getting tagtypes
- resend current image when a tag reboots and the content type is 'static image' (all other content types were already regenerating the content)
- fixed timing of main loop
---
ESP32_AP-Flasher/include/newproto.h | 2 +-
ESP32_AP-Flasher/include/storage.h | 5 ++-
ESP32_AP-Flasher/include/tag_db.h | 2 ++
ESP32_AP-Flasher/include/util.h | 20 +++++++++++
ESP32_AP-Flasher/src/contentmanager.cpp | 43 +++++++++++++++++------
ESP32_AP-Flasher/src/main.cpp | 16 ++++++---
ESP32_AP-Flasher/src/newproto.cpp | 29 +++++++++++-----
ESP32_AP-Flasher/src/tag_db.cpp | 4 +++
ESP32_AP-Flasher/src/web.cpp | 33 ++++++++++++------
ESP32_AP-Flasher/wwwroot/index.html | 6 ++++
ESP32_AP-Flasher/wwwroot/main.js | 46 +++++++++++++++++++++++--
11 files changed, 167 insertions(+), 39 deletions(-)
diff --git a/ESP32_AP-Flasher/include/newproto.h b/ESP32_AP-Flasher/include/newproto.h
index 7746a2db..90a95986 100644
--- a/ESP32_AP-Flasher/include/newproto.h
+++ b/ESP32_AP-Flasher/include/newproto.h
@@ -7,7 +7,7 @@ extern void processBlockRequest(struct espBlockRequest* br);
extern void prepareCancelPending(const uint8_t dst[8]);
extern void prepareIdleReq(const uint8_t* dst, uint16_t nextCheckin);
extern void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, const uint8_t* dst);
-extern bool prepareDataAvail(String& filename, uint8_t dataType, const uint8_t* dst, uint16_t nextCheckin);
+extern bool prepareDataAvail(String& filename, uint8_t dataType, const uint8_t* dst, uint16_t nextCheckin, bool resend = false);
extern void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP);
extern void processXferComplete(struct espXferComplete* xfc, bool local);
extern void processXferTimeout(struct espXferComplete* xfc, bool local);
diff --git a/ESP32_AP-Flasher/include/storage.h b/ESP32_AP-Flasher/include/storage.h
index a1732e25..c17713e8 100644
--- a/ESP32_AP-Flasher/include/storage.h
+++ b/ESP32_AP-Flasher/include/storage.h
@@ -35,5 +35,8 @@ class DynStorage {
extern DynStorage Storage;
extern fs::FS *contentFS;
+extern void copyFile(File in, File out);
+
+#endif
+
-#endif
\ No newline at end of file
diff --git a/ESP32_AP-Flasher/include/tag_db.h b/ESP32_AP-Flasher/include/tag_db.h
index 2125dc08..1d415fb7 100644
--- a/ESP32_AP-Flasher/include/tag_db.h
+++ b/ESP32_AP-Flasher/include/tag_db.h
@@ -66,6 +66,8 @@ struct Config {
uint8_t preview;
uint8_t wifiPower;
char timeZone[52];
+ uint8_t sleepTime1;
+ uint8_t sleepTime2;
};
struct HwType {
diff --git a/ESP32_AP-Flasher/include/util.h b/ESP32_AP-Flasher/include/util.h
index 6719cacc..551c4cb5 100644
--- a/ESP32_AP-Flasher/include/util.h
+++ b/ESP32_AP-Flasher/include/util.h
@@ -104,4 +104,24 @@ static inline bool isEmptyOrNull(const String &str) {
return str.isEmpty() || str == "null";
}
+/// @brief checks if the current time is between sleeptime1 and sleeptime2
+///
+/// @param sleeptime1 Start of time block
+/// @param sleeptime2 End of time block
+/// @return True if within time block, false is outside time block
+static bool isSleeping(int sleeptime1, int sleeptime2) {
+ if (sleeptime1 == sleeptime2) return false;
+
+ struct tm timeinfo;
+ getLocalTime(&timeinfo);
+ int currentHour = timeinfo.tm_hour;
+
+ if (sleeptime1 < sleeptime2) {
+ return currentHour >= sleeptime1 && currentHour < sleeptime2;
+ } else {
+ return currentHour >= sleeptime1 || currentHour < sleeptime2;
+ }
+}
+
+
} // namespace util
diff --git a/ESP32_AP-Flasher/src/contentmanager.cpp b/ESP32_AP-Flasher/src/contentmanager.cpp
index 82b2635d..34e2a188 100644
--- a/ESP32_AP-Flasher/src/contentmanager.cpp
+++ b/ESP32_AP-Flasher/src/contentmanager.cpp
@@ -20,9 +20,7 @@
#include "storage.h"
-#if defined CONTENT_RSS || defined CONTENT_CAL
#include "U8g2_for_TFT_eSPI.h"
-#endif
#include "commstructs.h"
#include "makeimage.h"
#include "newproto.h"
@@ -45,19 +43,31 @@ void contentRunner() {
time(&now);
for (tagRecord *taginfo : tagDB) {
- if (taginfo->RSSI && (now >= taginfo->nextupdate || taginfo->wakeupReason == WAKEUP_REASON_GPIO || taginfo->wakeupReason == WAKEUP_REASON_NFC) && config.runStatus == RUNSTATUS_RUN && Storage.freeSpace() > 31000) {
+ if (taginfo->RSSI && (now >= taginfo->nextupdate || taginfo->wakeupReason == WAKEUP_REASON_GPIO || taginfo->wakeupReason == WAKEUP_REASON_NFC) && config.runStatus == RUNSTATUS_RUN && Storage.freeSpace() > 31000 && !util::isSleeping(config.sleepTime1, config.sleepTime2)) {
drawNew(taginfo->mac, (taginfo->wakeupReason == WAKEUP_REASON_GPIO), taginfo);
taginfo->wakeupReason = 0;
}
if (taginfo->expectedNextCheckin > now - 10 && taginfo->expectedNextCheckin < now + 30 && taginfo->pendingIdle == 0 && taginfo->pending == false) {
- uint16_t minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60;
+ int16_t minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60;
if (minutesUntilNextUpdate > config.maxsleep) {
minutesUntilNextUpdate = config.maxsleep;
}
+ if (util::isSleeping(config.sleepTime1, config.sleepTime2)) {
+ struct tm timeinfo;
+ getLocalTime(&timeinfo);
+ struct tm nextSleepTimeinfo = timeinfo;
+ nextSleepTimeinfo.tm_hour = config.sleepTime2;
+ nextSleepTimeinfo.tm_min = 0;
+ nextSleepTimeinfo.tm_sec = 0;
+ time_t nextWakeTime = mktime(&nextSleepTimeinfo);
+ if (nextWakeTime < now) nextWakeTime += 24 * 3600;
+ minutesUntilNextUpdate = (nextWakeTime - now) / 60 - 2;
+ }
if (minutesUntilNextUpdate > 1 && (wsClientCount() == 0 || config.stopsleep == 0)) {
taginfo->pendingIdle = minutesUntilNextUpdate;
if (taginfo->isExternal == false) {
+ Serial.printf("sleeping for %d more minutes\n", minutesUntilNextUpdate);
prepareIdleReq(taginfo->mac, minutesUntilNextUpdate);
}
}
@@ -186,15 +196,29 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo
switch (taginfo->contentMode) {
case 0: // Image
{
- const String configFilename = cfgobj["filename"].as();
- if (!util::isEmptyOrNull(configFilename) && !cfgobj["#fetched"].as()) {
- imageParams.dither = cfgobj["dither"] && cfgobj["dither"] == "1";
- jpg2buffer(configFilename, filename, imageParams);
+ String configFilename = cfgobj["filename"].as();
+ if (!util::isEmptyOrNull(configFilename)) {
+ if (!configFilename.startsWith("/")) {
+ configFilename = "/" + configFilename;
+ }
+ if (contentFS->exists(configFilename)) {
+ imageParams.dither = cfgobj["dither"] && cfgobj["dither"] == "1";
+ jpg2buffer(configFilename, filename, imageParams);
+ } else {
+ filename = "/current/" + String(hexmac) + ".raw";
+ if (contentFS->exists(filename)) {
+ prepareDataAvail(filename, imageParams.dataType, mac, cfgobj["timetolive"].as(), true);
+ wsLog("File " + configFilename + " not found, resending image " + filename);
+ } else {
+ wsErr("File " + configFilename + " not found");
+ }
+ taginfo->nextupdate = 3216153600;
+ break;
+ }
if (imageParams.hasRed) {
imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
}
if (prepareDataAvail(filename, imageParams.dataType, mac, cfgobj["timetolive"].as())) {
- cfgobj["#fetched"] = true;
if (cfgobj["delete"].as() == "1") {
contentFS->remove("/" + configFilename);
}
@@ -509,7 +533,6 @@ void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams) {
struct tm timeinfo;
localtime_r(&now, &timeinfo);
- // const int weekday_number = timeinfo.tm_wday;
const int month_number = timeinfo.tm_mon;
const int year_number = timeinfo.tm_year + 1900;
diff --git a/ESP32_AP-Flasher/src/main.cpp b/ESP32_AP-Flasher/src/main.cpp
index 0a67e62d..357edea9 100644
--- a/ESP32_AP-Flasher/src/main.cpp
+++ b/ESP32_AP-Flasher/src/main.cpp
@@ -37,19 +37,25 @@ void timeTask(void* parameter) {
wsSendSysteminfo();
util::printHeap();
while (1) {
+ unsigned long startMillis = millis();
time_t now;
time(&now);
-
if (now % 5 == 0 || apInfo.state != AP_STATE_ONLINE || config.runStatus != RUNSTATUS_RUN) {
wsSendSysteminfo();
}
- if (now % 10 == 8 && config.runStatus != RUNSTATUS_STOP) {
+ if (now % 10 == 9 && config.runStatus != RUNSTATUS_STOP) {
checkVars();
}
- if (now % 300 == 6 && config.runStatus != RUNSTATUS_STOP) saveDB("/current/tagDB.json");
- if (apInfo.state == AP_STATE_ONLINE) contentRunner();
+ if (now % 300 == 7 && config.runStatus != RUNSTATUS_STOP) {
+ saveDB("/current/tagDB.json");
+ }
+ if (apInfo.state == AP_STATE_ONLINE) {
+ contentRunner();
+ }
- vTaskDelay(1000 / portTICK_PERIOD_MS);
+ if (millis() - startMillis < 1000) {
+ vTaskDelay((1000 - millis() + startMillis) / portTICK_PERIOD_MS);
+ }
}
}
diff --git a/ESP32_AP-Flasher/src/newproto.cpp b/ESP32_AP-Flasher/src/newproto.cpp
index 9a881393..f759c09a 100644
--- a/ESP32_AP-Flasher/src/newproto.cpp
+++ b/ESP32_AP-Flasher/src/newproto.cpp
@@ -64,7 +64,6 @@ void prepareCancelPending(const uint8_t dst[8]) {
}
void prepareIdleReq(const uint8_t* dst, uint16_t nextCheckin) {
- if (nextCheckin > config.maxsleep) nextCheckin = config.maxsleep;
if (nextCheckin > 0) {
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
@@ -93,7 +92,7 @@ void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, const uint8
memcpy(taginfo->data, data, len);
taginfo->pending = true;
taginfo->len = len;
- taginfo->expectedNextCheckin = 0;
+ taginfo->pendingIdle = 0;
taginfo->filename = String();
taginfo->dataType = dataType;
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
@@ -114,7 +113,7 @@ void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, const uint8
wsSendTaginfo(dst, SYNC_TAGSTATUS);
}
-bool prepareDataAvail(String& filename, uint8_t dataType, const uint8_t* dst, uint16_t nextCheckin) {
+bool prepareDataAvail(String& filename, uint8_t dataType, const uint8_t* dst, uint16_t nextCheckin, bool resend) {
if (nextCheckin > config.maxsleep) nextCheckin = config.maxsleep;
if (wsClientCount() && config.stopsleep == 1) nextCheckin = 0;
#ifdef YELLOW_IPS_AP
@@ -164,7 +163,7 @@ bool prepareDataAvail(String& filename, uint8_t dataType, const uint8_t* dst, ui
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)) {
+ if (contentFS->exists(filename) && resend == false) {
contentFS->remove(filename);
}
return true;
@@ -188,13 +187,15 @@ bool prepareDataAvail(String& filename, uint8_t dataType, const uint8_t* dst, ui
if (contentFS->exists(dst_path)) {
contentFS->remove(dst_path);
}
- contentFS->rename(filename, dst_path);
- filename = String(dst_path);
+ if (resend == false) {
+ contentFS->rename(filename, dst_path);
+ filename = String(dst_path);
+ wsLog("new image: " + String(dst_path));
+ }
- wsLog("new image: " + String(dst_path));
time_t now;
time(&now);
- taginfo->expectedNextCheckin = now + nextCheckin * 60 + 60;
+ taginfo->pendingIdle = now + nextCheckin * 60 + 60;
clearPending(taginfo);
taginfo->filename = filename;
taginfo->len = filesize;
@@ -255,6 +256,16 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
File file = contentFS->open(filename, "w");
http.writeToStream(&file);
file.close();
+ } else if (httpCode == 404) {
+ imageUrl = "http://" + remoteIP.toString() + "/current/" + String(hexmac) + ".raw";
+ http.end();
+ http.begin(imageUrl);
+ httpCode = http.GET();
+ if (httpCode == 200) {
+ File file = contentFS->open(filename, "w");
+ http.writeToStream(&file);
+ file.close();
+ }
}
http.end();
@@ -424,7 +435,7 @@ void processXferTimeout(struct espXferComplete* xfc, bool local) {
time(&now);
tagRecord* taginfo = tagRecord::findByMAC(xfc->src);
if (taginfo != nullptr) {
- taginfo->expectedNextCheckin = now + 60;
+ taginfo->pendingIdle = now + 60;
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
clearPending(taginfo);
}
diff --git a/ESP32_AP-Flasher/src/tag_db.cpp b/ESP32_AP-Flasher/src/tag_db.cpp
index dc3007d0..a08309b6 100644
--- a/ESP32_AP-Flasher/src/tag_db.cpp
+++ b/ESP32_AP-Flasher/src/tag_db.cpp
@@ -300,6 +300,8 @@ void initAPconfig() {
config.maxsleep = APconfig["maxsleep"] | 10;
config.stopsleep = APconfig["stopsleep"] | 1;
config.preview = APconfig["preview"] | 1;
+ config.sleepTime1 = APconfig["sleeptime1"] | 0;
+ config.sleepTime2 = APconfig["sleeptime2"] | 0;
// default wifi power 8.5 dbM
// see https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiGeneric.h#L111
config.wifiPower = APconfig["wifipower"] | 34;
@@ -322,6 +324,8 @@ void saveAPconfig() {
APconfig["preview"] = config.preview;
APconfig["wifipower"] = config.wifiPower;
APconfig["timezone"] = config.timeZone;
+ APconfig["sleeptime1"] = config.sleepTime1;
+ APconfig["sleeptime2"] = config.sleepTime2;
serializeJsonPretty(APconfig, configFile);
configFile.close();
}
diff --git a/ESP32_AP-Flasher/src/web.cpp b/ESP32_AP-Flasher/src/web.cpp
index c03c14b1..ae2b85cf 100644
--- a/ESP32_AP-Flasher/src/web.cpp
+++ b/ESP32_AP-Flasher/src/web.cpp
@@ -76,11 +76,15 @@ void wsSendSysteminfo() {
JsonObject sys = doc.createNestedObject("sys");
time_t now;
time(&now);
+ static int freeSpaceLastRun = 0;
sys["currtime"] = now;
sys["heap"] = ESP.getFreeHeap();
sys["recordcount"] = tagDB.size();
sys["dbsize"] = dbSize();
- sys["littlefsfree"] = Storage.freeSpace();
+ if (millis() - freeSpaceLastRun > 30000) {
+ sys["littlefsfree"] = Storage.freeSpace();
+ freeSpaceLastRun = millis();
+ }
sys["apstate"] = apInfo.state;
sys["runstate"] = config.runStatus;
#if !defined(CONFIG_IDF_TARGET_ESP32)
@@ -90,18 +94,23 @@ void wsSendSysteminfo() {
sys["wifistatus"] = WiFi.status();
sys["wifissid"] = WiFi.SSID();
- uint32_t timeoutcount = 0;
- uint32_t tagcount = getTagCount(timeoutcount);
- char result[40];
- if (timeoutcount > 0) {
- snprintf(result, sizeof(result), "%lu / %lu, %lu timed out", tagcount, tagDB.size(), timeoutcount);
- } else {
- snprintf(result, sizeof(result), "%lu / %lu", tagcount, tagDB.size());
- }
- setVarDB("ap_tagcount", result);
setVarDB("ap_ip", WiFi.localIP().toString());
setVarDB("ap_ch", String(apInfo.channel));
+ static uint32_t tagcounttimer = 0;
+ if (millis() - tagcounttimer > 60000) {
+ uint32_t timeoutcount = 0;
+ uint32_t tagcount = getTagCount(timeoutcount);
+ char result[40];
+ if (timeoutcount > 0) {
+ snprintf(result, sizeof(result), "%lu / %lu, %lu timed out", tagcount, tagDB.size(), timeoutcount);
+ } else {
+ snprintf(result, sizeof(result), "%lu / %lu", tagcount, tagDB.size());
+ }
+ setVarDB("ap_tagcount", result);
+ tagcounttimer = millis();
+ }
+
xSemaphoreTake(wsMutex, portMAX_DELAY);
ws.textAll(doc.as());
xSemaphoreGive(wsMutex);
@@ -357,6 +366,10 @@ void init_web() {
if (request->hasParam("preview", true)) {
config.preview = static_cast(request->getParam("preview", true)->value().toInt());
}
+ if (request->hasParam("sleeptime1", true)) {
+ config.sleepTime1 = static_cast(request->getParam("sleeptime1", true)->value().toInt());
+ config.sleepTime2 = static_cast(request->getParam("sleeptime2", true)->value().toInt());
+ }
if (request->hasParam("wifipower", true)) {
config.wifiPower = static_cast(request->getParam("wifipower", true)->value().toInt());
WiFi.setTxPower(static_cast(config.wifiPower));
diff --git a/ESP32_AP-Flasher/wwwroot/index.html b/ESP32_AP-Flasher/wwwroot/index.html
index 817771c2..3438374d 100644
--- a/ESP32_AP-Flasher/wwwroot/index.html
+++ b/ESP32_AP-Flasher/wwwroot/index.html
@@ -117,6 +117,12 @@ Latency will be around 40 seconds.">
+
+
+
+ and
+
+
diff --git a/ESP32_AP-Flasher/wwwroot/main.js b/ESP32_AP-Flasher/wwwroot/main.js
index 74e35d44..7d49c84e 100644
--- a/ESP32_AP-Flasher/wwwroot/main.js
+++ b/ESP32_AP-Flasher/wwwroot/main.js
@@ -32,6 +32,9 @@ let servertimediff = 0;
let paintLoaded = false, paintShow = false;
let cardconfig;
let otamodule;
+let socket;
+let finishedInitialLoading = false;
+let getTagtypeBusy = false;
window.addEventListener("load", function () {
fetch('/content_cards.json')
@@ -55,16 +58,17 @@ window.addEventListener("load", function () {
}
});
dropUpload();
+ populateTimes($('#apcnight1'));
+ populateTimes($('#apcnight2'));
});
-let socket;
-
function loadTags(pos) {
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));
}
@@ -489,6 +493,8 @@ $('#apconfigbutton').onclick = function () {
$("#apcpreview").value = data.preview;
$("#apcwifipower").value = data.wifipower;
$("#apctimezone").value = data.timezone;
+ $("#apcnight1").value = data.sleeptime1;
+ $("#apcnight2").value = data.sleeptime2;
})
$('#apconfigbox').style.display = 'block'
}
@@ -504,6 +510,8 @@ $('#apcfgsave').onclick = function () {
formData.append('preview', $('#apcpreview').value);
formData.append('wifipower', $('#apcwifipower').value);
formData.append('timezone', $('#apctimezone').value);
+ formData.append('sleeptime1', $('#apcnight1').value);
+ formData.append('sleeptime2', $('#apcnight2').value);
fetch("/save_apcfg", {
method: "POST",
body: formData
@@ -706,11 +714,15 @@ function processQueue() {
return;
}
isProcessing = true;
+ if (!finishedInitialLoading) {
+ setTimeout(processQueue, 100);
+ return;
+ }
const { id, imageSrc } = imageQueue.shift();
const hwtype = $('#tag' + id).dataset.hwtype;
if (tagTypes[hwtype]?.busy) {
imageQueue.push({ id, imageSrc });
- setTimeout(processQueue, 50);
+ setTimeout(processQueue, 100);
return;
};
@@ -869,6 +881,21 @@ $('#toggleFilters').addEventListener('click', () => {
});
async function getTagtype(hwtype) {
+ if (tagTypes[hwtype]) {
+ return tagTypes[hwtype];
+ }
+
+ if (getTagtypeBusy) {
+ await new Promise(resolve => {
+ const checkBusy = setInterval(() => {
+ if (!getTagtypeBusy) {
+ clearInterval(checkBusy);
+ resolve();
+ }
+ }, 50);
+ });
+ }
+
if (tagTypes[hwtype]?.busy) {
await new Promise(resolve => {
const checkBusy = setInterval(() => {
@@ -886,10 +913,12 @@ async function getTagtype(hwtype) {
try {
tagTypes[hwtype] = { busy: true };
+ getTagtypeBusy = true;
const response = await fetch('/tagtypes/' + hwtype.toString(16).padStart(2, '0').toUpperCase() + '.json');
if (!response.ok) {
let data = { name: 'unknown id ' + hwtype, width: 0, height: 0, bpp: 0, rotatebuffer: 0, colortable: [], busy: false };
tagTypes[hwtype] = data;
+ getTagtypeBusy = false;
return data;
}
const jsonData = await response.json();
@@ -904,10 +933,12 @@ async function getTagtype(hwtype) {
busy: false
};
tagTypes[hwtype] = data;
+ getTagtypeBusy = false;
return data;
} catch (error) {
console.error('Error fetching data:', error);
+ getTagtypeBusy = false;
return null;
}
}
@@ -1074,3 +1105,12 @@ $('#taglist').addEventListener('contextmenu', (e) => {
document.addEventListener('click', () => {
contextMenu.style.display = 'none';
});
+
+function populateTimes(element) {
+ for (let i = 0; i < 24; i++) {
+ const option = document.createElement("option");
+ option.value = i;
+ option.text = i.toString().padStart(2, "0") + ":00";
+ element.appendChild(option);
+ }
+}
\ No newline at end of file