inkplate-6-arduino-library/examples/3. Projects/3-Google_calendar_example/3-Google_calendar_example.ino

438 lines
11 KiB
C++

// Include Inkplate library to the sketch
#include "Inkplate.h"
// Including fonts
#include "Fonts/FreeSans12pt7b.h"
#include "Fonts/FreeSans9pt7b.h"
// Includes
#include <ctime>
#include <algorithm>
#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", &ltm);
// create start and end event structs
struct tm event, event2;
time_t epoch = mktime(&ltm) + (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", &ltm2);
time_t epoch2 = mktime(&ltm2) + (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();
}