From b0cfa1b2b47fe0d8b2e69a6e68fd5f02fedc1d55 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Sat, 6 Jan 2024 21:09:08 +0100 Subject: [PATCH] new week calendar added --- ESP32_AP-Flasher/data/fonts/BellCent10.vlw | Bin 0 -> 10909 bytes ESP32_AP-Flasher/data/tagtypes/01.json | 2 + ESP32_AP-Flasher/data/tagtypes/02.json | 9 +- ESP32_AP-Flasher/data/tagtypes/05.json | 9 +- ESP32_AP-Flasher/data/tagtypes/2E.json | 6 + ESP32_AP-Flasher/data/tagtypes/2F.json | 6 + ESP32_AP-Flasher/data/tagtypes/30.json | 2 +- ESP32_AP-Flasher/data/tagtypes/31.json | 12 +- ESP32_AP-Flasher/data/tagtypes/33.json | 11 +- ESP32_AP-Flasher/data/tagtypes/35.json | 6 + ESP32_AP-Flasher/include/contentmanager.h | 2 +- ESP32_AP-Flasher/src/contentmanager.cpp | 270 ++++++++++++++++++--- 12 files changed, 287 insertions(+), 48 deletions(-) create mode 100644 ESP32_AP-Flasher/data/fonts/BellCent10.vlw diff --git a/ESP32_AP-Flasher/data/fonts/BellCent10.vlw b/ESP32_AP-Flasher/data/fonts/BellCent10.vlw new file mode 100644 index 0000000000000000000000000000000000000000..b1d32e74e3ad837c034791272a447e275b5b070a GIT binary patch literal 10909 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 W=N>zJ^2q6D9yxOU(Gw?T(Ek9f#9^fX literal 0 HcmV?d00001 diff --git a/ESP32_AP-Flasher/data/tagtypes/01.json b/ESP32_AP-Flasher/data/tagtypes/01.json index cd23367d..9773536d 100644 --- a/ESP32_AP-Flasher/data/tagtypes/01.json +++ b/ESP32_AP-Flasher/data/tagtypes/01.json @@ -56,6 +56,8 @@ "pos": [149, 27] }, "11": { + "mode": 0, + "days": 1, "title": [5, 2, "fonts/bahnschrift20"], "date": [290, 2], "items": 7, diff --git a/ESP32_AP-Flasher/data/tagtypes/02.json b/ESP32_AP-Flasher/data/tagtypes/02.json index 011c2897..361416eb 100644 --- a/ESP32_AP-Flasher/data/tagtypes/02.json +++ b/ESP32_AP-Flasher/data/tagtypes/02.json @@ -48,11 +48,10 @@ "pos": [ 200, 35 ] }, "11": { - "title": [ 10, 10, "fonts/bahnschrift30" ], - "date": [ 390, 10 ], - "items": 12, - "red": [ 0, 48, 400, 17 ], - "line": [ 10, 61, 18, "7x14_tf", 60 ] + "rotate": 0, + "mode": 1, + "days": 4, + "gridparam": [ 5, 17, 20, "calibrib16.vlw", "BellCent10.vlw", 14 ] } } } diff --git a/ESP32_AP-Flasher/data/tagtypes/05.json b/ESP32_AP-Flasher/data/tagtypes/05.json index 85fac766..c86bfca5 100644 --- a/ESP32_AP-Flasher/data/tagtypes/05.json +++ b/ESP32_AP-Flasher/data/tagtypes/05.json @@ -48,11 +48,10 @@ "pos": [ 320, 40 ] }, "11": { - "title": [ 10, 10, "fonts/bahnschrift30" ], - "date": [ 390, 10 ], - "items": 12, - "red": [ 0, 48, 400, 17 ], - "line": [ 10, 61, 18, "7x14_tf", 60 ] + "rotate": 0, + "mode": 1, + "days": 7, + "gridparam": [ 3, 17, 30, "calibrib16.vlw", "BellCent10.vlw", 14 ] } } } diff --git a/ESP32_AP-Flasher/data/tagtypes/2E.json b/ESP32_AP-Flasher/data/tagtypes/2E.json index 9e909d2f..dea87c20 100644 --- a/ESP32_AP-Flasher/data/tagtypes/2E.json +++ b/ESP32_AP-Flasher/data/tagtypes/2E.json @@ -30,6 +30,12 @@ "10": { "title": [ 480, 0, "Signika-SB.ttf", 50 ], "pos": [ 480, 70 ] + }, + "11": { + "rotate": 0, + "mode": 1, + "days": 7, + "gridparam": [ 7, 19, 30, "calibrib16.vlw", "REFSAN12.vlw", 16 ] } } } \ No newline at end of file diff --git a/ESP32_AP-Flasher/data/tagtypes/2F.json b/ESP32_AP-Flasher/data/tagtypes/2F.json index 9ffc93e8..52e72bdb 100644 --- a/ESP32_AP-Flasher/data/tagtypes/2F.json +++ b/ESP32_AP-Flasher/data/tagtypes/2F.json @@ -30,6 +30,12 @@ "items": 1, "line": [ 9, 40, "calibrib16.vlw" ], "desc": [ 2, 8, "REFSAN12.vlw", 1.2 ] + }, + "11": { + "rotate": 1, + "mode": 1, + "days": 1, + "gridparam": [ 5, 17, 20, "calibrib16.vlw", "BellCent10.vlw", 14 ] } } } diff --git a/ESP32_AP-Flasher/data/tagtypes/30.json b/ESP32_AP-Flasher/data/tagtypes/30.json index 9a6106c3..97bf153e 100644 --- a/ESP32_AP-Flasher/data/tagtypes/30.json +++ b/ESP32_AP-Flasher/data/tagtypes/30.json @@ -13,6 +13,6 @@ }, "shortlut": 0, "options": ["button"], - "contentids": [ 0, 1, 2, 3, 4, 8, 7, 19, 10, 11, 21 ], + "contentids": [ 0, 1, 2, 3, 4, 8, 7, 19, 10, 21 ], "usetemplate": 0 } \ No newline at end of file diff --git a/ESP32_AP-Flasher/data/tagtypes/31.json b/ESP32_AP-Flasher/data/tagtypes/31.json index f7f7890f..0ce9c617 100644 --- a/ESP32_AP-Flasher/data/tagtypes/31.json +++ b/ESP32_AP-Flasher/data/tagtypes/31.json @@ -56,11 +56,13 @@ "pos": [149, 30] }, "11": { - "title": [5, 2, "fonts/bahnschrift20"], - "date": [290, 2], - "items": 7, - "red": [0, 21, 296, 14], - "line": [5, 32, 15, "t0_14b_tf", 50] + "mode": 0, + "days": 1, + "title": [ 5, 2, "fonts/bahnschrift20" ], + "date": [ 290, 2 ], + "items": 10, + "red": [ 0, 21, 296, 14 ], + "line": [ 5, 32, 15, "t0_14b_tf", 50 ] } } } diff --git a/ESP32_AP-Flasher/data/tagtypes/33.json b/ESP32_AP-Flasher/data/tagtypes/33.json index 77fac9f7..c897e1f9 100644 --- a/ESP32_AP-Flasher/data/tagtypes/33.json +++ b/ESP32_AP-Flasher/data/tagtypes/33.json @@ -39,6 +39,15 @@ "10": { "title": [ 192, 5, "fonts/bahnschrift20" ], "pos": [ 192, 30 ] - } + }, + "11": { + "mode": 0, + "days": 1, + "title": [ 5, 2, "fonts/bahnschrift20" ], + "date": [ 378, 2 ], + "items": 10, + "red": [ 0, 21, 384, 16 ], + "line": [ 5, 23, 18, "calibrib16.vlw", 55 ] + } } } diff --git a/ESP32_AP-Flasher/data/tagtypes/35.json b/ESP32_AP-Flasher/data/tagtypes/35.json index 06f55d51..a7135c6a 100644 --- a/ESP32_AP-Flasher/data/tagtypes/35.json +++ b/ESP32_AP-Flasher/data/tagtypes/35.json @@ -28,6 +28,12 @@ "items": 6, "line": [ 9, 40, "calibrib16.vlw" ], "desc": [ 2, 8, "REFSAN12.vlw", 1.2 ] + }, + "11": { + "rotate": 0, + "mode": 1, + "days": 7, + "gridparam": [ 7, 17, 30, "calibrib16.vlw", "BellCent10.vlw", 14 ] } } } diff --git a/ESP32_AP-Flasher/include/contentmanager.h b/ESP32_AP-Flasher/include/contentmanager.h index b714cdd9..e60edf06 100644 --- a/ESP32_AP-Flasher/include/contentmanager.h +++ b/ESP32_AP-Flasher/include/contentmanager.h @@ -28,7 +28,7 @@ void drawWeather(String &filename, JsonObject &cfgobj, const tagRecord *taginfo, void drawForecast(String &filename, JsonObject &cfgobj, const 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); +bool getCalFeed(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/src/contentmanager.cpp b/ESP32_AP-Flasher/src/contentmanager.cpp index d3b767e0..f2a56a72 100644 --- a/ESP32_AP-Flasher/src/contentmanager.cpp +++ b/ESP32_AP-Flasher/src/contentmanager.cpp @@ -370,7 +370,7 @@ void drawNew(const uint8_t mac[8], const bool buttonPressed, tagRecord *&taginfo case 11: // Calendar: - if (getCalFeed(filename, cfgobj["apps_script_url"], cfgobj["title"], taginfo, imageParams)) { + if (getCalFeed(filename, cfgobj, taginfo, imageParams)) { const int interval = cfgobj["interval"].as(); taginfo->nextupdate = now + 60 * (interval < 3 ? 15 : interval); updateTagImage(filename, mac, interval, taginfo, imageParams); @@ -1019,6 +1019,8 @@ bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo, // http://feeds.feedburner.com/tweakers/nieuws // https://www.nu.nl/rss/Algemeen + wsLog("get rss feed"); + const char *url = URL.c_str(); const int rssTitleSize = 255; const int rssDescSize = 1000; @@ -1094,19 +1096,24 @@ char *epoch_to_display(time_t utc) { return display; } -bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams) { +bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams) { #ifdef CONTENT_CAL // google apps scripts method to retrieve calendar // see https://github.com/jjwbruijn/OpenEPaperLink/wiki/Google-Apps-Scripts for description wsLog("get calendar"); + StaticJsonDocument<512> loc; + getTemplate(loc, 11, taginfo->hwType); + + String URL = cfgobj["apps_script_url"].as() + "?days=" + loc["days"].as(); + time_t now; time(&now); struct tm timeinfo; localtime_r(&now, &timeinfo); char dateString[40]; - strftime(dateString, sizeof(dateString), "%d.%m.%Y", &timeinfo); + strftime(dateString, sizeof(dateString), "%d-%m-%Y", &timeinfo); HTTPClient http; // logLine("http getCalFeed " + URL); @@ -1130,36 +1137,239 @@ bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, U8g2_for_TFT_eSPI u8f; u8f.begin(spr); - StaticJsonDocument<512> loc; - getTemplate(loc, 11, taginfo->hwType); - initSprite(spr, imageParams.width, imageParams.height, imageParams); + if (loc["rotate"] == 1) { + int temp = imageParams.height; + imageParams.height = imageParams.width; + imageParams.width = temp; + imageParams.rotatebuffer = 1 - imageParams.rotatebuffer; + initSprite(spr, imageParams.width, imageParams.height, imageParams); + } else { + initSprite(spr, imageParams.width, imageParams.height, imageParams); + } - if (util::isEmptyOrNull(title)) title = "Calendar"; - 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); + switch (loc["mode"].as()) { + case 0: { + // appointment list + String title = cfgobj["title"]; + if (util::isEmptyOrNull(title)) title = "Calendar"; + 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); - int n = doc.size(); - if (n > loc["items"]) n = loc["items"]; - for (int i = 0; i < n; i++) { - const JsonObject &obj = doc[i]; - const String eventtitle = obj["title"]; - const time_t starttime = obj["start"]; - const time_t endtime = obj["end"]; - setU8G2Font(loc["line"][3], u8f); - if (starttime <= now && endtime > now) { - u8f.setForegroundColor(TFT_WHITE); - u8f.setBackgroundColor(TFT_RED); - spr.fillRect(loc["red"][0], loc["red"][1].as() + i * loc["line"][2].as(), loc["red"][2], loc["red"][3], TFT_RED); - } else { - u8f.setForegroundColor(TFT_BLACK); - u8f.setBackgroundColor(TFT_WHITE); + u8f.setFontMode(0); + u8f.setFontDirection(0); + int n = doc.size(); + if (n > loc["items"]) n = loc["items"]; + for (int i = 0; i < n; i++) { + const JsonObject &obj = doc[i]; + const String eventtitle = obj["title"]; + const time_t starttime = obj["start"]; + 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); + } 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); + } + } + break; + } + case 1: { + // week view + + // gridparam: + // 0: 10; offset top + // 1: 20; offset cal block + // 2: 30; offset left + // 3: calibrib16.vlw; headers font + // 4: BellCent10.vlw; appointments font + // 5: 14; line height + + timeinfo.tm_hour = 0; + timeinfo.tm_min = 0; + timeinfo.tm_sec = 0; + time_t midnightEpoch = mktime(&timeinfo); + + int calWidth = imageParams.width - 1; + int calHeight = imageParams.height; + int calDays = loc["days"]; + + int colLeft = loc["gridparam"][2].as(); + int colWidth = (calWidth - colLeft) / calDays; + + int calTop = loc["gridparam"][0].as(); + int calBottom = calHeight; + int calYOffset = loc["gridparam"][1].as(); + int lineHeight = loc["gridparam"][5].as(); + + // drawString(spr, String(timeinfo.tm_mday), calWidth / 2, -calHeight/5, "Signika-SB.ttf", TC_DATUM, TFT_RED, calHeight * 1.2); + + for (int i = 0; i < calDays; i++) { + struct tm dayTimeinfo = *localtime(&midnightEpoch); + dayTimeinfo.tm_mday += i; + time_t dayEpoch = mktime(&dayTimeinfo); + struct tm *dayInfo = localtime(&dayEpoch); + + int colStart = colLeft + i * colWidth; + spr.drawLine(colStart,calTop,colStart,calBottom,TFT_BLACK); + drawString(spr, String(languageDaysShort[dayInfo->tm_wday]) + " " + String(dayInfo->tm_mday), colStart + colWidth / 2, calTop, loc["gridparam"][3], TC_DATUM, TFT_BLACK); + + int grid = 3; + if (dayInfo->tm_wday == 0 || dayInfo->tm_wday == 6) { + for (int y = calTop + calYOffset; y < calHeight; y += 1) { + for (int x = colStart + (y % 2); x < colStart + colWidth; x += 2) { + spr.drawPixel(x, y, TFT_BLACK); + } + } + } else { + for (int y = calTop + calYOffset; y < calHeight; y += 2) { + for (int x = colStart; x < colStart + colWidth; x += 2) { + spr.drawPixel(x, y, TFT_BLACK); + } + } + } + } + + int colStart = colLeft + calDays * colWidth; + spr.drawLine(colStart, calTop, colStart, calBottom, TFT_BLACK); + spr.drawLine(0, calTop + calYOffset, calWidth, calTop + calYOffset, TFT_BLACK); + + int minHour = 9; + int maxHour = 17; + + int n = doc.size(); + int maxBlock = 0; + int *block = new int[n]; + + for (int i = 0; i < n; i++) { + const JsonObject &obj = doc[i]; + String eventtitle = obj["title"]; + const time_t startdatetime = obj["start"]; + const time_t enddatetime = obj["end"]; + const bool isallday = obj["isallday"]; + + if (!isallday) { + localtime_r(&startdatetime, &timeinfo); + if (timeinfo.tm_hour < minHour) minHour = timeinfo.tm_hour; + + localtime_r(&enddatetime, &timeinfo); + if (timeinfo.tm_hour> maxHour) maxHour = timeinfo.tm_hour; + if (timeinfo.tm_min > 1 && timeinfo.tm_hour + 1 > maxHour) maxHour = timeinfo.tm_hour + 1; + } else { + int fulldaystart = constrain((startdatetime - midnightEpoch) / (24 * 3600), 0, calDays); + int fulldayend = constrain((enddatetime - midnightEpoch) / (24 * 3600), 0, calDays); + if (fulldaystart < calDays) { + int line = 1; + bool overlap = false; + do { + overlap = false; + for (int j = 0; j < i; j++) { + const JsonObject &obj2 = doc[j]; + const bool isallday2 = obj2["isallday"]; + const time_t startdatetime2 = obj2["start"]; + const time_t enddatetime2 = obj2["end"]; + if (startdatetime < enddatetime2 && enddatetime > startdatetime2 && + line == block[j] && isallday2) { + Serial.printf("overlap %d met %d, %d-%d met %d-%d, block[j]=%d",i,j,startdatetime, enddatetime,startdatetime2,enddatetime2,block[j]); + overlap == true; + line++; + } + } + } while (overlap == true); + + int16_t eventX = colLeft + fulldaystart * colWidth + 3; + int16_t eventY = calTop + calYOffset + (line - 1) * lineHeight + 3; + spr.drawRect(eventX - 2, eventY - 3, colWidth * (fulldayend - fulldaystart) - 1, lineHeight + 1, TFT_BLACK); + spr.fillRect(eventX - 1, eventY - 2, colWidth * (fulldayend - fulldaystart) - 3, lineHeight - 1, TFT_WHITE); + drawTextBox(spr, eventtitle, eventX, eventY, colWidth * (fulldayend - fulldaystart) - 3, 15, loc["gridparam"][4], TFT_BLACK); + + block[i] = line; + if (line > maxBlock) maxBlock = line; + } + } + + const int starttime = timeinfo.tm_hour * 60 + timeinfo.tm_min; + + int hours = timeinfo.tm_hour; + int minutes = timeinfo.tm_min; + } + calYOffset += maxBlock * lineHeight; + + spr.drawLine(0, calTop + calYOffset, calWidth, calTop + calYOffset, TFT_BLACK); + int hourHeight = (calHeight - calYOffset - calTop) / (maxHour - minHour); + for (int i = 0; i < (maxHour - minHour); i++) { + spr.drawLine(0, calTop + calYOffset + i * hourHeight, colLeft + 10, calTop + calYOffset + i * hourHeight, TFT_BLACK); + for (int j = 1; j <= calDays; j++) { + spr.drawLine(colLeft + j * colWidth - 5, calTop + calYOffset + i * hourHeight, colLeft + j * colWidth + 5, calTop + calYOffset + i * hourHeight, TFT_BLACK); + } + drawString(spr, String(minHour + i), colLeft - 2, calTop + calYOffset + i * hourHeight + 2, loc["gridparam"][3], TR_DATUM, TFT_BLACK); + } + spr.drawLine(0, calTop + calYOffset + (maxHour - minHour) * hourHeight, calWidth, calTop + calYOffset + (maxHour - minHour) * hourHeight, TFT_BLACK); + + for (int i = 0; i < n; i++) { + const JsonObject &obj = doc[i]; + String eventtitle = obj["title"]; + const time_t startdatetime = obj["start"]; + const time_t enddatetime = obj["end"]; + const bool isallday = obj["isallday"]; + + if (!isallday) { + int fulldaystart = constrain((startdatetime - midnightEpoch) / (24 * 3600), 0, calDays); + int fulldayend = constrain((enddatetime - midnightEpoch) / (24 * 3600), 0, calDays); + + for (int day = fulldaystart; day <= fulldayend; day++) { + localtime_r(&startdatetime, &timeinfo); + int starttime = timeinfo.tm_hour * 60 + timeinfo.tm_min - minHour * 60; + int duration = (enddatetime - startdatetime) / 60; + if (day > fulldaystart) { + starttime = 0; + localtime_r(&enddatetime, &timeinfo); + duration = timeinfo.tm_hour * 60 + timeinfo.tm_min - minHour * 60; + } + char formattedTime[6]; + strftime(formattedTime, sizeof(formattedTime), "%H:%M", &timeinfo); + String formattedTimeString = String(formattedTime); + + int indent = 1; + bool overlap = false; + do { + overlap = false; + for (int j = 0; j < i; j++) { + const JsonObject &obj2 = doc[j]; + const bool isallday2 = obj2["isallday"]; + const time_t startdatetime2 = obj2["start"]; + const time_t enddatetime2 = obj2["end"]; + if (startdatetime < enddatetime2 && enddatetime > startdatetime2 && + indent == block[j] && isallday2 == false) { + Serial.println("overlap met " + String(j) + " (indent " + String(indent) + ")"); + Serial.printf("overlap %d met %d, %d-%d met %d-%d, block[j]=%d", i, j, startdatetime, enddatetime, startdatetime2, enddatetime2, block[j]); + overlap == true; + indent++; + } + } + } while (overlap == true); + + block[i] = indent; + int16_t eventX = colLeft + day * colWidth + (indent - 1) * 5; + int16_t eventY = calTop + calYOffset + (starttime * hourHeight / 60); + spr.drawRect(eventX + 1, eventY, colWidth - 1, (duration * hourHeight / 60) + 1, TFT_BLACK); + spr.fillRect(eventX + 2, eventY + 1, colWidth - 3, (duration * hourHeight / 60) - 1, TFT_WHITE); + eventX += 2; + eventY += 2; + if (day == fulldaystart) { + eventtitle = formattedTimeString + " " + String(eventtitle); + } else { + eventtitle = obj["title"].as(); + } + drawTextBox(spr, eventtitle, eventX, eventY, colWidth - 1, (duration * hourHeight / 60) - 1, loc["gridparam"][4], TFT_BLACK, TFT_WHITE, 1); + } + } + } + + delete[] block; } - u8f.setCursor(loc["line"][0], loc["line"][1].as() + i * loc["line"][2].as()); - if (starttime > 0) u8f.print(epoch_to_display(obj["start"])); - u8f.setCursor(loc["line"][4], loc["line"][1].as() + i * loc["line"][2].as()); - u8f.print(eventtitle); } spr2buffer(spr, filename, imageParams);