Ok, so we know what we’re doing is working. Let’s do some proper Web Bluetooth, without the libraries.
First, let’s set the badge up with some demo code that’s pre-loaded on it, which creates a service which allows you to control the RGB LED and Vibration motor.
nRF Connect
app or and Web Bluetooth pages open and connected to the badgeBlueooth Workshop
LED/Buzzer Control
So what just happened? The Badge has now configured itself as the following:
If we write to one of the characteristics, things should happen!
So how do we do that?
<html>
<body>
<button onclick="connect()">Connect to Web Bluetooth</button>
<script>
function connect() {
var options = {
filters: [
{namePrefix: 'Pixl.js'},
],
optionalServices: [ "7b340000-105b-2b38-3a74-2932f884e90e" ]
};
var busy = false;
var gatt, service;
// Bring up the Web Bluetooth Device Chooser
navigator.bluetooth.requestDevice(options).then(function(device) {
console.log('Device: ' + JSON.stringify(device));
return device.gatt.connect();
}).then(function(g) {
gatt = g;
// Get our custom service
return gatt.getPrimaryService("7b340000-105b-2b38-3a74-2932f884e90e");
}).then(function(service) {
// Get the RGB LED characteristic
return service.getCharacteristic("7b340001-105b-2b38-3a74-2932f884e90e");
}).then(function(characteristic) {
// Make a random color
var rgb = new Uint8Array([
Math.random()*255,
Math.random()*255,
Math.random()*255]);
// Write it to the characteristic
return characteristic.writeValue(rgb);
}).then(function() {
gatt.disconnect();
console.log("All Done!");
}).catch(function(error) {
console.log("Something went wrong. " + error);
});
}
</script>
</body>
</html>
Connect to Web Bluetooth
buttonSo what just happened? A few things:
navigator.bluetooth.requestDevice
was used to pop up a window and grab your devicedevice.gatt.connect()
then initiated a connectiongatt.getPrimaryService("7b340000-105b-2b38-3a74-2932f884e90e")
found the service matching the UUID we gave itservice.getCharacteristic("7b340001-105b-2b38-3a74-2932f884e90e")
found the matching characteristic (for the LEDs this time) on the given service (there can be other matching characteristics on another service)characteristic.writeValue(rgb)
to write aUint8Array
of data to the badgegatt.disconnect()
disconnectsAnd Promises
glued it all together so we didn’t end up with lots of nested callbacks.
You could try modifying this to:
characteristic.writeValue
You can add colour selectors and sliders for setting the motor speed if you want to. Take a look at this example!
You do need to ensure that you don’t try and perform two Bluetooth LE operations on the same connection at once.
To simplify this I’ve used a busy
flag, but ideally you’d
have a queue of some kind.
<html>
<body>
<button onclick="connect()">Connect to Web Bluetooth</button>
<input type="color" id="color" value="#e66465" style="display:none"/>
<input type="range" id="vibrateleft" min="0" max="255" value="0" style="display:none"/>
<input type="range" id="vibrateright" min="0" max="255" value="0" style="display:none"/>
<script>
function connect() {
var options = {
filters: [
{namePrefix: 'Pixl.js'},
],
optionalServices: [ "7b340000-105b-2b38-3a74-2932f884e90e" ]
};
var busy = false;
var gatt, service;
navigator.bluetooth.requestDevice(options).then(function(device) {
console.log('Device: ' + JSON.stringify(device));
return device.gatt.connect();
}).then(function(g) {
gatt = g;
// Get our custom service
return gatt.getPrimaryService("7b340000-105b-2b38-3a74-2932f884e90e");
}).then(function(s) {
service = s;
// Get the RGB LED characteristic
return service.getCharacteristic("7b340001-105b-2b38-3a74-2932f884e90e");
}).then(function(characteristic) {
var colorPicker = document.querySelector("#color");
colorPicker.style.display = "block";
colorPicker.addEventListener("input", function(event) {
var col = event.target.value; // #bbggrr
var rgb = new Uint8Array([
parseInt(col.substr(5,2),16),
parseInt(col.substr(3,2),16),
parseInt(col.substr(1,2),16)]);
if (busy) return;
busy = true;
characteristic.writeValue(rgb).then(function() {
busy = false;
});
}, false);
// Get the Vibration characteristic
return service.getCharacteristic("7b340002-105b-2b38-3a74-2932f884e90e");
}).then(function(characteristic) {
var vibl = document.querySelector("#vibrateleft");
var vibr = document.querySelector("#vibrateright");
vibl.style.display = "block";
vibr.style.display = "block";
function vibChange(event) {
console.log(vibl.value,vibr.value);
if (busy) return;
busy = true;
var v = new Uint8Array([vibl.value,vibr.value]);
characteristic.writeValue(v).then(function() {
busy = false;
});
}
vibl.addEventListener("input", vibChange, false);
vibr.addEventListener("input", vibChange, false);
}).then(function() {
//gatt.disconnect();
console.log("Done!");
}).catch(function(error) {
console.log("Something went wrong. " + error);
});
}
</script>
</body>
</html>
The code needed to handle this on the peripheral (which is being called when you choose the menu item) is simply:
NRF.setServices({
"7b340000-105b-2b38-3a74-2932f884e90e" : {
"7b340001-105b-2b38-3a74-2932f884e90e" : {
writable : true,
value : [0,0,0],
maxLen : 3,
onWrite : function(evt) {
var d = new Uint8Array(evt.data);
var c = [0|d[0],0|d[1],0|d[2]];
NC.backlight(c.concat(c,c,c));
NC.ledTop(c);
NC.ledBottom(c);
Terminal.println("LED "+d);
},
},
"7b340002-105b-2b38-3a74-2932f884e90e" : {
writable : true,
value : [0,0],
maxLen : 2,
onWrite : function(evt) {
var d = new Uint8Array(evt.data);
analogWrite(VIBL,(0|d[0])/255);
analogWrite(VIBR,(0|d[1])/255);
Terminal.println("BUZZ "+d);
}
}
}
},{uart:false});