RGB LED Meeting Warning Light (Lotus Notes Edition)

At the end of my last post I mentioned trying to get the same set up working with my work Lotus Notes calendar.

After a little poking around with the Lotus Notes Java API here is the result:

package uk.me.hardill.notes;

/**
 * NextCalEntry
 * 
 * Sets the RGB values for Blink(1)/Digispark+RGB
 * according to the time to the next meeting in 
 * your Lotus Notes Calendar
 * 
 * This should be run with the JRE that ships with
 * Lotus notes as it has the required classes on 
 * classpath and have Lotus notes directory on 
 * library path e.g.
 * LD_LIBRARY_PATH=/opt/ibm/lotus/notes
 * /opt/ibm/lotus/notes/jvm/bin/java NextCalEntry -d
 */

import java.text.SimpleDateFormat;
import java.util.Date;

import lotus.domino.Database;
import lotus.domino.DateTime;
import lotus.domino.DbDirectory;
import lotus.domino.Document;
import lotus.domino.NotesFactory;
import lotus.domino.NotesThread;
import lotus.domino.Session;
import lotus.domino.View;
import lotus.domino.ViewEntry;
import lotus.domino.ViewEntryCollection;

public class NextCalEntry extends NotesThread {

   static SimpleDateFormat dateFormat = new SimpleDateFormat(
         "dd/MM/yy H:m:s z");

   static int rgb[] = { 0, 200, 0 };

   static int pollInterval = 300;
   static boolean mqtt = false;
   static String topic = "";
   static boolean digi = true;

   public static void main(String argv[]) {

      for (int i = 0; i < argv.length; i++) {
         if (argv[i].equals("-b")) {

         } else if (argv[i].equals("-d")) {

         } else if (argv[i].equals("-t")) {
            mqtt = true;
            topic = argv[++i];
         }
      }

      NextCalEntry nextCalEntry = new NextCalEntry();
      nextCalEntry.start();
   }

