Friday, December 20, 2013

Displaying Calendar Events on Digital Signage Using a Raspberry Pi and Google Apps Script

In our office many of us are often out of the building, so we decided to hang a TV that would display our calendar events. The hardware consists of a 40" LED mounted on the wall, with a Raspberry Pi (with a Wi-Fi adapter) connected to the HDMI input.

To display the calendars, a Google Apps Script edits a Google Sites page, that is refreshed on the Raspberry Pi every 30 minutes. Google Apps Script is great at parsing Google Calendars, but unfortunately there is some incompatibility with our Exchange calendars, so I ended up using Yahoo!Pipes to parse the ICS files served by our Exchange server.

I'll write out some instructions for recreating what I've done, but your results may vary. Feel free to ask for help in the comments or on social media.

Start by creating yourself a Google Site that has no navigation elements or other clutter. Probably the easiest is to create one from this template I've shared.

Next, a Yahoo!Pipe to parse the ICS files. If you can subscribe to the calendars in Google Calendar then you don't need this because you can use the Calendar Service in Google Apps Script. I'll leave it to you to figure out how to do that, though. If you want to use Yahoo!Pipes to parse ICS files, check out the pipe I created.

Then a Google Apps Script to add events to the Google Site you created. If you're not familiar with Google Sites, there are many tutorials and code examples. I'll just paste my script code in here and hope you can make sense of it.

//Remember to set up a trigger set up to run every 30 minutes, or at whatever frequency you prefer
function runThis() {
  var page = SitesApp.getPageByUrl("https://sites.google.com/whatever-your-site-is-should-be-here");
  var dateToday = new Date();
  //format the date to print at the top of the webpage
  var dayToday = Utilities.formatDate(dateToday, Session.getTimeZone(), "EEEE, MMMM dd");
  //put the date at the top of the web page
  var pageContent = "<div style='font-size:larger'>Today is " + dayToday + "</div><br>";
  //add the calendar stuff by calling the appropriate functions and appending to the variable
  var pageContent = pageContent + parseCalendar("Your Name","#00FF00","http://your-link-to-an-online-ics-file.ics");
  var pageContent = pageContent + parseCalendar("Another Name","#FF0000","http://a-link-to-another-online-ics-file.ics");
  page.setHtmlContent(pageContent);
}

function parseCalendar(name,color,iCalUrl) {
  //declare and empty pageContent variable that we will fill with calendar entries
  var pageContent = "";
  //format the iCal URL for submission to Yahoo!Pipes
   var replaceColon = iCalUrl.replace(":","%3A");
   var replaceSlash = replaceColon.replace(/\//g,"%2F");
   var translatediCalUrl = replaceSlash.replace(/@/g,"%40");
   //replace spaces with + signs
   var translatedName = name.replace(" ","+");
   //concatenate the strings to make a URL
   var rssUrl = "http://pipes.yahoo.com/pipes/pipe.run?CalendarURL=" + translatediCalUrl + "&Name=" + translatedName + "&_id=9e11d02f251ade5c10a6f5501bfe181f&_render=rss"; 
  //fetch the RSS feed from that URL
  var rssContent = UrlFetchApp.fetch(rssUrl).getContentText();
  //parse the RSS feed that we just fetched
  var items = XmlService.parse(rssContent).getRootElement().getChild("channel").getChildren("item");
  //loop through the items we just parsed
  for (var i = 0; i < items.length; i++) {
    var item = items[i];
    var title = item.getChild("title").getText();
    // if there is a location, then get it from the "description" field
    if (item.getChild("description") != null) {
      var location = item.getChild("description").getText();
      var output = title + " at " + location;
      }
    // if there isn't a location, output just the title
    else {output = title};
    var pageContent = pageContent + "<div style='color:" + color + "'>" + output + "</div>\r";
    }
  return pageContent;
}
Finally, on the Raspberry Pi:
  1. Set up Debian Linux on the Pi:http://www.raspberrypi.org/downloads
  2. (optional): Force the Raspberry Pi to use HDMI output even if it doesn't detect a display there:
    1. in the terminal emulator type  sudo leafpad /boot/config.txt
    2. add the following lines to that file:
      1. hdmi_force_hotplug=1
      2. hdmi_drive=2
    3. remove (or comment out) any similar lines at the bottom of the file that may have been added by the NOOBS install process
    4. save and close the config.txt file
  3. Have the mouse cursor auto-hide using unclutter: in the terminal emulator type  sudo apt-get install unclutter
  4. Edit the autostart file: in the terminal emulator type  sudo leafpad /etc/xdg/lxsession/LXDE/autostart
  5. Disable screen sleeping and autostart the browser by adding the following lines to the file you just opened for editing (include the @ signs, but not the line numbers):
    1. @xset s off
    2. @set -dpms
    3. @xset s noblank
    4. @midori -e Fullscreen -i 1800 -a https://sites.google.com/whatever-your-site-is-should-be-here
    5. @unclutter -display :0.0 -idle 5
  6. Reboot the Raspberry Pi, and you're done.