From e19c9dc612b689f3b81ee05e8c7efb9147706586 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Thu, 23 Mar 2023 23:40:06 +0100 Subject: [PATCH] added content: google calendar via google apps script, see /data/calendar.txt --- ESP32_AP-Flasher/data/calendar.txt | 46 +++++++++ ESP32_AP-Flasher/data/www/index.html | 1 + ESP32_AP-Flasher/data/www/main.js | 3 +- ESP32_AP-Flasher/include/contentmanager.h | 3 +- ESP32_AP-Flasher/platformio.ini | 2 +- ESP32_AP-Flasher/src/contentmanager.cpp | 118 +++++++++++++++++++++- 6 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 ESP32_AP-Flasher/data/calendar.txt diff --git a/ESP32_AP-Flasher/data/calendar.txt b/ESP32_AP-Flasher/data/calendar.txt new file mode 100644 index 00000000..5997a802 --- /dev/null +++ b/ESP32_AP-Flasher/data/calendar.txt @@ -0,0 +1,46 @@ +To use Google Apps Script to get all events for the next day and return them via JSON in a web app, you can follow these steps: + +Create a new Google Apps Script project by going to https://script.google.com and clicking on "New project". + +In the script editor, create a new function called "getEventsForNextDay" that will fetch all events for the next day and return them in JSON format: + +function getEventsForNextDay() { + var start = new Date(); + var end = new Date(); + end.setDate(end.getDate() + 1); + var calendars = CalendarApp.getAllCalendars(); + var events = []; + for (var i = 0; i < calendars.length; i++) { + var calendar = calendars[i]; + var eventsInCalendar = calendar.getEvents(start, end); + for (var j = 0; j < eventsInCalendar.length; j++) { + var event = eventsInCalendar[j]; + events.push({ + title: event.getTitle(), + start: Math.floor(event.getStartTime().getTime() / 1000), + end: Math.floor(event.getEndTime().getTime() / 1000), + location: event.getLocation(), + description: event.getDescription() + }); + } + } + // Sort events by start date/time + events.sort(function(a, b) { + return a.start - b.start; + }); + return JSON.stringify(events); +} + +function doGet() { + var content = getEventsForNextDay(); + var output = ContentService.createTextOutput(content); + output.setMimeType(ContentService.MimeType.JSON); + return output; +} + + +This function calls the getEventsForNextDay() function to get the events in JSON format, creates a text output with the JSON content, sets the MIME type to JSON, and returns the output. + +Deploy the web app by clicking on "Deploy > New Deployment" in the script editor. Choose "Web app" as the deployment type, set the access to "Anyone, even anonymous", and click on "Deploy". Make sure to take note of the web app URL generated by Google. + +Test the web app by visiting the web app URL in a web browser. You should see the events for the next day in JSON format. diff --git a/ESP32_AP-Flasher/data/www/index.html b/ESP32_AP-Flasher/data/www/index.html index 337e9c9e..9b70dc31 100644 --- a/ESP32_AP-Flasher/data/www/index.html +++ b/ESP32_AP-Flasher/data/www/index.html @@ -35,6 +35,7 @@ +

