manniru
11/13/2016 - 3:25 PM

BBQ Thermometer with the Web Bluetooth API

BBQ Thermometer with the Web Bluetooth API

* {
  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;
}
{
  "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"
}
<!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>
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`);
};

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.