This is the fourth in a series of Bluetooth™ beacon-related posts by me (@jcmrim). You can find the three previous posts here, here and here if you haven’t already read them and the subject of beacons is new to you.
In this article, I wanted to change the underlying technology slightly. All of the previous examples have been written as BlackBerry 10 Cascades Native applications. I wanted to explore how to approach an application that used Beacon technology yet was written in WebWorks / Cordova.
The vehicle I used to tackle this was an existing Cascades Beacon Application that I’ve written and re-cast in terms of WebWorks. The application I chose was:
(The source code for this can be found here.)
Why this application? Well, it splits nicely between a Bluetooth Low Energy native component written as a headless application and a user-facing GUI component written in Cascades. The idea was to replace the Cascades GUI part by a WebWorks application and retain the headless service unchanged.
I don’t intend to describe the details of how Bluetooth Low Energy APIs are used since the new WebWorks variant of the original Cascades application is functionally identical and simply reuses the same headless service. So this would be a good time for you, dear reader, to re-acquaint yourself with the original application: Wake Me By Beacon! Beacon Technology in a BlackBerry 10 Headless App.
By replacing the GUI Cascades application with a WebWorks application, I had to think about which aspects of the interaction with the headless service might be impacted. If there was no impact, this would be good. If there was an impact, this would be a good learning point to document for future applications based on the same design.
In the original Cascades variant of the application, the interfaces between the GUI application and the headless service can be enumerated as follows:
- The GUI headless service sends Invocation Requests to the headless service to instruct it to perform tasks like:
- Starting the Bluetooth Low Energy scanning process.
- Stopping the Bluetooth Low Energy scanning process.
- Stopping the headless service.
- Starting the headless service. (This one is handled by the Invocation Framework, which launches the headless service application if it’s not running.)
- The headless service uses the Hub to send notifications that can be used to communicate information to the GUI by launching it if it isn’t already running.
- The headless service acts as a QLocalSocket (aka Unix Domain Sockets) server, and the GUI connects to the headless service. This is used to:
- Enable the GUI and headless service to be aware of whether the other application component (headless or GUI is running).
- Send status information between the GUI and headless service such as beacon discovery events.
So let’s look at each of those interfaces and, in turn, see whether any changes were required to be made to the design of the application. I’m not going to dwell on any of the HTML5 or CSS GUI aspects of this application since these topics are covered very well elsewhere.
The Invocation Interface
I found some side effects when passing data through an Invocation Request when using WebWorks as compared to Cascades/Native. It was highlighted by one of the requests the GUI sends to the headless service to establish the parameters of a Bluetooth Low Energy scan request.
In essence, a Bluetooth MAC address (like “AB:1D:65:EF:00:24”or “00:00:00:00:00:00” for a wild-card address) is passed in the data attribute of an Invoke Request. In the Cascades case I did it like this, passing the address directly in the data attribute:
Unfortunately, this didn’t work in the WebWorks case. What happens is that the “:” characters seem to cause problems, and in reading the documentation, there are limitations on the characters that can be passed in this field. I addressed this by changing the way I passed data through this attribute by encoding it in JSON format. The result was a better-behaved string. This is what it looks like in WebWorks:
By consistently using JSON encoding for all data passed through the Invocation Interface, I was able to get this interface between the WebWorks GUI and the headless service working very nicely.
Let’s look at the interaction through the Hub next.
Interaction Through the BlackBerry Hub
This interface was easy to get working correctly with very few changes, using the lesson learned in terms of the consistent use of JSON in all Invocation Requests. The only issue I stumbled upon was that, for the WebWorks GUI application to be able to receive Invocation Requests initiated through the Hub, it was necessary to ensure that both the “com.blackberry.invoked” Cordova and the “com.blackberry.invoke” plugins were installed:
I hadn’t realized that the “com.blackberry.invoked” plugin is not added to a new WebWorks headless application project by default, whereas the “com.blackberry.invoke” plugin is already in the template.
Having made these small changes, Notifications through the Hub worked very nicely. Suitably encouraged by how simple it had been to alter my original design decisions in these two cases to a form that would work with both WebWorks and Cascades, I moved on to the question of the QLocalSocket interface I’d used in the Cascades version of the application.
The QLocalSocket Interface Between the GUI and Headless Server
This is where things became a little more interesting, and I began to realize that my original choice to use QLocalSocket had probably been a poor one for a number of reasons. Here were the issues I came across:
- There is no API in WebWorks corresponding to QLocalSocket in a native application, so I had to look for an alternative.
- I fell back to looking at ordinary TCP socket(s), which has a nice Qt API called QTcpSocket on the native side.
- I was stymied! On the other hand, I told myself that WebSockets couldn’t be all that difficult to implement on the native headless server side. In fact, that turned out to be the case.
So, having worked out that WebWorks uses version “13” of the WebSocket specification, and armed with the IETF document (http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-13), I set to work. It turned out to be very easy. I approached this by changing the implementation of the SocketListener class I used in the Cascades version of the application to establish a WebSocket connection rather than a QLocalSocket connection, leaving the interfaces to this class unchanged.
WebSockets essentially work by being able to “Upgrade” an existing HTTP (or HTTPS) TCP connection to a WebSocket connection, which then uses a simple framing protocol to allow a bi-directional communication path to be established between the two ends.
So when the WebWorks GUI attempts to open a WebSocket to the headless service using the URL “ws://localhost:14354” (the port number of 14354 is just my arbitrary choice) as shown here:
… the headless server receives an HTTP GET request much like shown below. The important point is the key exchange that needs to occur by taking the “Sec-WebSocket-Key:” value and constructing a response.
The response key is simply generated using the QCryptographicHash API as shown here:
Similarly, the message that should be returned is simply an HTTP response built like this:
And, voilà, we’ve established a WebSocket connection with the GUI. Subsequent messages received from the WebWorks GUI will be WebSocket messages wrapped in simple WebSocket frames. Here’s what one looks like:
Essentially, there is a simple header with an “opCode” indicating whether the message is data (UTF-8 or binary) or a control message; an indication (FIN) of whether the message is the last in a sequence; the message payload length; and some other indications that are used for client-to-server messages whereby the message payload is obfuscated by a mask. You can look up the details in the specification yourself, but the protocol is very light-weight, and it’s fairly easy to parse and create such messages.
So, on the WebWorks GUI application side, WebSocket messages are received from the headless server in this fashion:
This shows how I’m toggling the state of buttons dependent on receipt of the JSON encoded status messages received from the headless service. In the Cascades code sample, this was performed in QML. In WebWorks, it’s just a case of changing the DOM objects’ attributes.
If you want to see the details, just take a look at the code.
For the purpose of comparison, here is a screenshot of the original Cascades application on the left and the WebWorks version of the application on the right, both using the same underlying headless service to detect Beacon Events.
As you can see, I can’t claim to be an artistic User Interface designer, so I’ll leave that to others. However, this exercise has teased out a couple of basic design principles that are worth following when you work with headless service applications that could communicate with either Cascades or WebWorks front-end GUI applications.
- Consider using JSON encoding whenever possible to allow consistent behavior when you’re dealing with a native or a WebWorks application and using the Invocation Framework.
- Think about using WebSockets when you need to communicate between a headless service and a GUI. There needs to be consistency with native Cascades and WebWorks. WebSockets are not difficult to implement J.
I’ve released V1.0.0 of the WebWorksBeacon application containing the features described in this article, so you can download, review and reuse the full source code. You can find the URL in the “Resources” section below.
I hope this has been interesting and will help set you on your way to developing real-world beacon applications for Blackberry 10.
Please feel free to contact me – @jcmrim via Twitter – if you need any help on anything related to Bluetooth Low Energy.
John Murray – @jcmrim