Raspberry Pi & Physical Web: Getting Started

A couple of months ago I came across an interesting new project called: Physical Web. The tag line for the project "Walk up and use anything" describes it best I think. The idea is that as we create more and more connected devices ("Internet of Things" in marketing speak) we need a way of interacting with these devices and rather than having to install a separate app for each which would soon balloon out of control an open standard would be created to facilitate this.

So, when you walk up to a vending machine, parking meter or whatever at the moment the available options to you might include:

  • It's old, not connected and you end up pushing the germ covered buttons
  • There's a phone number you can call or send SMS message to get credit/use the service
  • There's a web address you can type in to your phone
  • There's a QR code that you can scan

Ranging from worst to best (at least in my opinion), granted, we can't really do anything about the first one. The second, third and fourth are all a bit fiddly and rely on the information being legible ie. it hasn't faded away, been vandalized or even worse; changed to something that looks legitimate but just takes your monies.

So rather, what if we could use our phone and select from a list of nearby devices that we can interact with? Let's do that!

In the last two posts we've setup a development environment & workflow and played with some basic electronics. This time we'll be using our already setup environment and our simple LED example as a starting point but adding the ability for the Pi to be discovered and controlled by anyone.

Goals

What we're hoping to accomplish here:

  • Setup the Pi as a Physical Web beacon
  • Broadcast information allowing user discover and "connect" to the Pi
  • Serve a mobile friendly page that allows switching the LED On and Off

Requirements

To play along you will need at minimum:

And then, whatever you would like to be able to control, the previous post contains a list of things required for controlling an LED.

I will be assuming that you have followed along or at least read the two previous posts, if not I recommend you do that now as the steps taken below rely on the setup and knowledge from previous posts.

Disclaimer

As you are enjoying your Raspberry Pi and following the instructions below do keep in mind that even though I have tried to be as careful as possible it is possible that the instructions are incorrect and your Raspberry Pi may become permanently damaged, I cannot accept responsibility for this and ask that you only proceed if you are willing to accept this risk.

Installing the Bluetooth drivers & libraries

Before you plug your Pi in, plug the USB bluetooth adapter to one of the Pi USB ports. Then plug it in.

Once your Pi has finished booting up, open up an SSH connection to it. The packages we will be using to interact with the Bluetooth adapter require the drivers to be installed so we need to install them.

We should first update the repositories and then install the drivers and supporting libraries with the following commands:

$ sudo apt-get update
$ sudo apt-get install bluetooth bluez-utils libbluetooth-dev

Do note that this might take a bit of time to complete.

Now unfortunately the underlying package Bleno we are using requires the application to be run with sudo or under root account. I'll be looking to see if I can find a nicer solution but for now we'll go with a dirty workaround and we'll run the application with sudo.

Check to see if you have any forever processes currently running:

$ forever list

If you receive a message stating No forever processes running, then excellent, if there are any processes you can stop them with command such as:

$ forever stop index.js

Provided your running script was index.js of course.

Next open up our post-receive git hook we created in the first article, such as:

$ vim /home/pi/testing_app.git/hooks/post-receive

add the word sudo at the beginning of lines 9 and 13. So change:

forever stop index.js  

to

sudo forever stop index.js  

and same for the line that reads: forever start index.js

Save the file and we're done on the Pi side.

Installing uri-beacon and ngrok packages

With the Pi setup we'll now install two new node packages: uri-beacon and ngrok.

The uri-beacon package is a really simple package to create a Bluetooth URI Beacon and was written specifically for the Physical Web project to make it as easy as possible to create an advertising beacon.

The ngrok package is a simple node wrapper for ngrok. ngrok is an amazing service that allows you to easily access your local machines using a special URL. It has a pay-what-you-want model so we can use it for free and no sign up is required! If you find ngrok useful I'd very much recommend signing up at ngrok.com and making a small contribution. Why this is required will become apparent later.

To install these two package in your local project folder run:

$ npm install uri-beacon --save
$ npm install ngrok --save

Note: The uri-beacon package might fail to install locally if you are running OSX or Windows, Linux users should be fine. If this happens add: -- force at the end of the line.

