E-Ink Family Calendar Using ESP32

10,954

98

29

Introduction: E-Ink Family Calendar Using ESP32

About: I enjoy exploring new technologies and making practical things that fit into our everyday life as a family. Especially like making things with and for my 6 year old son :) Current focus is microcontrollers/se…

For many years I have been playing with the idea of breaking the barrier between physical and digital calendars - more specifically creating a nice looking e-ink calendar that can hang in our living room/kitchen. Now the idea has materialized in a very satisfying way, and I would love to share how I made it come true.

The calendar displays the first 9 events for all selected google calendars for a specific user. In my case I have selected that of my wife, myself and our shared family calendar. Besides the calendar, I have created a mini-weather display in the corner, showing an icon from OpenWeather Maps, as well as temperature and windspeed.

The project combine a 7.5 Waveshare e-ink screen, with an ESP32 microcontroller and a LIPO battery. It is packaged in 13x18 IKEA Ribba frame. Besides Arduino code for the microcontroller, I also had to create a google script to extract the calendar entries from google.

Credits to the ESP32 E-Ink Weather Station project on Git, from which I have learned a lot when coding the project.

Find the code for the project here: https://github.com/kristiantm/eink-family-calendar...

Thanks to Ibsendk for sparring and sanity checking of instructable and code.

Supplies

Total cost: ~100 EUR

As an alternative, Waveshare is offering a custom ESP32 unit with an E-Ink port (so you avoid wiring) - however this do not come with the battery plug of LOLIN32, so you have to power it via a 5V powerbank or wire your own battery directly to the 3V and GND connectors.

Step 1: Connect the Screen to the ESP32 Board

The screen comes with a connector cable, that you can connect directly to the board. However, I found that it took much too much space in the frame, so I decided to use my own wires to connect the boards.

Waveshare 7.5 ↔ LOLIN32
Vcc ↔ 3V
GND ↔ GND
DIN ↔ 14
CLK ↔13
CS ↔15
DC ↔27
RST ↔ 26
BUSY ↔25

To test the wiring, I recommend to download the GxEPD2 library and test it out. You can get it both via PlatformIO if you use Visual Studio Code or via Arduino Libraries.

To initialize the display with the right pins, use the following code in the example from the library:

GxEPD2_3C display(GxEPD2_750c_Z08(/*CS=*/ 15, /*DC=*/ 27, /*RST=*/ 26, /*BUSY=*/ 25));

When you make the example work (get demo-text/graphics on the display), you can move on to the next step.

Step 2: Getting Events From Google Calendar

Google has made it a bit hard to integrate with the calendar, so I had to do a workaround via Google Scripts, to make the calendar entries accessible.

To get access, create a new web-app on scripts.google.com, and paste the following code into it.

  1. Go to scripts.google.com and select new project
  2. Paste the below code into it
  3. Save it with a good name
  4. Click "Publish" and select "Anyone, even anonymous" as security setting
  5. Copy the link "https://script.google.com/macros/s/[UNIQUE CODE]/exec" as you need it in the project

Notice: This makes calendar entries from your calendar publicly available to anyone with the link. However, the link is unique and only you have it. I would love other ideas for how to integrate - but for now this works.

To test the script, paste the URL with the unique code into your browser. You should see a list of events separated by semi-colon. Do not move to the next step, before you have seen this.

