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.
This commit is contained in:
Nic Limper
2023-08-11 18:46:46 +02:00
parent 0cbb9e770e
commit f4273630ee
17 changed files with 486 additions and 226 deletions

View File

@@ -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 ] }
]
}
}

View File

@@ -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 ] }
]
}
}

View File

@@ -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" ] }
]
}
}

View File

@@ -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);

View File

@@ -3,6 +3,8 @@
#pragma once
extern TFT_eSPI tft;
struct imgParam {
bool hasRed;
uint8_t dataType;

View File

@@ -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<tagRecord*> tagDB;
extern std::unordered_map<int, HwType> hwtype;
extern std::unordered_map<std::string, varStruct> 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)
#pragma pack(pop)

View File

@@ -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<JsonObject>();
if (cfgobj["filename"]) {
String jsonfile = cfgobj["filename"].as<String>();
File file = contentFS->open(jsonfile, "r");
if (file) {
size_t fileSize = file.size();
std::unique_ptr<char[]> 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<int>(), taginfo, imageParams);
} else {
wsErr("error opening file " + cfgobj["filename"].as<String>());
}
@@ -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<String>();
@@ -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<uint8_t *>(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<uint8_t *>(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<String>();
if (count > 99) font = loc["fonts"][1].as<String>();
@@ -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<time_t>();
struct tm *datum = localtime(&weatherday);
drawString(spr, String(languageDaysShort[getCurrentLanguage()][datum->tm_wday]), dag * loc["column"][1].as<int>() + loc["day"][0].as<int>(), loc["day"][1], loc["day"][2], TC_DATUM, PAL_BLACK);
drawString(spr, String(languageDaysShort[getCurrentLanguage()][datum->tm_wday]), dag * loc["column"][1].as<int>() + loc["day"][0].as<int>(), loc["day"][1], loc["day"][2], TC_DATUM, TFT_BLACK);
uint8_t weathercode = doc["daily"]["weathercode"][dag].as<int>();
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<int>() + dag * loc["column"][1].as<int>(), 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<int>() + dag * loc["column"][1].as<int>(), 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<int>() + dag * loc["column"][1].as<int>(), loc["wind"][1], "/fonts/weathericons.ttf", TC_DATUM, TFT_BLACK, loc["icon"][2]);
int8_t tmin = round(doc["daily"]["temperature_2m_min"][dag].as<double>());
int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as<double>());
@@ -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<double>());
if (rain > 0) {
drawString(spr, String(rain) + "mm", dag * loc["column"][1].as<int>() + loc["rain"][0].as<int>(), loc["rain"][1], "", TC_DATUM, (rain > 10 ? PAL_RED : PAL_BLACK));
drawString(spr, String(rain) + "mm", dag * loc["column"][1].as<int>() + loc["rain"][0].as<int>(), loc["rain"][1], "", TC_DATUM, (rain > 10 ? TFT_RED : TFT_BLACK));
}
}
drawString(spr, String(tmin) + " ", dag * loc["column"][1].as<int>() + loc["day"][0].as<int>(), loc["day"][4], "", TR_DATUM, (tmin < 0 ? PAL_RED : PAL_BLACK));
drawString(spr, String(" ") + String(tmax), dag * loc["column"][1].as<int>() + loc["day"][0].as<int>(), loc["day"][4], "", TL_DATUM, (tmax < 0 ? PAL_RED : PAL_BLACK));
drawString(spr, String(" ") + String(wind), dag * loc["column"][1].as<int>() + loc["day"][0].as<int>(), loc["day"][3], "", TL_DATUM, (wind > 5 ? PAL_RED : PAL_BLACK));
drawString(spr, String(tmin) + " ", dag * loc["column"][1].as<int>() + loc["day"][0].as<int>(), loc["day"][4], "", TR_DATUM, (tmin < 0 ? TFT_RED : TFT_BLACK));
drawString(spr, String(" ") + String(tmax), dag * loc["column"][1].as<int>() + loc["day"][0].as<int>(), loc["day"][4], "", TL_DATUM, (tmax < 0 ? TFT_RED : TFT_BLACK));
drawString(spr, String(" ") + String(wind), dag * loc["column"][1].as<int>() + loc["day"][0].as<int>(), 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<int>(), i, PAL_BLACK);
spr.drawPixel(dag * loc["column"][1].as<int>(), 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<int>() + i * loc["line"][2].as<int>(), 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<int>() + i * loc["line"][2].as<int>(), 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<int>() + i * loc["line"][2].as<int>());
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<int>() + loc["bars"][0].as<int>(), loc["bars"][1].as<int>() - (value - 70), loc["bars"][2], (value - 70), (value > 130 ? PAL_RED : PAL_BLACK));
spr.fillRect(i * loc["cols"][2].as<int>() + loc["bars"][0].as<int>(), loc["bars"][1].as<int>() - (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<int>() + loc["cols"][0].as<int>(), 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<JsonArray>();
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<int>(), textArray[1].as<int>(), textArray[3], align, getColor(color), size);
String bgcolorstr = textArray[7].as<String>();
uint16_t bgcolor = (bgcolorstr.length() > 0) ? getColor(bgcolorstr) : TFT_WHITE;
drawString(spr, textArray[2], textArray[0].as<int>(), textArray[1].as<int>(), 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<int>(), boxArray[1].as<int>(), boxArray[2].as<int>(), boxArray[3].as<int>(), getColor(color));
spr.fillRect(boxArray[0].as<int>(), boxArray[1].as<int>(), boxArray[2].as<int>(), boxArray[3].as<int>(), getColor(boxArray[4]));
} else if (element.containsKey("line")) {
const JsonArray &lineArray = element["line"];
uint16_t color = lineArray[4] | 1;
spr.drawLine(lineArray[0].as<int>(), lineArray[1].as<int>(), lineArray[2].as<int>(), lineArray[3].as<int>(), getColor(color));
spr.drawLine(lineArray[0].as<int>(), lineArray[1].as<int>(), lineArray[2].as<int>(), lineArray[3].as<int>(), getColor(lineArray[4]));
} else if (element.containsKey("triangle")) {
const JsonArray &lineArray = element["triangle"];
uint16_t color = lineArray[6] | 1;
spr.fillTriangle(lineArray[0].as<int>(), lineArray[1].as<int>(), lineArray[2].as<int>(), lineArray[3].as<int>(), lineArray[4].as<int>(), lineArray[5].as<int>(), getColor(color));
spr.fillTriangle(lineArray[0].as<int>(), lineArray[1].as<int>(), lineArray[2].as<int>(), lineArray[3].as<int>(), lineArray[4].as<int>(), lineArray[5].as<int>(), 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;
}
}
}

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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) {

View File

@@ -5,7 +5,6 @@
#include <HTTPClient.h>
#include "storage.h"
#include <MD5Builder.h>
#include <makeimage.h>
#include <time.h>
#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, "<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]);
// 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]);
}
Serial.print(buffer);
wsSendTaginfo(eadr->src, SYNC_TAGSTATUS);
if (local) {
udpsync.netProcessDataReq(eadr);

View File

@@ -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() {

View File

@@ -11,6 +11,7 @@
#include "storage.h"
std::vector<tagRecord*> tagDB;
std::unordered_map<std::string, varStruct> varDB;
std::unordered_map<int, HwType> 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;
}
}
}

View File

@@ -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<String>());
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<JsonObject>()) {
std::string key = kv.key().c_str();
String val = kv.value().as<String>();
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);

View File

@@ -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]
}
]

View File

@@ -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 = "<span>last seen</span>" + 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 = "<span>expected checkin</span>" + 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];

View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Variables upload Form</title>
</head>
<body>
<h3>demo Json variables</h3>
<p>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 <pre>{variablename}</pre>. As soon as a variable is changed, the tag is being refreshed with the new info.</p>
<p>
Change one variable:<br>
<form method="POST" action="/set_var">
key: <input type="text" name="key" value="testkey"><br>
value: <input type="text" name="val" value="MyCoolValue"><br>
<input type="submit" value="submit"><br>
</form>
</p>
<p>
Change multiple variables, using json:<br>
<form method="POST" action="/set_vars">
<p>
<label for="vars">Place the json string here</label><br>
<textarea id="vars" name="json" style="width:500px;height:80px;">
{ "temperature": 28.5,
"door": "open",
"testkey": "MyCoolValue"
}
</textarea>
</p>
<p>
<input type="submit" value="submit json">
</p>
</form>
</p>
</body>
</html>