Instantly share code, notes, and snippets.

Embed
What would you like to do?
BBQ Thermometer with the Web Bluetooth API
node_modules
*.pem

demo

An experiment with the Web Bluetooth API.

Demo

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;
}
@scroach

This comment has been minimized.

Show comment
Hide comment
@scroach

scroach Sep 2, 2018

hey,
I've got one of those thermometers too and I'm quite annoyed about the app :D
so I've tried to reverse engineer the app, which is pretty time consuming only having obfuscated app code..
how did you get those UUIDs? did you extract them from the app?

scroach commented Sep 2, 2018

hey,
I've got one of those thermometers too and I'm quite annoyed about the app :D
so I've tried to reverse engineer the app, which is pretty time consuming only having obfuscated app code..
how did you get those UUIDs? did you extract them from the app?

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