A personal Beacon with Puck.js

12th June 2017

Introduction

Puck.js is a fantastic Open Source JavaScript Beacon powered by Espruino, a JavaScript interpreter that has featured in many of my hardware projects. The tiny battery powered microcontroller has a multitude of sensors and features, most notably Bluetooth that allows it to become an iBeacon or Eddystone Beacon.

Recently I turned my Puck.js into a personal Beacon that can be configured to a multitude of environments. The first version was simple & worked well but having been hacked together in a just a couple of minutes there were many things that I thought could have been done better. Happy with the outcome of these tweaks I thought I’d share my code and findings.

Video Demo

Code

Espruino & JavaScript makes this all work is remarkably easy to code. Lets break down some of the components before moving onto the final code:

Turn on an LED

The constants LED1, LED2 & LED3 are setup for the Red, Green & Blue LEDs respectively. To turn on the Red LED simply run:

digitalWrite(LED1, true);

Broadcast a URL

Advertising a URL with Eddystone is easy. Simply required the module and then call advertise with your URL.

Note: Your URL should be no more than 17 characters long but you can use a URL shortener.

var ble = require('ble_eddystone');
ble.advertise('https://google.com');

To stop advertising your URL just run:

NRF.setAdvertising({});

Detecting button presses

var downTime = undefined;

function onUp() {
  const delta = Date().ms - downTime;
  console.log('Button down for ' + delta + 'ms');
}

function onDown() {
  setWatch(onUp, BTN, { edge: 'falling' });
}

setWatch(onDown, BTN, { repeat: true, edge: 'rising' });

Full code

Putting it all together we end up with the following source:

var ble = require("ble_eddystone");

// Here are your list of URLs. Change these to your own site & use a URL
// shortener to keep them small.
const urls = [
  { url: "https://google.com", led: LED1 },
  { url: "https://duckduckgo.com", led: LED3 },
  { url: "https://bing.com", led: LED2 },
];

let currentUrl = undefined;
let downTime = undefined;
let allowPush = true;

function setLed(led, duration, callback) {
  if (duration === undefined) {
    duration = 1500;
  }

  digitalWrite(led, true);

  setTimeout(function(){
    digitalWrite(led, false);
    if (callback) {
      callback();
    }
  }, duration);
}

function checkBattery() {
  if(Puck.getBatteryPercentage() <= 20) {
    alert(LED3);
  } else {
    allowPush = true;
  }
}

function toggleBeacon() {
  if (currentUrl >= 0) {
    currentUrl++;
  } else {
    currentUrl = 0;
  }

  if (urls[currentUrl]) {
    ble.advertise(urls[currentUrl].url);
    setLed(urls[currentUrl].led, undefined, afterToggleBeacon);
  } else {
    currentUrl = undefined;
    NRF.setAdvertising({});
    alert(LED1, 500, 1000);
  }
}

function afterToggleBeacon() {
  checkBattery();
}

function alert(led, onDuration, gap) {
  if (onDuration === undefined) {
    onDuration = 50;
  }

  if (gap === undefined) {
    gap = 100;
  }

  setLed(led, onDuration);
  setTimeout(function(){ setLed(led, onDuration); }, gap);
  setTimeout(function(){ setLed(led, onDuration); allowPush = true; }, gap * 2);
}

function onUp() {
  const delta = Date().ms - downTime;
  if (delta > 750 && delta < 1500) {
    toggleBeacon();
  } else {
    alert(LED1);
  }
}

function onDown() {
  if (allowPush) {
    allowPush = false;
    downTime = Date().ms;
    setWatch(onUp, BTN, { edge: 'falling', debounce: 50 });
  }
}

setWatch(onDown, BTN, { repeat: true, edge: 'rising', debounce: 50 });

// Saving will store the code on the Puck.js to allow it to be run after removing the battery.
save();

Analytics

I’m keen to see how this experiment goes and how effective it is. To measure this I’ll be using Google URL Shortener to provide me with basic analytics as well as shorten any URLs that I want to broadcast. I plan on having the beacon with me when I give talks and changing the URL to point to my slides and feedback questionnaires.

Google URL Shortener

Conclusion

I’ve really enjoyed the approachability of coding with Puck.js and Espruino. Having used other dedicated iBeacons in the past as well as Estimote beacons the Puck.js lends itself far better to being a versatile & programmable and packs much more functionality that I’m keen to explore further.