diff --git a/ESP32_AP-Flasher/data/tagtypes/01.json b/ESP32_AP-Flasher/data/tagtypes/01.json index 31aec876..80762e2b 100644 --- a/ESP32_AP-Flasher/data/tagtypes/01.json +++ b/ESP32_AP-Flasher/data/tagtypes/01.json @@ -13,7 +13,7 @@ }, "shortlut": 2, "options": ["button", "customlut"], - "contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21 ], + "contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21, 27 ], "template": { "1": { "weekday": [ 148, -3, "Signika-SB.ttf", 60 ], @@ -72,6 +72,12 @@ { "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 ] } - ] + ], + "27": { + "bars": [ 9, 288, 90, 10 ], + "time": [ "BellCent10.vlw" ], + "yaxis": [ "BellCent10.vlw", 0, 6 ], + "head": [ "calibrib30.vlw" ] + } } } diff --git a/ESP32_AP-Flasher/data/tagtypes/21.json b/ESP32_AP-Flasher/data/tagtypes/21.json index f181d0cd..73781e12 100644 --- a/ESP32_AP-Flasher/data/tagtypes/21.json +++ b/ESP32_AP-Flasher/data/tagtypes/21.json @@ -7,8 +7,10 @@ "colors": 2, "colortable": { "white": [255, 255, 255], - "black": [0, 0, 0] + "black": [0, 0, 0], + "gray": [ 150, 150, 150 ] }, + "highlight_color": 5, "shortlut": 0, "options": ["button", "customlut"], "contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21 ], diff --git a/ESP32_AP-Flasher/data/tagtypes/2F.json b/ESP32_AP-Flasher/data/tagtypes/2F.json index 4db62b9a..fdf60a9c 100644 --- a/ESP32_AP-Flasher/data/tagtypes/2F.json +++ b/ESP32_AP-Flasher/data/tagtypes/2F.json @@ -2,7 +2,7 @@ "name": "M3 4.3\"", "width": 522, "height": 152, - "rotatebuffer": 1, + "rotatebuffer": 1, "bpp": 2, "colors": 3, "colortable": { @@ -13,7 +13,7 @@ }, "shortlut": 0, "options": ["button"], - "contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 11, 17, 18, 19, 20], + "contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 11, 17, 18, 19, 20, 27], "usetemplate": 1, "template": { "1": { @@ -36,6 +36,12 @@ "mode": 1, "days": 1, "gridparam": [ 5, 17, 20, "calibrib16.vlw", "BellCent10.vlw", 14 ] + }, + "27": { + "bars": [ 27, 468, 100, 18 ], + "time": [ "calibrib16.vlw" ], + "yaxis": [ "BellCent10.vlw", 5, 6 ], + "head": [ "calibrib30.vlw" ] } } } diff --git a/ESP32_AP-Flasher/data/tagtypes/33.json b/ESP32_AP-Flasher/data/tagtypes/33.json index 4b8e3804..cd3e8dd6 100644 --- a/ESP32_AP-Flasher/data/tagtypes/33.json +++ b/ESP32_AP-Flasher/data/tagtypes/33.json @@ -13,7 +13,7 @@ }, "shortlut": 0, "options": ["button", "led"], - "contentids": [22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20], + "contentids": [22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 27], "usetemplate": 1, "template": { "1": { @@ -56,6 +56,12 @@ "items": 10, "red": [ 0, 21, 384, 16 ], "line": [ 5, 23, 18, "calibrib16.vlw", 55 ] - } + }, + "27": { + "bars": [ 12, 360, 110, 20 ], + "time": [ "calibrib16.vlw" ], + "yaxis": [ "BellCent10.vlw", 1, 6 ], + "head": [ "calibrib30.vlw" ] + } } } diff --git a/ESP32_AP-Flasher/data/tagtypes/60.json b/ESP32_AP-Flasher/data/tagtypes/60.json index 3b83b72b..743fd257 100644 --- a/ESP32_AP-Flasher/data/tagtypes/60.json +++ b/ESP32_AP-Flasher/data/tagtypes/60.json @@ -11,8 +11,17 @@ "yellow": [200, 200, 0], "gray": [150, 150, 150] }, + "highlight_color": 2, "shortlut": 0, "options": ["led"], - "contentids": [ 22, 23, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19, 20 ], - "usetemplate": 51 + "contentids": [ 22, 23, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19, 20, 27 ], + "usetemplate": 51, + "template": { + "27": { + "bars": [ 12, 360, 125, 20 ], + "time": [ "calibrib16.vlw" ], + "yaxis": [ "BellCent10.vlw", 1, 6 ], + "head": [ "calibrib30.vlw" ] + } + } } diff --git a/ESP32_AP-Flasher/data/tagtypes/62.json b/ESP32_AP-Flasher/data/tagtypes/62.json index 42dceb5e..02c40be1 100644 --- a/ESP32_AP-Flasher/data/tagtypes/62.json +++ b/ESP32_AP-Flasher/data/tagtypes/62.json @@ -3,14 +3,14 @@ "width": 384, "height": 184, "rotatebuffer": 1, - "bpp": 2, + "bpp": 1, "colors": 2, "colortable": { "white": [255, 255, 255], "black": [0, 0, 0], - "red": [255, 0, 0], "gray": [150, 150, 150] }, + "highlight_color": 5, "shortlut": 0, "options": ["led"], "contentids": [ 22, 23, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19, 20 ], diff --git a/ESP32_AP-Flasher/include/contentmanager.h b/ESP32_AP-Flasher/include/contentmanager.h index 13888a02..cc8092ec 100644 --- a/ESP32_AP-Flasher/include/contentmanager.h +++ b/ESP32_AP-Flasher/include/contentmanager.h @@ -28,6 +28,7 @@ void drawForecast(String &filename, JsonObject &cfgobj, const tagRecord *taginfo 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, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams); +bool getDayAheadFeed(String &filename, JsonObject &cfgobj, 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); diff --git a/ESP32_AP-Flasher/include/makeimage.h b/ESP32_AP-Flasher/include/makeimage.h index c614d1eb..c3a65e27 100644 --- a/ESP32_AP-Flasher/include/makeimage.h +++ b/ESP32_AP-Flasher/include/makeimage.h @@ -16,6 +16,7 @@ struct imgParam { bool grayLut = false; uint8_t bufferbpp = 8; uint8_t rotate = 0; + uint16_t highlightColor = 2; uint16_t width; uint16_t height; diff --git a/ESP32_AP-Flasher/include/tag_db.h b/ESP32_AP-Flasher/include/tag_db.h index 69d1eb72..8563b4c2 100644 --- a/ESP32_AP-Flasher/include/tag_db.h +++ b/ESP32_AP-Flasher/include/tag_db.h @@ -78,6 +78,7 @@ struct HwType { uint8_t bpp; uint8_t shortlut; uint8_t zlib; + uint16_t highlightColor; }; struct varStruct { diff --git a/ESP32_AP-Flasher/src/contentmanager.cpp b/ESP32_AP-Flasher/src/contentmanager.cpp index 48f83b86..14534262 100644 --- a/ESP32_AP-Flasher/src/contentmanager.cpp +++ b/ESP32_AP-Flasher/src/contentmanager.cpp @@ -10,6 +10,7 @@ #define CONTENT_BUIENRADAR #define CONTENT_NFCLUT #define CONTENT_TAGCFG +#define CONTENT_DAYAHEAD #include #include @@ -198,10 +199,11 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) { imageParams.height = hwdata.height; imageParams.bpp = hwdata.bpp; imageParams.rotatebuffer = hwdata.rotatebuffer; + imageParams.highlightColor = getColor(String(hwdata.highlightColor)); imageParams.hasRed = false; imageParams.dataType = DATATYPE_IMG_RAW_1BPP; - imageParams.dither = 0; + imageParams.dither = 2; if (taginfo->hasCustomLUT && taginfo->lut != 1) imageParams.grayLut = true; imageParams.invert = taginfo->invert; @@ -385,6 +387,7 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) { break; #endif +#ifdef CONTENT_CAL case 11: // Calendar: if (getCalFeed(filename, cfgobj, taginfo, imageParams)) { @@ -395,6 +398,7 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) { taginfo->nextupdate = now + 300; } break; +#endif case 12: // RemoteAP @@ -508,7 +512,18 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) { drawTimestamp(filename, cfgobj, taginfo, imageParams); updateTagImage(filename, mac, 0, taginfo, imageParams); taginfo->nextupdate = 3216153600; + +#ifdef CONTENT_DAYAHEAD + case 27: // Day Ahead: + + if (getDayAheadFeed(filename, cfgobj, taginfo, imageParams)) { + taginfo->nextupdate = now + (3600 - now % 3600); + updateTagImage(filename, mac, 0, taginfo, imageParams); + } else { + taginfo->nextupdate = now + 300; + } break; +#endif } taginfo->modeConfigJson = doc.as(); @@ -724,14 +739,14 @@ void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams) { const auto &date = loc["date"]; const auto &weekday = loc["weekday"]; if (date) { - drawString(spr, languageDays[timeinfo.tm_wday], weekday[0], weekday[1], weekday[2], TC_DATUM, TFT_RED, weekday[3]); + drawString(spr, languageDays[timeinfo.tm_wday], weekday[0], weekday[1], weekday[2], TC_DATUM, imageParams.highlightColor, weekday[3]); drawString(spr, String(timeinfo.tm_mday) + " " + languageMonth[timeinfo.tm_mon], date[0], date[1], date[2], TC_DATUM, TFT_BLACK, date[3]); } else { const auto &month = loc["month"]; const auto &day = loc["day"]; drawString(spr, languageDays[timeinfo.tm_wday], weekday[0], weekday[1], weekday[2], TC_DATUM, TFT_BLACK, weekday[3]); drawString(spr, String(languageMonth[timeinfo.tm_mon]), month[0], month[1], month[2], TC_DATUM, TFT_BLACK, month[3]); - drawString(spr, String(timeinfo.tm_mday), day[0], day[1], day[2], TC_DATUM, TFT_RED, day[3]); + drawString(spr, String(timeinfo.tm_mday), day[0], day[1], day[2], TC_DATUM, imageParams.highlightColor, day[3]); } spr2buffer(spr, filename, imageParams); @@ -768,7 +783,7 @@ void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord initSprite(spr, imageParams.width, imageParams.height, imageParams); uint16_t color = TFT_BLACK; if (countTemp > thresholdred) { - color = TFT_RED; + color = imageParams.highlightColor; } String font = loc["fonts"][0].as(); uint8_t size = loc["fonts"][1].as(); @@ -864,15 +879,15 @@ void drawWeather(String &filename, JsonObject &cfgobj, const tagRecord *taginfo, const auto &location = doc["location"]; drawString(spr, cfgobj["location"], location[0], location[1], location[2]); const auto &wind = doc["wind"]; - drawString(spr, String(windval), wind[0], wind[1], wind[2], TR_DATUM, (beaufort > 4 ? TFT_RED : TFT_BLACK)); + drawString(spr, String(windval), wind[0], wind[1], wind[2], TR_DATUM, (beaufort > 4 ? imageParams.highlightColor : TFT_BLACK)); char tmpOutput[5]; dtostrf(temperature, 2, 1, tmpOutput); const auto &temp = doc["temp"]; - drawString(spr, String(tmpOutput), temp[0], temp[1], temp[2], TL_DATUM, (temperature < 0 ? TFT_RED : TFT_BLACK)); + drawString(spr, String(tmpOutput), temp[0], temp[1], temp[2], TL_DATUM, (temperature < 0 ? imageParams.highlightColor : TFT_BLACK)); const int iconcolor = (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 96 || weathercode == 99) - ? TFT_RED + ? imageParams.highlightColor : TFT_BLACK; const auto &icon = doc["icon"]; drawString(spr, getWeatherIcon(weathercode, isNight), icon[0], icon[1], "/fonts/weathericons.ttf", icon[3], iconcolor, icon[2]); @@ -880,7 +895,7 @@ void drawWeather(String &filename, JsonObject &cfgobj, const tagRecord *taginfo, drawString(spr, windDirectionIcon(winddirection), dir[0], dir[1], "/fonts/weathericons.ttf", TC_DATUM, TFT_BLACK, dir[2]); if (weathercode > 10) { const auto &umbrella = doc["umbrella"]; - drawString(spr, "\uf084", umbrella[0], umbrella[1], "/fonts/weathericons.ttf", TC_DATUM, TFT_RED, umbrella[2]); + drawString(spr, "\uf084", umbrella[0], umbrella[1], "/fonts/weathericons.ttf", TC_DATUM, imageParams.highlightColor, umbrella[2]); } spr2buffer(spr, filename, imageParams); @@ -929,7 +944,7 @@ void drawForecast(String &filename, JsonObject &cfgobj, const tagRecord *taginfo if (weathercode > 40) weathercode -= 40; const int iconcolor = (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 96 || weathercode == 99) - ? TFT_RED + ? imageParams.highlightColor : TFT_BLACK; drawString(spr, getWeatherIcon(weathercode), loc["icon"][0].as() + dag * column1, loc["icon"][1], "/fonts/weathericons.ttf", TC_DATUM, iconcolor, loc["icon"][2]); @@ -948,13 +963,13 @@ void drawForecast(String &filename, JsonObject &cfgobj, const tagRecord *taginfo if (loc["rain"]) { const int8_t rain = round(daily["precipitation_sum"][dag].as()); if (rain > 0) { - drawString(spr, String(rain) + "mm", dag * column1 + loc["rain"][0].as(), loc["rain"][1], day[2], TC_DATUM, (rain > 10 ? TFT_RED : TFT_BLACK)); + drawString(spr, String(rain) + "mm", dag * column1 + loc["rain"][0].as(), loc["rain"][1], day[2], TC_DATUM, (rain > 10 ? imageParams.highlightColor : TFT_BLACK)); } } - drawString(spr, String(tmin) + " ", dag * column1 + day[0].as(), day[4], day[2], TR_DATUM, (tmin < 0 ? TFT_RED : TFT_BLACK)); - drawString(spr, String(" ") + String(tmax), dag * column1 + day[0].as(), day[4], day[2], TL_DATUM, (tmax < 0 ? TFT_RED : TFT_BLACK)); - drawString(spr, String(wind), dag * column1 + column1 - 10, day[3], day[2], TR_DATUM, (beaufort > 5 ? TFT_RED : TFT_BLACK)); + drawString(spr, String(tmin) + " ", dag * column1 + day[0].as(), day[4], day[2], TR_DATUM, (tmin < 0 ? imageParams.highlightColor : TFT_BLACK)); + drawString(spr, String(" ") + String(tmax), dag * column1 + day[0].as(), day[4], day[2], TL_DATUM, (tmax < 0 ? imageParams.highlightColor : TFT_BLACK)); + drawString(spr, String(wind), dag * column1 + column1 - 10, day[3], day[2], TR_DATUM, (beaufort > 5 ? imageParams.highlightColor : TFT_BLACK)); if (dag > 0) { for (int i = loc["line"][0]; i < loc["line"][1]; i += 3) { spr.drawPixel(dag * column1, i, TFT_BLACK); @@ -1115,8 +1130,8 @@ char *epoch_to_display(time_t utc) { return display; } -bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams) { #ifdef CONTENT_CAL +bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams) { // google apps scripts method to retrieve calendar // see https://github.com/jjwbruijn/OpenEPaperLink/wiki/Google-Apps-Scripts for description @@ -1181,9 +1196,9 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa const time_t endtime = obj["end"]; if (starttime <= now && endtime > now) { - spr.fillRect(loc["red"][0], loc["red"][1].as() + i * loc["line"][2].as(), loc["red"][2], loc["red"][3], TFT_RED); - drawString(spr, epoch_to_display(obj["start"]), loc["line"][0], loc["line"][1].as() + i * loc["line"][2].as(), loc["line"][3], TL_DATUM, TFT_WHITE, 0, TFT_RED); - drawString(spr, eventtitle, loc["line"][4], loc["line"][1].as() + i * loc["line"][2].as(), loc["line"][3], TL_DATUM, TFT_WHITE, 0, TFT_RED); + spr.fillRect(loc["red"][0], loc["red"][1].as() + i * loc["line"][2].as(), loc["red"][2], loc["red"][3], imageParams.highlightColor); + drawString(spr, epoch_to_display(obj["start"]), loc["line"][0], loc["line"][1].as() + i * loc["line"][2].as(), loc["line"][3], TL_DATUM, TFT_WHITE, 0, imageParams.highlightColor); + drawString(spr, eventtitle, loc["line"][4], loc["line"][1].as() + i * loc["line"][2].as(), loc["line"][3], TL_DATUM, TFT_WHITE, 0, imageParams.highlightColor); } else { drawString(spr, epoch_to_display(obj["start"]), loc["line"][0], loc["line"][1].as() + i * loc["line"][2].as(), loc["line"][3], TL_DATUM, TFT_BLACK, 0, TFT_WHITE); drawString(spr, eventtitle, loc["line"][4], loc["line"][1].as() + i * loc["line"][2].as(), loc["line"][3], TL_DATUM, TFT_BLACK, 0, TFT_WHITE); @@ -1220,9 +1235,7 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa int calYOffset = loc["gridparam"][1].as(); int lineHeight = loc["gridparam"][5].as(); - imageParams.dither = 2; - - // drawString(spr, String(timeinfo.tm_mday), calWidth / 2, -calHeight/5, "Signika-SB.ttf", TC_DATUM, TFT_RED, calHeight * 1.2); + // drawString(spr, String(timeinfo.tm_mday), calWidth / 2, -calHeight/5, "Signika-SB.ttf", TC_DATUM, imageParams.highlightColor, calHeight * 1.2); for (int i = 0; i < calDays; i++) { struct tm dayTimeinfo = *localtime(&midnightEpoch); @@ -1382,9 +1395,173 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa spr2buffer(spr, filename, imageParams); spr.deleteSprite(); -#endif return true; } +#endif + +#ifdef CONTENT_DAYAHEAD +uint16_t getPercentileColor(const double *prices, int numPrices, double price) { + double percentile = 100.0; + int colorIndex = 3; + const char *colors[] = {"black", "darkgray", "pink", "red"}; + const int numColors = sizeof(colors) / sizeof(colors[0]); + + const double boundaries[] = {40.0, 80.0, 90.0}; + const int numBoundaries = sizeof(boundaries) / sizeof(boundaries[0]); + + for (int i = 0; i < numBoundaries; i++) { + if (price < prices[int(numPrices * boundaries[i] / 100.0)]) { + colorIndex = i; + break; + } + } + return getColor(String(colors[constrain(colorIndex, 0, numColors - 1)])); +} + +struct YAxisScale { + double min; + double max; + double step; +}; + +double roundToNearest(double value, double factor) { + return round(value / factor) * factor; +} + +int mapDouble(double value, double fromMin, double fromMax, int toMin, int toMax) { + return static_cast(round((value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin)); +} + +YAxisScale calculateYAxisScale(double priceMin, double priceMax, int divisions) { + double range = priceMax - priceMin; + double stepSize = range / divisions; + double orderOfMagnitude = pow(10, floor(log10(stepSize))); + double significantDigit = stepSize / orderOfMagnitude; + + double roundedSignificantDigit; + double thresholds[] = {1.0, 2.0, 5.0, 10.0}; + for (double threshold : thresholds) { + if (significantDigit <= threshold) { + roundedSignificantDigit = threshold; + break; + } + } + + double roundedStepSize = roundedSignificantDigit * orderOfMagnitude; + double newRange = roundedStepSize * divisions; + double minY = floor(priceMin / roundedStepSize) * roundedStepSize; + double maxY = minY + newRange; + + return {minY, maxY, roundedStepSize}; +} + +bool getDayAheadFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams) { + wsLog("get dayahead prices"); + + StaticJsonDocument<512> loc; + getTemplate(loc, 27, taginfo->hwType); + + // This is a link to a Google Apps Script script, which fetches (and caches) the tariff from https://transparency.entsoe.eu/ + // Please don't use this link in any other projects. + String URL = "https://script.google.com/macros/s/AKfycbwMmeGAaPrWzVZrESSpmPmD--O132PzW_acnBsuEottKNATTqCRn6h8zN0Yts7S56ggsg/exec?country=" + cfgobj["country"].as(); + + time_t now; + time(&now); + struct tm timeinfo; + localtime_r(&now, &timeinfo); + + char dateString[40]; + strftime(dateString, sizeof(dateString), languageDateFormat[0].c_str(), &timeinfo); + + HTTPClient http; + http.begin(URL); + http.setTimeout(10000); + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + int httpCode = http.GET(); + if (httpCode != 200) { + wsErr("getDayAhead http error " + String(httpCode)); + return false; + } + + DynamicJsonDocument doc(5000); + DeserializationError error = deserializeJson(doc, http.getString()); + if (error) { + wsErr(error.c_str()); + } + http.end(); + + TFT_eSprite spr = TFT_eSprite(&tft); + + initSprite(spr, imageParams.width, imageParams.height, imageParams); + + int n = doc.size(); + + double tarifkwh = cfgobj["tariffkwh"].as(); + double tariftax = cfgobj["tarifftax"].as(); + double minPrice = (doc[0]["price"].as() / 10 + tarifkwh) * (1 + tariftax / 100); + double maxPrice = minPrice; + double prices[n]; + + for (int i = 0; i < n; i++) { + const JsonObject &obj = doc[i]; + const double price = (obj["price"].as() / 10 + tarifkwh) * (1 + tariftax / 100); + minPrice = min(minPrice, price); + maxPrice = max(maxPrice, price); + prices[i] = price; + } + std::sort(prices, prices + n); + + YAxisScale yAxisScale = calculateYAxisScale(minPrice, maxPrice, loc["yaxis"][2].as()); + minPrice = yAxisScale.min; + + uint16_t yAxisX = loc["yaxis"][1].as(); + uint16_t barBottom = loc["bars"][3].as(); + + for (double i = minPrice; i <= maxPrice; i += yAxisScale.step) { + int y = mapDouble(i, minPrice, maxPrice, spr.height() - barBottom, spr.height() - barBottom - loc["bars"][2].as()); + spr.drawLine(0, y, spr.width(), y, TFT_BLACK); + drawString(spr, String(int(i)), yAxisX, y - 8, loc["yaxis"][0], TL_DATUM, TFT_BLACK); + } + + uint16_t barwidth = loc["bars"][1].as() / n; + uint16_t barheight = loc["bars"][2].as() / (maxPrice - minPrice); + + uint16_t barX = loc["bars"][0].as(); + double pricenow = std::numeric_limits::quiet_NaN(); + + for (int i = 0; i < n; i++) { + const JsonObject &obj = doc[i]; + const time_t item_time = obj["time"]; + struct tm item_timeinfo; + localtime_r(&item_time, &item_timeinfo); + + const double price = (obj["price"].as() / 10 + tarifkwh) * (1 + tariftax / 100); + + uint16_t barcolor = getPercentileColor(prices, n, price); + uint16_t thisbarh = mapDouble(price, minPrice, maxPrice, 0, loc["bars"][2].as()); + spr.fillRect(barX + i * barwidth, spr.height() - barBottom - thisbarh, barwidth - 1, thisbarh, barcolor); + if (i % 2 == 0) { + drawString(spr, String(item_timeinfo.tm_hour), barX + i * barwidth + barwidth / 3 + 1, spr.height() - barBottom + 3, loc["time"][0], TC_DATUM, TFT_BLACK); + } + + if (now - item_time < 3600 && std::isnan(pricenow)) { + spr.fillRect(barX + i * barwidth + 3, 5, barwidth - 6, 10, imageParams.highlightColor); + spr.fillTriangle(barX + i * barwidth, 15, + barX + i * barwidth + barwidth - 1, 15, + barX + i * barwidth + barwidth / 2, 15 + barwidth, imageParams.highlightColor); + spr.drawLine(barX + i * barwidth + barwidth / 2, 20 + barwidth, barX + i * barwidth + barwidth / 2, spr.height(), getColor("pink")); + pricenow = price; + } + } + + drawString(spr, String(timeinfo.tm_hour) + ":00", barX, 5, loc["head"][0], TL_DATUM, TFT_BLACK, 30); + drawString(spr, String(pricenow) + "/kWh", spr.width() - barX, 5, loc["head"][0], TR_DATUM, TFT_BLACK, 30); + + spr2buffer(spr, filename, imageParams); + spr.deleteSprite(); + return true; +} +#endif #ifdef CONTENT_QR void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginfo, imgParam &imageParams) { @@ -1488,7 +1665,7 @@ uint8_t drawBuienradar(String &filename, JsonObject &cfgobj, tagRecord *&taginfo } value = value - 70; - spr.fillRect(i * cols2 + bars0, bars1 - (value * factor), bars2, value * factor, (value > 50 ? TFT_RED : TFT_BLACK)); + spr.fillRect(i * cols2 + bars0, bars1 - (value * factor), bars2, value * factor, (value > 50 ? imageParams.highlightColor : TFT_BLACK)); if (minutes % 15 == 0) { drawString(spr, timestring, i * cols2 + cols0, cols1, cols3); @@ -1720,7 +1897,6 @@ void drawJsonStream(Stream &stream, String &filename, tagRecord *&taginfo, imgPa TFT_eSprite spr = TFT_eSprite(&tft); initSprite(spr, imageParams.width, imageParams.height, imageParams); uint8_t screenCurrentOrientation = 0; - imageParams.dither = 2; DynamicJsonDocument doc(500); if (stream.find("[")) { do { diff --git a/ESP32_AP-Flasher/src/espflasher.cpp b/ESP32_AP-Flasher/src/espflasher.cpp index 16cb79e7..3d55df34 100644 --- a/ESP32_AP-Flasher/src/espflasher.cpp +++ b/ESP32_AP-Flasher/src/espflasher.cpp @@ -260,7 +260,7 @@ bool doC6flash(uint8_t doDownload) { return false; } } else { - wsSerial("Connection failed"); + wsSerial("Connection to the C6 failed"); loader_port_esp32_deinit(); return false; } diff --git a/ESP32_AP-Flasher/src/tag_db.cpp b/ESP32_AP-Flasher/src/tag_db.cpp index 395214f1..c6b9086e 100644 --- a/ESP32_AP-Flasher/src/tag_db.cpp +++ b/ESP32_AP-Flasher/src/tag_db.cpp @@ -372,14 +372,15 @@ HwType getHwType(const uint8_t id) { File jsonFile = contentFS->open(filename, "r"); if (jsonFile) { - StaticJsonDocument<100> filter; + StaticJsonDocument<150> filter; filter["width"] = true; filter["height"] = true; filter["rotatebuffer"] = true; filter["bpp"] = true; filter["shortlut"] = true; filter["zlib_compression"] = true; - StaticJsonDocument<250> doc; + filter["highlight_color"] = true; + StaticJsonDocument<1000> doc; DeserializationError error = deserializeJson(doc, jsonFile, DeserializationOption::Filter(filter)); jsonFile.close(); if (error) { @@ -396,10 +397,11 @@ HwType getHwType(const uint8_t id) { } else { hwdata[id].zlib = 0; } + hwdata[id].highlightColor = doc.containsKey("highlight_color") ? doc["highlight_color"].as() : 2; return hwdata.at(id); } } - return {0, 0, 0, 0, 0}; + return {0, 0, 0, 0, 0, 0, 0}; } } diff --git a/ESP32_AP-Flasher/src/web.cpp b/ESP32_AP-Flasher/src/web.cpp index caff3023..383f0f80 100644 --- a/ESP32_AP-Flasher/src/web.cpp +++ b/ESP32_AP-Flasher/src/web.cpp @@ -442,13 +442,33 @@ void init_web() { server.on("/get_ap_config", HTTP_GET, [](AsyncWebServerRequest *request) { UDPcomm udpsync; udpsync.getAPList(); + AsyncResponseStream *response = request->beginResponseStream("application/json"); + File configFile = contentFS->open("/current/apconfig.json", "r"); if (!configFile) { request->send(500, "text/plain", "Error opening apconfig.json file"); return; } - request->send(configFile, "application/json"); + response->print("{"); +#if defined YELLOW_IPS_AP || defined C6_OTA_FLASHING + response->print("\"C6\": \"1\", "); +#else + response->print("\"C6\": \"1\", "); +#endif +#if defined SAVE_SPACE + response->print("\"savespace\": \"1\", "); +#else + response->print("\"savespace\": \"0\", "); +#endif + configFile.seek(1); + const size_t bufferSize = 64; + uint8_t buffer[bufferSize]; + while (configFile.available()) { + size_t bytesRead = configFile.read(buffer, bufferSize); + response->write(buffer, bytesRead); + } configFile.close(); + request->send(response); }); server.on("/save_apcfg", HTTP_POST, [](AsyncWebServerRequest *request) { diff --git a/ESP32_AP-Flasher/wwwroot/content_cards.json b/ESP32_AP-Flasher/wwwroot/content_cards.json index 5e188e41..aa7c3cad 100644 --- a/ESP32_AP-Flasher/wwwroot/content_cards.json +++ b/ESP32_AP-Flasher/wwwroot/content_cards.json @@ -3,7 +3,7 @@ "id": 0, "name": "", "desc": "Not configured", - "param": [] + "param": [ ] }, { "id": 22, @@ -44,7 +44,7 @@ "id": 1, "name": "Current date", "desc": "Shows the current date", - "param": [] + "param": [ ] }, { "id": 2, @@ -195,6 +195,7 @@ "id": 9, "name": "RSS feed", "desc": "Gets an RSS feed, and display the first few lines of it", + "properties": [ "savespace" ], "param": [ { "key": "title", @@ -239,6 +240,7 @@ "id": 10, "name": "QR code", "desc": "Displayes a full screen QR code", + "properties": [ "savespace" ], "param": [ { "key": "title", @@ -279,6 +281,49 @@ } ] }, + { + "id": 27, + "name": "Dayahead prices", + "desc": "Dayahead dynamic electricity tariffs. Source: transparency.entsoe.eu", + "properties": [ "savespace" ], + "param": [ + { + "key": "country", + "name": "Country", + "desc": "Select your country. If your country is not in the list, I couldn't find prices for it on transparency.entsoe.eu . ", + "type": "select", + "options": { + "AT": "Austria", + "BE": "Belgium", + "CH": "Switzerland", + "CZ": "Czech Republic", + "EE": "Estonia", + "ES": "Spain", + "FI": "Finland", + "FR": "France", + "LT": "Lithuania", + "LV": "Latvia", + "NL": "Netherlands", + "PL": "Poland", + "RO": "Romania", + "SI": "Slovenia", + "SK": "Slovakia" + } + }, + { + "key": "tariffkwh", + "name": "Fixed surcharge", + "desc": "Fixed surcharge per kWh, in cents", + "type": "text" + }, + { + "key": "tarifftax", + "name": "Tax percentage", + "desc": "Percentage to add to the total (for example, 21, for 21% VAT)", + "type": "text" + } + ] + }, { "id": 19, "name": "Json template", diff --git a/ESP32_AP-Flasher/wwwroot/main.js b/ESP32_AP-Flasher/wwwroot/main.js index 0a88d333..03a3c2c4 100644 --- a/ESP32_AP-Flasher/wwwroot/main.js +++ b/ESP32_AP-Flasher/wwwroot/main.js @@ -52,6 +52,8 @@ window.addEventListener("loadConfig", function () { $(".logo").innerHTML = data.alias; this.document.title = data.alias; } + if (data.C6) { + } }); }); @@ -969,7 +971,7 @@ function populateSelectTag(hwtype, capabilities) { let option; cardconfig.forEach(item => { const capcheck = item.capabilities ?? 0; - if (tagTypes[hwtype].contentids.includes(item.id) && (capabilities & capcheck || capcheck == 0)) { + if (tagTypes[hwtype].contentids.includes(item.id) && (capabilities & capcheck || capcheck == 0) && (apConfig.savespace == 0 || !item.properties?.includes("savespace"))) { option = document.createElement("option"); option.value = item.id; option.text = item.name; @@ -995,10 +997,15 @@ function populateSelectTag(hwtype, capabilities) { option = document.createElement("option"); option.value = "0"; - option.text = "auto"; + console.log('shortlut: ' + tagTypes[hwtype].shortlut); + if (tagTypes[hwtype].shortlut == 0) { + option.text = "Always full refresh"; + } else { + option.text = "auto"; + } lutTag.appendChild(option); - if (hwtype != 240) { + if (tagTypes[hwtype].shortlut > 0) { option = document.createElement("option"); option.value = "1"; option.text = "Always full refresh"; @@ -1329,6 +1336,7 @@ async function getTagtype(hwtype) { contentids: Object.values(jsonData.contentids ?? []), options: Object.values(jsonData.options ?? []), zlib: parseInt(jsonData.zlib_compression || "0", 16), + shortlut: parseInt(jsonData.shortlut), busy: false }; tagTypes[hwtype] = data; diff --git a/ESP32_AP-Flasher/wwwroot/painter.js b/ESP32_AP-Flasher/wwwroot/painter.js index aa904c85..e5bffdb2 100644 --- a/ESP32_AP-Flasher/wwwroot/painter.js +++ b/ESP32_AP-Flasher/wwwroot/painter.js @@ -87,8 +87,10 @@ function startPainter(mac, width, height) { clearButton.innerHTML = '🖵'; clearButton.addEventListener('click', () => { if (isAddingText) handleFinish(false); - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, canvas.width, canvas.height); + if (window.confirm("Are you sure you want to clear the canvas?")) { + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } }); const uploadButton = document.createElement('button');