   public void runNotes() {
      try {
         Session s = NotesFactory.createSession();

         DbDirectory dir = s.getDbDirectory(null);
         Database db = dir.getFirstDatabase(DbDirectory.DATABASE);

         db = dir.openMailDatabase();

         if (db.isOpen() == false)
            db.open();

         db = dir.openMailDatabase();
         View calendarView = db.getView("($Calendar)");

         DateTime sdt = s.createDateTime("today");
         sdt.setNow();
         DateTime edt = s.createDateTime("today");
         edt.setNow();
         edt.adjustDay(+1);

         ViewEntryCollection vec = calendarView.getAllEntries();

         ViewEntry entry = vec.getFirstEntry();

         int offset = 3600;
         boolean poisonPill = false;
         while (entry != null) {
            Document caldoc = entry.getDocument();
            String sub = caldoc.getItemValueString("Subject");
            DateTime startDate = null;
            try {
               startDate = (DateTime) caldoc.getItemValueDateTimeArray(
                     "StartDate").firstElement();

            } catch (Exception e) {

            }

            if (startDate != null) {

               for (int i = 0; i < caldoc.getItemValueDateTimeArray(
                     "StartDateTime").size(); i++) {

                  int st = sdt.timeDifference((DateTime) caldoc
                        .getItemValueDateTimeArray("StartDateTime")
                        .get(i));
                  int en = edt.timeDifference((DateTime) caldoc
                        .getItemValueDateTimeArray("EndDateTime")
                        .get(i));

                  Date start = dateFormat.parse(caldoc
                        .getItemValueDateTimeArray("StartDateTime")
                        .get(i).toString());
                  Date end = dateFormat.parse(caldoc
                        .getItemValueDateTimeArray("EndDateTime")
                        .get(i).toString());
                  Date now = new Date();

                  if ((st <= 0) & (en >= 0)) {
                     if ((-1 * st) < offset) {
                        offset = (-1 * st);
                     }

                  } else if (now.after(start) && now.before(end)) {
                     offset = -1;
                     poisonPill = true;
                     break;
                  }
               }
            }
            if (poisonPill) {
               break;
            }
            entry = vec.getNextEntry();
         }

         if (offset > 0 && offset <= 300) {
            // red
            rgb[0] = 20;
            rgb[1] = 0;
            rgb[2] = 0;
         } else if (offset > 300 && offset <= 600) {
            // redish
            rgb[0] = 15;
            rgb[1] = 5;
            rgb[2] = 0;
         } else if (offset > 600 && offset <= 900) {
            // greenish/redish
            rgb[0] = 10;
            rgb[1] = 10;
            rgb[2] = 0;
         } else if (offset > 900 && offset <= 1200) {
            // greenish
            rgb[0] = 5;
            rgb[1] = 15;
            rgb[2] = 0;
         } else if (offset == -1) {
            // blue
            rgb[0] = 0;
            rgb[1] = 0;
            rgb[2] = 20;
         } else {
            // green
            rgb[0] = 0;
            rgb[1] = 20;
            rgb[2] = 0;
         }

         if (mqtt) {
            // TODO
            // connect to broker and publish
         } else {
            Runtime runtime = Runtime.getRuntime();
            String cmd[];
            if (digi) {
               System.out.println(rgb[0] + " " + rgb[1] + " " + rgb[2]);
               cmd = new String[4];
               cmd[0] = "DigiRGB.py";
               cmd[1] = rgb[0] + "";
               cmd[2] = rgb[1] + "";
               cmd[3] = rgb[2] + "";
            } else {
               System.out.println(rgb[0] + "," + rgb[1] + "," + rgb[2]);
               cmd = new String[3];
               cmd[0] = "blink1-tool";
               cmd[1] = "--rgb";
               cmd[2] = rgb[0] + "," + rgb[1] + "," + rgb[2];
            }
            Process proc = runtime.exec(cmd);
            proc.waitFor();
         }

      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

It is also available as gist here (not embedded as there seams to be CSS background colour issue)

Run this in a script every 5 mins either with a sleep loop or as a crontab entry similar to the ones described at the end of the previous post

RGB LED Meeting Warning Light

In my previous post I talked about the Digispark board and the RGB LED shield I had just received.

Digispark and RGB Shield

This afternoon in a spare 30mins I had while some tests ran I built my first little hack to use one of them. It is inspired by this really cool hack that got mentioned on twitter by Dave CJ while we were debating Blink(1) vs Digispark/RGB Shields. The idea of a strip of LEDs that represent the working day with different colours for free time and meetings is cool, but I only have 1 LED to work with, so I thought how about just showing how close to the next meeting I am.

The idea is that if the next entry in my calendar is more than 20mins away then the light is green, as the meeting start time gets closer the LED changes closer to red and finally during the meeting if glows blue.

I started out prototyping this against my google calendar as it’s pretty easy to get at the data, if you poke around in the settings page you can get hold of a URL that points to either a XML or ICAL version of the data.

I’m running this on my Raspberry Pi so python seamed like a good choice to run this up in.

#!/usr/bin/python

import sys
import pycurl
import StringIO
from icalendar import Calendar, Event
from datetime import datetime, timedelta

curl = pycurl.Curl()
curl.setopt(pycurl.URL,sys.argv[1])
body = StringIO.StringIO()
curl.setopt(pycurl.WRITEFUNCTION, body.write)
curl.perform()

cal = Calendar.from_ical(body.getvalue())
body.close()

now = datetime.now()

offset = 3600

for component in cal.walk():
  if component.name == "VEVENT":
    dtstart = component["dtstart"].to_ical()
    dtend = component["dtend"].to_ical()
    if "T" in dtstart:
      dt = datetime.strptime(dtstart, "%Y%m%dT%H%M%SZ")
      end = datetime.strptime(dtend, "%Y%m%dT%H%M%SZ")

      if dt > now:
        delta = dt - now
        if delta.days == 0:
          if delta.seconds < offset:
            offset = delta.seconds
      elif end > now and dt < now:
        offset = -1
        break

if offset > 0 and offset < 300:
  print "200 0 0"
elif offset >= 300 and offset < 600:
  print "150 50 0"
elif offset >= 600 and offset < 900:
  print "100 100 0"
elif offset >= 900 and offset < 1200:
  print "50 150 0"
elif offset == -1:
  print "0 0 200"
else:
  print "0 200 0"

This script outputs RGB values that can be fed directly to DigiBlink.py script like this in a cron job

*/5 * * * * DigiBlink.py `calColur.py <calendar URL>`

Or since I’ve set up a little script to control my DigiSpark via MQTT the following publishes the output of the script on the ‘digiblink’ topic on the local broker.


*/5 * * * * calColur.py <calendar URL> | mosquitto_pub -h localhost -t digiblink -l

This script should also work with the command line tool for the Blink(1)

I still need to tweak the colours for each step and next work out how to get the same feed out of the company calendaring system, but it’s a good start.

Budget Blink(1)

Production blink(1) units

About a month ago I spotted Kickstarter project for a device called a Blink(1). This was a little USB device that acts like Ambient Orb and it made by the same folk that make the BlinkM 3 colour LED board that is used in them. I had loads of ideas with what to do with one.

The project had already reached it’s funding target and it had actually delivered. The devices where available to order directly from ThinkM here. I was all set to buy one except for 1 thing, the price. While I don’t really have a problem with ThinkM wanting $30 each for them, the problem is not even with the fact that HMRC will want to charge me VAT (at 20% on anything imported with a price of more than £15) on this when it is imported the real issue is that which ever courier handles the UK end of the delivery will charge me at least £8 in “handling” fee for the privileged.

Digispark and RGB Shield

I put the idea to the back of my mind for a while with the hope that the price would come down or a UK supplier would be found. While waiting I came across another Kickstarter project for a really tiny Arduino called a Digispark from the Digistump team. As well as the digispark boards themselves they where doing a range of shield, one of which was a RGB LED shield. Being a miniature arduino means I should be able to add the odd sensor or two to the board which I can use to influence the colour shown by the LED.

And to top it off I could order 2 Digisparks and 2 RGB LED shields + shipping for $26 which comes in just under the import limit for VAT.

The package was waiting for me when I got back from a weekend away and I managed to grab 5 mins today at the office to use the soldering iron to put one of the shields together and add the headers to the first digispark.

I plugged it into my laptop and the standard blink sketch seamed to be running fine. I started to follow the instructions on the wiki to set up the development environment under Linux and I think I followed it all properly but I could not get the tools to upload a new sketch to the device. I have added a comment to a similar question on the forum. In the mean time I have managed to get it all to work on my Windows machine.

EDIT: Following a comment on the forum suggesting I try plugging the digispark into a powered hub to program it I have managed to get my second digispark programmed from Linux.

Along with the RGB shield there is some sample code that makes the digispark behave as a HID device and some python code that allows you to send it RGB values to set the colour shown much the same way the Blink(1) works. When I get 5 mins I will be doing the obvious MQTT hack to allow RGB values to be posted to a topic to set the colour then look at hooking it up to some of the data feeds available.