From f4273630ee5f3733246e39f2a169e3e40a8eee58 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Fri, 11 Aug 2023 18:46:46 +0200 Subject: [PATCH] rework of apinfo screen + variables in jsontemplate - AP info screen content card. Can run on any tag. - now, you can use {variables} in the 'text' entries in a json template. You can set variables via a http call. If you update a variable, all tags with a json template containing that variable get updated. - font name in json template is more flexible. You can just use 'filename.ttf' or 'filename.vlw'. A full path is still possible. - colors in the json template can now be set using #rrggbb color values, and using 'black', 'white' or 'red'. - added direct output for the TFT display for the yellow esp32-s3. No file writes needed. - added POST variable 'ttl' to json template upload and image upload, to set the next checkin time - added /variables-demo.html to demonstrate the variables. - json templates received from jsonupload are now saved in /current, and reused. - known issue: 'backup db' button doesn't work due to some browser policy change. Fixing. thanks to @steinwedel for the inspiration on the variables and some other fixes. --- ESP32_AP-Flasher/data/tagtypes/00.json | 12 +- ESP32_AP-Flasher/data/tagtypes/01.json | 11 +- ESP32_AP-Flasher/data/tagtypes/E0.json | 16 +- ESP32_AP-Flasher/include/contentmanager.h | 8 +- ESP32_AP-Flasher/include/makeimage.h | 2 + ESP32_AP-Flasher/include/tag_db.h | 10 +- ESP32_AP-Flasher/src/contentmanager.cpp | 392 +++++++++++-------- ESP32_AP-Flasher/src/ips_display.cpp | 5 +- ESP32_AP-Flasher/src/main.cpp | 6 +- ESP32_AP-Flasher/src/makeimage.cpp | 8 + ESP32_AP-Flasher/src/newproto.cpp | 17 +- ESP32_AP-Flasher/src/serialap.cpp | 16 +- ESP32_AP-Flasher/src/tag_db.cpp | 44 ++- ESP32_AP-Flasher/src/web.cpp | 84 +++- ESP32_AP-Flasher/wwwroot/content_cards.json | 16 +- ESP32_AP-Flasher/wwwroot/main.js | 24 +- ESP32_AP-Flasher/wwwroot/variables-demo.html | 41 ++ 17 files changed, 486 insertions(+), 226 deletions(-) create mode 100644 ESP32_AP-Flasher/wwwroot/variables-demo.html diff --git a/ESP32_AP-Flasher/data/tagtypes/00.json b/ESP32_AP-Flasher/data/tagtypes/00.json index 711498b5..27035fcc 100644 --- a/ESP32_AP-Flasher/data/tagtypes/00.json +++ b/ESP32_AP-Flasher/data/tagtypes/00.json @@ -33,6 +33,16 @@ "10": { "title": [ 10, 15, "t0_14b_tf" ], "pos": [ 76, 20 ] - } + }, + "21": [ + { "text": [ 2, 5, "OpenEpaperLink", "bahnschrift20", 1, 0, 0 ] }, + { "text": [ 2, 25, "Access Point", "bahnschrift20", 1, 0, 0 ] }, + { "text": [ 3, 65, "IP address:", "glasstown_nbp_tf", 1, 0, 0 ] }, + { "text": [ 10, 80, "{ap_ip}", "t0_14b_tf", 1, 0, 0 ] }, + { "text": [ 3, 95, "Channel:", "glasstown_nbp_tf", 1, 0, 0 ] }, + { "text": [ 10, 110, "{ap_ch}", "t0_14b_tf", 1, 0, 0 ] }, + { "text": [ 3, 125, "Tag count:", "glasstown_nbp_tf", 1, 0, 0 ] }, + { "text": [ 10, 140, "{ap_tagcount}", "t0_14b_tf", 1, 0, 0 ] } + ] } } diff --git a/ESP32_AP-Flasher/data/tagtypes/01.json b/ESP32_AP-Flasher/data/tagtypes/01.json index 1843494f..34546bf0 100644 --- a/ESP32_AP-Flasher/data/tagtypes/01.json +++ b/ESP32_AP-Flasher/data/tagtypes/01.json @@ -59,6 +59,15 @@ "items": 7, "red": [0, 21, 296, 14], "line": [5, 32, 15, "t0_14b_tf", 50] - } + }, + "21": [ + { "text": [ 5, 5, "OpenEpaperLink AP", "bahnschrift20", 1, 0, 0 ] }, + { "text": [ 5, 50, "IP address:", "t0_14b_tf", 1, 0, 0 ] }, + { "text": [ 120, 50, "{ap_ip}", "t0_14b_tf", 1, 0, 0 ] }, + { "text": [ 5, 70, "Channel:", "t0_14b_tf", 1, 0, 0 ] }, + { "text": [ 120, 70, "{ap_ch}", "t0_14b_tf", 1, 0, 0 ] }, + { "text": [ 5, 90, "Tag count:", "t0_14b_tf", 1, 0, 0 ] }, + { "text": [ 120, 90, "{ap_tagcount}", "t0_14b_tf", 1, 0, 0 ] } + ] } } diff --git a/ESP32_AP-Flasher/data/tagtypes/E0.json b/ESP32_AP-Flasher/data/tagtypes/E0.json index 5632e359..99e04246 100644 --- a/ESP32_AP-Flasher/data/tagtypes/E0.json +++ b/ESP32_AP-Flasher/data/tagtypes/E0.json @@ -12,6 +12,18 @@ "gray": [ 150, 150, 150 ] }, "capabilities": [ ], - "contentids": [ 0, 1, 2, 3, 4, 8, 16, 9, 7, 19, 10, 11 ], - "usetemplate": 1 + "contentids": [ 0, 1, 2, 3, 4, 8, 16, 9, 7, 19, 10, 11, 21 ], + "usetemplate": 1, + "template": { + "21": [ + { "box": [ 0, 0, 320, 170, 1 ] }, + { "text": [ 5, 5, "OpenEpaperLink AP", "calibrib30", 2, 0, 0, 1 ] }, + { "text": [ 5, 60, "IP address:", "bahnschrift20", "#888888", 0, 0, 1 ] }, + { "text": [ 120, 60, "{ap_ip}", "bahnschrift20", 0, 0, 0, 1 ] }, + { "text": [ 5, 85, "Channel:", "bahnschrift20", "#888888", 0, 0, 1 ] }, + { "text": [ 120, 85, "{ap_ch}", "bahnschrift20", 0, 0, 0, "1" ] }, + { "text": [ 5, 110, "Tag count:", "bahnschrift20", "#888888", 0, 0, 1 ] }, + { "text": [ 120, 110, "{ap_tagcount}", "bahnschrift20", 0, 0, 0, "1" ] } + ] + } } diff --git a/ESP32_AP-Flasher/include/contentmanager.h b/ESP32_AP-Flasher/include/contentmanager.h index c8e3d689..99adb942 100644 --- a/ESP32_AP-Flasher/include/contentmanager.h +++ b/ESP32_AP-Flasher/include/contentmanager.h @@ -16,25 +16,26 @@ struct contentTypes { }; void contentRunner(); +void checkVars(); void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo); bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin, tagRecord *&taginfo, imgParam &imageParams); -void drawString(TFT_eSprite &spr, String content, int16_t posx, int16_t posy, String font, byte align = 0, uint16_t color = TFT_BLACK, uint16_t size = 0); +void drawString(TFT_eSprite &spr, String content, int16_t posx, int16_t posy, String font, byte align = 0, uint16_t color = TFT_BLACK, uint16_t size = 30, uint16_t bgcolor = TFT_WHITE); void initSprite(TFT_eSprite &spr, int w, int h, imgParam &imageParams); void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams); void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord *&taginfo, imgParam &imageParams); void drawWeather(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams); void drawForecast(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams); -void drawIdentify(String &filename, tagRecord *&taginfo, imgParam &imageParams); int getImgURL(String &filename, String URL, time_t fetched, imgParam &imageParams, String MAC); bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams); bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams); void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginfo, imgParam &imageParams); uint8_t drawBuienradar(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams); +void drawAPinfo(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams); int getJsonTemplateFile(String &filename, String jsonfile, tagRecord *&taginfo, imgParam &imageParams); int getJsonTemplateUrl(String &filename, String URL, time_t fetched, String MAC, tagRecord *&taginfo, imgParam &imageParams); void drawJsonStream(Stream &stream, String &filename, tagRecord *&taginfo, imgParam &imageParams); void drawElement(const JsonObject &element, TFT_eSprite &spr); -uint16_t getColor(uint8_t color); +uint16_t getColor(String color); char *formatHttpDate(time_t t); String urlEncode(const char *msg); int windSpeedToBeaufort(float windSpeed); @@ -45,4 +46,3 @@ void prepareLUTreq(uint8_t *dst, String input); void prepareConfigFile(uint8_t *dst, JsonObject config); void getTemplate(JsonDocument &json, uint8_t id, uint8_t hwtype); void setU8G2Font(const String &title, U8g2_for_TFT_eSPI &u8f); -void showIpAddress(String dst); diff --git a/ESP32_AP-Flasher/include/makeimage.h b/ESP32_AP-Flasher/include/makeimage.h index 64b6fcfc..8d67fd9e 100644 --- a/ESP32_AP-Flasher/include/makeimage.h +++ b/ESP32_AP-Flasher/include/makeimage.h @@ -3,6 +3,8 @@ #pragma once +extern TFT_eSPI tft; + struct imgParam { bool hasRed; uint8_t dataType; diff --git a/ESP32_AP-Flasher/include/tag_db.h b/ESP32_AP-Flasher/include/tag_db.h index ccd58f19..7ee6386a 100644 --- a/ESP32_AP-Flasher/include/tag_db.h +++ b/ESP32_AP-Flasher/include/tag_db.h @@ -76,10 +76,16 @@ struct HwType { uint8_t bpp; }; +struct varStruct { + String value; + bool changed; +}; + // extern SemaphoreHandle_t tagDBOwner; extern Config config; extern std::vector tagDB; extern std::unordered_map hwtype; +extern std::unordered_map varDB; extern DynamicJsonDocument APconfig; String tagDBtoJson(uint8_t mac[8] = nullptr, uint8_t startPos = 0); bool deleteRecord(uint8_t mac[8]); @@ -88,11 +94,13 @@ void saveDB(String filename); void loadDB(String filename); void destroyDB(); uint32_t getTagCount(); +uint32_t getTagCount(uint32_t& timeoutcount); void mac2hex(uint8_t* mac, char* hexBuffer); bool hex2mac(const String& hexString, uint8_t* mac); void clearPending(tagRecord* taginfo); void initAPconfig(); void saveAPconfig(); HwType getHwType(uint8_t id); +bool setVarDB(const std::string& key, const String& value); -#pragma pack(pop) \ No newline at end of file +#pragma pack(pop) diff --git a/ESP32_AP-Flasher/src/contentmanager.cpp b/ESP32_AP-Flasher/src/contentmanager.cpp index 887022dc..5740895f 100644 --- a/ESP32_AP-Flasher/src/contentmanager.cpp +++ b/ESP32_AP-Flasher/src/contentmanager.cpp @@ -35,10 +35,6 @@ #include "truetype.h" #include "web.h" -#define PAL_BLACK TFT_BLACK -#define PAL_WHITE TFT_WHITE -#define PAL_RED TFT_RED - // https://csvjson.com/json_beautifier void contentRunner() { @@ -47,8 +43,7 @@ void contentRunner() { time_t now; time(&now); - // xSemaphoreTake(tagDBOwner, portMAX_DELAY); - for (int16_t c = 0; c < tagDB.size(); c++) { + for (int32_t c = 0; c < tagDB.size(); c++) { tagRecord *taginfo = nullptr; taginfo = tagDB.at(c); @@ -69,7 +64,40 @@ void contentRunner() { vTaskDelay(1 / portTICK_PERIOD_MS); // add a small delay to allow other threads to run } - // xSemaphoreGive(tagDBOwner); +} + +void checkVars() { + DynamicJsonDocument doc(500); + for (int32_t c = 0; c < tagDB.size(); c++) { + tagRecord *tag = nullptr; + tag = tagDB.at(c); + if (tag->contentMode == 19) { + deserializeJson(doc, tag->modeConfigJson); + JsonObject cfgobj = doc.as(); + if (cfgobj["filename"]) { + String jsonfile = cfgobj["filename"].as(); + File file = contentFS->open(jsonfile, "r"); + if (file) { + size_t fileSize = file.size(); + std::unique_ptr fileContent(new char[fileSize + 1]); + file.readBytes(fileContent.get(), fileSize); + file.close(); + fileContent[fileSize] = '\0'; + char *contentPtr = fileContent.get(); + for (const auto &entry : varDB) { + if (entry.second.changed && strstr(contentPtr, entry.first.c_str()) != nullptr) { + Serial.println("updating " + jsonfile + " because of var " + entry.first.c_str()); + tag->nextupdate = 0; + } + } + } + file.close(); + } + } + } + for (const auto &entry : varDB) { + if (entry.second.changed) varDB[entry.first].changed = false; + } } void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) { @@ -83,9 +111,21 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) { return; } + uint8_t wifimac[8]; + WiFi.macAddress(wifimac); + memset(&wifimac[6], 0, 2); + + if ((taginfo->wakeupReason == WAKEUP_REASON_FIRSTBOOT || taginfo->wakeupReason == WAKEUP_REASON_WDT_RESET) && taginfo->contentMode == 0 && memcmp(mac, wifimac, 8) == 0) { + taginfo->contentMode = 21; + taginfo->nextupdate = 0; + } + char hexmac[17]; mac2hex(mac, hexmac); String filename = "/" + String(hexmac) + ".raw"; +#ifdef YELLOW_IPS_AP + if (memcmp(mac, wifimac, 8) == 0) filename = "direct"; +#endif struct tm time_info; getLocalTime(&time_info); @@ -291,7 +331,7 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) { if (cfgobj["filename"]) { int result = getJsonTemplateFile(filename, cfgobj["filename"], taginfo, imageParams); if (result) { - updateTagImage(filename, mac, 0, taginfo, imageParams); + updateTagImage(filename, mac, cfgobj["interval"].as(), taginfo, imageParams); } else { wsErr("error opening file " + cfgobj["filename"].as()); } @@ -310,6 +350,22 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) { } break; } + + case 20: // display a copy + break; + + case 21: // ap info + drawAPinfo(filename, cfgobj, taginfo, imageParams); + /* + if (imageParams.bpp == 16) { + taginfo->nextupdate = now + 60; + } else { + taginfo->nextupdate = now + 600; + } + */ + taginfo->nextupdate = 3216153600; + updateTagImage(filename, mac, 0, taginfo, imageParams); + break; } taginfo->modeConfigJson = doc.as(); @@ -325,63 +381,94 @@ bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin, tagRec return true; } -void drawString(TFT_eSprite &spr, String content, int16_t posx, int16_t posy, String font, byte align, uint16_t color, uint16_t size) { - // drawString(spr,"test",100,10,"bahnschrift30",TC_DATUM,PAL_RED); - if (font != "" && font != "null" && !font.startsWith("fonts/") && !font.startsWith("/fonts/")) { - // u8g2 font - U8g2_for_TFT_eSPI u8f; - u8f.begin(spr); - setU8G2Font(font, u8f); - u8f.setForegroundColor(color); - u8f.setBackgroundColor(PAL_WHITE); - if (align == TC_DATUM) { - posx -= u8f.getUTF8Width(content.c_str()) / 2; - } - if (align == TR_DATUM) { - posx -= u8f.getUTF8Width(content.c_str()); - } - u8f.setCursor(posx, posy); - u8f.print(content); +uint8_t processFontPath(String &font) { + if (font == "") return 3; + if (font == "glasstown_nbp_tf") return 1; + if (font == "7x14_tf") return 1; + if (font == "t0_14b_tf") return 1; + if (font.indexOf('/') == -1) font = "/fonts/" + font; + if (!font.startsWith("/")) font = "/" + font; + if (font.endsWith(".vlw")) font = font.substring(0, font.length() - 4); + if (font.endsWith(".ttf")) return 2; + return 3; +} - } else if (size > 0) { - // truetype - time_t t = millis(); - truetypeClass truetype = truetypeClass(); - void *framebuffer = spr.getPointer(); - truetype.setFramebuffer(spr.width(), spr.height(), spr.getColorDepth(), static_cast(framebuffer)); - File fontFile = contentFS->open(font, "r"); - if (!truetype.setTtfFile(fontFile)) { - Serial.println("read ttf failed"); - return; - } +void replaceVariables(String &format) { + size_t startIndex = 0; + size_t openBraceIndex, closeBraceIndex; - truetype.setCharacterSize(size); - truetype.setCharacterSpacing(0); - if (align == TC_DATUM) { - posx -= truetype.getStringWidth(content) / 2; + while ((openBraceIndex = format.indexOf('{', startIndex)) != -1 && + (closeBraceIndex = format.indexOf('}', openBraceIndex + 1)) != -1) { + std::string variableName = format.substring(openBraceIndex + 1, closeBraceIndex).c_str(); + std::string varKey = "{" + variableName + "}"; + if (varDB.count(variableName) > 0) { + format.replace(varKey.c_str(), varDB.at(variableName).value); } - if (align == TR_DATUM) { - posx -= truetype.getStringWidth(content); - } - truetype.setTextBoundary(posx, spr.width(), spr.height()); - truetype.setTextColor(spr.color16to8(color), spr.color16to8(color)); - truetype.textDraw(posx, posy, content); - truetype.end(); - // Serial.println("text: '" + content + "' " + String(millis() - t) + "ms"); + startIndex = closeBraceIndex + 1; + } +} - } else { - // vlw bitmap font - spr.setTextDatum(align); - if (font != "") spr.loadFont(font, *contentFS); - spr.setTextColor(color, PAL_WHITE); - spr.drawString(content, posx, posy); - if (font != "") spr.unloadFont(); +void drawString(TFT_eSprite &spr, String content, int16_t posx, int16_t posy, String font, byte align, uint16_t color, uint16_t size, uint16_t bgcolor) { + // drawString(spr,"test",100,10,"bahnschrift30",TC_DATUM,TFT_RED); + replaceVariables(content); + switch (processFontPath(font)) { + case 1: { + // u8g2 font + U8g2_for_TFT_eSPI u8f; + u8f.begin(spr); + setU8G2Font(font, u8f); + u8f.setForegroundColor(color); + u8f.setBackgroundColor(bgcolor); + if (align == TC_DATUM) { + posx -= u8f.getUTF8Width(content.c_str()) / 2; + } + if (align == TR_DATUM) { + posx -= u8f.getUTF8Width(content.c_str()); + } + u8f.setCursor(posx, posy); + u8f.print(content); + } break; + case 2: { + // truetype + time_t t = millis(); + truetypeClass truetype = truetypeClass(); + void *framebuffer = spr.getPointer(); + truetype.setFramebuffer(spr.width(), spr.height(), spr.getColorDepth(), static_cast(framebuffer)); + File fontFile = contentFS->open(font, "r"); + if (!truetype.setTtfFile(fontFile)) { + Serial.println("read ttf failed"); + return; + } + + truetype.setCharacterSize(size); + truetype.setCharacterSpacing(0); + if (align == TC_DATUM) { + posx -= truetype.getStringWidth(content) / 2; + } + if (align == TR_DATUM) { + posx -= truetype.getStringWidth(content); + } + truetype.setTextBoundary(posx, spr.width(), spr.height()); + truetype.setTextColor(spr.color16to8(color), spr.color16to8(color)); + truetype.textDraw(posx, posy, content); + truetype.end(); + // Serial.println("text: '" + content + "' " + String(millis() - t) + "ms"); + } break; + case 3: { + // vlw bitmap font + spr.setTextDatum(align); + if (font != "") spr.loadFont(font.substring(1), *contentFS); + spr.setTextColor(color, bgcolor); + spr.drawString(content, posx, posy); + if (font != "") spr.unloadFont(); + } } } void initSprite(TFT_eSprite &spr, int w, int h, imgParam &imageParams) { spr.setColorDepth(8); spr.createSprite(w, h); + spr.setRotation(3); if (spr.getPointer() == nullptr) { wsErr("low on memory. Fallback to 1bpp"); Serial.println("Maximum Continuous Heap Space: " + String(heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT))); @@ -393,7 +480,7 @@ void initSprite(TFT_eSprite &spr, int w, int h, imgParam &imageParams) { if (spr.getPointer() == nullptr) { wsErr("Failed to create sprite"); } - spr.fillSprite(PAL_WHITE); + spr.fillSprite(TFT_WHITE); } void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams) { @@ -412,7 +499,6 @@ void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams) { return; } - TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft); StaticJsonDocument<512> loc; @@ -421,12 +507,12 @@ void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams) { initSprite(spr, imageParams.width, imageParams.height, imageParams); if (loc["date"]) { - drawString(spr, languageDays[getCurrentLanguage()][timeinfo.tm_wday], loc["weekday"][0], loc["weekday"][1], loc["weekday"][2], TC_DATUM, PAL_RED); + drawString(spr, languageDays[getCurrentLanguage()][timeinfo.tm_wday], loc["weekday"][0], loc["weekday"][1], loc["weekday"][2], TC_DATUM, TFT_RED); drawString(spr, String(timeinfo.tm_mday) + " " + languageMonth[getCurrentLanguage()][timeinfo.tm_mon], loc["date"][0], loc["date"][1], loc["date"][2], TC_DATUM); } else { - drawString(spr, languageDays[getCurrentLanguage()][timeinfo.tm_wday], loc["weekday"][0], loc["weekday"][1], loc["weekday"][2], TC_DATUM, PAL_BLACK); + drawString(spr, languageDays[getCurrentLanguage()][timeinfo.tm_wday], loc["weekday"][0], loc["weekday"][1], loc["weekday"][2], TC_DATUM, TFT_BLACK); drawString(spr, String(languageMonth[getCurrentLanguage()][timeinfo.tm_mon]), loc["month"][0], loc["month"][1], loc["month"][2], TC_DATUM); - drawString(spr, String(timeinfo.tm_mday), loc["day"][0], loc["day"][1], loc["day"][2], TC_DATUM, PAL_RED); + drawString(spr, String(timeinfo.tm_mday), loc["day"][0], loc["day"][1], loc["day"][2], TC_DATUM, TFT_RED); } spr2buffer(spr, filename, imageParams); @@ -453,7 +539,6 @@ void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord return; } - TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft); StaticJsonDocument<512> loc; @@ -462,9 +547,9 @@ void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord initSprite(spr, imageParams.width, imageParams.height, imageParams); spr.setTextDatum(MC_DATUM); if (count > thresholdred) { - spr.setTextColor(PAL_RED, PAL_WHITE); + spr.setTextColor(TFT_RED, TFT_WHITE); } else { - spr.setTextColor(PAL_BLACK, PAL_WHITE); + spr.setTextColor(TFT_BLACK, TFT_WHITE); } String font = loc["fonts"][0].as(); if (count > 99) font = loc["fonts"][1].as(); @@ -541,26 +626,25 @@ void drawWeather(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgP weatherIcons[2] = "\uf086"; } - TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft); tft.setTextWrap(false, false); initSprite(spr, imageParams.width, imageParams.height, imageParams); drawString(spr, cfgobj["location"], loc["location"][0], loc["location"][1], loc["location"][2]); - drawString(spr, String(wind), loc["wind"][0], loc["wind"][1], loc["wind"][2], TR_DATUM, (wind > 4 ? PAL_RED : PAL_BLACK)); + drawString(spr, String(wind), loc["wind"][0], loc["wind"][1], loc["wind"][2], TR_DATUM, (wind > 4 ? TFT_RED : TFT_BLACK)); char tmpOutput[5]; dtostrf(temperature, 2, 1, tmpOutput); - drawString(spr, String(tmpOutput), loc["temp"][0], loc["temp"][1], loc["temp"][2], TL_DATUM, (temperature < 0 ? PAL_RED : PAL_BLACK)); + drawString(spr, String(tmpOutput), loc["temp"][0], loc["temp"][1], loc["temp"][2], TL_DATUM, (temperature < 0 ? TFT_RED : TFT_BLACK)); - int iconcolor = PAL_BLACK; + int iconcolor = TFT_BLACK; if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 96 || weathercode == 99) { - iconcolor = PAL_RED; + iconcolor = TFT_RED; } drawString(spr, weatherIcons[weathercode], loc["icon"][0], loc["icon"][1], "/fonts/weathericons.ttf", loc["icon"][3], iconcolor, loc["icon"][2]); - drawString(spr, windDirectionIcon(winddirection), loc["dir"][0], loc["dir"][1], "/fonts/weathericons.ttf", TC_DATUM, PAL_BLACK, loc["dir"][2]); + drawString(spr, windDirectionIcon(winddirection), loc["dir"][0], loc["dir"][1], "/fonts/weathericons.ttf", TC_DATUM, TFT_BLACK, loc["dir"][2]); if (weathercode > 10) { - drawString(spr, "\uf084", loc["umbrella"][0], loc["umbrella"][1], "/fonts/weathericons.ttf", TC_DATUM, PAL_RED, loc["umbrella"][2]); + drawString(spr, "\uf084", loc["umbrella"][0], loc["umbrella"][1], "/fonts/weathericons.ttf", TC_DATUM, TFT_RED, loc["umbrella"][2]); } spr2buffer(spr, filename, imageParams); @@ -572,7 +656,6 @@ void drawWeather(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgP } void drawForecast(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams) { - TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft); wsLog("get weather"); @@ -608,23 +691,23 @@ void drawForecast(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, img getTemplate(loc, 8, taginfo->hwType); initSprite(spr, imageParams.width, imageParams.height, imageParams); - drawString(spr, cfgobj["location"], loc["location"][0], loc["location"][1], loc["location"][2], TL_DATUM, PAL_BLACK); + drawString(spr, cfgobj["location"], loc["location"][0], loc["location"][1], loc["location"][2], TL_DATUM, TFT_BLACK); for (uint8_t dag = 0; dag < loc["column"][0]; dag++) { time_t weatherday = doc["daily"]["time"][dag].as(); struct tm *datum = localtime(&weatherday); - drawString(spr, String(languageDaysShort[getCurrentLanguage()][datum->tm_wday]), dag * loc["column"][1].as() + loc["day"][0].as(), loc["day"][1], loc["day"][2], TC_DATUM, PAL_BLACK); + drawString(spr, String(languageDaysShort[getCurrentLanguage()][datum->tm_wday]), dag * loc["column"][1].as() + loc["day"][0].as(), loc["day"][1], loc["day"][2], TC_DATUM, TFT_BLACK); uint8_t weathercode = doc["daily"]["weathercode"][dag].as(); if (weathercode > 40) weathercode -= 40; - int iconcolor = PAL_BLACK; + int iconcolor = TFT_BLACK; if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 96 || weathercode == 99) { - iconcolor = PAL_RED; + iconcolor = TFT_RED; } drawString(spr, weatherIcons[weathercode], loc["icon"][0].as() + dag * loc["column"][1].as(), loc["icon"][1], "/fonts/weathericons.ttf", TC_DATUM, iconcolor, loc["icon"][2]); - drawString(spr, windDirectionIcon(doc["daily"]["winddirection_10m_dominant"][dag]), loc["wind"][0].as() + dag * loc["column"][1].as(), loc["wind"][1], "/fonts/weathericons.ttf", TC_DATUM, PAL_BLACK, loc["icon"][2]); + drawString(spr, windDirectionIcon(doc["daily"]["winddirection_10m_dominant"][dag]), loc["wind"][0].as() + dag * loc["column"][1].as(), loc["wind"][1], "/fonts/weathericons.ttf", TC_DATUM, TFT_BLACK, loc["icon"][2]); int8_t tmin = round(doc["daily"]["temperature_2m_min"][dag].as()); int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as()); @@ -635,17 +718,17 @@ void drawForecast(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, img if (loc["rain"]) { int8_t rain = round(doc["daily"]["precipitation_sum"][dag].as()); if (rain > 0) { - drawString(spr, String(rain) + "mm", dag * loc["column"][1].as() + loc["rain"][0].as(), loc["rain"][1], "", TC_DATUM, (rain > 10 ? PAL_RED : PAL_BLACK)); + drawString(spr, String(rain) + "mm", dag * loc["column"][1].as() + loc["rain"][0].as(), loc["rain"][1], "", TC_DATUM, (rain > 10 ? TFT_RED : TFT_BLACK)); } } - drawString(spr, String(tmin) + " ", dag * loc["column"][1].as() + loc["day"][0].as(), loc["day"][4], "", TR_DATUM, (tmin < 0 ? PAL_RED : PAL_BLACK)); - drawString(spr, String(" ") + String(tmax), dag * loc["column"][1].as() + loc["day"][0].as(), loc["day"][4], "", TL_DATUM, (tmax < 0 ? PAL_RED : PAL_BLACK)); - drawString(spr, String(" ") + String(wind), dag * loc["column"][1].as() + loc["day"][0].as(), loc["day"][3], "", TL_DATUM, (wind > 5 ? PAL_RED : PAL_BLACK)); + drawString(spr, String(tmin) + " ", dag * loc["column"][1].as() + loc["day"][0].as(), loc["day"][4], "", TR_DATUM, (tmin < 0 ? TFT_RED : TFT_BLACK)); + drawString(spr, String(" ") + String(tmax), dag * loc["column"][1].as() + loc["day"][0].as(), loc["day"][4], "", TL_DATUM, (tmax < 0 ? TFT_RED : TFT_BLACK)); + drawString(spr, String(" ") + String(wind), dag * loc["column"][1].as() + loc["day"][0].as(), loc["day"][3], "", TL_DATUM, (wind > 5 ? TFT_RED : TFT_BLACK)); spr.unloadFont(); if (dag > 0) { for (int i = loc["line"][0]; i < loc["line"][1]; i += 3) { - spr.drawPixel(dag * loc["column"][1].as(), i, PAL_BLACK); + spr.drawPixel(dag * loc["column"][1].as(), i, TFT_BLACK); } } } @@ -700,7 +783,6 @@ bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo, const char *tag = "title"; const int rssArticleSize = 128; - TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft); U8g2_for_TFT_eSPI u8f; u8f.begin(spr); @@ -710,13 +792,13 @@ bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo, initSprite(spr, imageParams.width, imageParams.height, imageParams); if (title == "" || title == "null") title = "RSS feed"; - drawString(spr, title, loc["title"][0], loc["title"][1], loc["title"][2], TL_DATUM, PAL_BLACK); + drawString(spr, title, loc["title"][0], loc["title"][1], loc["title"][2], TL_DATUM, TFT_BLACK); setU8G2Font(loc["font"], u8f); u8f.setFontMode(0); u8f.setFontDirection(0); - u8f.setForegroundColor(PAL_BLACK); - u8f.setBackgroundColor(PAL_WHITE); + u8f.setForegroundColor(TFT_BLACK); + u8f.setBackgroundColor(TFT_WHITE); int n = reader.getArticles(url, tag, rssArticleSize, loc["items"]); for (int i = 0; i < n; i++) { @@ -782,7 +864,6 @@ bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, } http.end(); - TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft); U8g2_for_TFT_eSPI u8f; u8f.begin(spr); @@ -792,8 +873,8 @@ bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, initSprite(spr, imageParams.width, imageParams.height, imageParams); if (title == "" || title == "null") title = "Calendar"; - drawString(spr, title, loc["title"][0], loc["title"][1], loc["title"][2], TL_DATUM, PAL_BLACK); - drawString(spr, dateString, loc["date"][0], loc["date"][1], loc["title"][2], TR_DATUM, PAL_BLACK); + drawString(spr, title, loc["title"][0], loc["title"][1], loc["title"][2], TL_DATUM, TFT_BLACK); + drawString(spr, dateString, loc["date"][0], loc["date"][1], loc["title"][2], TR_DATUM, TFT_BLACK); u8f.setFontMode(0); u8f.setFontDirection(0); @@ -806,12 +887,12 @@ bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, time_t endtime = obj["end"]; setU8G2Font(loc["line"][3], u8f); if (starttime <= now && endtime > now) { - u8f.setForegroundColor(PAL_WHITE); - u8f.setBackgroundColor(PAL_RED); - spr.fillRect(loc["red"][0], loc["red"][1].as() + i * loc["line"][2].as(), loc["red"][2], loc["red"][3], PAL_RED); + u8f.setForegroundColor(TFT_WHITE); + u8f.setBackgroundColor(TFT_RED); + spr.fillRect(loc["red"][0], loc["red"][1].as() + i * loc["line"][2].as(), loc["red"][2], loc["red"][3], TFT_RED); } else { - u8f.setForegroundColor(PAL_BLACK); - u8f.setBackgroundColor(PAL_WHITE); + u8f.setForegroundColor(TFT_BLACK); + u8f.setBackgroundColor(TFT_WHITE); } u8f.setCursor(loc["line"][0], loc["line"][1].as() + i * loc["line"][2].as()); if (starttime > 0) u8f.print(epoch_to_display(obj["start"])); @@ -827,7 +908,6 @@ bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginfo, imgParam &imageParams) { #ifdef CONTENT_QR - TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft); Storage.begin(); @@ -851,7 +931,7 @@ void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginf for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { if (qrcode_getModule(&qrcode, x, y)) { - spr.fillRect(xpos + x * dotsize, ypos + y * dotsize, dotsize, dotsize, PAL_BLACK); + spr.fillRect(xpos + x * dotsize, ypos + y * dotsize, dotsize, dotsize, TFT_BLACK); } } } @@ -877,7 +957,6 @@ uint8_t drawBuienradar(String &filename, JsonObject &cfgobj, tagRecord *&taginfo int httpCode = http.GET(); if (httpCode == 200) { - TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft); U8g2_for_TFT_eSPI u8f; u8f.begin(spr); @@ -893,13 +972,10 @@ uint8_t drawBuienradar(String &filename, JsonObject &cfgobj, tagRecord *&taginfo drawString(spr, cfgobj["location"], loc["location"][0], loc["location"][1], loc["location"][2]); for (int i = 0; i < 295; i += 4) { - spr.drawPixel(i, 110, PAL_BLACK); - spr.drawPixel(i, 91, PAL_BLACK); - spr.drawPixel(i, 82, PAL_BLACK); - spr.drawPixel(i, 72, PAL_BLACK); - spr.drawPixel(i, 62, PAL_BLACK); - spr.drawPixel(i, 56, PAL_BLACK); - spr.drawPixel(i, 52, PAL_BLACK); + int yCoordinates[] = {110, 91, 82, 72, 62, 56, 52}; + for (int y : yCoordinates) { + spr.drawPixel(i, y, TFT_BLACK); + } } drawString(spr, "Buienradar", loc["title"][0], loc["title"][1], loc["title"][2]); @@ -914,7 +990,7 @@ uint8_t drawBuienradar(String &filename, JsonObject &cfgobj, tagRecord *&taginfo if (value > 70 && i < 12) refresh = 5; if (value > 70 && refresh > 5) refresh = 15; - spr.fillRect(i * loc["cols"][2].as() + loc["bars"][0].as(), loc["bars"][1].as() - (value - 70), loc["bars"][2], (value - 70), (value > 130 ? PAL_RED : PAL_BLACK)); + spr.fillRect(i * loc["cols"][2].as() + loc["bars"][0].as(), loc["bars"][1].as() - (value - 70), loc["bars"][2], (value - 70), (value > 130 ? TFT_RED : TFT_BLACK)); if (minutes % 15 == 0) { drawString(spr, timestring, i * loc["cols"][2].as() + loc["cols"][0].as(), loc["cols"][1], loc["cols"][3]); @@ -931,6 +1007,27 @@ uint8_t drawBuienradar(String &filename, JsonObject &cfgobj, tagRecord *&taginfo return refresh; } +void drawAPinfo(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams) { + if (taginfo->hwType == SOLUM_SEG_UK) { + imageParams.symbols = 0x00; + sprintf(imageParams.segments, ""); + return; + } + + TFT_eSprite spr = TFT_eSprite(&tft); + StaticJsonDocument<2048> loc; + getTemplate(loc, 21, taginfo->hwType); + + initSprite(spr, imageParams.width, imageParams.height, imageParams); + JsonArray jsonArray = loc.as(); + for (JsonVariant elem : jsonArray) { + drawElement(elem, spr); + } + + spr2buffer(spr, filename, imageParams); + spr.deleteSprite(); +} + int getJsonTemplateFile(String &filename, String jsonfile, tagRecord *&taginfo, imgParam &imageParams) { if (jsonfile.c_str()[0] != '/') { jsonfile = "/" + jsonfile; @@ -939,7 +1036,7 @@ int getJsonTemplateFile(String &filename, String jsonfile, tagRecord *&taginfo, if (file) { drawJsonStream(file, filename, taginfo, imageParams); file.close(); - contentFS->remove(jsonfile); + // contentFS->remove(jsonfile); return 1; } return 0; @@ -966,7 +1063,6 @@ int getJsonTemplateUrl(String &filename, String URL, time_t fetched, String MAC, } void drawJsonStream(Stream &stream, String &filename, tagRecord *&taginfo, imgParam &imageParams) { - TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft); initSprite(spr, imageParams.width, imageParams.height, imageParams); DynamicJsonDocument doc(300); @@ -991,35 +1087,34 @@ void drawJsonStream(Stream &stream, String &filename, tagRecord *&taginfo, imgPa void drawElement(const JsonObject &element, TFT_eSprite &spr) { if (element.containsKey("text")) { const JsonArray &textArray = element["text"]; - uint16_t color = textArray[4] | 1; uint16_t align = textArray[5] | 0; uint16_t size = textArray[6] | 0; - drawString(spr, textArray[2], textArray[0].as(), textArray[1].as(), textArray[3], align, getColor(color), size); + String bgcolorstr = textArray[7].as(); + + uint16_t bgcolor = (bgcolorstr.length() > 0) ? getColor(bgcolorstr) : TFT_WHITE; + drawString(spr, textArray[2], textArray[0].as(), textArray[1].as(), textArray[3], align, getColor(textArray[4]), size, bgcolor); } else if (element.containsKey("box")) { const JsonArray &boxArray = element["box"]; - uint16_t color = boxArray[4] | 1; - spr.fillRect(boxArray[0].as(), boxArray[1].as(), boxArray[2].as(), boxArray[3].as(), getColor(color)); + spr.fillRect(boxArray[0].as(), boxArray[1].as(), boxArray[2].as(), boxArray[3].as(), getColor(boxArray[4])); } else if (element.containsKey("line")) { const JsonArray &lineArray = element["line"]; - uint16_t color = lineArray[4] | 1; - spr.drawLine(lineArray[0].as(), lineArray[1].as(), lineArray[2].as(), lineArray[3].as(), getColor(color)); + spr.drawLine(lineArray[0].as(), lineArray[1].as(), lineArray[2].as(), lineArray[3].as(), getColor(lineArray[4])); } else if (element.containsKey("triangle")) { const JsonArray &lineArray = element["triangle"]; - uint16_t color = lineArray[6] | 1; - spr.fillTriangle(lineArray[0].as(), lineArray[1].as(), lineArray[2].as(), lineArray[3].as(), lineArray[4].as(), lineArray[5].as(), getColor(color)); + spr.fillTriangle(lineArray[0].as(), lineArray[1].as(), lineArray[2].as(), lineArray[3].as(), lineArray[4].as(), lineArray[5].as(), getColor(lineArray[6])); } } -uint16_t getColor(uint8_t color) { - switch (color) { - case 0: - return PAL_WHITE; - case 1: - return PAL_BLACK; - case 2: - return PAL_RED; +uint16_t getColor(String color) { + if (color == "0" or color == "white") return TFT_WHITE; + if (color == "1" or color == "" or color == "black") return TFT_BLACK; + if (color == "2" or color == "red") return TFT_RED; + uint16_t r, g, b; + if (color.length() == 7 && color[0] == '#' && + sscanf(color.c_str(), "#%2hx%2hx%2hx", &r, &g, &b) == 3) { + return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); } - return PAL_WHITE; + return TFT_WHITE; } char *formatHttpDate(time_t t) { @@ -1153,7 +1248,7 @@ void prepareConfigFile(uint8_t *dst, JsonObject config) { void getTemplate(JsonDocument &json, uint8_t id, uint8_t hwtype) { StaticJsonDocument<80> filter; - StaticJsonDocument<1024> doc; + StaticJsonDocument<2048> doc; const String idstr = String(id); const char *templateKey = "template"; @@ -1167,11 +1262,14 @@ void getTemplate(JsonDocument &json, uint8_t id, uint8_t hwtype) { filter["usetemplate"] = true; DeserializationError error = deserializeJson(doc, jsonFile, DeserializationOption::Filter(filter)); jsonFile.close(); + if (!error && doc.containsKey(templateKey) && doc[templateKey].containsKey(idstr)) { + json.set(doc[templateKey][idstr]); + return; + } if (!error && doc.containsKey("usetemplate")) { getTemplate(json, id, doc["usetemplate"]); return; } - if (!error && json.set(doc[templateKey][idstr])) return; Serial.println("json error in " + String(filename)); Serial.println(error.c_str()); } else { @@ -1184,47 +1282,3 @@ void setU8G2Font(const String &title, U8g2_for_TFT_eSPI &u8f) { if (title == "7x14_tf") u8f.setFont(u8g2_font_7x14_tf); if (title == "t0_14b_tf") u8f.setFont(u8g2_font_t0_14b_tf); } - -void showIpAddress(String dst) { - uint8_t mac[8]; - if (hex2mac(dst, mac)) { - tagRecord *taginfo = nullptr; - taginfo = tagRecord::findByMAC(mac); - if (taginfo != nullptr) { - String json = String("["); - json += String("{\"text\": [0,5,\"OpenEPaperLink\",\"fonts/bahnschrift20\",2]}"); - json += String(","); - json += String("{\"text\": [0,25,\"MAC:\",\"fonts/bahnschrift20\",1]}"); - json += String(","); - json += String("{\"text\": [10,55,\"") + dst + String("\",\"glasstown_nbp_tf\",1]}"); - - if ((uint32_t)WiFi.localIP() == (uint32_t)0) { - json += String(","); - json += String("{\"text\": [0,65,\"Connect to my \",\"fonts/bahnschrift20\",1]}"); - json += String(","); - json += String("{\"text\": [0,85,\"WiFi, browse to:\",\"fonts/bahnschrift20\",1]}"); - json += String(","); - json += String("{\"text\": [0,105,\"192.168.4.1/setup\",\"fonts/bahnschrift20\",1]}"); - json += String(","); - json += String("{\"text\": [0,125,\"to configure me\",\"fonts/bahnschrift20\",1]}"); - } else { - json += String(","); - json += String("{\"text\": [0,65,\"IP:\",\"fonts/bahnschrift20\",1]}"); - json += String(","); - json += String("{\"text\": [0,85,\"") + WiFi.localIP().toString() + String("\",\"fonts/bahnschrift20\",1]}"); - } - - json += String("]"); - File file = LittleFS.open("/" + dst + ".json", "w"); - if (!file) { - Serial.print("Failed to create file\n"); - return; - } - file.print(json); - file.close(); - taginfo->modeConfigJson = "{\"filename\":\"" + dst + ".json\"}"; - taginfo->contentMode = 19; - taginfo->nextupdate = 0; - } - } -} diff --git a/ESP32_AP-Flasher/src/ips_display.cpp b/ESP32_AP-Flasher/src/ips_display.cpp index e37aa41c..7d5043f4 100644 --- a/ESP32_AP-Flasher/src/ips_display.cpp +++ b/ESP32_AP-Flasher/src/ips_display.cpp @@ -57,11 +57,14 @@ void yellow_ap_display_loop(void) { sendAvail(0); last_checkin = millis(); } - if (millis() - last_update >= 500) { + if (millis() - last_update >= 1000) { if (first_run == 0) { sendAvail(0xFC); first_run = 1; } + + // if ((uint32_t)WiFi.localIP() == (uint32_t)0) {} + tagRecord* tag = nullptr; tag = tagDB.at(tftid); if (tag->pending) { diff --git a/ESP32_AP-Flasher/src/main.cpp b/ESP32_AP-Flasher/src/main.cpp index 255b46fb..22520bc3 100644 --- a/ESP32_AP-Flasher/src/main.cpp +++ b/ESP32_AP-Flasher/src/main.cpp @@ -6,7 +6,6 @@ #include "storage.h" #include "contentmanager.h" #include "flasher.h" -#include "makeimage.h" #include "serialap.h" #include "settings.h" #include "system.h" @@ -40,7 +39,10 @@ void timeTask(void* parameter) { time_t now; time(&now); - if (now % 5 == 0 || apInfo.state != AP_STATE_ONLINE || config.runStatus != RUNSTATUS_RUN) wsSendSysteminfo(); + if (now % 5 == 0 || apInfo.state != AP_STATE_ONLINE || config.runStatus != RUNSTATUS_RUN) { + wsSendSysteminfo(); + checkVars(); + } if (now % 300 == 6 && config.runStatus != RUNSTATUS_STOP) saveDB("/current/tagDB.json"); if (apInfo.state == AP_STATE_ONLINE) contentRunner(); diff --git a/ESP32_AP-Flasher/src/makeimage.cpp b/ESP32_AP-Flasher/src/makeimage.cpp index cede5774..c6604037 100644 --- a/ESP32_AP-Flasher/src/makeimage.cpp +++ b/ESP32_AP-Flasher/src/makeimage.cpp @@ -203,6 +203,14 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) { long t = millis(); Storage.begin(); +#ifdef YELLOW_IPS_AP + if (fileout == "direct") { + tft.setRotation(3); + spr.pushSprite(0, 0); + return; + } +#endif + fs::File f_out = contentFS->open(fileout, "w"); switch (imageParams.bpp) { diff --git a/ESP32_AP-Flasher/src/newproto.cpp b/ESP32_AP-Flasher/src/newproto.cpp index 92d8080c..ea65856b 100644 --- a/ESP32_AP-Flasher/src/newproto.cpp +++ b/ESP32_AP-Flasher/src/newproto.cpp @@ -5,7 +5,6 @@ #include #include "storage.h" #include -#include #include #include "storage.h" @@ -121,8 +120,16 @@ void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, uint8_t* ds bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t nextCheckin) { if (nextCheckin > config.maxsleep) nextCheckin = config.maxsleep; if (wsClientCount() && config.stopsleep == 1) nextCheckin=0; - - tagRecord* taginfo = nullptr; +#ifdef YELLOW_IPS_AP + if (*filename == "direct") { + char dst_path[64]; + sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.raw\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); + contentFS->remove(dst_path); + return true; + } +#endif + + tagRecord* taginfo = nullptr; taginfo = tagRecord::findByMAC(dst); if (taginfo == nullptr) { wsErr("Tag not found, this shouldn't happen."); @@ -507,11 +514,11 @@ void processDataReq(struct espAvailDataReq* eadr, bool local) { } if (local) { sprintf(buffer, "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, "src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]); + // sprintf(buffer, "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); wsSendTaginfo(eadr->src, SYNC_TAGSTATUS); if (local) { udpsync.netProcessDataReq(eadr); diff --git a/ESP32_AP-Flasher/src/serialap.cpp b/ESP32_AP-Flasher/src/serialap.cpp index 51674531..075043f7 100644 --- a/ESP32_AP-Flasher/src/serialap.cpp +++ b/ESP32_AP-Flasher/src/serialap.cpp @@ -533,17 +533,11 @@ void rxSerialTask(void* parameter) { } void ShowAPInfo() { - Serial.printf("| AP Information - type %02X |\n", apInfo.type); - Serial.printf("| Channel | 0x%02X |\n", apInfo.channel); - Serial.printf("| Power | %02X |\n", apInfo.power); - Serial.printf("| MAC | %02X%02X%02X%02X%02X%02X%02X%02X |\n", apInfo.mac[7], apInfo.mac[6], apInfo.mac[5], apInfo.mac[4], apInfo.mac[3], apInfo.mac[2], apInfo.mac[1], apInfo.mac[0]); - Serial.printf("| Version | 0x%04X |\n", apInfo.version); - - if (apInfo.type == SOLUM_154_SSD1619 || apInfo.type == SOLUM_29_SSD1619 || apInfo.type == SOLUM_29_UC8151 || apInfo.type == SOLUM_42_SSD1619) { - char macString[50]; - sprintf(macString, "%02X%02X%02X%02X%02X%02X%02X%02X", apInfo.mac[7], apInfo.mac[6], apInfo.mac[5], apInfo.mac[4], apInfo.mac[3], apInfo.mac[2], apInfo.mac[1], apInfo.mac[0]); - showIpAddress(macString); - } + Serial.printf("| AP Info - type %02X |\n", apInfo.type); + Serial.printf("| Ch | 0x%02X |\n", apInfo.channel); + Serial.printf("| Power| %02X |\n", apInfo.power); + Serial.printf("| MAC | %02X%02X%02X%02X%02X%02X%02X%02X |\n", apInfo.mac[7], apInfo.mac[6], apInfo.mac[5], apInfo.mac[4], apInfo.mac[3], apInfo.mac[2], apInfo.mac[1], apInfo.mac[0]); + Serial.printf("| Ver | 0x%04X |\n", apInfo.version); } void notifySegmentedFlash() { diff --git a/ESP32_AP-Flasher/src/tag_db.cpp b/ESP32_AP-Flasher/src/tag_db.cpp index 7210bc04..66b8cfa3 100644 --- a/ESP32_AP-Flasher/src/tag_db.cpp +++ b/ESP32_AP-Flasher/src/tag_db.cpp @@ -11,6 +11,7 @@ #include "storage.h" std::vector tagDB; +std::unordered_map varDB; std::unordered_map hwdata = { {0, {152, 152, 0, 2}}, {1, {296, 128, 1, 2}}, @@ -20,7 +21,7 @@ Config config; // SemaphoreHandle_t tagDBOwner; tagRecord* tagRecord::findByMAC(uint8_t mac[8]) { - for (int16_t c = 0; c < tagDB.size(); c++) { + for (int32_t c = 0; c < tagDB.size(); c++) { tagRecord* tag = nullptr; tag = tagDB.at(c); if (memcmp(tag->mac, mac, 8) == 0) { @@ -31,7 +32,7 @@ tagRecord* tagRecord::findByMAC(uint8_t mac[8]) { } bool deleteRecord(uint8_t mac[8]) { - for (int16_t c = 0; c < tagDB.size(); c++) { + for (int32_t c = 0; c < tagDB.size(); c++) { tagRecord* tag = nullptr; tag = tagDB.at(c); if (memcmp(tag->mac, mac, 8) == 0) { @@ -91,7 +92,7 @@ String tagDBtoJson(uint8_t mac[8], uint8_t startPos) { break; } } - if (doc.capacity() - doc.memoryUsage() < doc.memoryUsage() / (c + 1) + 150) { + if (doc.capacity() - doc.memoryUsage() < doc.memoryUsage() / (c + 1) + 500) { doc["continu"] = c + 1; break; } @@ -143,7 +144,7 @@ void saveDB(String filename) { file.write('['); - for (int16_t c = 0; c < tagDB.size(); c++) { + for (int32_t c = 0; c < tagDB.size(); c++) { doc.clear(); tagRecord* taginfo = nullptr; taginfo = tagDB.at(c); @@ -205,7 +206,7 @@ void loadDB(String filename) { taginfo->lastseen = (uint32_t)tag["lastseen"]; taginfo->nextupdate = (uint32_t)tag["nextupdate"]; taginfo->expectedNextCheckin = (uint16_t)tag["nextcheckin"]; - if (taginfo->expectedNextCheckin < now - 1800) { + if (taginfo->expectedNextCheckin < now) { taginfo->expectedNextCheckin = now + 1800; } taginfo->pending = false; @@ -255,11 +256,25 @@ void destroyDB() { } uint32_t getTagCount() { + uint32_t temp = 0; + return getTagCount(temp); +} + +uint32_t getTagCount(uint32_t& timeoutcount) { uint32_t tagcount = 0; + time_t now; + time(&now); + // Serial.printf("now: %d\n", now); for (uint32_t c = 0; c < tagDB.size(); c++) { tagRecord* taginfo = nullptr; taginfo = tagDB.at(c); if (taginfo->isExternal == false) tagcount++; + int32_t timeout1 = now - taginfo->lastseen; + int32_t timeout2 = taginfo->lastseen - taginfo->expectedNextCheckin; + // Serial.printf("%d expected: %d lastseen: %d -> %d %d\n", c, taginfo->expectedNextCheckin, timeout1, timeout2); + if (((taginfo->expectedNextCheckin < 3600 && timeout1 > 3600) || + (taginfo->expectedNextCheckin > 3600 && timeout2 > 600)) && + now > 3600 && millis() > 60000) timeoutcount++; } return tagcount; } @@ -356,3 +371,22 @@ HwType getHwType(uint8_t id) { return {0, 0, 0, 0}; } } + +bool setVarDB(const std::string& key, const String& value) { + auto it = varDB.find(key); + if (it == varDB.end()) { + varStruct newVar; + newVar.value = value; + newVar.changed = true; + varDB[key] = newVar; + return true; + } else { + if (it->second.value != value) { + it->second.value = value; + it->second.changed = true; + return true; + } else { + return false; + } + } +} diff --git a/ESP32_AP-Flasher/src/web.cpp b/ESP32_AP-Flasher/src/web.cpp index 1b423d30..f98bd849 100644 --- a/ESP32_AP-Flasher/src/web.cpp +++ b/ESP32_AP-Flasher/src/web.cpp @@ -89,6 +89,18 @@ 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 timeout", 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)); + xSemaphoreTake(wsMutex, portMAX_DELAY); ws.textAll(doc.as()); xSemaphoreGive(wsMutex); @@ -364,6 +376,37 @@ void init_web() { request->send(200, "text/plain", "Ok, saved"); }); + server.on("/set_var", HTTP_POST, [](AsyncWebServerRequest *request) { + if (request->hasParam("key", true) && request->hasParam("val", true)) { + std::string key = request->getParam("key", true)->value().c_str(); + String val = request->getParam("val", true)->value(); + Serial.printf("set key %s value %s\n", key.c_str(), val); + setVarDB(key, val); + request->send(200, "text/plain", "Ok, saved"); + } else { + request->send(500, "text/plain", "param error"); + } + }); + server.on("/set_vars", HTTP_POST, [](AsyncWebServerRequest *request) { + if (request->hasParam("json", true)) { + DynamicJsonDocument jsonDocument(2048); + DeserializationError error = deserializeJson(jsonDocument, request->getParam("json", true)->value()); + if (error) { + request->send(400, "text/plain", "Failed to parse JSON"); + return; + } + for (JsonPair kv : jsonDocument.as()) { + std::string key = kv.key().c_str(); + String val = kv.value().as(); + Serial.printf("set key %s value %s\n", key.c_str(), val); + setVarDB(key, val); + } + request->send(200, "text/plain", "JSON uploaded and processed"); + } else { + request->send(400, "text/plain", "No 'json' parameter found in request"); + } + }); + // setup server.on("/setup", HTTP_GET, [](AsyncWebServerRequest *request) { @@ -438,9 +481,14 @@ void init_web() { // end of setup server.on("/backup_db", HTTP_GET, [](AsyncWebServerRequest *request) { - saveDB("/current/tagDB.json"); + // saveDB("/current/tagDB.json"); File file = contentFS->open("/current/tagDB.json", "r"); + if (!file) { + request->send(404); + return; + } AsyncWebServerResponse *response = request->beginResponse(file, "tagDB.json", String(), true); + response->addHeader("Content-Disposition", "attachment; filename=tagDB.json"); request->send(response); file.close(); }); @@ -490,16 +538,20 @@ void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index request->_tempFile.close(); if (request->hasParam("mac", true)) { String dst = request->getParam("mac", true)->value(); - bool dither = true; - if (request->hasParam("dither", true)) { - if (request->getParam("dither", true)->value() == "0") dither = false; - } uint8_t mac[8]; if (hex2mac(dst, mac)) { tagRecord *taginfo = nullptr; taginfo = tagRecord::findByMAC(mac); if (taginfo != nullptr) { - taginfo->modeConfigJson = "{\"filename\":\"" + dst + ".jpg\",\"timetolive\":\"0\",\"dither\":\"" + String(dither) + "\",\"delete\":\"1\"}"; + bool dither = true; + if (request->hasParam("dither", true)) { + if (request->getParam("dither", true)->value() == "0") dither = false; + } + uint32_t ttl = 0; + if (request->hasParam("ttl", true)) { + ttl = request->getParam("ttl", true)->value().toInt(); + } + taginfo->modeConfigJson = "{\"filename\":\"" + dst + ".jpg\",\"timetolive\":\"" + String(ttl) + "\",\"dither\":\"" + String(dither) + "\",\"delete\":\"1\"}"; taginfo->contentMode = 0; taginfo->nextupdate = 0; wsSendTaginfo(mac, SYNC_USERCFG); @@ -521,19 +573,23 @@ void doJsonUpload(AsyncWebServerRequest *request) { } if (request->hasParam("mac", true) && request->hasParam("json", true)) { String dst = request->getParam("mac", true)->value(); - File file = LittleFS.open("/" + dst + ".json", "w"); - if (!file) { - request->send(400, "text/plain", "Failed to create file"); - return; - } - file.print(request->getParam("json", true)->value()); - file.close(); uint8_t mac[8]; if (hex2mac(dst, mac)) { + File file = LittleFS.open("/current/" + dst + ".json", "w"); + if (!file) { + request->send(400, "text/plain", "Failed to create file"); + return; + } + file.print(request->getParam("json", true)->value()); + file.close(); tagRecord *taginfo = nullptr; taginfo = tagRecord::findByMAC(mac); if (taginfo != nullptr) { - taginfo->modeConfigJson = "{\"filename\":\"/" + dst + ".json\"}"; + uint32_t ttl = 0; + if (request->hasParam("ttl", true)) { + ttl = request->getParam("ttl", true)->value().toInt(); + } + taginfo->modeConfigJson = "{\"filename\":\"/current/" + dst + ".json\",\"interval\":\"" + String(ttl) + "\"}"; taginfo->contentMode = 19; taginfo->nextupdate = 0; wsSendTaginfo(mac, SYNC_USERCFG); diff --git a/ESP32_AP-Flasher/wwwroot/content_cards.json b/ESP32_AP-Flasher/wwwroot/content_cards.json index 08dc92b5..07092b75 100644 --- a/ESP32_AP-Flasher/wwwroot/content_cards.json +++ b/ESP32_AP-Flasher/wwwroot/content_cards.json @@ -293,13 +293,19 @@ { "key": "url", "name": "URL", - "desc": "Full URL of the json template. See OpenEpaperLink wiki for the right json format", + "desc": "Full URL of the json template. See OpenEpaperLink wiki for the right json format. Specify a url OR a filename", + "type": "text" + }, + { + "key": "filename", + "name": "Filename", + "desc": "Filename of the json template. See OpenEpaperLink wiki for the right json format. Specify a url OR a filename", "type": "text" }, { "key": "interval", "name": "Interval", - "desc": "How often (in minutes) the template is being fetched. Minimum is 3 minutes.", + "desc": "In case of an url, wow often (in minutes) the template is being fetched. Minimum is 3 minutes.", "type": "int" } ] @@ -607,5 +613,11 @@ "type": "text" } ] + }, + { + "id": 21, + "name": "Display access point info", + "desc": "Displays information about the currently connected access point", + "hwtype": [0, 1] } ] \ No newline at end of file diff --git a/ESP32_AP-Flasher/wwwroot/main.js b/ESP32_AP-Flasher/wwwroot/main.js index b1694376..fb0ef5db 100644 --- a/ESP32_AP-Flasher/wwwroot/main.js +++ b/ESP32_AP-Flasher/wwwroot/main.js @@ -271,10 +271,11 @@ function processTags(tagArray) { } function updatecards() { + if (servertimediff > 1000000000) servertimediff = 0; $('#taglist').querySelectorAll('[data-mac]').forEach(item => { let tagmac = item.dataset.mac; - if (item.dataset.lastseen && item.dataset.lastseen > 1672531200) { + if (item.dataset.lastseen && item.dataset.lastseen > (Date.now() / 1000) - servertimediff - 30 * 24 * 3600 * 60) { let idletime = (Date.now() / 1000) - servertimediff - item.dataset.lastseen; $('#tag' + tagmac + ' .lastseen').innerHTML = "last seen" + displayTime(Math.floor(idletime)) + " ago"; if ((Date.now() / 1000) - servertimediff - 600 > item.dataset.nextcheckin) { @@ -297,6 +298,8 @@ function updatecards() { if (item.dataset.nextcheckin > 1672531200 && parseInt(item.dataset.wakeupreason) == 0) { let nextcheckin = item.dataset.nextcheckin - ((Date.now() / 1000) - servertimediff); $('#tag' + tagmac + ' .nextcheckin').innerHTML = "expected checkin" + displayTime(Math.floor(nextcheckin)); + } else { + $('#tag' + tagmac + ' .nextcheckin').innerHTML = ""; } }) } @@ -387,7 +390,7 @@ $('#cfgsave').onclick = function () { formData.append("alias", $('#cfgalias').value); if (contentMode) { - extraoptions.forEach(element => { + extraoptions?.forEach(element => { if ($('#opt' + element.key)) { obj[element.key] = $('#opt' + element.key).value; } @@ -573,7 +576,7 @@ function contentselected() { } $('#paintbutton').style.display = (contentMode == 0 ? 'inline-block' : 'none'); let extraoptions = contentDef?.param ?? null; - extraoptions.forEach(element => { + extraoptions?.forEach(element => { var label = document.createElement("label"); label.innerHTML = element.name; label.setAttribute("for", 'opt' + element.key); @@ -629,7 +632,7 @@ function populateSelectTag(hwtype, capabilities) { var option; cardconfig.forEach(item => { var capcheck = item.capabilities ?? 0; - var hwtypeArray = item.hwtype; + var hwtypeArray = item.hwtype ?? []; if ((hwtypeArray.includes(hwtype) || tagTypes[hwtype].contentids.includes(item.id)) && (capabilities & capcheck || capcheck == 0)) { option = document.createElement("option"); option.value = item.id; @@ -706,10 +709,15 @@ function processQueue() { } isProcessing = true; const { id, imageSrc } = imageQueue.shift(); + const hwtype = $('#tag' + id).dataset.hwtype; + if (tagTypes[hwtype] && tagTypes[hwtype].busy) { + imageQueue.push({ id, imageSrc }); + setTimeout(processQueue, 50); + return; + }; + const canvas = $('#tag' + id + ' .tagimg'); canvas.style.display = 'block'; - const hwtype = $('#tag' + id).dataset.hwtype; - if (tagTypes[hwtype] && tagTypes[hwtype].busy) setTimeout(processQueue, 50); fetch(imageSrc, { cache: "force-cache" }) .then(response => response.arrayBuffer()) @@ -719,11 +727,11 @@ function processQueue() { const ctx = canvas.getContext('2d'); const imageData = ctx.createImageData(canvas.width, canvas.height); const data = new Uint8ClampedArray(buffer); + if (data.length == 0) canvas.style.display = 'none'; if (tagTypes[hwtype].bpp == 16) { - const is16Bit = data.length == tagTypes[hwtype].width * tagTypes[hwtype].height * 2; - for (let i = 0; i < tagTypes[hwtype].width * tagTypes[hwtype].height; i++) { + for (let i = 0; i < min(tagTypes[hwtype].width * tagTypes[hwtype].height, data.length); i++) { const dataIndex = is16Bit ? i * 2 : i; const rgb = is16Bit ? (data[dataIndex] << 8) | data[dataIndex + 1] : data[dataIndex]; diff --git a/ESP32_AP-Flasher/wwwroot/variables-demo.html b/ESP32_AP-Flasher/wwwroot/variables-demo.html new file mode 100644 index 00000000..fe0a71d7 --- /dev/null +++ b/ESP32_AP-Flasher/wwwroot/variables-demo.html @@ -0,0 +1,41 @@ + + + + + + Variables upload Form + + + +

demo Json variables

+

You can use this as an example how to change variables to be uses in a json template. Look at the html source for the workings. You can use the variables in any json template, using

{variablename}
. As soon as a variable is changed, the tag is being refreshed with the new info.

+

+ Change one variable:
+

+ key:
+ value:
+
+
+

+

+ Change multiple variables, using json:
+

+

+
+ +

+ +

+ +

+ +
+

+ + +