diff --git a/ESP32_AP-Flasher/data/www/main.js b/ESP32_AP-Flasher/data/www/main.js index 51de5f1e..531d7ad0 100644 --- a/ESP32_AP-Flasher/data/www/main.js +++ b/ESP32_AP-Flasher/data/www/main.js @@ -8,7 +8,7 @@ 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", "QR code"]; +const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url", "Weather forecast", "RSS feed", "QR code", "Calendar"]; const models = ["1.54\" 152x152px", "2.9\" 296x128px", "4.2\" 400x300px"]; const displaySizeLookup = { 0: [152, 152], 1: [128, 296], 2: [400, 300] }; const colorTable = { 0: [255, 255, 255], 1: [0, 0, 0], 2: [255, 0, 0], 3: [255, 0, 0] }; @@ -24,6 +24,7 @@ contentModeOptions[7] = ["url","interval"]; contentModeOptions[8] = ["location"]; contentModeOptions[9] = ["title", "url", "interval"]; contentModeOptions[10] = ["title", "qr-content"]; +contentModeOptions[11] = ["title", "apps_script_url", "interval"]; const imageQueue = []; let isProcessing = false; diff --git a/ESP32_AP-Flasher/include/contentmanager.h b/ESP32_AP-Flasher/include/contentmanager.h index 5b5bcb48..fa9a1441 100644 --- a/ESP32_AP-Flasher/include/contentmanager.h +++ b/ESP32_AP-Flasher/include/contentmanager.h @@ -26,7 +26,8 @@ void drawWeather(String &filename, String location, tagRecord *&taginfo, imgPara void drawForecast(String &filename, String location, tagRecord *&taginfo, imgParam &imageParams); void drawIdentify(String &filename, tagRecord *&taginfo, imgParam &imageParams); bool getImgURL(String &filename, String URL, time_t fetched, imgParam &imageParams); -bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams); +bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams); +bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams); void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginfo, imgParam &imageParams); char *formatHttpDate(time_t t); String urlEncode(const char *msg); diff --git a/ESP32_AP-Flasher/platformio.ini b/ESP32_AP-Flasher/platformio.ini index e587642a..345759ba 100644 --- a/ESP32_AP-Flasher/platformio.ini +++ b/ESP32_AP-Flasher/platformio.ini @@ -79,5 +79,5 @@ monitor_port = COM21 build_flags = -D SIMPLE_AP=2 -D PINOUT=SIMPLE_AP -src_filter = +build_src_filter = +<*>- diff --git a/ESP32_AP-Flasher/src/contentmanager.cpp b/ESP32_AP-Flasher/src/contentmanager.cpp index e02a00aa..4beb2238 100644 --- a/ESP32_AP-Flasher/src/contentmanager.cpp +++ b/ESP32_AP-Flasher/src/contentmanager.cpp @@ -31,6 +31,7 @@ enum contentModes { Forecast, RSSFeed, QRcode, + Calendar, }; void contentRunner() { @@ -180,7 +181,7 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) { case RSSFeed: - if (getRSSfeed(filename, cfgobj["url"], cfgobj["title"], taginfo, imageParams)) { + if (getRssFeed(filename, cfgobj["url"], cfgobj["title"], taginfo, imageParams)) { taginfo->nextupdate = now + 60 * (cfgobj["interval"].as() < 5 ? 5 : cfgobj["interval"].as()); updateTagImage(filename, mac, cfgobj["interval"].as(), imageParams); } else { @@ -194,6 +195,17 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) { taginfo->nextupdate = now + 12 * 3600; updateTagImage(filename, mac, 0, imageParams); break; + + case Calendar: + + if (getCalFeed(filename, cfgobj["apps_script_url"], cfgobj["title"], taginfo, imageParams)) { + taginfo->nextupdate = now + 60 * (cfgobj["interval"].as() < 5 ? 5 : cfgobj["interval"].as()); + updateTagImage(filename, mac, cfgobj["interval"].as(), imageParams); + } else { + taginfo->nextupdate = now + 300; + } + break; + } taginfo->modeConfigJson = doc.as(); @@ -595,7 +607,7 @@ bool getImgURL(String &filename, String URL, time_t fetched, imgParam &imagePara rssClass reader; -bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams) { +bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams) { // https://github.com/garretlab/shoddyxml2 // http://feeds.feedburner.com/tweakers/nieuws @@ -649,6 +661,108 @@ bool getRSSfeed(String &filename, String URL, String title, tagRecord *&taginfo, return true; } +char *epoch_to_display(time_t utc) { + static char display[6]; + struct tm local_tm; + localtime_r(&utc, &local_tm); + time_t now; + time(&now); + struct tm now_tm; + localtime_r(&now, &now_tm); + if (local_tm.tm_year < now_tm.tm_year || + (local_tm.tm_year == now_tm.tm_year && local_tm.tm_mon < now_tm.tm_mon) || + (local_tm.tm_year == now_tm.tm_year && local_tm.tm_mon == now_tm.tm_mon && local_tm.tm_mday < now_tm.tm_mday) || + (local_tm.tm_hour == 0 && local_tm.tm_min == 0)) { + strftime(display, sizeof(display), "%d-%m", &local_tm); + } else { + strftime(display, sizeof(display), "%H:%M", &local_tm); + } + return display; +} + +bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams) { + // google apps scripts method to retrieve calendar + // see /data/calendar.txt for description + + wsLog("get calendar"); + + time_t now; + time(&now); + struct tm timeinfo; + localtime_r(&now, &timeinfo); + + HTTPClient http; + http.begin(URL); + http.setTimeout(10000); // timeout in ms + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + int httpCode = http.GET(); + if (httpCode != 200) { + wsErr("http error " + String(httpCode)); + return false; + } + + DynamicJsonDocument doc(5000); + DeserializationError error = deserializeJson(doc, http.getString()); + if (error) { + wsErr(error.c_str()); + } + http.end(); + + 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 = "Calendar"; + + u8f.setFont(u8g2_font_t0_22b_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(5, 16); + u8f.print(title); + + // u8g2_font_nine_by_five_nbp_tr + // u8g2_font_7x14_tr + // u8g2_font_crox1h_tr + // u8g2_font_miranda_nbp_tr + // u8g2_font_glasstown_nbp_tr + // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall + + int n = doc.size(); + if (n>7) n=7; + for (int i = 0; i < n; i++) { + JsonObject obj = doc[i]; + String eventtitle = obj["title"]; + String startz = obj["start"]; + time_t starttime = obj["start"]; + time_t endtime = obj["end"]; + if (starttimenow) { + u8f.setFont(u8g2_font_t0_14b_tr); + u8f.setForegroundColor(PAL_WHITE); + u8f.setBackgroundColor(PAL_RED); + spr.fillRect(0, i * 15 + 21, 296, 14, PAL_RED); + } else { + u8f.setFont(u8g2_font_t0_14_tr); + u8f.setForegroundColor(PAL_BLACK); + u8f.setBackgroundColor(PAL_WHITE); + } + u8f.setCursor(5, 32 + i * 15); + u8f.print(epoch_to_display(obj["start"])); + u8f.setCursor(50, 32 + i * 15); + u8f.print(eventtitle); + } + } + + spr2buffer(spr, filename, imageParams); + spr.deleteSprite(); + + return true; +} + void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginfo, imgParam &imageParams) { TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft);