Sensor APIs
For many years, mobile phones have contained a wide range of sensors - primarily to help provide a good user experience when using the device. E.g.
- Proximity sensors, to detect if the user is holding the phone close to the face, preventing accidental input
- Ambient light sensors, to adjust the screen backlight
- Accelerometers to automatically switch orientation or silence the device on orientation change
- GPS receivers and magnetometers to support maps and navigation
During the early days of modern web browsers on these devices, different approaches have been made to make a subset of these sensors available to web applications.
Around 2015, primarily driven by Intel, an effort was done to standardize sensor APIs to provide a consistent way for web applications to retrieve sensor data. The result is the Generic Sensor API spec.
Today, a wide range of sensors are made available using this API, some are behind a flag and more proposals are on the way, e.g. the TemperatureSensor.
Developer Experience
On desktop computers, these sensors are (usually) not available. This requires developers to have a target phone connected, to run - on target - to get proper sensor data while testing the application under development. It's also possible to spoof the data by using sensor emulation in e.g. Chrome DevTools.
Sometimes neither of these approaches are convenient. E.g. what if you want to interact with the screen UI while emulating accelerometer based control of a game or maybe the phone you have available just doesn't provide all the sensors you want to test with.
A third approach proposed here, is to introduce a module that allows the developer to connect to an external device to get live sensor data, while still being able to develop and test on a desktop.
Ideally, this could be provided inside e.g. DevTools as an extension to the emulation, by capturing sensor data from a connected mobile phone and injecting the data in the application under development. Until that happens, because of hardware connectivity APIs like Web Bluetooth, Web USB and the Serial API, it's possible to build such a solution already today.
Thingy:52
The Thingy:52 by Nordic Semiconductor is a very powerful little unit, packing a lot of onboard sensors and actuators (Audio, button, temperature, humidity, pressure, air quality, color, light, accelerometer, gyroscope, compass/magnetometer, RGB LED and NFC). which are all exposed over BLE GATT.
The GATT protocol documentation for the device is available here.
Connecting to the Thingy:52 from a web application is quite easy, just remember to list the services that might be required for your app in the list of optionalServices
:
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: ['ef680100-9b35-4933-9b10-52ffa9740042'] }],
optionalServices: [
"battery_service",
"ef680200-9b35-4933-9b10-52ffa9740042",
"ef680300-9b35-4933-9b10-52ffa9740042",
"ef680400-9b35-4933-9b10-52ffa9740042",
"ef680500-9b35-4933-9b10-52ffa9740042"
]
});
Hardware driver
In order to make it easily possible to extend the solution with different hardware devices in the future, a simple 'hardware driver' is created.
I decided to make a singleton instance of a class that extends EventTarget and then add the Web Bluetooth hardware access calls inside the class. This allows any component subscribing to the driver instance (using Thingy52Driver.addEventListener(...)
) to receive sensor data updates without having any knowledge of the underlying Bluetooth APIs and GATT protocol.
E.g. on Thermometer GATT characteristic updates, an event is emitted:
_onThermometerChange(event) {
const target = event.target;
const integer = target.value.getUint8(0);
const decimal = target.value.getUint8(1);
const celsius = Number.parseFloat(`${integer}.${decimal}`);
const temperature = {
celsius,
fahrenheit: celsius * 9 / 5 + 32,
kelvin: celsius + 273.15
}
this.dispatchEvent(new CustomEvent('thermometer', {
detail: temperature
}));
}
The full implementation is available here
Hijacking the local sensor classes
To make things easy for developers, I decided to make a simple mechanism that allows for specific sensor classes to be 'hijacked', receiving data from a connected Thingy:52 and not from potential local sensors. This is done by replacing the sensor classes available on the global object with hybrids that expose the same API but retrieve sensor data via Web Bluetooth.
The developer only needs to inject the following (between the comments) in the top of the main html file (usually index.html):
<!DOCTYPE html>
<!-- BEGIN GenericSensor Thingy52 initialization -->
<script type="module" >
import './src/genericsensor-thingy52.js';
import { replaceSensors } from './src/sensors.js';
replaceSensors([
'Accelerometer',
'AmbientLightSensor',
'Gyroscope',
'Magnetometer',
'TemperatureSensor'
]);
</script>
<!-- END GenericSensor Thingy52 initialization -->
<html>
...
To connect, disconnect and to see battery level of the Thingy:52, a small controller widget is injected in the lower left corner of the web application.
A small demo app is provided with the repo containing the driver.
When connected and up and running, it looks something like this:
Final thoughts
Having an option to only fetch and expose sensor data from a connected device - without the need to also run the full application on the target device would be a nice addition to DevTools. However, I was pleased to see how easy it was to build this solution by using off the shelf hardware like the Thingy:52 combined with the Web Bluetooth API.
A link to the repo is here: https://github.com/larsgk/genericsensor-thingy52
A link to the simple live demo page is here: https://larsgk.github.io/genericsensor-thingy52/
Feedback, request, issue reports and PRs are very welcome!
Enjoy ;)
Top comments (0)