Skip to content

Instantly share code, notes, and snippets.

@mattdsteele
Last active July 31, 2023 11:26
Show Gist options
  • Save mattdsteele/0fa9cafc4a95738181137547eae21fc8 to your computer and use it in GitHub Desktop.
Save mattdsteele/0fa9cafc4a95738181137547eae21fc8 to your computer and use it in GitHub Desktop.
BBQ Thermometer with the Web Bluetooth API
node_modules
*.pem

demo

An experiment with the Web Bluetooth API.

I bought a Bluetooth-enabled BBQ thermometer on a daily-deal site a while back as a dumb impulse purchase.

To use it on a phone, you're supposed to download an app, but eff that noise.

I decided to see if I could get something working using the Web Bluetooth API. It's a little hacky, but it works! Tested on Chrome for Android, and OS X.

A few notes:

  • Web Bluetooth API's not on by default, so I enabled it in chrome://flags
  • Hope y'all like deducing values with no documentation, and that you learned about little-endian notation
  • To actually get data from this thermometer you have to do a weird dance where you call .startNotifications() in a particular order
  • As mentioned there was no documentation, so this app to play with raw Bluetooth data was really helpful in figuring out what the hell was going on
  • The BBQ thermometer didn't use a well-known GATT service, so I had to find the raw BT Service UUIDs, and hardcode them in the code
  • Thermometer has two interesting characteristics (i.e. sensors), but don't try to connect to them at the same time; Chrome will yell at you, so you should do them in series. Chromium bug filed here.
const display = document.querySelector('.value');
const status1 = document.querySelector('.status-1');
const status2 = document.querySelector('.status-2');
const logs = document.querySelector('.logs');
const errorLogs = document.querySelector('.error-logs');
const html = document.documentElement;
document.querySelector('.bt').addEventListener('click', handleClick);
const setBgColor = (temp) => {
const classList = [];
if (temp < 60) {
classList.push('cold');
}
if (temp > 110) {
classList.push('hot');
}
html.classList = classList;
};
const setupEventListener = (char, el) => {
char.addEventListener('characteristicvaluechanged', e => {
const { value } = e.target;
const temp = value.getUint16(12, true) / 10;
if (temp !== 3686.3) {
el.innerHTML = temp;
setBgColor(temp);
} else {
el.innerHTML = '----';
}
});
};
let customService = '2899fe00-c277-48a8-91cb-b29ab0f01ac4';
const main = '28998e03-c277-48a8-91cb-b29ab0f01ac4';
const sensor1 = '28998e10-c277-48a8-91cb-b29ab0f01ac4';
const sensor2 = '28998e11-c277-48a8-91cb-b29ab0f01ac4';
const getSensor = (chars, uuid) => chars.find(c => c.uuid === uuid);
let chars;
function handleClick() {
console.log('clicked! accept all devices...');
navigator.bluetooth.requestDevice({
//acceptAllDevices: true
filters: [{ services: [customService] }]
})
.then(device => {
console.log('connected, have device');
return device.gatt.connect();
}).then(server => server.getPrimaryService(customService))
.then(service => service.getCharacteristics())
.then(cs => {
chars = cs;
const m = getSensor(chars, main);
return m.startNotifications();
})
.then(_ => getSensor(chars, sensor1).startNotifications())
.then(char1 => {
setupEventListener(char1, status1);
return getSensor(chars, sensor2).startNotifications();
})
.then(char2 => {
setupEventListener(char2, status2);
})
.then(_ => {
console.log('done');
})
.catch(error => {
console.error('failed', error);
alert(error) ;
});
}
window.onerror = (message, source, line, col, err) => {
alert(message);
console.error(message);
};
console.log = (...args) => {
args.forEach(arg => logs.innerHTML += `${arg}\n`);
};
console.error = (...args) => {
args.forEach(arg => errorLogs.innerHTML += `${arg}\n`);
};
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Test</title>
<style>
</style>
</head>
<body>
<button class="bt">Scan</button>
<h1 class="value"></h1>
<h1>Sensor 1</h1>
<h2 class="status-1"></h2>
<h1>Sensor 2</h1>
<h2 class="status-2"></h2>
<p>
<pre class="logs"></pre>
</p>
<p>
<pre class="error-logs"></pre>
</p>
</body>
<script src="app.js?b"></script>
</html>
{
"name": "bbq",
"version": "1.0.0",
"description": "",
"main": "app.js",
"dependencies": {
"http-server": "^0.9.0"
},
"devDependencies": {},
"scripts": {
"start": "http-server --ssl"
},
"author": "",
"license": "ISC"
}
* {
box-sizing: border-box;
}
:root {
--ice-cold: #2196f3;
--medium: #9e9e9e;
--boiling-hot: #f45236;
}
body, html {
margin: 0;
padding: 1em;
width: 100%;
height: 100%;
}
html {
background-color: var(--medium);
}
.cold {
background-color: var(--ice-cold);
}
.hot {
background-color: var(--boiling-hot);
}
button {
font-size: 2em;
display: block;
width: 100%;
border: 0;
}
.error-logs {
background: #990000;
}
@mattdsteele
Copy link
Author

mattdsteele commented Apr 29, 2020

I've been looking to do something similar but I want to get the temp readout visible on my Raspberry PI4. Is that possible?

@pstoric83 I think this would be possible as the Pi 4 has a Bluetooth LE chip, but I'm only familiar with how to read Bluetooth from a browser.

how did you get those UUIDs? did you extract them from the app?

@scroach Sorry for the delayed response; I pulled this out of the Android app by dumping Bluetooth access and reading in Wireshark, as described in the video on this page (around minute 25): https://steele.blue/web-bluetooth/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment