Connect the Puck.js to The Things Network using a Pycom LoPy

I still wasn’t out of “what if…” scenario’s for the devices I had been playing with in relation to The Things Network / LoRaWAN. Because, although I had used the WiFi (and of course LoRA) capabilities of the LoPy a lot, I had not yet played with its BLE (Bluetooth Low Energy) capabilities. For that I needed something else that could use BLE to connect to it. My iPad Mini, the micro:bit, my desktop machine (thanks to the BLE USB adapter), they all could do that. But I wanted to use the Puck-js buttons that I had for that.

The use-case:  If I press the button on the Puck.js (I have two of them, I can press either of them), then the Puck.js connects over bluetooth to the LoPy. After the connection has been made, the Puck.js sends 1) its device-id 2) the measurement of the light sensor of the Puck.js 3) the measurement of the temperature sensor of the Puck.js and 4) the battery voltage of the Puck-js. Once the LoPy has received those 4 values (encoded as a single HEX-string), it sends it to The Things Network (TTN) via LoRaWAN.

Like with the Adafruit Circuit Playground, one of the challenges was that both devices (the LoPy and the Puck.js) use different programming languages and there were no existing examples that handles the cross platform connection.

I uploaded all code to github.com

Programming the Puck.js

To program the Puck.js, you need the Espruino IDE. I have had my fair share of problems connecting to the Puck.js from within that IDE. It is probably one of the challenges of using BLE as a connection to program. But in the end it got the job done. I wasted most time trying to understand how I can send data from the Puck.js to the LoPy after connecting to it. There was an example using 2 Puck.js to send data, but I could not figure out what the UUID of the PrimaryService and the UUID of the Characteristic for the LoPy that allowed me to write data to it where.

I tried to figure that out using code on the Puck.js, but that didn’t work. In the LoPy code that I found, the service and characteristic were defined like this:

srv1 = bluetooth.service(uuid=b'1234567890123456', isprimary=True)

chr1 = srv1.characteristic(uuid=b'ab34567890123456', value=5)

I finally discovered how these needed to be added in the Puck.js code by using nRF Connect, a free tool for iOS. After setting up the LoPy so that it broadcasts using BLE (see the code below), I connected to the LoPy using the nRF Connect app. It then shows you the correct UUID’s that are in the Send_BT_to_LoPy.js script:

return d.getPrimaryService("36353433-3231-3039-3837-363534333231");

return s.getCharacteristic("36353433-3231-3039-3837-363534336261");

Easy, once you know it.

Note #1:  The Puck.js connects to a device named “LoPy01” so if you change the name of the device in main.py for the LoPy, you also have to change it in the code for the Puck.js
Note #2:  I added the id for the Puck.js in the code, first line. You need to change that to the code for your Puck.js if you have more than 1 Puck.js and want to be able to keep the transmitted values apart afterwards.

Let’s continue with the LoPy.

Programming the LoPy

Programming the LoPy was a combination of the regular TTN setup and the BLE code provided. All the fun stuff happens in main.py, make sure you change all the needed values in lib\config.js so that it connects to TTN.

The LoPy does the usual stuff of connecting to the TTN network. But it also initializes the BLE. It makes itself discoverable using “LoPy01”:

bluetooth.set_advertisement(name='LoPy01', service_uuid=b'1234567890123456')

and

bluetooth.advertise(True)

and it defines a conn_cb(bt_o) function that is attached as a callback function when someone connects via BLE or disconnects:

bluetooth.callback(trigger=Bluetooth.CLIENT_CONNECTED | Bluetooth.CLIENT_DISCONNECTED, handler=conn_cb)

It also defines a service:

srv1 = bluetooth.service(uuid=b'1234567890123456', isprimary=True)

And a characteristic:

chr1 = srv1.characteristic(uuid=b'ab34567890123456', value=5)

It then defines the char1_cb(chr) function that is attached as a callback function for the characteristic.

char1_cb = chr1.callback(trigger=Bluetooth.CHAR_WRITE_EVENT, handler=char1_cb)

This means that if a device writes to the characteristic, this function is called. So this is where the send call for LoRaWAN is now located. So the node doesn’t send anything to TTN unless a connection made via BLE sends data to the defined characteristic.

The Things Network

Like before, the LoPy node was registered at The Things Network (providing me with the needed keys for config.py).

To decode the data that the Puck.js sends to the LoPy once the arrive at TTN, I created a Payload function. It automatically translates the single HEX-value into the 4 different elements, also making sure that they get converted back to the correct units. So, temperature is divided by 10, the light value (between 0 and 1) is divided by 1,000 and the battery voltage is divided by 100.

Like before, I connected the HTTP integration to loggly.com so that I would have a location where the data is being stored (log retention during my trial period is 15 days, but this is test data anyhow so I don’t mind).

[update 10-4-2017]

On the Pycom forums I received a response by Jose Marcelino. He added explanation to my struggles with the UUID which I think a valuable to add here:

The UUID on LoPy and the Puck are just two different ways to store the same thing, though the Puck is usually the ‘standard’ way.

I have a little helper function which given the standard (Puck.js) way, converts it to the LoPy:

def uuid2bytes(uuid):
    uuid = uuid.encode().replace(b'-',b'')
    tmp = binascii.unhexlify(uuid)
    return bytes(reversed(tmp))

You can then use it as

srv1 = bluetooth.service(uuid=uuid2bytes('15ECCA29-0B6E-40B3-9181-BE9509B53200'), isprimary=True)

Since this is your own custom service it’s best if you generate your own new UUID, otherwise other devices might be confused if they scan by service (a very common way sadly not yet supported by LoPy)

You can use the uuidgen command to get your own on Linux or Mac or through one of the many UUID generator sites like https://www.uuidgenerator.net

Thanks Jose!