Building a Bluetooth LE temperature sensor
For my hydroponics project, I would like to measure the ambient temperature. I've done this in the past as a WiFi temperature sensor, but this time round I wanted to use my Pixl.js board with Bluetooth LE connectivity.
Here is the code to read data from a DS18B20 temperature sensor, display a temperature graph on the Pixl.js screen, and also make it available via Bluetooth LE as an Environmental Sensing Service.
const PIN_SENSOR = A1; // Pin temperature sensor is connected to
let ow, sensor, history;
let avrSum = 0, avrNum = 0;
let lastTemp = 0;
// Draw the temperature and graph to the screem
function draw() {
g.clear();
g.setFontVector(30);
g.setFontAlign(0,-1);
g.drawString(lastTemp.toFixed(1), 64,5);
g.setFontBitmap();
g.drawString("Temperature", 64, 0);
require("graph").drawLine(g, history, {
axes : true,
gridy : 5,
x:0, y:37,
width:128,
height:27
});
g.flip();
}
// Called when we get a temperature reading
function onTemp(temp) {
if (temp === null) return; // bad reading
avrSum += temp;
avrNum++;
lastTemp = temp;
// send on BLE
const th = Math.round(lastTemp * 100);
NRF.updateServices({
0x181A : { // environmental_sensing
0x2A6E : { // temperature
value : [th&255,th>>8],
readable: true,
notify: true
}
}
});
draw();
}
// take temp reading and update graph
var readingInterval = setInterval(function() {
if (sensor) sensor.getTemp(onTemp);
}, 10000);
// save average to history
var histInterval = setInterval(function() {
history.set(new Float32Array(history.buffer,4));
history[history.length-1] = avrSum / avrNum;
avrSum = 0;
avrNum = 0;
}, 60000); // 1 minute
// At startup, set up OneWire
function onInit() {
try {
ow = new OneWire(PIN_SENSOR);
sensor = require("DS18B20").connect(ow);
history = new Float32Array(128);
avrSum=0;
avrNum=0;
NRF.setServices({
0x181A: { // environmental_sensing
0x2A6E: { // temperature
notify: true,
readable: true,
value : [0x00, 0x00, 0x7F, 0xFF, 0xFF],
}
}
}, { advertise: [ '181A' ] });
} catch (e) {
console.log('Error:', e);
}
This code is based on the lovely Pixl.js Freezer Alarm example.
I then wanted to be able to connect to the sensor via Web Bluetooth in the browser, which led to this web app:
document.addEventListener('DOMContentLoaded', event => {
const button = document.getElementById('connectButton')
let device
button.addEventListener('click', async (e) => {
try {
if (button.innerHTML === 'Disconnect') {
device.gatt.disconnect()
button.innerHTML = 'Connect'
return
}
document.getElementById('p1').innerHTML = 'Connecting'
console.log('Requesting Bluetooth Device...')
device = await navigator.bluetooth.requestDevice({
filters: [{
namePrefix: 'Pixl.js'
}],
optionalServices: ['environmental_sensing']
})
device.addEventListener('gattserverdisconnected', onDisconnected)
console.log('Connecting to GATT Server...')
const server = await device.gatt.connect()
button.innerHTML = 'Disconnect'
console.log('Getting Environmental Sensing Service...')
const service = await server.getPrimaryService('environmental_sensing')
console.log('Getting Temperature Characteristic...')
const characteristic = await service.getCharacteristic('temperature')
console.log('Reading temperature...')
const value = await characteristic.readValue()
const temp = value.getUint16(0, true) / 100
console.log('Temperature is ' + temp)
document.getElementById('p1').innerHTML = temp
await characteristic.startNotifications()
console.log('Notifications started')
characteristic.addEventListener('characteristicvaluechanged', handleNotifications)
} catch (error) {
console.log('Argh! ' + error)
document.getElementById('p1').innerHTML = error
button.innerHTML = 'Connect'
}
})
function handleNotifications (event) {
const value = event.target.value
const temp = value.getUint16(0, true) / 100
console.log('Temperature is now ' + temp)
document.getElementById('p1').innerHTML = temp
}
function onDisconnected () {
console.log('Disconnected.')
button.innerHTML = 'Connect'
document.getElementById('p1').innerHTML = 'Disconnected'
}
})
The index.html
file looks like this:
<html>
<head>
<title>Hydroloop</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="main.js"></script>
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<button id="connectButton" class="buttons">Connect</button>
<p id="p1" class="text"></p>
</body>
</html>
One Web Bluetooth discrepancy that I discovered between desktop Chrome and Chrome on Android, is that desktop Chrome will still read a value from a service, even if the characteristic is not set to be readable. Chrome on Android, on the other hand, will throw an error. That's why you see both readable: true
and notify: true
in the code above.
This works great! Temperature readings in the browser are automatically updated, and it even shows you when you get disconnected from the sensor.
I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting https://100daystooffload.com.