Files
OpenEPaperLink/esp32_fw/src/contentmanager.cpp
Nic Limper ef09cf4999 dithering/raw previews/css
- fixed preview images on webpage (raw buffer is rendered client side)
- added responsive css, webpage works on mobile now
- content is now rendered on 4 bit sprite using indexed color
- added floyd steinberg dithering
2023-03-16 22:16:42 +01:00

687 lines
25 KiB
C++

#include "contentmanager.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include "newproto.h"
#include <MD5Builder.h>
#include <locale.h>
#include <rssClass.h>
#include <time.h>
#include "U8g2_for_TFT_eSPI.h"
#include "commstructs.h"
#include "makeimage.h"
#include "web.h"
#define PAL_BLACK 0
#define PAL_WHITE 9
#define PAL_RED 2
enum contentModes {
Image,
Today,
CountDays,
CountHours,
Weather,
Firmware,
Memo,
ImageUrl,
Forecast,
RSSFeed,
};
void contentRunner() {
time_t now;
time(&now);
for (int16_t c = 0; c < tagDB.size(); c++) {
tagRecord* taginfo = nullptr;
taginfo = tagDB.at(c);
if (taginfo->RSSI && (now >= taginfo->nextupdate || taginfo->wakeupReason == WAKEUP_REASON_GPIO)) {
uint8_t mac8[8] = {0, 0, 0, 0, 0, 0, 0, 0};
memcpy(mac8 + 2, taginfo->mac, 6);
uint8_t src[8];
*((uint64_t *)src) = swap64(*((uint64_t *)mac8));
drawNew(src, (taginfo->wakeupReason == WAKEUP_REASON_GPIO), taginfo);
taginfo->wakeupReason = 0;
}
}
}
void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
time_t now;
time(&now);
char buffer[64];
uint8_t src[8];
*((uint64_t *)src) = swap64(*((uint64_t *)mac));
sprintf(buffer, "%02X%02X%02X%02X%02X%02X\0", src[2], src[3], src[4], src[5], src[6], src[7]);
String dst = (String)buffer;
String filename = "/" + dst + ".raw";
struct tm time_info;
getLocalTime(&time_info);
time_info.tm_hour = 0;
time_info.tm_min = 0;
time_info.tm_sec = 0;
time_info.tm_mday++;
time_t midnight = mktime(&time_info);
DynamicJsonDocument doc(500);
deserializeJson(doc, taginfo->modeConfigJson);
JsonObject cfgobj = doc.as<JsonObject>();
wsLog("Updating " + dst);
taginfo->nextupdate = now + 60;
switch (taginfo->contentMode) {
case Image:
if (cfgobj["filename"].as<String>() && cfgobj["filename"].as<String>() != "null" && !cfgobj["#fetched"].as<bool>()) {
jpg2buffer(cfgobj["filename"].as<String>(), filename);
if (prepareDataAvail(&filename, DATATYPE_IMG_RAW_2BPP, mac, cfgobj["timetolive"].as<int>())) {
cfgobj["#fetched"] = true;
} else {
wsErr("Error accessing " + filename);
}
taginfo->nextupdate = 3216153600;
}
break;
case Today:
drawDate(filename, taginfo);
taginfo->nextupdate = midnight;
updateTagImage(filename, mac, (midnight - now) / 60 - 10);
break;
case CountDays:
if (buttonPressed) cfgobj["counter"] = 0;
drawNumber(filename, (int32_t)cfgobj["counter"], (int32_t)cfgobj["thresholdred"], taginfo);
taginfo->nextupdate = midnight;
updateTagImage(filename, mac, (buttonPressed ? 0 : 15));
cfgobj["counter"] = (int32_t)cfgobj["counter"] + 1;
break;
case CountHours:
if (buttonPressed) cfgobj["counter"] = 0;
drawNumber(filename, (int32_t)cfgobj["counter"], (int32_t)cfgobj["thresholdred"], taginfo);
taginfo->nextupdate = now + 3600;
updateTagImage(filename, mac, (buttonPressed ? 0 : 5));
cfgobj["counter"] = (int32_t)cfgobj["counter"] + 1;
break;
case Weather:
// https://open-meteo.com/
// https://geocoding-api.open-meteo.com/v1/search?name=eindhoven
// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&current_weather=true
// https://github.com/erikflowers/weather-icons
drawWeather(filename, cfgobj["location"], taginfo);
taginfo->nextupdate = now + 3600;
updateTagImage(filename, mac, 15);
break;
case Forecast:
drawForecast(filename, cfgobj["location"], taginfo);
taginfo->nextupdate = now + 3 * 3600;
updateTagImage(filename, mac, 15);
break;
case Firmware:
filename = cfgobj["filename"].as<String>();
if (filename && filename != "null" && !cfgobj["#fetched"].as<bool>()) {
if (prepareDataAvail(&filename, DATATYPE_FW_UPDATE, mac, cfgobj["timetolive"].as<int>())) {
cfgobj["#fetched"] = true;
} else {
wsErr("Error accessing " + filename);
}
cfgobj["filename"]="";
taginfo->nextupdate = 3216153600;
taginfo->contentMode = Image;
} else {
taginfo->nextupdate = now + 300;
}
break;
case Memo:
drawIdentify(filename, taginfo);
taginfo->nextupdate = now + 12 * 3600;
updateTagImage(filename, mac, 0);
break;
case ImageUrl:
if (getImgURL(filename, cfgobj["url"], (time_t)cfgobj["#fetched"])) {
taginfo->nextupdate = now + 60 * (cfgobj["interval"].as<int>() < 5 ? 5 : cfgobj["interval"].as<int>());
updateTagImage(filename, mac, cfgobj["interval"].as<int>());
cfgobj["#fetched"] = now;
} else {
taginfo->nextupdate = now + 300;
}
break;
case RSSFeed:
if (getRSSfeed(filename, cfgobj["url"], cfgobj["title"], taginfo)) {
taginfo->nextupdate = now + 60 * (cfgobj["interval"].as<int>() < 5 ? 5 : cfgobj["interval"].as<int>());
updateTagImage(filename, mac, cfgobj["interval"].as<int>());
} else {
taginfo->nextupdate = now + 300;
}
break;
}
taginfo->modeConfigJson = doc.as<String>();
}
bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin) {
prepareDataAvail(&filename, DATATYPE_IMG_RAW_2BPP, dst, nextCheckin);
return true;
}
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,PAL_RED);
spr.setTextDatum(align);
if (font != "") spr.loadFont(font, LittleFS);
spr.setTextColor(color, PAL_WHITE);
spr.drawString(content, posx, posy);
if (font != "") spr.unloadFont();
}
void initSprite(TFT_eSprite &spr, int w, int h) {
spr.setColorDepth(4); // 4 bits per pixel, uses indexed color
spr.createSprite(w, h);
if (spr.getPointer() == nullptr) {
wsErr("Failed to create sprite");
}
spr.fillSprite(PAL_WHITE);
}
void drawDate(String &filename, tagRecord *&taginfo) {
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
time_t now;
time(&now);
struct tm timeinfo;
localtime_r(&now, &timeinfo);
String Dag[] = {"zondag","maandag","dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"};
String Maand[] = {"januari", "februari", "maart", "april", "mei", "juni","juli", "augustus", "september", "oktober", "november", "december"};
int weekday_number = timeinfo.tm_wday;
int month_number = timeinfo.tm_mon;
LittleFS.begin();
if (taginfo->hwType == SOLUM_29_033) {
initSprite(spr,296,128);
drawString(spr, Dag[timeinfo.tm_wday], 296 / 2, 10, "fonts/calibrib62", TC_DATUM, PAL_RED);
drawString(spr, String(timeinfo.tm_mday) + " " + Maand[timeinfo.tm_mon], 296 / 2, 73, "fonts/calibrib50", TC_DATUM);
} else if (taginfo->hwType == SOLUM_154_033) {
initSprite(spr, 152, 152);
drawString(spr, Dag[timeinfo.tm_wday], 152 / 2, 10, "fonts/calibrib30", TC_DATUM);
drawString(spr, String(Maand[timeinfo.tm_mon]), 152 / 2, 120, "fonts/calibrib30", TC_DATUM);
drawString(spr, String(timeinfo.tm_mday), 152 / 2, 42, "fonts/numbers2-1", TC_DATUM, PAL_RED);
}
spr2buffer(spr, filename);
spr.deleteSprite();
}
void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord *&taginfo) {
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
LittleFS.begin();
if (taginfo->hwType == SOLUM_29_033) {
initSprite(spr, 296, 128);
spr.setTextDatum(MC_DATUM);
if (count > thresholdred) {
spr.setTextColor(PAL_RED, PAL_WHITE);
} else {
spr.setTextColor(PAL_BLACK, PAL_WHITE);
}
String font = "fonts/numbers1-2";
if (count > 999) font = "fonts/numbers2-2";
if (count > 9999) font = "fonts/numbers3-2";
spr.loadFont(font, LittleFS);
spr.drawString(String(count), 296 / 2, 128 / 2 + 10);
spr.unloadFont();
} else if (taginfo->hwType == SOLUM_154_033) {
initSprite(spr, 152, 152);
spr.setTextDatum(MC_DATUM);
if (count > thresholdred) {
spr.setTextColor(PAL_RED, PAL_WHITE);
} else {
spr.setTextColor(PAL_BLACK, PAL_WHITE);
}
String font = "fonts/numbers1-1";
if (count > 99) font = "fonts/numbers2-1";
if (count > 999) font = "fonts/numbers3-1";
spr.loadFont(font, LittleFS);
spr.drawString(String(count), 152 / 2, 152 / 2 + 7);
spr.unloadFont();
}
spr2buffer(spr, filename);
spr.deleteSprite();
}
void drawWeather(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) {
StaticJsonDocument<1000> doc;
DeserializationError error = deserializeJson(doc, http.getStream());
http.end();
http.begin("https://api.open-meteo.com/v1/forecast?latitude=" + doc["results"][0]["latitude"].as<String>() + "&longitude=" + doc["results"][0]["longitude"].as<String>() + "&current_weather=true&windspeed_unit=ms&timezone=" + doc["results"][0]["timezone"].as<String>());
http.setTimeout(5000); // timeout in ms
int httpCode = http.GET();
if (httpCode == 200) {
StaticJsonDocument<200> filter;
filter["current_weather"]["temperature"] = true;
filter["current_weather"]["windspeed"] = true;
filter["current_weather"]["winddirection"] = true;
filter["current_weather"]["weathercode"] = true;
doc.clear();
DeserializationError error = deserializeJson(doc, http.getString(), DeserializationOption::Filter(filter));
if (error) {
Serial.println(F("deserializeJson() failed: "));
Serial.println(error.c_str());
}
auto temperature = doc["current_weather"]["temperature"].as<double>();
auto windspeed = doc["current_weather"]["windspeed"].as<int>();
auto winddirection = doc["current_weather"]["winddirection"].as<int>();
uint8_t weathercode = doc["current_weather"]["weathercode"].as<int>();
if (weathercode > 40) weathercode -= 40;
int wind = windSpeedToBeaufort(windspeed);
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"};
if (1==0) { //nacht
weatherIcons[0] = "\0uf02e";
weatherIcons[1] = "\0uf083";
weatherIcons[2] = "\0uf086";
}
doc.clear();
LittleFS.begin();
tft.setTextWrap(false,false);
if (taginfo->hwType == SOLUM_29_033) {
initSprite(spr, 296, 128);
drawString(spr, location, 5, 5, "fonts/bahnschrift30");
drawString(spr, String(wind), 280, 5, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? PAL_RED : PAL_BLACK));
char tmpOutput[5];
dtostrf(temperature, 2, 1, tmpOutput);
drawString(spr, String(tmpOutput), 5, 65, "fonts/bahnschrift70", TL_DATUM, (temperature < 0 ? PAL_RED : PAL_BLACK));
spr.loadFont("fonts/weathericons70", LittleFS);
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
spr.setTextColor(PAL_RED, PAL_WHITE);
} else {
spr.setTextColor(PAL_BLACK, PAL_WHITE);
}
spr.setCursor(185, 32);
spr.printToSprite(weatherIcons[weathercode]);
spr.unloadFont();
spr.loadFont("fonts/weathericons30", LittleFS);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
spr.setCursor(235, -3);
spr.printToSprite(windDirectionIcon(winddirection));
if (weathercode > 10) {
spr.setTextColor(PAL_RED, PAL_WHITE);
spr.setCursor(190, 0);
spr.printToSprite("\uf084");
}
spr.unloadFont();
} else if (taginfo->hwType == SOLUM_154_033) {
initSprite(spr, 152, 152);
spr.setTextDatum(TL_DATUM);
spr.setTextFont(2);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
spr.drawString(location, 10, 130);
char tmpOutput[5];
dtostrf(temperature, 2, 1, tmpOutput);
drawString(spr, String(tmpOutput), 10, 10, "fonts/bahnschrift30", TL_DATUM, (temperature < 0 ? PAL_RED : PAL_BLACK));
spr.loadFont("fonts/weathericons78", LittleFS);
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
spr.setTextColor(PAL_RED, PAL_WHITE);
} else {
spr.setTextColor(PAL_BLACK, PAL_WHITE);
}
spr.setCursor(30, 33);
spr.printToSprite(weatherIcons[weathercode]);
spr.unloadFont();
drawString(spr, String(wind), 140, 10, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? PAL_RED : PAL_BLACK));
spr.loadFont("fonts/weathericons30", LittleFS);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
spr.setCursor(100, -2);
spr.printToSprite(windDirectionIcon(winddirection));
if (weathercode > 10) {
spr.setTextColor(PAL_RED, PAL_WHITE);
spr.setCursor(115, 110);
spr.printToSprite("\uf084");
}
spr.unloadFont();
}
spr2buffer(spr, filename);
spr.deleteSprite();
}
}
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<String>() + "&longitude=" + doc["results"][0]["longitude"].as<String>() + "&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<String>());
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(PAL_BLACK, PAL_WHITE);
spr.drawString(location, 5, 0);
for (uint8_t dag = 0; dag < 5; dag++) {
time_t weatherday = doc["daily"]["time"][dag].as<time_t>();
struct tm *datum = localtime(&weatherday);
drawString(spr, String(weekday_name[datum->tm_wday]), dag * 59 + 30, 18, "fonts/twbold20", TC_DATUM, PAL_BLACK);
uint8_t weathercode = doc["daily"]["weathercode"][dag].as<int>();
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(PAL_RED, PAL_WHITE);
} else {
spr.setTextColor(PAL_BLACK, PAL_WHITE);
}
spr.setTextDatum(TL_DATUM);
spr.setCursor(12 + dag * 59, 58);
spr.printToSprite(weatherIcons[weathercode]);
spr.setTextColor(PAL_BLACK, PAL_WHITE);
spr.setCursor(17 + dag * 59, 27);
spr.printToSprite(windDirectionIcon(doc["daily"]["winddirection_10m_dominant"][dag]));
spr.unloadFont();
int8_t tmin = round(doc["daily"]["temperature_2m_min"][dag].as<double>());
int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as<double>());
uint8_t wind = windSpeedToBeaufort(doc["daily"]["windspeed_10m_max"][dag].as<double>());
spr.loadFont("fonts/GillSC20", LittleFS);
drawString(spr, String(tmin) + " ", dag * 59 + 30, 108, "", TR_DATUM, (tmin < 0 ? PAL_RED : PAL_BLACK));
drawString(spr, String(" ") + String(tmax), dag * 59 + 30, 108, "", TL_DATUM, (tmax < 0 ? PAL_RED : PAL_BLACK));
drawString(spr, String(" ") + String(wind), dag * 59 + 30, 43, "", TL_DATUM, (wind > 5 ? PAL_RED : PAL_BLACK));
spr.unloadFont();
if (dag>0) {
for (int i = 20; i < 128; i+=3) {
spr.drawPixel(dag * 59, i, PAL_BLACK);
}
}
}
}
spr2buffer(spr, filename);
spr.deleteSprite();
}
}
http.end();
}
void drawIdentify(String &filename, tagRecord *&taginfo) {
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
LittleFS.begin();
if (taginfo->hwType == SOLUM_29_033) {
initSprite(spr, 296, 128);
drawString(spr, taginfo->alias, 10, 10, "fonts/bahnschrift20");
drawString(spr, mac62hex(taginfo->mac), 10, 50, "fonts/bahnschrift20", TL_DATUM, PAL_RED);
} else if (taginfo->hwType == SOLUM_154_033) {
initSprite(spr, 152, 152);
drawString(spr, taginfo->alias, 5, 5, "fonts/bahnschrift20");
drawString(spr, mac62hex(taginfo->mac), 10, 50, "fonts/bahnschrift20", TL_DATUM, PAL_RED);
}
spr2buffer(spr, filename);
spr.deleteSprite();
}
bool getImgURL(String &filename, String URL, time_t fetched) {
// https://images.klari.net/kat-bw29.jpg
LittleFS.begin();
Serial.println("get external " + URL);
HTTPClient http;
http.begin(URL);
http.addHeader("If-Modified-Since", formatHttpDate(fetched));
http.setTimeout(5000); //timeout in ms
int httpCode = http.GET();
if (httpCode == 200) {
File f = LittleFS.open("/temp/temp.jpg", "w");
if (f) {
http.writeToStream(&f);
f.close();
jpg2buffer("/temp/temp.jpg", filename);
}
} else {
if (httpCode!=304) {
wsErr("http " + URL + " " + String(httpCode));
} else {
wsLog("http " + URL + " " + String(httpCode));
}
}
http.end();
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, PAL_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(PAL_BLACK);
u8f.setBackgroundColor(PAL_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]);
}
}
spr2buffer(spr, filename);
spr.deleteSprite();
return true;
}
char *formatHttpDate(time_t t) {
static char buf[40];
struct tm *timeinfo;
timeinfo = gmtime(&t);
strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
return buf;
}
String urlEncode(const char *msg) {
const char *hex = "0123456789ABCDEF";
String encodedMsg = "";
while (*msg != '\0') {
if (
('a' <= *msg && *msg <= 'z') || ('A' <= *msg && *msg <= 'Z') || ('0' <= *msg && *msg <= '9') || *msg == '-' || *msg == '_' || *msg == '.' || *msg == '~') {
encodedMsg += *msg;
} else {
encodedMsg += '%';
encodedMsg += hex[(unsigned char)*msg >> 4];
encodedMsg += hex[*msg & 0xf];
}
msg++;
}
return encodedMsg;
}
int windSpeedToBeaufort(float windSpeed) {
int beaufort = 0;
float speeds[] = {0.3, 1.5, 3.3, 5.5, 8, 10.8, 13.9, 17.2, 20.8, 24.5, 28.5, 32.7};
for (int i = 0; i < 12; i++) {
if (windSpeed >= speeds[i]) {
beaufort = i + 1;
}
}
return beaufort;
}
String windDirectionIcon(int degrees) {
String directions[] = {"\uf044", "\uf088", "\uf04d", "\uf057", "\uf058", "\uf087", "\uf048", "\uf043"};
int index = (degrees + 22) / 45;
if (index >= 8) {
index = 0;
}
return directions[index];
}
String mac62hex(uint8_t *mac) {
char buffer[16];
sprintf(buffer, "%02X%02X%02X%02X%02X%02X\0", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return (String)buffer;
}