(Note: these posts are migrated from my previous medium.com blog)
w4ilun
/
web-bluetooth-curie-imu
Arduino/Genuino 101 CurieIMU Web Bluetooth Orientation Visualiser
The Web Bluetooth API allows you to interact with Bluetooth LE peripherals from your browser using JavaScript. It’s been available since Chrome 45+, but you’ll have to enable it manually since it’s still an experimental feature. You can check out the API and examples here.
To start, I combined code examples for the Arduino 101 on using the built-in IMU and Bluetooth LE features; all of the Curie’s IMU readings are sent as notifications over Bluetooth LE by updating the characteristic’s value. The sketch below shows how to do so with the CurieBLE and CurieIMU libraries.
#include <CurieBLE.h> | |
#include <CurieIMU.h> | |
#include <MadgwickAHRS.h> | |
BLEPeripheral blePeripheral; | |
BLEService imuService("BEEF"); //give your custom service an ID | |
BLECharacteristic imuChar("FEED", BLERead | BLENotify, 20); //give your custom characteristic an ID, properties, and length | |
Madgwick filter; | |
unsigned long microsPerReading, microsPrevious; | |
float accelScale, gyroScale; | |
void setup(){ | |
Serial.begin(9600); | |
// instantiate BLE peripheral | |
blePeripheral.setLocalName("CurieIMU"); //broadcast device name | |
blePeripheral.setAdvertisedServiceUuid(imuService.uuid()); // add the service UUID | |
blePeripheral.addAttribute(imuService); // add your custom service | |
blePeripheral.addAttribute(imuChar); // add your custom characteristic | |
blePeripheral.begin(); | |
Serial.println("Bluetooth device active, waiting for connections..."); | |
// start the IMU and filter | |
CurieIMU.begin(); | |
CurieIMU.setGyroRate(25); | |
CurieIMU.setAccelerometerRate(25); | |
filter.begin(25); | |
// set the accelerometer range to 2G | |
CurieIMU.setAccelerometerRange(2); | |
// set the gyroscope range to 250 degrees/second | |
CurieIMU.setGyroRange(250); | |
// initialize variables to pace updates to correct rate | |
microsPerReading = 1000000 / 25; | |
microsPrevious = micros(); | |
} | |
void loop() { | |
int aix, aiy, aiz; | |
int gix, giy, giz; | |
float ax, ay, az; | |
float gx, gy, gz; | |
float roll, pitch, heading; | |
unsigned long microsNow; | |
String str = ""; | |
// check if it's time to read data and update the filter | |
microsNow = micros(); | |
if(microsNow - microsPrevious >= microsPerReading){ | |
// read raw data from CurieIMU | |
CurieIMU.readMotionSensor(aix, aiy, aiz, gix, giy, giz); | |
// convert from raw data to gravity and degrees/second units | |
ax = convertRawAcceleration(aix); | |
ay = convertRawAcceleration(aiy); | |
az = convertRawAcceleration(aiz); | |
gx = convertRawGyro(gix); | |
gy = convertRawGyro(giy); | |
gz = convertRawGyro(giz); | |
// update the filter, which computes orientation | |
filter.updateIMU(gx, gy, gz, ax, ay, az); | |
// print the heading, pitch and roll | |
roll = filter.getRoll(); | |
pitch = filter.getPitch(); | |
heading = filter.getYaw(); | |
str = str + roll + "," + pitch + "," + heading; | |
Serial.println(str); | |
BLECentral central = blePeripheral.central(); | |
if(central){ // if a central is connected to peripheral | |
const unsigned char imuCharArray[20] = { | |
str[0],str[1],str[2],str[3],str[4], | |
str[5],str[6],str[7],str[8],str[9], | |
str[10],str[11],str[12],str[13],str[14], | |
str[15],str[16],str[17],str[18],str[19] | |
}; | |
imuChar.setValue(imuCharArray, 20); //notify central with new data | |
} | |
// increment previous time, so we keep proper pace | |
microsPrevious = microsPrevious + microsPerReading; | |
} | |
} | |
float convertRawAcceleration(int aRaw){ | |
// since we are using 2G range | |
// -2g maps to a raw value of -32768 | |
// +2g maps to a raw value of 32767 | |
float a = (aRaw * 2.0) / 32768.0; | |
return a; | |
} | |
float convertRawGyro(int gRaw){ | |
// since we are using 250 degrees/seconds range | |
// -250 maps to a raw value of -32768 | |
// +250 maps to a raw value of 32767 | |
float g = (gRaw * 250.0) / 32768.0; | |
return g; | |
} |
On the web side, Chrome’s Web Bluetooth APIs were used to detect, connect, and read notifications from a Bluetooth LE peripheral device (our Arduino 101). For security reasons, all of this is enabled only after the user interacts with the page, so the navigator.bluetooth method is called after you click connect. From then on, you can discover the peripherals services and characteristics; we subscribe to these characteristics and receive IMU data in the callback function:
//BLE | |
let service = 0xBEEF; | |
let characteristic = 0xFEED; | |
let byteLength = 20; | |
//IMU global vars for processing | |
let yaw = 0.0; | |
let pitch = 0.0; | |
let roll = 0.0; | |
window.onload = () => { | |
document.getElementById('connect').onclick = connectBLE; | |
} | |
function connectBLE(){ | |
//BLE setup. Connect and get service/characteristic notifications | |
navigator.bluetooth.requestDevice({ filters: [{ services: [service] }] }) | |
.then(device => device.gatt.connect()) | |
.then(server => server.getPrimaryService(service)) | |
.then(service => service.getCharacteristic(characteristic)) | |
.then(characteristic => { | |
return characteristic.startNotifications() | |
.then(_ => { | |
characteristic.addEventListener('characteristicvaluechanged', | |
handleCharacteristicValueChanged); | |
}); | |
}) | |
.then(_ => { | |
console.log('Notifications have been started.'); | |
}) | |
.catch(error => { console.log(error); }); | |
function handleCharacteristicValueChanged(event){ | |
var value = event.target.value; | |
var str = ""; | |
//concat and split string for roll, pitch, yaw (e.g. "-0.58,2.20,328.76") | |
for(var i=0; i<byteLength; i++){ | |
str = str + String.fromCharCode(value.getUint8(i)); | |
} | |
var imu = str.split(','); | |
//update globals | |
roll = parseFloat(imu[0]); | |
pitch = parseFloat(imu[1]); | |
yaw = parseFloat(imu[2]); | |
} | |
} |
The IMU data (roll, pitch, & yaw) gets updated, and we update them as globals as well in our script; this allows our Processing.js sketch to read from the global scope the IMU data, and update the canvas WebGL rendering.
Since everything is done in Chrome and no additional server side processing is required, I hosted everything on GitHub pages, so everyone can try it out for themselves https://w4ilun.github.io/web-bluetooth-curie-imu/
You can also find all of the code above on GitHub: https://github.com/w4ilun/web-bluetooth-curie-imu
Enjoy!
Top comments (0)