Google Calendar Clock (ESP8266)

By our entrepreneur in residence, Sander Vonk

There is no better feeling than going to bed at night and not having to set an alarm

Note to self: when using ESP-IDF, use esp-idf-ssd1306 as the driver for the OLED display.

ESP8266 based alarm clock. This project gets the time and your next alarm in Google Calendar from the Internet and displays this information. The alarm will go off and the board will beep and vibrate.

alarm_clock_plexiglass1024

The software is written using the Arduino IDE with the ESP8266 board package.  The main components are time synchronization ….

The code can be found at

Remember to update the wireless settings (_wifi in alarm_clock.ino); install your Google script and update dstPath inGoogleCalEvent.cpp accordingly.

Libraries required

Google Script

We use a Google script to determine the next alarm time based on your Google calendar.  For instructions of how to enter the google scripts refer to ESP8266 reads Google Calendar.  The code below can be found at GitHub in the extra directory.

Since this writing, the ESP8266 support package switched from AxTLS to BearSSL. This code still uses the AxTLS variant.

/* Fetch first event of the day to set alarm clock
    * Platform: Google WebApp Script
    * (c) Copyright 2016, Coert Vonk, Sander Vonk
    */

function _alarmTime(event) {
    if (event == undefined ) {
    return undefined;
    }
    // https://code.google.com/p/google-apps-script-issues/issues/detail?id=4433
    var reminders = event.getPopupReminders();  // known issue: method getPopupReminders() doesn't return anything if there is only one reminder so try setting two reminders for the same timed
    if (reminders.length == 0) {
    reminder = 0;   
    }
    var longestReminder = 0;
    for (var ii=0; ii < reminders.length; ii++) {
    if (reminders&#91;ii&#93; > longestReminder) {
        longestReminder = reminders[ii];
    }
    }
    
    date = new Date(event.getStartTime().getTime()-longestReminder*60*1000);
    return date;
}

function _findFirstAlarm(events) {
    var firstAlarmTime = undefined;
    var firstAlarmIdx = undefined;
    
    for (var ii = 0; ii < events.length; ii++) {
    var event=events&#91;ii&#93;;
    switch(event.getMyStatus()) {
        case CalendarApp.GuestStatus.OWNER:
        case CalendarApp.GuestStatus.YES:
        case CalendarApp.GuestStatus.MAYBE:
            var alarmTime = _alarmTime(event);
            if (firstAlarmTime == undefined || alarmTime < firstAlarmTime) {
            firstAlarmIdx = ii;
            firstAlarmTime = alarmTime;
        }
        break;
        default:
        break;
    }
    }
    if (firstAlarmIdx == undefined) {
    return undefined;
    }
    return events&#91;firstAlarmIdx&#93;;
}

function doGet(e) {

    // open calendar

    var cal = CalendarApp.getCalendarById('your.e.mail@gmail.com');
    if (cal == undefined) {
    return ContentService.createTextOutput("no access to calendar");
    }

    // find the first event today that I'm participating in, and that is not an all day event.

    const now = new Date();
    var todayStart = new Date(); todayStart.setHours(0, 0, 0);  // start at midnight this day
    const oneday = 24*3600000; // &#91;msec&#93;
    const todayStop = new Date(todayStart.getTime() + oneday - 1);
    var eventsToday = cal.getEvents(todayStart, todayStop);
    var firstAlarmToday = _findFirstAlarm(eventsToday);
    
    // find the first event tomorrow that I'm participating in, and that is not an all day event.

    const tomorrowStart = new Date(todayStart.getTime() + oneday);
    const tomorrowStop = new Date(tomorrowStart.getTime() + oneday - 1);
    var eventsTomorrow = cal.getEvents(tomorrowStart, tomorrowStop);
    var firstAlarmTomorrow = _findFirstAlarm(eventsTomorrow);
    
    // select the alarm event

    const event = firstAlarmToday != undefined && _alarmTime(firstAlarmToday) > now ? firstAlarmToday : firstAlarmTomorrow;

    // print event details that should trigger alarm

    var str = '';
    if ( event != undefined ) {
    const date = event.getStartTime();
    const alarm = _alarmTime(event);
    const startMinutes = date.getHours()*60 + date.getMinutes();
    const alarmMinutes = alarm.getHours()*60 + alarm.getMinutes();
    
    str += alarmMinutes + '\n' +   // alarm [minutes since midnight]
        startMinutes + '\n' +   // start time of event in [minutes since midnight]
        event.getTitle() +'\n'; // event title
    
    }
    Logger.log("<REPLY>" + str + "</REPLY>");
    
    return ContentService.createTextOutput(str);
}

Google Calendar (github)

Schematic

The Huzzah Feather does not output enough current to supply the haptic motor and the buzzer so we used a transistor to amplify the current. Resistor (R2) allows the buzzer to discharge. The Diode (D1) before the haptic element provides a clear path for the induced current to flow. Both the haptic and buzzer are connected to 5 volts so they can produce more vibration and sound. Teh photo transistor (Q1) and resistor (R4) form a voltage divider that feeds the analog input (Adc). These circuits are built on the Feather Proto board.

The Oled screen is stacked on top of the Huzzah Feather and uses the input from I2C.

The project is powered with a micro USB cable or Optional Li-Poly batteries.

alarm-clock

Parts

Picture Link Price
adafruit_huzzah_feather ESP8266 Huzzah Feather $15.95
adafruit_feather_oled OLED Feather $14.95
FeatherWing Proto - Prototyping Add-on For All Feather Boards FeatherWing Proto $4.95
Feather Stacking Headers - 12-pin and 16-pin female headers Feather Stacking Headers $1.25
haptic Haptic $1.95
Buzzer 5V - Breadboard friendly Buzzer $0.95
lighttransistor Photo Transistor $0.95
Vishay / Beyschlag MBB02070C1001FC100 1k resistor 3 needed  $0.10 each $0.30
Yageo MFR-25FBF52-1K5 1.5k resistor $0.10
Fairchild Semiconductor PN2222ATFR NPN Transistor $0.19
Rectron 1N4148-T Diode $0.03
Optional batteries: (Not included in Total Price):
  • $5.95
  • $5.95
  • $6.95
  • $7.95
  • $9.95
  • $12.50
  • $14.95
Total Price: $41.38

Leave a Reply

Your email address will not be published. Required fields are marked *

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.