Creating the beacon and broadcasting our URL

To start broadcasting our URL with uri-beacon is easy we can just do:

var beacon = require('uri-beacon');  
beacon.advertise('http://some.url');  

The problem is that your Pi probably does not have a URL accessible outside of your network. This wouldn't be a problem as we're just playing locally but the Physical Web app call an external API to resolve information about the URL being broadcast, such as title and description so it can be displayed nicely in the app. For the external API to be able to read this information we need a public URL for the Pi.

This is where ngrok comes in.

Instead of advertising our local IP address we create an on-demand tunnel using ngrok and advertise that instead.

To do this add these two lines to the top of your index.js

var ngrok = require('ngrok');  
var beacon = require('uri-beacon');  

Then at the end of our index.js file after we've created our server add the following

ngrok.connect(80, function(err, url) {  
  beacon.advertise(url);
});

That's all we need to do to get our Pi to broadcast an accessible URL!

Serving a page

Next we need to serve a HTML page that contains data to display the beacon information as well as some controls to turn the LED On and Off.

We could be all ghetto and just write the document straight to response with something like this in the create server block:

  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write('<html>');
  res.write('<head>');
  ...
  res.end('</html>');

But eurgh ain't nobody got time for dat.

So instead we'll use a minimal templating library, handlebars to create the page from a layout.

Let's install the nodejs handlebars package

$ npm install handlebars --save

At the beginning of the index.js we'll require it along with the NodeJS file system package:

var handlebars = require('handlebars');  
var fs = require('fs');  

And then before we create our server, we'll read the layout and compile it

var layout = handlebars.compile(fs.readFileSync('./layout.html', 'utf8'));  

Note that I'm using the synchronous version of the asynchronous readFile method here to keep things simple.

Next we'll change the create server block to:

http.createServer(function (req, res) {  
  var command = url.parse(req.url).pathname.slice(1);

  res.writeHead(200, { 'Content-Type': 'text/html' });
  var data = {
    text: 'Switch Off',
    state: 'on',
    action: 'off'
  };

  if (command === 'on') {
      piblaster.setPwm(17, 1);
  } else {
    piblaster.setPwm(17, 0);
    data = {
        text: 'Switch On',
        state: 'off',
        action: 'on'
      };
  }

  res.end(layout(data));

}).listen(80);

The following things take place:

  • We read the last part of the URL and use it as our command, much like in the previous post
  • We set the correct content type so the page is treated as an HTML page
  • We create a data object that we will use together with our layout to render the correct text, links and classes on to the page
  • We set the LED to be either Off (0) or On (1) and update our data if need be
  • Using the data object we've built, we render the layout, and write the resulting HTML page to the response

This should all be pretty familiar from previous posts except maybe for the handlebars bit, they have excellent documentation and if you're not sure what is happening here I would recommend you have a quick read through as JS templating engines are mighty handy.

The core of the layout.html is this line:

<a href="{{action}}" class="{{state}}">{{text}}</a>  

This will render the link to turn the LED On and Off to the page with the correct URL, classes and display text.

Now you can write your own HTML or have a look at my example one in Github

The gist contains full listings for layout.html, index.js and package.json

Deploying and Testing

Before we start testing we need the Physical Web app. You can either download from the Google Play Store or the iOS App Store. Or if you would prefer you can build your own from the source code in the Physical Web Github repo.

Right it's that time again: let's deploy!

$ git push pi master

If you now launch the app you should see a screen like so
Screenshot of beacon

Clicking through should open a page displaying a button to turn the LED on

Switch on button on device

And yes it's ok to be jealous of my maaaad design skills...sigh

What now?

Same answer as last time: Go and do cool stuff with this! I'm hoping the Physical Web standard will be worked on, refined and adopted and will start being included in the popular mobile OSs removing the need to download the separate app. And if all this happens, there's no excuses for not having your new smart/connected/IoT/thingymajic support it.

I sincerely hope you found this helpful, educational and/or interesting. I would love to hear what you think, anything from essays criticising my writing to 'cool story bro' tweets. You can get in touch with me via twitter @efexen or via email [fxn at fxndev dot com]