CMGResearch Ltd

Blogging about random things

Raspberry Pi BTLE Device

We’ve just finished the first iOSCon hackathon. Some great work was done with the Affectiva emotion SDK along with some great stuff with Estimotes.

I played with my Raspberry Pi and turned it into a fully functional bluetooth device communicating with an app running on my iPhone. I also plugged in the Scentee SDK and got the phone making a smell when the Pi picked up key presses on the remote control. I really wanted the Pi to make the smells, but the volume from the Pi’s audio jack wasn’t loud enough to activate the device.

To get my raspberry pi communicating I installed node and then used belno.

Belno is a really nice node.js module for implementing Bluetooth low energy peripherals. For example to get an iBeacon running you just need the following code:

1
2
3
4
5
6
7
var bleno = require('bleno');
var uuid = 'e2c56db5dffb48d2b060d0f5a71096e0';
var major = 0; // 0x0000 - 0xffff
var minor = 0; // 0x0000 - 0xffff
var measuredPower = -59; // -128 - 127

bleno.startAdvertisingIBeacon(uuid, major, minor, measuredPower);

Assuming you’ve saved this file to ibeacon.js you can run it using:

1
$ sudo node ibeacon.js

To create a more fully functional device than a simple iBeacon we need to do a bit more work. I wanted to fully exercise my Raspberry Pi, using all the sensors that I’d wired up so far, connected up with my iPhone. This meant that I’d need to be able to write strings to my LCD display, get a stream of temperature readings and also get notified when an IR remote control button was pushed.

The basic outline of a bluetooth device looks like this using bleno:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var bleno = require('bleno');

bleno.on('stateChange', function(state) {
    console.log('on -> stateChange: ' + state);
    if (state === 'poweredOn') {
        bleno.startAdvertising('MyDevice',['b1a6752152eb4d36e13e357d7c225465']);
    } else {
        bleno.stopAdvertising();
    }
});

bleno.on('advertisingStart', function(error) {
    console.log('on -> advertisingStart: ' + (error ? 'error ' + error : 'success'));
    if (!error) {
        bleno.setServices([
            new bleno.PrimaryService({
                uuid : 'b1a6752152eb4d36e13e357d7c225465',
                characteristics : [
                  // add characteristics here
                ]
            })
        ]);
    }
});

We can now add some characteristics to our service.

For my lcd characteristic I created this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var data = new Buffer('Send me some data to display');

// new characteristic added to the service
new bleno.Characteristic({
  uuid : '9e739ec2b3a24af0c4dc14f059a8a62d',
  properties : ['read','writeWithoutResponse'],
  onReadRequest : function(offset, callback) {
      if(offset > data.length) {
          callback(bleno.Characteristic.RESULT_INVALID_OFFSET);
      } else {
          callback(bleno.Characteristic.RESULT_SUCCESS, data.slice(offset));
      }
  },
  onWriteRequest : function(newData, offset, withoutResponse, callback) {
      if(offset > 0) {
          callback(bleno.Characteristic.RESULT_INVALID_OFFSET);
      } else {
          exec('sudo ./lcd "'+newData.toString('utf8')+'"');
          data = newData;
          callback(bleno.Characteristic.RESULT_SUCCESS);
      }
  }
})

This characteristic provides a read function and a write function. The write function shells out to a simple command line program that sets the contents of the LCD. The read function lets you read back whatever it is you stored.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <wiringPi.h>
#include <mcp23s17.h>
#include <lcd.h>

// BASE port for the SPI GPIO extension
#define BASE    100

int main (int argc, char *argv[])
{
  printf("Will set text to %s", argv[1]);
  wiringPiSetup () ;
  // initialise the SPI extension
  mcp23s17Setup (BASE, 0, 0) ;
  // setup the LCD
  int fd = lcdInit(2, 16, 4, BASE + 10, BASE + 11, BASE + 12,
                  BASE + 13, BASE + 14, BASE + 15, 0, 0, 0, 0);
  // print whatever is in argv[1]
  lcdClear (fd);
  lcdHome (fd);
  lcdPrintf(fd, argv[1]);
}

For my InfraRed receiver characteristic I wanted to be able to notify a connected client that there was a new value. To do this we use the onSubscribe event coupled with the node module infrared. The infrared module gives us a nice wrapper around lirc.

1
2
3
4
5
6
7
8
9
10
11
12
var IRW = require('infrared').irw;

new bleno.Characteristic({
  uuid : 'b747cdd0ddcc11e38b680800200c9a66',
  properties : ['notify'],
  onSubscribe : function(maxValueSize, updateValueCallback) {
      irw.on('stdout', function(data) {
          updateValueCallback(new Buffer(data.split(' ')[2]));
      });
      irw.start();
  }
}),

When someone subscribes to this property we start up irw and whenever it gets an event we send the button push back to the client.