<p>function doGet(e) {</p><p>  var calendars = CalendarApp.getAllCalendars();</p><p>  
  //var cal = CalendarApp.getCalendarsByName('NAME_OF_CALENDAR')[0]; // 0 is subcalendar ID, mostly "0"
  //var cal = CalendarApp.getDefaultCalendar();
  var calendars = CalendarApp.getAllCalendars();
  
  if (calendars == undefined) {
    Logger.log("No data");
    return ContentService.createTextOutput("no access to calendar hubba");
  }</p><p>  var calendars_selected = [];
  
  for (var ii = 0; ii < calendars.length; ii++) {
    if (calendars[ii].isSelected()) {
      calendars_selected.push(calendars[ii]);
      Logger.log(calendars[ii].getName());
    }
  }
  
  Logger.log("Old: " + calendars.length + " New: " + calendars_selected.length);</p><p>  const now = new Date();
  var start = new Date(); start.setHours(0, 0, 0);  // start at midnight
  const oneday = 24*3600000; // [msec]
  const stop = new Date(start.getTime() + 14 * oneday); //get appointments for the next 14 days
  
  //var events = cal.getEvents(start, stop); //pull start/stop time
  var events = mergeCalendarEvents(calendars_selected, start, stop); //pull start/stop time
  
  
  var str = '';
  for (var ii = 0; ii < events.length; ii++) {</p><p>    var event=events[ii];    
    var myStatus = event.getMyStatus();
    
    
    // define valid entryStatus to populate array
    switch(myStatus) {
      case CalendarApp.GuestStatus.OWNER:
      case CalendarApp.GuestStatus.YES:
      case CalendarApp.GuestStatus.NO:  
      case CalendarApp.GuestStatus.INVITED:
      case CalendarApp.GuestStatus.MAYBE:
      default:
        break;
    }
    
    // Show just every entry regardless of GuestStatus to also get events from shared calendars where you haven't set up the appointment on your own
    str += event.getStartTime() + ';' +
    //event.isAllDayEvent() + '\t' +
    //event.getPopupReminders()[0] + '\t' +
    event.getTitle() +';' + 
    event.isAllDayEvent() + ';';
  }
  
  return ContentService.createTextOutput(str);
}</p><p>function mergeCalendarEvents(calendars, startTime, endTime) {</p><p>  var params = { start:startTime, end:endTime, uniqueIds:[] };</p><p>  return calendars.map(toUniqueEvents_, params)
                  .reduce(toSingleArray_)
                  .sort(byStart_);
}</p><p>function toCalendars_(id) { return CalendarApp.getCalendarById(id); }</p><p>function toUniqueEvents_ (calendar) {
  return calendar.getEvents(this.start, this.end)
                 .filter(onlyUniqueEvents_, this.uniqueIds);
}</p><p>function onlyUniqueEvents_(event) {
  var eventId = event.getId();
  var uniqueEvent = this.indexOf(eventId) < 0;
  if(uniqueEvent) this.push(eventId);
  return uniqueEvent;
}</p><p>function toSingleArray_(a, b) { return a.concat(b) }</p><p>function byStart_(a, b) {
  return a.getStartTime().getTime() - b.getStartTime().getTime();
}</p>

Step 3: Enable Battery Level Measurement

To avoid the battery becoming completely discharged, you should enable the battery measurement gauge.

The code in the project, reads the current voltage of the battery, and displays a battery icon on the screen, showing either full, three quarters, half, quarter or empty. When empty the project goes into permanent deep-sleep until it is recharged again.

If you have a LOLIN D32 battery measurement is already build into the GPIO35 pin - so you just have to adjust the pin in the code "uint8_t batteryPin = 35".

If you have a normal ESP32, you need to insert a voltage divider between the battery and a selected analogue IO pin - to bring the battery's 3.7 voltage below the 3V that the board are able to measure.

In my setup, I have used a 30K and a 100K resistor setup, and read from pin 34.

It is a bit complicated to set up, but without it you might drain and damage your battery if you forget to recharge it.

Step 4: Configuring the Project

Now is the time to get the code ready for programming the ESP32 board.

To do this you can use either Visual Studio Code (with Platform IO) or Arduino.

For both platforms download the code, and place it in your project library.

Code here: https://github.com/kristiantm/eink-family-calendar-esp32

For Platform IO:

  1. Make sure you have Platformio installed and open the project folder as a workspace
  2. If configured correctly, PlatformIO) should fetch the required libraries itself. If not, you will have to go to PlatformIO / Libraries and install "GxEPD2" and "ArduinoJson"

For Arduino:

  1. Go to settings and paste "https://dl.espressif.com/dl/package_esp32_index.json" in "Additional Boards manager URL"
  2. Go to Tools/Boards/Boards Manager. Search for ESP32 and install the board package.
  3. Select the board WEMOS LOLIN32 (or the board you have bought)
  4. Go to Library manager and install "GxEPD2" and "ArduinoJson"

Now click compile, and hopefully you will not get errors. When ready connect the LOLIN32 board with a microusb cable to your computer, and program the board.

Boot up and connect to new wifi network:

When done, you need to configure the calendar over wifi. It should appear as a separate wifi network called "espressif32". Connect to this, and you will be redirected to a configuration page.

Configure Calendar:

Before connecting to your home wifi, you should configure the google-api, the open weather api, as well as your longitude and lattitude. You can also change these values later, but by doing it first, you do not have the trouble of finding the calendars new IP on your home network.

1) Register a free account on OpenWeatherMaps.com, get an API and paste it in.

2) Change your location to get local weather - google maps is your friend for getting latitude and longitude.

3) Find the webapp API (the [UNIQUE CODE]) from script.google (from the previous step), and add it to integrate with your calendar.

In the "Configure AP"

Set your home network SSID and password. The calendar will then connect here, and the Espressif hotspot will dissapear forever.

And it works:

If all is well, you will after 20 seconds see a refresh of the Waveshare board with your next 14 days of events, as well as your local weather. If this is not the case, try to do a serial connect to the COM port presented by the LOLIN32 board (you should be able to identify the port number via the device manager in Windows).

You can use a program like PuTTY to connect to the serial port, and observe where the chain is breaking. Also you can use this to find the calendar IP adress if you need to change any settings.

