From 4ab924b41f18bbe59da00f6185a3f19454533f43 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Fri, 10 Feb 2023 02:13:37 +0100 Subject: [PATCH 1/3] weather forecast --- esp32_fw/data/fonts/weathericons50.vlw | Bin 82975 -> 0 bytes esp32_fw/data/fonts/weathericons70.vlw | Bin 0 -> 83807 bytes esp32_fw/data/www/index.html | 1 + esp32_fw/data/www/main.js | 3 +- esp32_fw/include/contentmanager.h | 1 + esp32_fw/src/contentmanager.cpp | 148 ++++++++++++++++++++----- 6 files changed, 127 insertions(+), 26 deletions(-) delete mode 100644 esp32_fw/data/fonts/weathericons50.vlw create mode 100644 esp32_fw/data/fonts/weathericons70.vlw diff --git a/esp32_fw/data/fonts/weathericons50.vlw b/esp32_fw/data/fonts/weathericons50.vlw deleted file mode 100644 index 4019f1313036d659e9d7ffe41f5ea97be6d0f244..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82975 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 days + diff --git a/esp32_fw/data/www/main.js b/esp32_fw/data/www/main.js index 504bd8cf..ad46320f 100644 --- a/esp32_fw/data/www/main.js +++ b/esp32_fw/data/www/main.js @@ -1,6 +1,6 @@ const $ = document.querySelector.bind(document); -const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url"]; +const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url", "Weather forecast"]; const models = ["1.54\" 152x152px", "2.9\" 296x128px", "4.2\" 400x300px"]; const contentModeOptions = []; contentModeOptions[0] = ["filename","timetolive"]; @@ -11,6 +11,7 @@ contentModeOptions[4] = ["location"]; contentModeOptions[5] = ["filename"]; contentModeOptions[6] = ["text"]; contentModeOptions[7] = ["url","interval"]; +contentModeOptions[8] = ["location"]; const imageQueue = []; let isProcessing = false; diff --git a/esp32_fw/include/contentmanager.h b/esp32_fw/include/contentmanager.h index 02b58235..0a81963a 100644 --- a/esp32_fw/include/contentmanager.h +++ b/esp32_fw/include/contentmanager.h @@ -14,6 +14,7 @@ void initSprite(TFT_eSprite &spr, int w, int h); void drawDate(String &filename, tagRecord *&taginfo); void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord *&taginfo); void drawWeather(String &filename, String location, tagRecord *&taginfo); +void drawForecast(String &filename, String location, tagRecord *&taginfo); void drawIdentify(String &filename, tagRecord *&taginfo); bool getImgURL(String &filename, String URL, time_t fetched); char *formatHttpDate(time_t t); diff --git a/esp32_fw/src/contentmanager.cpp b/esp32_fw/src/contentmanager.cpp index fb5e96df..380415e3 100644 --- a/esp32_fw/src/contentmanager.cpp +++ b/esp32_fw/src/contentmanager.cpp @@ -21,6 +21,7 @@ enum contentModes { Firmware, Memo, ImageUrl, + Forecast, }; @@ -122,6 +123,13 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) { updateTagImage(filename, mac, 15); break; + case Forecast: + + drawForecast(filename, cfgobj["location"], taginfo); + taginfo->nextupdate = now + 3600; + updateTagImage(filename, mac, 15); + break; + case Firmware: filename = cfgobj["filename"].as(); @@ -169,10 +177,10 @@ bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin) { void drawString(TFT_eSprite &spr, String content, uint16_t posx, uint16_t posy, String font, byte align,uint16_t color) { // drawString(spr,"test",100,10,"bahnschrift30",TC_DATUM,TFT_RED); spr.setTextDatum(align); - spr.loadFont(font, LittleFS); + if (font != "") spr.loadFont(font, LittleFS); spr.setTextColor(color, TFT_WHITE); spr.drawString(content, posx, posy); - spr.unloadFont(); + if (font != "") spr.unloadFont(); } void initSprite(TFT_eSprite &spr, int w, int h) { @@ -316,8 +324,6 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo) { weatherIcons[2] = "\0uf086"; } - String windIcons[] = {"\uf0b7", "\uf0b8", "\uf0b9", "\uf0ba", "\uf0bb", "\uf0bc", "\uf0bd", "\uf0be", "\uf0bf", "\uf0c0", "\uf0c1", "\uf0c2", "\uf0c3"}; - doc.clear(); LittleFS.begin(); @@ -328,32 +334,26 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo) { initSprite(spr, 296, 128); drawString(spr, location, 10, 10, "fonts/bahnschrift30"); + drawString(spr, String(wind), 275, 10, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? TFT_RED : TFT_BLACK)); char tmpOutput[5]; dtostrf(temperature, 2, 1, tmpOutput); drawString(spr, String(tmpOutput), 5, 65, "fonts/bahnschrift70", TL_DATUM, (temperature < 0 ? TFT_RED : TFT_BLACK)); - spr.loadFont("fonts/weathericons78", LittleFS); + spr.loadFont("fonts/weathericons70", LittleFS); if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) { spr.setTextColor(TFT_RED, TFT_WHITE); } else { spr.setTextColor(TFT_BLACK, TFT_WHITE); } - spr.setCursor(185, 20); + + spr.setCursor(185, 32); spr.printToSprite(weatherIcons[weathercode]); spr.unloadFont(); spr.loadFont("fonts/weathericons30", LittleFS); - if (wind > 4) { - spr.setTextColor(TFT_RED, TFT_WHITE); - } else { - spr.setTextColor(TFT_BLACK, TFT_WHITE); - } - spr.setCursor(255, 0); - spr.printToSprite(windIcons[wind]); - spr.setTextColor(TFT_BLACK, TFT_WHITE); - spr.setCursor(230, -5); + spr.setCursor(235, -3); spr.printToSprite(windDirectionIcon(winddirection)); if (weathercode > 10) { spr.setTextColor(TFT_RED, TFT_WHITE); @@ -381,21 +381,16 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo) { } else { spr.setTextColor(TFT_BLACK, TFT_WHITE); } - spr.setCursor(35, 25); + + spr.setCursor(30, 33); spr.printToSprite(weatherIcons[weathercode]); spr.unloadFont(); - spr.loadFont("fonts/weathericons30", LittleFS); - if (wind > 4) { - spr.setTextColor(TFT_RED, TFT_WHITE); - } else { - spr.setTextColor(TFT_BLACK, TFT_WHITE); - } - spr.setCursor(115, -5); - spr.printToSprite(windIcons[wind]); + drawString(spr, String(wind), 140, 10, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? TFT_RED : TFT_BLACK)); + spr.loadFont("fonts/weathericons30", LittleFS); spr.setTextColor(TFT_BLACK, TFT_WHITE); - spr.setCursor(90, -5); + spr.setCursor(100, -2); spr.printToSprite(windDirectionIcon(winddirection)); if (weathercode > 10) { spr.setTextColor(TFT_RED, TFT_WHITE); @@ -413,6 +408,109 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo) { http.end(); } +void drawForecast(String &filename, String location, tagRecord *&taginfo) { + TFT_eSPI tft = TFT_eSPI(); + TFT_eSprite spr = TFT_eSprite(&tft); + + wsLog("get weather"); + HTTPClient http; + http.begin("https://geocoding-api.open-meteo.com/v1/search?name=" + urlEncode(location.c_str()) + "&count=1"); + http.setTimeout(5000); // timeout in ms + int httpCode = http.GET(); + if (httpCode == 200) { + DynamicJsonDocument doc(2000); + DeserializationError error = deserializeJson(doc, http.getStream()); + http.end(); + + http.begin("https://api.open-meteo.com/v1/forecast?latitude=" + doc["results"][0]["latitude"].as() + "&longitude=" + doc["results"][0]["longitude"].as() + "&daily=weathercode,temperature_2m_max,temperature_2m_min,precipitation_sum,windspeed_10m_max,winddirection_10m_dominant&windspeed_unit=ms&timeformat=unixtime&timezone=" + doc["results"][0]["timezone"].as()); + + doc.clear(); + http.setTimeout(5000); // timeout in ms + int httpCode = http.GET(); + + if (httpCode == 200) { + StaticJsonDocument<500> filter; + filter["daily"]["time"][0] = true; + filter["daily"]["weathercode"][0] = true; + filter["daily"]["temperature_2m_max"][0] = true; + filter["daily"]["temperature_2m_min"][0] = true; + filter["daily"]["precipitation_sum"][0] = true; + filter["daily"]["windspeed_10m_max"][0] = true; + filter["daily"]["winddirection_10m_dominant"][0] = true; + + //DeserializationError error = deserializeJson(doc, http.getString(), DeserializationOption::Filter(filter)); + DeserializationError error = deserializeJson(doc, http.getString()); + if (error) { + Serial.println(F("deserializeJson() failed: ")); + Serial.println(error.c_str()); + } + + static const char *weekday_name[] = {"ZO", "MA", "DI", "WO", "DO", "VR", "ZA"}; + + String weatherIcons[] = {"\uf00d", "\uf00c", "\uf002", "\uf013", "\uf013", "\uf014", "-", "-", "\uf014", "-", "-", + "\uf01a", "-", "\uf01a", "-", "\uf01a", "\uf017", "\uf017", "-", "-", "-", + "\uf019", "-", "\uf019", "-", "\uf019", "\uf015", "\uf015", "-", "-", "-", + "\uf01b", "-", "\uf01b", "-", "\uf01b", "-", "\uf076", "-", "-", "\uf01a", + "\uf01a", "\uf01a", "-", "-", "\uf064", "\uf064", "-", "-", "-", "-", + "-", "-", "-", "-", "\uf01e", "\uf01d", "-", "-", "\uf01e"}; + + LittleFS.begin(); + tft.setTextWrap(false, false); + + if (taginfo->hwType == SOLUM_29_033) { + initSprite(spr, 296, 128); + + spr.setTextFont(2); + spr.setTextColor(TFT_BLACK, TFT_WHITE); + spr.drawString(location, 5, 0); + + for (uint8_t dag = 0; dag < 5; dag++) { + time_t weatherday = doc["daily"]["time"][dag].as(); + struct tm *datum = localtime(&weatherday); + drawString(spr, String(weekday_name[datum->tm_wday]), dag * 59 + 30, 20, "fonts/twbold20", TC_DATUM, TFT_BLACK); + + uint8_t weathercode = doc["daily"]["weathercode"][dag].as(); + if (weathercode > 40) weathercode -= 40; + + spr.loadFont("fonts/weathericons30", LittleFS); + if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) { + spr.setTextColor(TFT_RED, TFT_WHITE); + } else { + spr.setTextColor(TFT_BLACK, TFT_WHITE); + } + spr.setTextDatum(TL_DATUM); + spr.setCursor(12 + dag * 59, 55); + spr.printToSprite(weatherIcons[weathercode]); + + spr.setTextColor(TFT_BLACK, TFT_WHITE); + spr.setCursor(17 + dag * 59, 24); + spr.printToSprite(windDirectionIcon(doc["daily"]["winddirection_10m_dominant"][dag])); + spr.unloadFont(); + + int8_t tmin = round(doc["daily"]["temperature_2m_min"][dag].as()); + int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as()); + uint8_t wind = windSpeedToBeaufort(doc["daily"]["windspeed_10m_max"][dag].as()); + + spr.loadFont("fonts/tw20", LittleFS); + drawString(spr, String(tmin) + " ", dag * 59 + 30, 105, "", TR_DATUM, (tmin < 0 ? TFT_RED : TFT_BLACK)); + drawString(spr, String(" ") + String(tmax), dag * 59 + 30, 105, "", TL_DATUM, (tmax < 0 ? TFT_RED : TFT_BLACK)); + drawString(spr, String(" ") + String(wind), dag * 59 + 30, 40, "", TL_DATUM, (wind > 5 ? TFT_RED : TFT_BLACK)); + spr.unloadFont(); + if (dag>0) { + for (int i = 20; i < 128; i+=3) { + spr.drawPixel(dag * 59, i, TFT_BLACK); + } + } + } + + } + spr2grays(spr, spr.width(), spr.height(), filename); + spr.deleteSprite(); + } + } + http.end(); +} + void drawIdentify(String &filename, tagRecord *&taginfo) { TFT_eSPI tft = TFT_eSPI(); From 577b2e541a5cfcef4551fc69a15213b0614a9890 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Fri, 10 Feb 2023 18:14:39 +0100 Subject: [PATCH 2/3] added rss feed to content --- esp32_fw/data/fonts/GillSC16.vlw | Bin 0 -> 6726 bytes esp32_fw/data/fonts/GillSC20.vlw | Bin 0 -> 8940 bytes esp32_fw/data/www/index.html | 1 + esp32_fw/data/www/main.js | 3 +- esp32_fw/include/contentmanager.h | 10 ++++ esp32_fw/platformio.ini | 2 + esp32_fw/src/contentmanager.cpp | 89 ++++++++++++++++++++++++++---- 7 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 esp32_fw/data/fonts/GillSC16.vlw create mode 100644 esp32_fw/data/fonts/GillSC20.vlw diff --git a/esp32_fw/data/fonts/GillSC16.vlw b/esp32_fw/data/fonts/GillSC16.vlw new file mode 100644 index 0000000000000000000000000000000000000000..04ebc30c1c9263ee1d70a8355e4fcdb475532093 GIT binary patch literal 6726 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 literal 0 HcmV?d00001 diff --git a/esp32_fw/data/www/index.html b/esp32_fw/data/www/index.html index d9080f3e..0cfbbcfa 100644 --- a/esp32_fw/data/www/index.html +++ b/esp32_fw/data/www/index.html @@ -31,6 +31,7 @@ + diff --git a/esp32_fw/data/www/main.js b/esp32_fw/data/www/main.js index ad46320f..377cfa9f 100644 --- a/esp32_fw/data/www/main.js +++ b/esp32_fw/data/www/main.js @@ -1,6 +1,6 @@ const $ = document.querySelector.bind(document); -const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url", "Weather forecast"]; +const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url", "Weather forecast","RSS feed"]; const models = ["1.54\" 152x152px", "2.9\" 296x128px", "4.2\" 400x300px"]; const contentModeOptions = []; contentModeOptions[0] = ["filename","timetolive"]; @@ -12,6 +12,7 @@ contentModeOptions[5] = ["filename"]; contentModeOptions[6] = ["text"]; contentModeOptions[7] = ["url","interval"]; contentModeOptions[8] = ["location"]; +contentModeOptions[9] = ["title", "url", "interval"]; const imageQueue = []; let isProcessing = false; diff --git a/esp32_fw/include/contentmanager.h b/esp32_fw/include/contentmanager.h index 0a81963a..ef6a41d0 100644 --- a/esp32_fw/include/contentmanager.h +++ b/esp32_fw/include/contentmanager.h @@ -6,6 +6,15 @@ #include "tag_db.h" #include +struct contentTypes { + uint16_t id; + String name; + uint16_t tagTypes; + void (*functionname)(); + String description; + String optionList; +}; + void contentRunner(); void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo); bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin); @@ -17,6 +26,7 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo); void drawForecast(String &filename, String location, tagRecord *&taginfo); void drawIdentify(String &filename, tagRecord *&taginfo); bool getImgURL(String &filename, String URL, time_t fetched); +bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo); char *formatHttpDate(time_t t); String urlEncode(const char *msg); int windSpeedToBeaufort(float windSpeed); diff --git a/esp32_fw/platformio.ini b/esp32_fw/platformio.ini index 77108edd..e92c1d78 100644 --- a/esp32_fw/platformio.ini +++ b/esp32_fw/platformio.ini @@ -24,5 +24,7 @@ lib_deps = bblanchon/ArduinoJson bodmer/TFT_eSPI https://github.com/Bodmer/TJpg_Decoder.git + https://github.com/garretlab/shoddyxml2 + https://github.com/Bodmer/U8g2_for_TFT_eSPI upload_port = COM12 monitor_port = COM12 diff --git a/esp32_fw/src/contentmanager.cpp b/esp32_fw/src/contentmanager.cpp index 380415e3..a5d0fdf4 100644 --- a/esp32_fw/src/contentmanager.cpp +++ b/esp32_fw/src/contentmanager.cpp @@ -6,8 +6,10 @@ #include "newproto.h" #include #include +#include #include +#include "U8g2_for_TFT_eSPI.h" #include "commstructs.h" #include "makeimage.h" #include "web.h" @@ -22,6 +24,7 @@ enum contentModes { Memo, ImageUrl, Forecast, + RSSFeed, }; @@ -126,7 +129,7 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) { case Forecast: drawForecast(filename, cfgobj["location"], taginfo); - taginfo->nextupdate = now + 3600; + taginfo->nextupdate = now + 3 * 3600; updateTagImage(filename, mac, 15); break; @@ -164,6 +167,16 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) { taginfo->nextupdate = now + 300; } break; + + case RSSFeed: + + if (getRSSfeed(filename, cfgobj["url"], cfgobj["title"], taginfo)) { + taginfo->nextupdate = now + 60 * (cfgobj["interval"].as() < 5 ? 5 : cfgobj["interval"].as()); + updateTagImage(filename, mac, cfgobj["interval"].as()); + } else { + taginfo->nextupdate = now + 300; + } + break; } taginfo->modeConfigJson = doc.as(); @@ -333,8 +346,8 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo) { initSprite(spr, 296, 128); - drawString(spr, location, 10, 10, "fonts/bahnschrift30"); - drawString(spr, String(wind), 275, 10, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? TFT_RED : TFT_BLACK)); + drawString(spr, location, 5, 5, "fonts/bahnschrift30"); + drawString(spr, String(wind), 280, 5, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? TFT_RED : TFT_BLACK)); char tmpOutput[5]; dtostrf(temperature, 2, 1, tmpOutput); @@ -467,7 +480,7 @@ void drawForecast(String &filename, String location, tagRecord *&taginfo) { for (uint8_t dag = 0; dag < 5; dag++) { time_t weatherday = doc["daily"]["time"][dag].as(); struct tm *datum = localtime(&weatherday); - drawString(spr, String(weekday_name[datum->tm_wday]), dag * 59 + 30, 20, "fonts/twbold20", TC_DATUM, TFT_BLACK); + drawString(spr, String(weekday_name[datum->tm_wday]), dag * 59 + 30, 18, "fonts/twbold20", TC_DATUM, TFT_BLACK); uint8_t weathercode = doc["daily"]["weathercode"][dag].as(); if (weathercode > 40) weathercode -= 40; @@ -479,11 +492,11 @@ void drawForecast(String &filename, String location, tagRecord *&taginfo) { spr.setTextColor(TFT_BLACK, TFT_WHITE); } spr.setTextDatum(TL_DATUM); - spr.setCursor(12 + dag * 59, 55); + spr.setCursor(12 + dag * 59, 58); spr.printToSprite(weatherIcons[weathercode]); spr.setTextColor(TFT_BLACK, TFT_WHITE); - spr.setCursor(17 + dag * 59, 24); + spr.setCursor(17 + dag * 59, 27); spr.printToSprite(windDirectionIcon(doc["daily"]["winddirection_10m_dominant"][dag])); spr.unloadFont(); @@ -491,10 +504,10 @@ void drawForecast(String &filename, String location, tagRecord *&taginfo) { int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as()); uint8_t wind = windSpeedToBeaufort(doc["daily"]["windspeed_10m_max"][dag].as()); - spr.loadFont("fonts/tw20", LittleFS); - drawString(spr, String(tmin) + " ", dag * 59 + 30, 105, "", TR_DATUM, (tmin < 0 ? TFT_RED : TFT_BLACK)); - drawString(spr, String(" ") + String(tmax), dag * 59 + 30, 105, "", TL_DATUM, (tmax < 0 ? TFT_RED : TFT_BLACK)); - drawString(spr, String(" ") + String(wind), dag * 59 + 30, 40, "", TL_DATUM, (wind > 5 ? TFT_RED : TFT_BLACK)); + spr.loadFont("fonts/GillSC20", LittleFS); + drawString(spr, String(tmin) + " ", dag * 59 + 30, 108, "", TR_DATUM, (tmin < 0 ? TFT_RED : TFT_BLACK)); + drawString(spr, String(" ") + String(tmax), dag * 59 + 30, 108, "", TL_DATUM, (tmax < 0 ? TFT_RED : TFT_BLACK)); + drawString(spr, String(" ") + String(wind), dag * 59 + 30, 43, "", TL_DATUM, (wind > 5 ? TFT_RED : TFT_BLACK)); spr.unloadFont(); if (dag>0) { for (int i = 20; i < 128; i+=3) { @@ -561,6 +574,62 @@ bool getImgURL(String &filename, String URL, time_t fetched) { return (httpCode == 200 || httpCode == 304); } +rssClass reader; + +bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo) { + // https://github.com/garretlab/shoddyxml2 + + // http://feeds.feedburner.com/tweakers/nieuws + // https://www.nu.nl/rss/Algemeen + + Serial.println("RSS feed"); + struct tm timeInfo; + char header[32]; + getLocalTime(&timeInfo); + sprintf(header, "%02d-%02d-%04d %02d:%02d", timeInfo.tm_mday, timeInfo.tm_mon + 1, timeInfo.tm_year + 1900, timeInfo.tm_hour, timeInfo.tm_min); + + const char *url = URL.c_str(); + const char *tag = "title"; + const int rssArticleSize = 128; + const int rssNumArticle = 8; + + TFT_eSPI tft = TFT_eSPI(); + TFT_eSprite spr = TFT_eSprite(&tft); + U8g2_for_TFT_eSPI u8f; + u8f.begin(spr); + + if (taginfo->hwType == SOLUM_29_033) { + initSprite(spr, 296, 128); + if (title=="" || title=="null") title="RSS feed"; + drawString(spr, title, 5, 3, "fonts/bahnschrift20", TL_DATUM, TFT_RED); + + u8f.setFont(u8g2_font_glasstown_nbp_tr); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall + u8f.setFontMode(0); + u8f.setFontDirection(0); + u8f.setForegroundColor(TFT_BLACK); + u8f.setBackgroundColor(TFT_WHITE); + u8f.setCursor(220, 20); + u8f.print(header); + + // u8g2_font_nine_by_five_nbp_tr + // u8g2_font_7x14_tr + // u8g2_font_crox1h_tr + // u8g2_font_miranda_nbp_tr + u8f.setFont(u8g2_font_glasstown_nbp_tr); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall + + int n = reader.getArticles(url, tag, rssArticleSize, rssNumArticle); + for (int i = 0; i < n; i++) { + u8f.setCursor(5, 34+i*13); // start writing at this position + u8f.print(reader.itemData[i]); + } + } + + spr2grays(spr, spr.width(), spr.height(), filename); + spr.deleteSprite(); + + return true; +} + char *formatHttpDate(time_t t) { static char buf[40]; struct tm *timeinfo; From e94ea870051899dd78510f54ec7c4ddb3a30101a Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Wed, 15 Feb 2023 14:54:46 +0100 Subject: [PATCH 3/3] luts, timing, wakeup reason, dither fix --- esp32_fw/data/www/main.js | 29 ++++++++++++++++++++++++ esp32_fw/include/commstructs.h | 15 +++++++++---- esp32_fw/include/tag_db.h | 6 ++--- esp32_fw/src/makeimage.cpp | 14 +++++++++--- esp32_fw/src/newproto.cpp | 40 +++++++++++++++++----------------- esp32_fw/src/web.cpp | 1 - 6 files changed, 74 insertions(+), 31 deletions(-) diff --git a/esp32_fw/data/www/main.js b/esp32_fw/data/www/main.js index 377cfa9f..7e29a4bd 100644 --- a/esp32_fw/data/www/main.js +++ b/esp32_fw/data/www/main.js @@ -1,5 +1,12 @@ const $ = document.querySelector.bind(document); +const WAKEUP_REASON_TIMED = 0; +const WAKEUP_REASON_GPIO = 2; +const WAKEUP_REASON_NFC = 3; +const WAKEUP_REASON_FIRSTBOOT = 0xFC; +const WAKEUP_REASON_NETWORK_SCAN = 0xFD; +const WAKEUP_REASON_WDT_RESET = 0xFE; + const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url", "Weather forecast","RSS feed"]; const models = ["1.54\" 152x152px", "2.9\" 296x128px", "4.2\" 400x300px"]; const contentModeOptions = []; @@ -125,6 +132,28 @@ function processTags(tagArray) { div.dataset.hash = element.hash; $('#tag' + tagmac + ' .warningicon').style.display = 'none'; $('#tag' + tagmac).style.background = "inherit"; + switch (element.wakeupReason) { + case WAKEUP_REASON_TIMED: + break; + case WAKEUP_REASON_GPIO: + $('#tag' + tagmac + ' .nextcheckin').innerHTML = "GPIO wakeup" + break; + case WAKEUP_REASON_NFC: + $('#tag' + tagmac + ' .nextcheckin').innerHTML = "NFC wakeup" + break; + case WAKEUP_REASON_FIRSTBOOT: + $('#tag' + tagmac + ' .nextcheckin').innerHTML = "First boot" + $('#tag' + tagmac).style.background = "purple"; + break; + case WAKEUP_REASON_NETWORK_SCAN: + $('#tag' + tagmac + ' .nextcheckin').innerHTML = "Network scan" + $('#tag' + tagmac).style.background = "green"; + break; + case WAKEUP_REASON_WDT_RESET: + $('#tag' + tagmac + ' .nextcheckin').innerHTML = "Watchdog reset!" + $('#tag' + tagmac).style.background = "red"; + break; + } $('#tag' + tagmac + ' .pendingicon').style.display = (element.pending ? 'inline-block' : 'none'); div.classList.add("tagflash"); (function(tagmac) { diff --git a/esp32_fw/include/commstructs.h b/esp32_fw/include/commstructs.h index 7918df07..a52e6c4b 100644 --- a/esp32_fw/include/commstructs.h +++ b/esp32_fw/include/commstructs.h @@ -22,10 +22,12 @@ struct blockData { #define SOLUM_29_033 1 #define SOLUM_42_033 2 -#define WAKEUP_REASON_TIMED 0 -#define WAKEUP_REASON_BOOTUP 1 -#define WAKEUP_REASON_GPIO 2 -#define WAKEUP_REASON_NFC 3 +#define WAKEUP_REASON_TIMED 0 +#define WAKEUP_REASON_GPIO 2 +#define WAKEUP_REASON_NFC 3 +#define WAKEUP_REASON_FIRSTBOOT 0xFC +#define WAKEUP_REASON_NETWORK_SCAN 0xFD +#define WAKEUP_REASON_WDT_RESET 0xFE struct AvailDataReq { uint8_t checksum; @@ -49,6 +51,11 @@ struct espAvailDataReq { #define DATATYPE_IMGRAW 2 #define DATATYPE_UPDATE 3 +#define EPD_LUT_DEFAULT 0 +#define EPD_LUT_NO_REPEATS 1 +#define EPD_LUT_FAST_NO_REDS 2 +#define EPD_LUT_FAST 3 + struct AvailDataInfo { uint8_t checksum; uint64_t dataVer; // MD5 of potential traffic diff --git a/esp32_fw/include/tag_db.h b/esp32_fw/include/tag_db.h index 4e8f147b..3e7e713e 100644 --- a/esp32_fw/include/tag_db.h +++ b/esp32_fw/include/tag_db.h @@ -18,7 +18,7 @@ class tagRecord { public: uint16_t nextCheckinpending; - tagRecord() : mac{0}, alias(""), lastseen(0), nextupdate(0), contentMode(0), pending(false), md5{0}, md5pending{0}, CheckinInMinPending(0), expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0) {} + tagRecord() : mac{0}, alias(""), lastseen(0), nextupdate(0), contentMode(0), pending(false), md5{0}, md5pending{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0) {} uint8_t mac[6]; String alias; @@ -28,7 +28,6 @@ class tagRecord { bool pending; uint8_t md5[16]; uint8_t md5pending[16]; - uint16_t CheckinInMinPending; uint32_t expectedNextCheckin; String modeConfigJson; uint8_t LQI; @@ -37,7 +36,8 @@ class tagRecord { uint16_t batteryMv; uint8_t hwType; uint8_t wakeupReason; - uint8_t capabilities; + uint8_t capabilities; + uint32_t lastfullupdate; static tagRecord* findByMAC(uint8_t mac[6]); }; diff --git a/esp32_fw/src/makeimage.cpp b/esp32_fw/src/makeimage.cpp index 54e8660d..2a35bcbe 100644 --- a/esp32_fw/src/makeimage.cpp +++ b/esp32_fw/src/makeimage.cpp @@ -152,6 +152,12 @@ void spr2grays(TFT_eSprite &spr, long w, long h, String &fileout) { f_out.write(0x00); } + const int dither_matrix[4][4] = { + {1, 9, 3, 11}, + {13, 5, 15, 7}, + {4, 12, 2, 10}, + {0, 8, 14, 6}}; + while (numRows--) { uint32_t pixelValsPackedSoFar = 0, numPixelsPackedSoFar = 0, valSoFar = 0, bytesIn = 0, bytesOut = 0, bitsSoFar = 0; @@ -170,8 +176,10 @@ void spr2grays(TFT_eSprite &spr, long w, long h, String &fileout) { uint8_t green = ((color565 >> 5) & 0x3F) * 4; uint8_t blue = (color565 & 0x1F) * 8; - if (dither) - ditherFudge = (rand() % 255 - 127) / (int)numGrays; + if (dither) { + // ditherFudge = (rand() % 255 - 127) / (int)numGrays; // -64 to 64 + ditherFudge = (dither_matrix[numRows % 4][c % 4] - 8) * 24 / (int)numGrays; + } for (i = 0; i < hdr.numColors; i++) { int64_t dist = 0; @@ -253,7 +261,7 @@ void bmp2grays(String filein, String fileout) { struct BitmapFileHeader hdr; enum EinkClut clutType; uint8_t clut[256][3]; - bool dither = true; + bool dither = false; int skipBytes; srand(0); diff --git a/esp32_fw/src/newproto.cpp b/esp32_fw/src/newproto.cpp index efad9f80..735d9782 100644 --- a/esp32_fw/src/newproto.cpp +++ b/esp32_fw/src/newproto.cpp @@ -124,7 +124,8 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t md5.getBytes(md5bytes); } - uint16_t attempts = 60; + uint16_t attempts = 60 * 24; + uint8_t lut = EPD_LUT_DEFAULT; uint8_t src[8]; *((uint64_t*)src) = swap64(*((uint64_t*)dst)); uint8_t mac[6]; @@ -141,9 +142,13 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t time_t now; time(&now); + time_t last_midnight = now - now % (24 * 60 * 60) + 3 * 3600; // somewhere in the middle of the night + if (taginfo->lastfullupdate > last_midnight) lut = EPD_LUT_NO_REPEATS; // fast updates during the day + /* uint16_t minutesUntilNextCheckin = 0; if (taginfo->expectedNextCheckin > now) minutesUntilNextCheckin = (taginfo->expectedNextCheckin - now) / 60; attempts += minutesUntilNextCheckin; + */ } else { wsErr("Tag not found, this shouldn't happen."); } @@ -154,6 +159,7 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t pending.availdatainfo.dataType = dataType; pending.availdatainfo.dataVer = *((uint64_t*)md5bytes); pending.availdatainfo.dataSize = file.size(); + pending.availdatainfo.dataTypeArgument = EPD_LUT_NO_REPEATS; pending.availdatainfo.nextCheckIn = nextCheckin; pending.attemptsLeft = attempts; sendDataAvail(&pending); @@ -185,14 +191,15 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t wsLog("new image pending: " + String(dst_path)); if (taginfo != nullptr) { + time_t now; + time(&now); taginfo->pending = true; - taginfo->CheckinInMinPending = nextCheckin; + taginfo->expectedNextCheckin = now + nextCheckin * 60 + 60; memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes)); - } - } - else { - wsLog("firmware upload pending"); } + } else { + wsLog("firmware upload pending"); + } file.close(); wsSendTaginfo(mac); @@ -275,17 +282,13 @@ void processXferComplete(struct espXferComplete* xfc) { if (taginfo != nullptr) { uint16_t minutesUntilNextUpdate = 0; - if (taginfo->nextupdate > now + 60 * taginfo->CheckinInMinPending + 3) { - minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60 - taginfo->CheckinInMinPending; - if (minutesUntilNextUpdate > taginfo->CheckinInMinPending) minutesUntilNextUpdate = taginfo->CheckinInMinPending; + if (taginfo->nextupdate > now + 2) { + minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60; if (minutesUntilNextUpdate > MIN_RESPONSE_TIME) minutesUntilNextUpdate = MIN_RESPONSE_TIME; - taginfo->expectedNextCheckin = now + 60 * minutesUntilNextUpdate + 60; - if (minutesUntilNextUpdate > 0) prepareIdleReq (xfc->src, minutesUntilNextUpdate); - taginfo->CheckinInMinPending = minutesUntilNextUpdate; + if (minutesUntilNextUpdate > 1) prepareIdleReq (xfc->src, minutesUntilNextUpdate); } else { taginfo->expectedNextCheckin = now + 60; - taginfo->CheckinInMinPending = 0; } taginfo->pending = false; @@ -310,7 +313,6 @@ void processXferTimeout(struct espXferComplete* xfc) { taginfo = tagRecord::findByMAC(mac); if (taginfo != nullptr) { taginfo->expectedNextCheckin = now + 60; - taginfo->CheckinInMinPending = 0; taginfo->pending = false; memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); } @@ -341,15 +343,13 @@ void processDataReq(struct espAvailDataReq* eadr) { taginfo->lastseen = now; uint16_t minutesUntilNextUpdate = 0; - if (taginfo->nextupdate > now + 60 * taginfo->CheckinInMinPending + 3 && taginfo->pending == false) { - minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60 - taginfo->CheckinInMinPending; - if (minutesUntilNextUpdate > taginfo->CheckinInMinPending) minutesUntilNextUpdate = taginfo->CheckinInMinPending; + if (taginfo->nextupdate > now + 2) { + minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60; if (minutesUntilNextUpdate > MIN_RESPONSE_TIME) minutesUntilNextUpdate = MIN_RESPONSE_TIME; taginfo->expectedNextCheckin = now + 60 * minutesUntilNextUpdate + 60; - if (minutesUntilNextUpdate > 0) prepareIdleReq(eadr->src, minutesUntilNextUpdate); - taginfo->CheckinInMinPending = minutesUntilNextUpdate; + if (minutesUntilNextUpdate > 1 && taginfo->pending == false) prepareIdleReq(eadr->src, minutesUntilNextUpdate); } else { - taginfo->expectedNextCheckin = now + 60 * taginfo->CheckinInMinPending + 60; + taginfo->expectedNextCheckin = now + 60; } if (eadr->adr.lastPacketRSSI != 0) { diff --git a/esp32_fw/src/web.cpp b/esp32_fw/src/web.cpp index e18d6f74..0bdcf4be 100644 --- a/esp32_fw/src/web.cpp +++ b/esp32_fw/src/web.cpp @@ -284,7 +284,6 @@ void init_web() { taginfo->modeConfigJson = request->getParam("modecfgjson", true)->value(); taginfo->contentMode = atoi(request->getParam("contentmode", true)->value().c_str()); taginfo->nextupdate = 0; - taginfo->CheckinInMinPending = 0; memset(taginfo->md5, 0, 16 * sizeof(uint8_t)); memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); wsSendTaginfo(mac);