We should probably also handle the onUnsubscribe event as well and stop irw.

For the temperature sensor we can use the ds18b20 module – there are several other modules that we could use, but this is the first one I found.

1
2
3
4
5
6
7
8
9
10
11
new bleno.Characteristic({
  uuid : '4b842c60ddd611e38b680800200c9a66',
  properties : ['notify'],
  onSubscribe : function(maxValueSize, updateValueCallback) {
      setInterval(function() {
          sense.temperature('28-0000057cc14e', function(err, value) {
              updateValueCallback(new Buffer(value + 'C'));
          });
      }, 1000);
  }
}),

When someone subscribes I kick off a repeating timer to read the temperature and send it back every second. Once again, we should really handle the unsubscribe event and kill our timer.

To run the server we do the following:

1
2
3
4
5
6
7
# load up the SPI driver
$ gpio load spi
#load up the temperature sensors
$ sudo modprobe w1_gpio
$ sudo modprobe w1_therm
# start up the server
$ sudo node server.js

That’s it for the Raspberry Pi side of things – if you have any other sensors or outputs you want to wire up then you can follow a similar pattern.

You can now test your device using one of the many bluetooth test applications that are on the app store or download the (source code)[https://github.com/StudioSophisti/BTLE-Tools] and run one yourself.

To build our own app we need to use the Core Bluetooth framework to access our peripheral.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// create the CBCentral manager
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];


// when it is powered on start looking for our peripheral
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    if(central.state==CBCentralManagerStatePoweredOn)
    {
        CBUUID *serviceUUID = [CBUUID UUIDWithString:@"b1a67521-52eb-4d36-e13e-357d7c225465"];
        [central scanForPeripheralsWithServices:@[serviceUUID] options:nil];
    }
}

// central manager discovered a peripheral
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
    self.peripheral = peripheral;
    [self.centralManager stopScan];
    self.peripheral.delegate = self;
    // make a connection to the peripheral
    [self.centralManager connectPeripheral:self.peripheral options:nil];
}

// connected, start discovering services
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    if(peripheral.state == CBPeripheralStateConnected) {
        [peripheral discoverServices:nil];
    }
}

// services discovered - find their characteristics
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    for (CBService *service in peripheral.services) {
        [peripheral discoverCharacteristics:nil forService:service];
    }
}

// discovered some characteristics
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    for (CBCharacteristic *characteristic in service.characteristics) {
        if([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"9e739ec2-b3a2-4af0-c4dc-14f059a8a62d"]]) {
          self.lcdCharacteristic = characteristic;
      }
      if([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"b747cdd0-ddcc-11e3-8b68-0800200c9a66"]]) {
            self.irCharacteristic = characteristic;
      }
      if([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"4b842c60-ddd6-11e3-8b68-0800200c9a66"]]) {
            self.tempCharacteristic = characteristic;
      }
        [peripheral readValueForCharacteristic:characteristic];
        [peripheral setNotifyValue:YES forCharacteristic:characteristic];
  }
}

// this is called in response to the readValueForCharacteristic and also when the peripheral notifies us of changes
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  // get the value
    NSString *value = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
    // see which characteristic was updated
    if(characteristic == self.lcdCharacteristic) {
        // LCD display
    }
    if(characteristic == self.irCharacteristic) {
        // InfraRed remote
    }
    if(characteristic == self.tempCharacteristic) {
        // Temperature
    }
}

The first step is to create the core bluetooth central manager. We have to wait for it to power on and then we can start scanning for peripherals.

Once we’ve discovered the peripheral we can connect to it and discover the services it offers and once we find the services we can ask the service what characteristics it supports.

When we get the set of characteristics for the service we can read the current value and we can also subscribe to changes in the value.

When we read values or are notified of values we get a call to didUpdateValueForCharacteristic.

To write values to our new LCD characteristic we call:

1
2
3
[self.peripheral writeValue:[text dataUsingEncoding:NSUTF8StringEncoding]
          forCharacteristic:self.lcdCharacteristic
                       type:CBCharacteristicWriteWithoutResponse];

That’s it! Bleno provides a really nice environment for wiring up bluetooth services and the Core Bluetooth frameworks makes it very easy to connect up to your device.

Raspberry Pi iBeacon

Following the instructions here I decided to try turning my Pi into an iBeacon.

According to the artical most Bluetooth 4.0 dongles should work so I ordered the first one I found:

And once it arrived plugged it in:

To get set up we need to install some stuff:

Make sure everything is up to date

1
2
sudo apt-get update
sudo apt-get upgrade

And then install the bluetooth package (this will take quite some time as it installs a lot of stuff):

1
sudo apt-get install bluetooth

Restart your pi and see if your dongle is detected.

1
2
lsusb
hcitool dev

Sadly my device shows up with lsusb, but doesn’t show up in hcitool. Forcing it to load makes things work:

1
2
3
4
5
6
7
sudo su
modprobe btusb
echo "050d 065a" > /sys/bus/usb/drivers/btusb/new_id
hcitool dev

Devices:
  hci0    00:02:72:C5:DE:61

To get my device to be recognised at boot time and on demand when it’s plugged in we need to add some udev rules – I found a nice guide here.

Create a new file /etc/udev/rules.d/95-belkin-bluetooth.rules

1
2
# Rules for hotplugging belkin low power bluetooth 
SUBSYSTEM=="usb", ATTRS{idVendor}=="050d", ATTRS{idProduct}=="065a", RUN="/etc/belkin_bluetooth.sh"

And another file /etc/belkin_bluetooth.sh

1
2
3
#!/bin/sh
modprobe btusb
echo 050d 065a > /sys/bus/usb/drivers/btusb/new_id

This file needs to be executable by root.

Now you’ve got your dongle working you can turn it into an iBeacon!

1
2
3
4
sudo hciconfig hciO up
sudo hciconfig hci0 leadv
sudo hciconfig hci0 noscan
sudo hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A FF 4C 00 02 15 E2 0A 39 F4 73 F5 4B C4 A1 2F 17 D1 AD 07 A9 61 00 00 00 00 C8 00

This will create an iBeacon with the uuid E20A39F4-73F5-4BC4-A12F-17D1AD07A961 and major minor version 0,0.

To monitor for this iBeacon on you would use the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
    NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:@"E20A39F4-73F5-4BC4-A12F-17D1AD07A961"];
    self.beaconRegion = [[CLBeaconRegion alloc]
                                    initWithProximityUUID:uuid
                                    identifier:@"test"];

    [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}

- (void)locationManager:(CLLocationManager *)manager
        didRangeBeacons:(NSArray *)beacons
               inRegion:(CLBeaconRegion *)region {

    if ([beacons count] > 0) {
        CLBeacon *nearestBeacon = [beacons firstObject];

        self.beaconInfo.text = [NSString stringWithFormat:@"%fm",nearestBeacon.accuracy];

        NSLog(@"%d", [nearestBeacon.minor intValue]);
    }
}

It works! (not the most exciting app I admit…)

Raspberry Pi Infrared Receiver

To get my infrared receiver working with my Raspberry Pi I followed the great instructions provided here.

First we need to wire up our IR sensor.

You can find a description of the pins here.

  • Pin 1 is the output so we wire this to a visible LED and resistor
  • Pin 2 is ground
  • Pin 3 is VCC, connect to 3.3V

And then we need to set up LIRC (Linux Infrared Remote Control)

1
sudo apt-get install lirc

And then add this to your /etc/modules file:

1
2
lirc_dev
lirc_rpi gpio_in_pin=18 gpio_out_pin=22

Now modify /etc/lirc/hardware.conf

To match the contents shown below (more details available here and here)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
########################################################
# /etc/lirc/hardware.conf
#
# Arguments which will be used when launching lircd
LIRCD_ARGS="--uinput"

# Don't start lircmd even if there seems to be a good config file
# START_LIRCMD=false

# Don't start irexec, even if a good config file seems to exist.
# START_IREXEC=false

# Try to load appropriate kernel modules
LOAD_MODULES=true

# Run "lircd --driver=help" for a list of supported drivers.
DRIVER="default"
# usually /dev/lirc0 is the correct setting for systems using udev
DEVICE="/dev/lirc0"
MODULES="lirc_rpi"

# Default configuration files for your hardware if any
LIRCD_CONF=""
LIRCMD_CONF=""
########################################################

And reboot your Pi.

You can now test that you’ve wired everything up correctly by typing:

1
2
sudo /etc/init.d/lirc stop
mode2 -d /dev/lirc0

When you point a remote control at your receiver you should see:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
space 3020711
pulse 9126
space 4512
pulse 582
space 559
pulse 553
space 590
pulse 581
space 593
pulse 534
space 548
pulse 586
space 579
pulse 542
space 556
pulse 614
space 533
pulse 551
space 589
pulse 590
...

You can now create or download a lirc config file for your remote. To do this I followed the instructions in the lined blog.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Stop lirc to free up /dev/lirc0
sudo /etc/init.d/lirc stop

# Create a new remote control configuration file (using /dev/lirc0) and save the output to ~/lircd.conf
irrecord -d /dev/lirc0 ~/lircd.conf

# Make a backup of the original lircd.conf file
sudo mv /etc/lirc/lircd.conf /etc/lirc/lircd_original.conf

# Copy over your new configuration file
sudo cp ~/lircd.conf /etc/lirc/lircd.conf

# Start up lirc again
sudo /etc/init.d/lirc start

You can test that things are working by doing:

1
irw

Connecting Up the MCP23S17 and HD44780U Based LCD