The calendar will be on the wifi for 5 minutes the first time the calendar boots, after which it will start its 24 hour cycle of refreshing at 5 in the morning.

Step 5: Package Your Family Calendar

Now all you have to do, is package the calendar nicely in your new IKEA frame. The Ribba frame is perfect for IOT projects like this, as it has a big closed room between the screen and the back-plate.

First put the display on top of the white Passepartout, and fiddle it a bit fort and back until you are satisfied.

Then place the paper that came with the frame on top of the screen, but lead the screen connector slip through at the side of the frame. Fix this with the white plastic inner frame - using the broad side to apply gentle pressure on the screen.

Then glue the e-paper connecting board, the LOLIN board and the battery to the paper in a way where they hang naturally given their connections.

When you click the battery in, the board will power up and refresh the screen. Use this as an opportunity to do a final adjustment to the placement of the display.

Now put on and secure the back-cover of the frame. Consider to add a short USB cable for powering the frame (cut a hole in the back cover as demonstrated). With a recharge cycle of 2-3 months, I decided that removing the back-cover is ok for me.

You are now the proud owner, of your very own E-Ink Family Calendar.

Be the First to Share

    Recommendations

    • Backyard Contest

      Backyard Contest
    • Fruit and Veggies Speed Challenge

      Fruit and Veggies Speed Challenge
    • DIY Summer Camp Contest

      DIY Summer Camp Contest

    29 Comments

    0
    Dolby dude
    Dolby dude

    3 months ago

    Kristian, a great project.

    Once I worked out your WiFi access point upgrade using autoconnect (connecting to the esp32ap SSID with the default autoconnect 'passpass' password), I have got your setup partially working.

    I'm having an issue with getting the calendar information in reliably. Initially the test for whether there was an internet connection using WiFiclientsecure failed (i.e. client.connect("script.google.com", 443) did not return true), however I thought it might be due to https not having SSL certificates etc. - and so I inserted the command "client.setInsecure();" to ignore this, and this then worked and proceeded to download the weather data and activate the google script.

    However whilst the weather icons display correctly, the google script only sometimes works. The code does seem to pick up the redirection correctly, but most of the time does not correctly return the string of text the calendar requires (the string is length zero). Any thoughts on what might be wrong?

    Simon

    0
    kristiantm
    kristiantm

    Reply 3 months ago

    Hi Simon

    I have fixed the connection issue now (see new code on github) - switched from manual http redirection to using HTTPRequest (which has been fixed in the new 3.2.0 version of the ESP32 platform).

    Let me know if it also works in your end.

    Best regards,
    Kristian

    0
    JakobFP
    JakobFP

    Reply 10 days ago

    Thank you for this update. I abandoned the project because of this intermittent error, but now it is up and running again.

    0
    Dolby dude
    Dolby dude

    Reply 3 months ago

    Kristian, that seems to have now worked.
    I had an initial issue with the Weather Data collection where the JSON serialisation returned "No Memory" (8 or 9 out of 10 times run). I didn't investigate much further as I experimented putting the weather data collection function ahead of the google calendar part to see if there was still a memory issue with collecting the weather data - and the No memory issue went away just be reordering the code - not sure why but it no longer matters!

    I also had another tweak I needed to make - the code wasn't getting a battery readout, might have been a coincidence but the simple fact of obtaining the pin read level and printing it to serial seems to have made it work.

    Now working great. Now looking to see how long the battery lasts - I modified the code to update a couple of times in the day as well as first thing each morning, but hopefully it doesn't drain the battery too fast to be a nuisance.

    Thanks again for sharing a great project that made it really accessible for someone like me that hasn't done anything like this before - and only ever fairly basic coding at school a long time ago. My family is very impressed !.

    Simon
    ps - for others looking at these comments, I note that the WiFi access point default password maybe 12345678 for the initial setup of the WiFi router and other parameters.

    0
    kristiantm
    kristiantm

    Reply 3 months ago

    Hi simon

    Have been fighting a bit with the reliability of google script myself recently. Tried to do a loop with five retries, which sometimes work, but is not a super solution. It worked great a year ago :/

    Experiment and let me know if you find a good workaround. I will too :)

    Best regards,
    Kristian

    0
    thoughttwice
    thoughttwice

    Question 6 months ago

    Hi Kristian,

    Very nice project! After reading above, I ordered this screen:
    https://www.tinytronics.nl/shop/nl/display/e-ink/w...
    For other project I already had some ESP32 DOIT Devkit V1 laying around.

    I followed your instructions and I managed to get the standard message Happy x-day and the weather icons. Furthermore I can see in the output that the calendar data is succesfully loaded, but The rest of screen stays empty.

    Any idea?

    Thanks in advance!

    Kinds Regards,
    Bart

    20210124_211217.jpg
    0
    kristiantm
    kristiantm

    Answer 6 months ago

    Hi.

    Am happy you have had fun building it - had a blast myself designing it during the first corona lockdown a year ago :)

    I have added wifi configuration since I made the instructable (sorry for not updating). You have to connect to the calendar, add your home wifi info, and add your personal url for the google script. Then it should work.

    Best regards, Kristian

    0
    JakobFP
    JakobFP

    11 months ago

    Hi Kristian.

    Nice project. Thank you for the instructables. This is a great project so I made one myself but struggle a bit to get unicode characters to work since I need æ,ø and å. I can see that you might need that as well :D Did you ever try to get unicode / UTF8 implemented?

    Best regards Jakob.

    0
    kristiantm
    kristiantm

    Reply 11 months ago

    Hi Jacob

    Am happy you like the project.

    Have not yet played with Unicode, so if you find a solution please share, and I will be happy to include it in the code.

    Kristian

    0
    JakobFP
    JakobFP

    Reply 11 months ago

    Hi Kristian.
    I got unicode to work with a fork of the Adafruit-GFX-Library from idea--list - . https://github.com/idea--list/Adafruit-GFX-Library

    You can download the library here for the next 15 days. https://easyupload.io/aflaxe This download also includes the new unicode fonts called FreeSerifXpt8b and FreeSerifBoldXpt8b since they add unicode charaters from 0x80 to 0xFF. They were missing in the default ones.

    Adding unicode charaters uses a bit more memory but it can still fit in the esp32.

    Remember to add display.utf8(true); to your code.

    The fork from github is missing the below update in gfxfont.h so you have to change it manually. I have already included it in the easyupload zip-file.

    uint16_t first; ///< ASCII extents (first char)
    uint16_t last; ///< ASCII extents (last char)

    Best regards Jakob.

    0
    ohnoflowno
    ohnoflowno

    Reply 8 months ago

    Hi Jacob,

    Could you maybe upload the library again, I cant seem to find the Danish letters it in the adafruit library.

    Best regards
    Mads

    0
    JakobFP
    JakobFP

    Reply 7 months ago

    Hi Mads
    I had to create the unicode letters myself since they were not included in the adafruit fork.
    I have uploaded the library here: https://easyupload.io/hl05yj

    0
    ohnoflowno
    ohnoflowno

    Reply 7 months ago

    Hi Jacob,

    That was why i couldnt find it then. Does the library you uploaded include the fonts FreeSerifXpt8b and FreeSerifBoldXpt8b you mentioned in the post above? I cannot seem to find them in the folder?

    Thanks,
    Mads

    0
    JakobFP
    JakobFP

    Reply 7 months ago

    Hi Mads
    They can be found in the fonts folder.
    FreeSerifBold9pt8b.h
    FreeSerifBold12pt8b.h
    FreeSerifBold18pt8b.h
    FreeSerifBold24pt8b.h
    FreeSerif9pt8b.h
    FreeSerif12pt8b.h
    FreeSerif18pt8b.h
    FreeSerif24pt8b.h

    Best regards
    Jakob.

    0
    pierre.kirchhofer
    pierre.kirchhofer

    Question 8 months ago on Step 1

    Can I use the same Connectors you describe for the D32 Pro Board for the waveshare epaper 7.5 HUB display?

    0
    kristiantm
    kristiantm

    Answer 7 months ago

    Hi Pierre

    Sorry for the late reply. It should work, but doing it again I probably would use the standard connectors for MOSI, SS etc

    I commented them out in the code as far as I remember.

    Best regards, Kristian

    0
    pierre.kirchhofer
    pierre.kirchhofer

    Reply 7 months ago

    Thanks Kristian for your answer!
    I will have a look an try again...:-)

    All the Best, Pierre

    0
    andjnewman
    andjnewman

    Question 11 months ago

    Hello, I've wanted to build something like this for a while, I thought the hardware would be the hard part but I was wrong...

    I have...
    Assembled the hardware
    Setup the google code
    Edited the configuration file in Visual Studio (which helpfully asked me to install PlatformIO).

    But now I have no idea how to get the code from Visual Studio on my MacBook onto the device I've built.

    I've never done a project like this before so all suggestions/help welcome!
    Thanks :-)

    0
    kristiantm
    kristiantm

    Answer 7 months ago

    Hi

    Sorry for the late reply - busy time at work recentlt. You have to install the platformIO extension to VS code. Then you can build and upload it directly via an microusb cable.

    Best regards, Kristian

    0
    daoduccuong1.hust
    daoduccuong1.hust

    1 year ago

    Hi Kristian,
    This is a pretty project. I have a set-up at home and trying to do again follow your instruction. Can you give more detail instruction in Step 3?. After installing "GxEPD2" and "ArduinoJson", I dont know how to do next.