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

515 lines
14 KiB
Arduino
Raw Normal View History

2020-08-03 08:45:50 +02:00
/*
3-Google_calendar_example for e-radionica.com Inkplate 6
For this example you will need only USB cable and Inkplate 6.
Select "Inkplate 6(ESP32)" from Tools -> Board menu.
Don't have "Inkplate 6(ESP32)" option? Follow our tutorial and add it:
https://e-radionica.com/en/blog/add-inkplate-6-to-arduino-ide/
This project shows you how Inkplate 6 can be used to display
events in your Google Calendar using their provided API
For this to work you need to change your timezone, wifi credentials and your private calendar url
which you can find following these steps:
1. Open your google calendar
2. Click the 3 menu dots of the calendar you want to access at the bottom of left hand side
2020-08-03 08:45:50 +02:00
3. Click 'Settings and sharing'
4. Navigate to 'Integrate Calendar'
2020-08-03 08:45:50 +02:00
5. Take the 'Secret address in iCal format'
(https://support.google.com/calendar/thread/2408874?hl=en)
Want to learn more about Inkplate? Visit www.inkplate.io
Looking to get support? Write on our forums: http://forum.e-radionica.com/en/
3 August 2020 by e-radionica.com
*/
//Include Inkplate library to the sketch
2020-07-31 15:31:48 +02:00
#include "Inkplate.h"
//Including fonts
2020-07-31 15:31:48 +02:00
#include "Fonts/FreeSans12pt7b.h"
#include "Fonts/FreeSans9pt7b.h"
//Includes
2020-07-31 15:31:48 +02:00
#include <ctime>
#include <algorithm>
#include "Network.h"
//CHANGE HERE ---------------
2020-08-03 08:45:50 +02:00
char *ssid = "";
char *pass = "";
char *calendarURL = "";
2020-07-31 15:31:48 +02:00
int timeZone = 2;
//Set to 3 to flip the screen 180 degrees
#define ROTATION 1
2020-08-03 08:45:50 +02:00
//---------------------------
//Delay between API calls
2020-07-31 15:31:48 +02:00
#define DELAY_MS 5000
//Variables to keep count of when to get new data, and when to just update time
2020-07-31 15:31:48 +02:00
int refreshes = 0;
const int refreshesToGet = 10;
//Initiate out Inkplate object
2020-07-31 15:31:48 +02:00
Inkplate display(INKPLATE_3BIT);
//Our networking functions, see Network.cpp for info
2020-07-31 15:31:48 +02:00
Network network;
//Variables for time and raw event info
2020-07-31 15:31:48 +02:00
char date[64];
char data[32000];
//Struct for storing calender event info
2020-07-31 15:31:48 +02:00
struct entry
{
char name[128];
char time[128];
char location[128];
int day;
int timeStamp;
};
//Here we store calendar entries
2020-07-31 15:31:48 +02:00
int entriesNum = 0;
entry entries[128];
//All our functions declared below setup and loop
void drawInfo();
void drawTime();
void drawGrid();
void getToFrom(char *dst, char *from, char *to, int *day, int *timeStamp);
bool drawEvent(entry *event, int day, int beginY, int maxHeigth, int *heigthNeeded);
int cmp(const void *a, const void *b);
void drawData();
void setup()
{
Serial.begin(115200);
//Initial display settings
display.begin();
display.clearDisplay();
display.clean();
display.setRotation(ROTATION);
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();
}
//Function for drawing calendar info
2020-07-31 15:31:48 +02:00
void drawInfo()
{
//Setting font and color
2020-07-31 15:31:48 +02:00
display.setTextColor(0, 7);
display.setFont(&FreeSans12pt7b);
display.setTextSize(1);
display.setCursor(20, 20);
//Find email in raw data
2020-07-31 15:31:48 +02:00
char temp[64];
char *start = strstr(data, "X-WR-CALNAME:");
//If not found return
2020-07-31 15:31:48 +02:00
if (!start)
return;
//Find where it ends
2020-07-31 15:31:48 +02:00
start += 13;
char *end = strchr(start, '\n');
strncpy(temp, start, end - start - 1);
temp[end - start - 1] = 0;
//Print it
2020-07-31 15:31:48 +02:00
display.println(temp);
}
//Drawing what time it is
2020-07-31 15:31:48 +02:00
void drawTime()
{
//Initial text settings
2020-07-31 15:31:48 +02:00
display.setTextColor(0, 7);
display.setFont(&FreeSans12pt7b);
display.setTextSize(1);
display.setCursor(410, 20);
2020-07-31 15:31:48 +02:00
//Our function to get time
2020-07-31 15:31:48 +02:00
network.getTime(date);
int t = date[16];
date[16] = 0;
display.println(date);
date[16] = t;
}
//Draw lines in which to put events
2020-07-31 15:31:48 +02:00
void drawGrid()
{
//upper left and low right coordinates
2020-07-31 15:31:48 +02:00
int x1 = 3, y1 = 30;
int x2 = 600 - 3, y2 = 798;
//header size, for day info
2020-07-31 15:31:48 +02:00
int header = 30;
//Columns and rows
2020-07-31 15:31:48 +02:00
int n = 1, m = 3;
//Line drawing
display.drawThickLine(x1, y1 + header, x2, y1 + header, 0, 2.0);
2020-07-31 15:31:48 +02:00
for (int i = 0; i < n + 1; ++i)
{
display.drawThickLine(x1, (int)((float)y1 + (float)i * (float)(y2 - y1) / (float)n), x2, (int)((float)y1 + (float)i * (float)(y2 - y1) / (float)n), 0, 2.0);
2020-07-31 15:31:48 +02:00
}
for (int i = 0; i < m + 1; ++i)
{
display.drawThickLine((int)((float)x1 + (float)i * (float)(x2 - x1) / (float)m), y1, (int)((float)x1 + (float)i * (float)(x2 - x1) / (float)m), y2, 0, 2.0);
2020-07-31 15:31:48 +02:00
display.setFont(&FreeSans9pt7b);
//Display day info using time offset
2020-07-31 15:31:48 +02:00
char temp[64];
network.getTime(temp, i * 3600L * 24);
temp[10] = 0;
//calculate where to put text and print it
display.setCursor(40 + (int)((float)x1 + (float)i * (float)(x2 - x1) / (float)m) + 15, y1 + header - 6);
2020-07-31 15:31:48 +02:00
display.println(temp);
}
}
//Format event times, example 13:00 to 14:00
2020-07-31 15:31:48 +02:00
void getToFrom(char *dst, char *from, char *to, int *day, int *timeStamp)
{
//ANSI C time struct
2020-07-31 15:31:48 +02:00
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
2020-07-31 15:31:48 +02:00
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
2020-07-31 15:31:48 +02:00
strptime(temp, "%Y-%m-%dT%H-%M-%SZ", &ltm);
//create start and end event structs
2020-07-31 15:31:48 +02:00
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
2020-07-31 15:31:48 +02:00
//https://github.com/esp8266/Arduino/issues/5141, quickfix
2020-07-31 15:31:48 +02:00
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
2020-07-31 15:31:48 +02:00
network.getTime(day0, 0);
network.getTime(day1, 24 * 3600);
network.getTime(day2, 48 * 3600);
*timeStamp = epoch;
//Getting the time from our function in Network.cpp
2020-07-31 15:31:48 +02:00
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
2020-07-31 15:31:48 +02:00
*day = -1;
}
//Function to draw event
2020-07-31 15:31:48 +02:00
bool drawEvent(entry *event, int day, int beginY, int maxHeigth, int *heigthNeeded)
{
//Upper left coordintes
2020-07-31 15:31:48 +02:00
int x1 = 3 + 4 + (594 / 3) * day;
int y1 = beginY + 3;
//Setting text font
2020-07-31 15:31:48 +02:00
display.setFont(&FreeSans12pt7b);
//Some temporary variables
2020-07-31 15:31:48 +02:00
int n = 0;
char line[128];
//Insert line brakes into setTextColor
2020-07-31 15:31:48 +02:00
int lastSpace = -100;
display.setCursor(x1 + 5, beginY + 26);
2020-07-31 15:31:48 +02:00
for (int i = 0; i < min((size_t)64, strlen(event->name)); ++i)
{
//Copy name letter by letter and check if it overflows space given
2020-07-31 15:31:48 +02:00
line[n] = event->name[i];
if (line[n] == ' ')
lastSpace = n;
line[++n] = 0;
int16_t xt1, yt1;
uint16_t w, h;
//Gets text bounds
2020-07-31 15:31:48 +02:00
display.getTextBounds(line, 0, 0, &xt1, &yt1, &w, &h);
//Char out of bounds, put in next line
if (w > 590 / 3 - 30)
2020-07-31 15:31:48 +02:00
{
//if there was a space 5 chars before, break line there
2020-07-31 15:31:48 +02:00
if (n - lastSpace < 5)
{
i -= n - lastSpace - 1;
line[lastSpace] = 0;
}
//Print text line
display.setCursor(x1 + 5, display.getCursorY());
2020-07-31 15:31:48 +02:00
display.println(line);
//Clears line (null termination on first charachter)
2020-07-31 15:31:48 +02:00
line[0] = 0;
n = 0;
}
}
//display last line
display.setCursor(x1 + 5, display.getCursorY());
2020-07-31 15:31:48 +02:00
display.println(line);
//Set cursor on same y but change x
display.setCursor(x1 + 3, display.getCursorY());
2020-07-31 15:31:48 +02:00
display.setFont(&FreeSans9pt7b);
//Print time
//also, if theres a location print it
2020-07-31 15:31:48 +02:00
if (strlen(event->location) != 1)
{
display.println(event->time);
display.setCursor(x1 + 5, display.getCursorY());
char line[128] = {0};
for (int i = 0; i < strlen(event->location); ++i)
{
line[i] = event->location[i];
line[i + 1] = 0;
int16_t xt1, yt1;
uint16_t w, h;
//Gets text bounds
display.getTextBounds(line, 0, 0, &xt1, &yt1, &w, &h);
if (w > (594 / 3))
{
for (int j = i - 1; j > max(-1, i - 4); --j)
line[j] = '.';
line[i] = 0;
}
}
display.print(line);
2020-07-31 15:31:48 +02:00
}
else
{
display.print(event->time);
}
int bx1 = x1 + 2;
int by1 = y1;
int bx2 = x1 + 590 / 3 - 7;
int by2 = display.getCursorY() + 7;
//Draw event rect bounds
display.drawThickLine(bx1, by1, bx1, by2, 0, 2.0);
display.drawThickLine(bx1, by2, bx2, by2, 0, 2.0);
display.drawThickLine(bx2, by2, bx2, by1, 0, 2.0);
display.drawThickLine(bx2, by1, bx1, by1, 0, 2.0);
2020-07-31 15:31:48 +02:00
//Set how high is the event
*heigthNeeded = display.getCursorY() + 12 - y1;
2020-07-31 15:31:48 +02:00
//Return is it overflowing
2020-07-31 15:31:48 +02:00
return display.getCursorY() < maxHeigth - 5;
}
//Struct event comparison function, by timestamp, used for qsort later on
2020-07-31 15:31:48 +02:00
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
2020-07-31 15:31:48 +02:00
void drawData()
{
long i = 0;
long n = strlen(data);
//reset count
2020-07-31 15:31:48 +02:00
entriesNum = 0;
//Search raw data for events
2020-07-31 15:31:48 +02:00
while (i < n && strstr(data + i, "BEGIN:VEVENT"))
{
//Find next event start and end
2020-07-31 15:31:48 +02:00
i = strstr(data + i, "BEGIN:VEVENT") - data + 12;
char *end = strstr(data + i, "END:VEVENT");
//Find all relevant event data
2020-07-31 15:31:48 +02:00
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
2020-07-31 15:31:48 +02:00
qsort(entries,
entriesNum,
sizeof(entry),
cmp);
//Events displayed and overflown counters
2020-07-31 15:31:48 +02:00
int columns[3] = {0};
bool clogged[3] = {0};
int cloggedCount[3] = {0};
//Displaying events one by one
2020-07-31 15:31:48 +02:00
for (int i = 0; i < entriesNum; ++i)
{
//If column overflowed just add event to not shown
2020-07-31 15:31:48 +02:00
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
2020-07-31 15:31:48 +02:00
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
2020-07-31 15:31:48 +02:00
if (!s)
{
++cloggedCount[entries[i].day];
clogged[entries[i].day] = 1;
}
}
//Display not shown events info
2020-07-31 15:31:48 +02:00
for (int i = 0; i < 3; ++i)
{
if (clogged[i])
{
//Draw notification showing that there are more events than drawn ones
2020-07-31 15:31:48 +02:00
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");
}
}
}