workshop-nodeconfeu2018

Step 4 : Web Bluetooth - Writing

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.

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>

So what just happened? A few things:

And Promises glued it all together so we didn’t end up with lots of nested callbacks.

You could try modifying this to:

More Features

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>

How do you do this on the Bluetooth Peripheral?

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});

Step 5 - Notifications