In my Raspberry Pi starter kit I also got a LCD display based compatible with the Hitachi HD44780U controller.

WiringPi has some nice simple support for this.

The LCD supports being driven in 8 bit mode (which requires 10 GPIO pins) and 4 bit mode (which required 6 GPIO pins). Although you can use all 17 GPIO pins for your own purposes I want to keep the I2C, UART and SPI functionality free for further projects, which means I only really have 8 pins to play with. I also want to connect up my 1 wire temperature sensor and later on my infrared sensor. This means that even using the LCD in 4 bit mode I’m going to be running out of pins.

Fortunately the GPIO is quite extensible using the I2C or SPI pins. The simplest way to do this is to use the MCP23S17 which is supported by WiringPi.

The Pi has 4 pins supporting SPI:

  • CEO, CE1 – chip select pins
  • SCLCK – Clock
  • MISO – Master in, slave out
  • MOSI – Master out, slave in

Connecting the MCP23S17 is simply a case of connecting these pins up.

  • PIN 11 (CS) –> CE0 (or CE1)
  • PIN 12 (SCK) –> SCLCK
  • PIN 14 (SO) –> MISO
  • PIN 13 (SI) –> MOSO

  • PIN 9 (VSS) –> 3v

  • PIN 10 (VDD) –> GND
  • PIN 15-17 (A0-A2) –> GND – This sets the address of our chip to ‘0’
  • PINT 18 (_RESET) –> 3V – Don’t forget this like I did – you’ll be scratching your head for a while…

You can then connect whatever you want to pint 21-28 and 1-8.

Connecting up the LCD display should be straightforward. I was fortunate that the pins on my connector married up really nicely with the pins on the MCP23S17 as shown here:

A and K (Anode and Cathode) for the back-light are connected the VSS and VDD pins – I’m able to do that with my module because it has a built in resistor – you should check your module first!

The data pins for the LCD match up with pins 1-8 on the MCP23S17. I’m going to use the LCD in 4 bit mode so I can use a couple of these pins to drive the E and RS connections on the LCD.

If your LCD module is 5v, make sure you tie the RW pin to ground.

VSS is connected to 5v and VDD is connected to GND.

V0 controls the contrast – initially I had this wired up to a 10K pot, but once I’d found a good setting I replaced that with two resistors dividing +5 and GND.

That’s it for the wiring up. To make it do something we have to install wiringPi and write some simple code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <wiringPi.h>
#include <mcp23s17.h>
#include <lcd.h>

#define BASE    100

int main (void)
{
        wiringPiSetup () ;
        mcp23s17Setup (BASE, 0, 0) ;
        int fd = lcdInit(2, 16, 4, BASE + 10, BASE + 11, BASE + 12, BASE + 13, BASE + 14, BASE + 15, 0, 0, 0, 0);

        lcdHome (fd);
        lcdClear (fd);

        lcdPuts(fd, "Testing 123");
        lcdPosition(fd, 0, 1);
        lcdPuts(fd, "Test the 23s17");
}

The first step is to initialise wiringPi. We then need to initialise the SPI mcp23s17 as shown here. The first number (BASE) is our pin offset, the second number is the SPI port (0 or 1) and the third number is the address/device id.

For the LCD we we give the number of rows, columns and bits (4 or 8) and then list the pins as detailed here.

To compile and run this program you have to link with wiringPi and wiringPiDev and before running you have to load up the spi driver module.

1
2
3
g++ spi.c -L/usr/local/lib -lwiringPi -lwiringPiDev
gpio load spi
sudo ./a.out

With the 3 address lines and two chip selects you could potentially connect up 16 extenders to your Pi giving you 256 IO pins!

Raspberry Pi Temperature Sensor

I recently got hold of a Raspberry Pi along with a starter kit. My starter kit came with a couple of sensors and components.

The temperature sensor is the DS18B20

Getting this up and running was pretty straightforward following various instructions on the internet – the most useful being the one from Adafruit

My sensor already comes with a resistor on it’s circuit board so the wiring was just a case of plugging the red wire to the 3.3v supply, the black wire to ground, and the white wire to Pin 7 on my breakout board.

To load up support for the device follow these steps:

1
2
3
4
5
6
7
8
9
sudo modprobe w1-gpio
sudo modprobe w1-therm
cd /sys/bus/w1/devices
ls
28-0000057cc14e  w1_bus_master1
cd 28-0000057cc14e
cat w1_slave
68 01 4b 46 7f ff 08 10 05 : crc=05 YES
68 01 4b 46 7f ff 08 10 05 t=22500

The second line of text is the temperature.

If you want to change the pin that w1 devices can connect to then you need to edit /boot/cmdline.txt and add this parameter:

1
bcm2708.w1_gpio_pin=<GPIO_pin_number> (for examplebcm2708.w1_gpio_pin=17).