--- type: video yt_id: VVU2YVRMdUlfajQtMHdpRFN6bWFQY3RRLlRpOE1YZ1RGSTdz videoId: Ti8MXgTFI7s title: "Let's Build a Temperature Sensor with a Raspberry Pi Part 2" date: "2018-07-15T20:52:46Z" slug: "lets-build-a-temperature-sensor-with-a-raspberry-pi-part-2" image: name: "let's-build-a-temperature-sensor-with-a-raspberry-pi-part-2.jpg" alt: "Let's Build a Temperature Sensor with a Raspberry Pi Part 2" width: 1280 height: 720 status: 'published' description: "Instructions on how to build this: https://sammeechward.com/lets-build-a-temperature-sensor-with-a-raspberry-pi-part-2/" tags: ['ble', 'ios', 'node js', 'raspberry pi', 'swift'] --- I set up a raspberry pi to emit temperature and humidity data using BLE advertising packets. Then I made an iPhone app that can read that data and display it to me. This is part two of the two part temperature sensor project. Make sure you check out [Part 1](/lets-build-a-temperature-sensor-with-a-raspberry-pi-part-1) first. In this part, I set up the pi to emit the temperature and humidity data using BLE advertising packets. I also made an iPhone app that can read that data and display it to me. ## Hardware * The raspberry pi and temperature sensor from the last [post](/lets-build-a-temperature-sensor-with-a-raspberry-pi-part-1/). * An iPhone and a Mac. ## How To Do This The first thing to do is to set up the raspberry pi as the Bluetooth peripheral (the thing advertising it's data). Then I will set up the iPhone app which acts as the central device (the thing that reads data from the peripheral) BLE peripherals can either be advertising themselves, or they can be connected to a central device. If they are advertising themselves, then lots of central devices can “see” them and choose to try and connect to them. Once connected, the peripheral device can only communicate with a single central device. When a peripheral is advertising, it is emitting a small amount of data in all directions for any other device to see. This is usually data about the device like the name and what it is, but this advertising data could really be anything. So you could just advertise temperature and humidity data and never actually have to “connect” to the device. This will allow any device to read the temperature and humidity, and makes the code easier to write. #### Raspberry Pi (Bluetooth Peripheral) Install [bleno](https://github.com/noble/bleno) I used [bleno](https://github.com/noble/bleno), a Node.js module for implementing BLE (Bluetooth Low Energy) peripherals. bleno uses the linux Bluez, so before you can use bleno, you need to install the bluez prerequisite software. ```c sudo apt-get install -y bluetooth bluez libbluetooth-dev libudev-dev ``` Then you can install bleno. ```c npm install bleno ``` Code the app. I created a new file and added the following code which is basically coppied from [bleno](https://github.com/noble/bleno)‘s documentation: ```js const bleno = require('bleno'); // bleno.state must be poweredOn before advertising is started. let state; bleno.on('stateChange', (s) => { state = s; if (state !== 'poweredOn') { bleno.stopAdvertising(); } }); /** * Start or restart advertising with custom data. * @param {A 31 byte buffer compatible with the ble advertising spec} buffer */ function startAdvertising(buffer) { return new Promise((resolve, reject) => { bleno.stopAdvertising(); if (state !== 'poweredOn') { reject(new Error("not powered on")); return; } bleno.startAdvertisingWithEIRData(buffer, (error) => { if (error) { reject(error); return; } resolve("?"); }); }); } ``` I can advertise 31 bytes of data using the `startAdvertising` function and passing in a 31 byte buffer. #### Note: If you're interested, here's some data on how the 31 bytes should be set up. * [Custom GAP advertising packet](https://docs.mbed.com/docs/ble-intros/en/latest/Advanced/CustomGAP/) * [GAP Data Types](https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile) All that's left to do here, is pass the temperature and humidity data to this function as a 31 byte buffer. So I wrote the following function that takes in two doubles, and creates a new buffer. The code is a little bit long because of the way an advertising packet works, but here it is: ```js /** * Create a new 31 byte buffer with temperature and humidity data. * For more information about how this function works, check out the following links: * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile * https://www.silabs.com/community/wireless/bluetooth/knowledge-base.entry.html/2017/02/10/bluetooth_advertisin-hGsf * @param {A Double} temperature * @param {A Double} humidity */ function advertisementData(temperature, humidity) { if (typeof temperature !== 'number' || typeof humidity !== 'number') { throw 'a fit'; } const buffer = Buffer.alloc(31); // maximum 31 bytes let bufferIndex = 0; // flags buffer.writeUInt8(2, bufferIndex++); buffer.writeUInt8(0x01, bufferIndex++); buffer.writeUInt8(0x06, bufferIndex++); // Complete Local Name const name = "Sensei" // Change this buffer.writeUInt8(1+name.length, bufferIndex++); buffer.writeUInt8(0x09, bufferIndex++); buffer.write(name, bufferIndex); bufferIndex += name.length; // Manufacturer Specific Data // 4 bytes for each number buffer.writeUInt8(1+8+8, bufferIndex++); buffer.writeUInt8(0xFF, bufferIndex++); buffer.writeDoubleLE(temperature, bufferIndex); bufferIndex+=8; buffer.writeDoubleLE(humidity, bufferIndex); bufferIndex+=8; return buffer; } ``` This will create a new 31 byte buffer with temperature and humidity data assigned to the Manufacturer Specific Data. [https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile](https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile) ```c Manufacturer Specific Data: 0xFF temperature: 8 byte little endian double humidity: 8 byte little endian double ``` To start advertising temperature and humidity, you could call the function like this: ```js const buffer = advertisementData(24.4, 65.6); startAdvertising(buffer); ``` This is just using some hardcoded numbers. I'll let you figure out how you want to connect this to the real temperature data from the [previous post](/lets-build-a-temperature-sensor-with-a-raspberry-pi-part-1/). My version of the peripheral code for this can be found here: [https://github.com/Sam-Meech-Ward/Sensei-Peripheral-JS](https://github.com/Sam-Meech-Ward/Sensei-Peripheral-JS) Once you've got the raspberry pi advertising, you can use an app like [LightBlue® Explorer](https://itunes.apple.com/ca/app/lightblue-explorer/id557428110?mt=8) to verify that it's working. #### Bluetooth Central (iOS) #### Warning: BLE apps must run this on a real iPhone, not the simulator! Create a new iPhone app. To interact with other ble devices from an iOS app, you will have to use the `CoreBluetooth` framework. This is a pretty well documented framework, so it was pretty easy to setup my iPhone as a central device. It still requires more code than I thought was necessary. Create a new `TemperatureDetector` class. I made a `TemperatureDetector` class and added the following code: ```swift import Foundation import CoreBluetooth class TemperatureDetector: NSObject { // The Central Manager is what will listen for advertising ble devices. var myCentralManager: CBCentralManager! override init() { super.init() myCentralManager = CBCentralManager(delegate: self, queue: nil) } } extension TemperatureDetector: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { // If ble is supported and available, start scanning; otherwise, stop scanning if central.state == .poweredOn { myCentralManager.scanForPeripherals(withServices: nil, options: nil) } else { myCentralManager.stopScan() } } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { // Only continue if we find a peripheral with the name "Sensei" // Change this to whatever you've called your peripheral guard let name = advertisementData["kCBAdvDataLocalName"] as? String, name == "Sensei" else { return } // Get the Manufacturer Data, that's where we stored the temperature and humidity guard let manData = advertisementData["kCBAdvDataManufacturerData"] as? Data else { return } // The data was stored in binary, now we have to read that data as an 8 byte double. // Temperature is the first 8 bytes let temperature: Double = manData.subdata(in: 0..<8).withUnsafeBytes { $0.pointee } // Humidity is the second 8 bytes let humidity: Double = manData.subdata(in: 8..<16).withUnsafeBytes { $0.pointee } print("Temperature: \(temperature), Humidity: \(humidity)") } } ``` When you create a new instance of `TemperatureDetector`, it will start scanning for BLE peripherals. If it finds the temperature sensor “Sensei” (oh yeah, the sensor's name is “Sensei”), it will print out the temperature and humidity data. Here's the code for my completed iPhone app: [https://github.com/Sam-Meech-Ward/Sensei-Central-iOS](https://github.com/Sam-Meech-Ward/Sensei-Central-iOS)