// Include Inkplate library to the sketch #include "Inkplate.h" // Including fonts #include "Fonts/FreeSans12pt7b.h" #include "Fonts/FreeSans9pt7b.h" // Includes #include #include #include "Network.h" char *ssid = "e-radionica.com"; char *pass = "croduino"; char *calendarURL = "https://calendar.google.com/calendar/ical/zvonimir3000%40gmail.com/private-bd11c7f112609813c4cd8e602de42f93/basic.ics"; int timeZone = 2; // Delay between API calls #define DELAY_MS 5000 // Variables to keep count of when to get new data, and when to just update time int refreshes = 0; const int refreshesToGet = 10; // Initiate out Inkplate object Inkplate display(INKPLATE_3BIT); // Our networking functions, see Network.cpp for info Network network; // Variables for time and raw event info char date[64]; char data[32000]; // For storing next 3 days info char dates[32 * 3]; // Struct for storing calender event info struct entry { char name[128]; char time[128]; char location[128]; int day; int timeStamp; }; // Here we store calendar entries int entriesNum = 0; entry entries[128]; // Function for drawing calendar info void drawInfo() { // Setting font and color display.setTextColor(0, 7); display.setFont(&FreeSans12pt7b); display.setTextSize(1); display.setCursor(20, 20); // Find email in raw data char temp[64]; char *start = strstr(data, "X-WR-CALNAME:"); // If not found return if (!start) return; // Find where it ends start += 13; char *end = strchr(start, '\n'); strncpy(temp, start, end - start - 1); temp[end - start - 1] = 0; // Print it display.println(temp); } // Drawing what time it is void drawTime() { // Initial text settings display.setTextColor(0, 7); display.setFont(&FreeSans12pt7b); display.setTextSize(1); display.setCursor(423, 20); // Our function to get time network.getTime(date); int t = date[16]; date[16] = 0; display.println(date); date[16] = t; } // Draw lines in which to put events void drawGrid() { // upper left and low right coordinates int x1 = 3, y1 = 30; int x2 = 600 - 3, y2 = 798; // header size, for day info int header = 30; // Columns and rows int n = 1, m = 3; // Line drawing display.drawFastHLine(x1, y1 + header, x2 - x1, 0); for (int i = 0; i < n + 1; ++i) { display.drawFastHLine(x1, (int)((float)y1 + (float)i * (float)(y2 - y1) / (float)n), x2 - x1, 0); } for (int i = 0; i < m + 1; ++i) { display.drawFastVLine((int)((float)x1 + (float)i * (float)(x2 - x1) / (float)m), y1, y2 - y1, 0); display.setFont(&FreeSans9pt7b); // Display day info using time offset char temp[64]; network.getTime(temp, i * 3600L * 24); temp[10] = 0; display.setCursor((int)((float)x1 + (float)i * (float)(x2 - x1) / (float)m) + 15, y1 + header - 6); display.println(temp); } } // Format event times, example 13:00 to 14:00 void getToFrom(char *dst, char *from, char *to, int *day, int *timeStamp) { // ANSI C time struct struct tm ltm = {0}, ltm2 = {0}; char temp[128], temp2[128]; strncpy(temp, from, 16); temp[16] = 0; // https://github.com/esp8266/Arduino/issues/5141, quickfix memmove(temp + 5, temp + 4, 16); memmove(temp + 8, temp + 7, 16); memmove(temp + 14, temp + 13, 16); memmove(temp + 16, temp + 15, 16); temp[4] = temp[7] = temp[13] = temp[16] = '-'; // time.h function strptime(temp, "%Y-%m-%dT%H-%M-%SZ", <m); // create start and end event structs struct tm event, event2; time_t epoch = mktime(<m) + (time_t)timeZone * 3600L; gmtime_r(&epoch, &event); strncpy(dst, asctime(&event) + 11, 5); dst[5] = '-'; strncpy(temp2, to, 16); temp2[16] = 0; // Same as above // https://github.com/esp8266/Arduino/issues/5141, quickfix memmove(temp2 + 5, temp2 + 4, 16); memmove(temp2 + 8, temp2 + 7, 16); memmove(temp2 + 14, temp2 + 13, 16); memmove(temp2 + 16, temp2 + 15, 16); temp2[4] = temp2[7] = temp2[13] = temp2[16] = '-'; strptime(temp2, "%Y-%m-%dT%H-%M-%SZ", <m2); time_t epoch2 = mktime(<m2) + (time_t)timeZone * 3600L; gmtime_r(&epoch2, &event2); strncpy(dst + 6, asctime(&event2) + 11, 5); dst[11] = 0; char day0[64], day1[64], day2[64]; // Find UNIX timestamps for next days to see where to put event network.getTime(day0, 0); network.getTime(day1, 24 * 3600); network.getTime(day2, 48 * 3600); *timeStamp = epoch; network.getTime(temp); if (strncmp(day0, asctime(&event), 10) == 0) *day = 0; else if (strncmp(day1, asctime(&event), 10) == 0) *day = 1; else if (strncmp(day2, asctime(&event), 10) == 0) *day = 2; else // event not in next 3 days, don't display *day = -1; } // Function to drw event bool drawEvent(entry *event, int day, int beginY, int maxHeigth, int *heigthNeeded) { // Upper left coordintes int x1 = 3 + 4 + (594 / 3) * day; int y1 = beginY + 3; // Setting text font display.setFont(&FreeSans12pt7b); int n = 0; char line[128]; // Insert line brakes into setTextColor int lastSpace = -100; display.setCursor(x1, beginY + 22); for (int i = 0; i < min((size_t)64, strlen(event->name)); ++i) { line[n] = event->name[i]; if (line[n] == ' ') lastSpace = n; line[++n] = 0; int16_t xt1, yt1; uint16_t w, h; display.getTextBounds(line, 0, 0, &xt1, &yt1, &w, &h); // Char out of bounds, put in next line if (w > 590 / 3 - 20) { // if there was a space 5 chars before, break line there if (n - lastSpace < 5) { i -= n - lastSpace - 1; line[lastSpace] = 0; } display.setCursor(x1, display.getCursorY()); display.println(line); line[0] = 0; n = 0; } } // display last line display.setCursor(x1, display.getCursorY()); display.println(line); display.setCursor(x1, display.getCursorY()); display.setFont(&FreeSans9pt7b); // Print time // also, if theres a location print it if (strlen(event->location) != 1) { display.println(event->time); display.setCursor(x1, display.getCursorY()); display.print(event->location); } else { display.print(event->time); } // Draw event rect bounds display.drawRoundRect(x1 - 2, y1, 590 / 3 - 2, display.getCursorY() + 5 - y1, 10, 0); // Set how high is the event *heigthNeeded = display.getCursorY() + 10 - y1; // Return is it overflowing return display.getCursorY() < maxHeigth - 5; } // Struct event comparison function, by timestamp int cmp(const void *a, const void *b) { entry *entryA = (entry *)a; entry *entryB = (entry *)b; return (entryA->timeStamp - entryB->timeStamp); } // Main data drawing data void drawData() { long i = 0; long n = strlen(data); // reset count entriesNum = 0; // Search raw data for events while (i < n && strstr(data + i, "BEGIN:VEVENT")) { // Find next event start and end i = strstr(data + i, "BEGIN:VEVENT") - data + 12; char *end = strstr(data + i, "END:VEVENT"); // Find all relevant event data char *summary = strstr(data + i, "SUMMARY:") + 8; char *location = strstr(data + i, "LOCATION:") + 9; char *timeStart = strstr(data + i, "DTSTART:") + 8; char *timeEnd = strstr(data + i, "DTEND:") + 6; if (summary && summary < end) { strncpy(entries[entriesNum].name, summary, strchr(summary, '\n') - summary); entries[entriesNum].name[strchr(summary, '\n') - summary] = 0; } if (location && location < end) { strncpy(entries[entriesNum].location, location, strchr(location, '\n') - location); entries[entriesNum].location[strchr(location, '\n') - location] = 0; } if (timeStart && timeStart < end && timeEnd < end) { getToFrom(entries[entriesNum].time, timeStart, timeEnd, &entries[entriesNum].day, &entries[entriesNum].timeStamp); } ++entriesNum; } // Sort entries by time qsort(entries, entriesNum, sizeof(entry), cmp); // Events displayed and overflown counters int columns[3] = {0}; bool clogged[3] = {0}; int cloggedCount[3] = {0}; // Displaying events one by one for (int i = 0; i < entriesNum; ++i) { // If column overflowed just add event to not shown if (entries[i].day != -1 && clogged[entries[i].day]) ++cloggedCount[entries[i].day]; if (entries[i].day == -1 || clogged[entries[i].day]) continue; // We store hot much height did one event take up int shift = 0; bool s = drawEvent(&entries[i], entries[i].day, columns[entries[i].day] + 64, 800 - 4, &shift); columns[entries[i].day] += shift; // If it overflowed, set column to clogged and add one event as not shown if (!s) { ++cloggedCount[entries[i].day]; clogged[entries[i].day] = 1; } } // Display not shown events info for (int i = 0; i < 3; ++i) { if (clogged[i]) { display.fillRoundRect(6 + i * (594 / 3), 800 - 24, (594 / 3) - 5, 20, 10, 0); display.setCursor(10, 800 - 6); display.setTextColor(7, 0); display.setFont(&FreeSans9pt7b); display.print(cloggedCount[i]); display.print(" more events"); } } } void setup() { Serial.begin(115200); // Initial display settings display.begin(); display.clearDisplay(); display.clean(); display.setRotation(1); display.setTextWrap(false); display.setTextColor(0, 7); // Welcome screen display.setCursor(5, 230); display.setTextSize(2); display.println(F("Welcome to Inkplate 6 Google Calendar example!")); display.setCursor(5, 250); display.println(F("Connecting to WiFi...")); display.display(); delay(5000); network.begin(); // Our begin function network.begin(); } void loop() { // Keep trying to get data if it fails the first time if (refreshes % refreshesToGet == 0) while (!network.getData(data)) { Serial.println("Failed getting data, retrying"); delay(1000); } // Initial screen clearing display.clearDisplay(); // Drawing all data, functions for that are above if (refreshes % refreshesToGet == 0) { drawInfo(); drawGrid(); drawData(); } drawTime(); // Actually display all data if (refreshes % refreshesToGet == 0) display.display(); else display.partialUpdate(); // Increment refreshes ++refreshes; // Go to sleep before checking again esp_sleep_enable_timer_wakeup(1000L * DELAY_MS); (void)esp_light_sleep_start(); }