Continuing from Getting Started, this project enhances the project to get more out of the device. The key is to show developers how to build up on our "HelloWorld" type application and develop more advanced Blueapps. In the previous tutorial, we showed you how to connect to a Hexiwear device and read the manufacture name. This time around, we will start reading the accelerometer in the device. The application will update the values every second.
Running the ApplicationThe UI for this application is still very basic and a copy of the index.html file from the previous example. We added an extra item in our list of data we read for the accelerometer data. We show the X, Y, and Z values which update every second.
The first major difference we will see is that we added another file. You'll see there is a hexiwear.js file. Unlike the previous example, we moved all the bluetooth and device specific code into this file. This makes the app.js file a lot simpler and keeps it's focus on the user interface.
Let's take a look at hexiwear.js and look over the additions we made compared to what we had in Getting Stared's app.js.
const DEVICE_INFORMATION_SERVICE = "0000180a-0000-1000-8000-00805f9b34fb";
const MANUFACTURER_NAME = "00002a29-0000-1000-8000-00805f9b34fb";
const MOTION_SERVICE = "00002000-0000-1000-8000-00805f9b34fb";
const ACCELEROMETER = "00002001-0000-1000-8000-00805f9b34fb";
const DEVICE_NAME = 'HEXIWEAR';
First we define the constants that we need for the device. That includes service UUIDs for Device Information Service and the Motion Service in Hexiwear. We also have the characteristic UUIDs for the manufacture name and the accelerometer data. Lastly, we include the device name as a constant.
var self;
function Hexiwear(bluetooth) {
self = this;
self.bluetooth = bluetooth;
self.initialize();
}
Hexiwear.prototype.initialize = function () {
self = this;
self.bluetoothDevice = undefined;
self.connected=false;
self.deviceInformationService = undefined;
self.motionService = undefined;
self.motionData = {};
self.name = undefined;
self.id = undefined;
self.manufacturerName = undefined;
};
We include a constructor and an initialize function that defines the variables that we will use as instance members. We also define a global variable self so that we can avoid any scope issues with 'this' and keep a reference to the object.
Hexiwear.prototype.connect = function () {
let options = {
filters: [{name: DEVICE_NAME}],
optionalServices: [
DEVICE_INFORMATION_SERVICE,
MOTION_SERVICE
]
};
return navigator.bluetooth.requestDevice(options)
.then(function (device) {
self.bluetoothDevice = device;
self.name = device.name;
self.id = device.id;
return device.gatt.connect();
})
.then(function (server) {
self.connected = true;
console.log("Discovering services");
return Promise.all([
server.getPrimaryService(DEVICE_INFORMATION_SERVICE)
.then(function (service) {
self.deviceInformationService = service;
self.readDeviceInformation();
}),
server.getPrimaryService(MOTION_SERVICE)
.then(function (service) {
self.motionService = service;
self.readMotion();
})
]);
}, function (error) {
console.warn('Service not found'+ error);
Promise.resolve(true);
})
};
For the most part, this is the same as what we did in the previous example. We have added optionalServices to the filters. This is because web bluetooth request we do so in order to access those services. Keep in mind, those are only used if you want to run this app on your browser.
The second difference is that we are now reading two services instead of one. Because of that, we chose to use Promise.all which takes an array of promises. After we get the services, we changed how we handle it (though it's still very similar when put together). We save the service reference and we call a function to get what we need from the service.
Hexiwear.prototype.readDeviceInformation = function() {
if (self.deviceInformationService) {
self.deviceInformationService.getCharacteristic(MANUFACTURER_NAME)
.then(characteristic => {
return characteristic.readValue();
})
.then(data => {
self.manufacturerName = dataToString(data);
self.updateUI();
})
.catch(error => {
console.log('Reading device info data failed. Error: ' + JSON.stringify(error));
});
}
}
The code here is very similar to what we did before after we received the service. After following the promises to get the characteristic and then the value, we store the value into a variable and we call updateUI. In our app.js, we will go over what this updateUI function is.
Hexiwear.prototype.readMotion = function() {
if (self.motionService) {
self.motionService.getCharacteristic(ACCELEROMETER)
.then(characteristic => {
return characteristic.readValue();
})
.then(data => {
self.motionData.x = data.getInt16(0, true) / 100;
self.motionData.y = data.getInt16(2, true) / 100;
self.motionData.z = data.getInt16(4, true) / 100;
self.updateUI();
})
.catch(error => {
console.log('Reading motion data failed. Error: ' + JSON.stringify(error));
});
}
}
Similarly, we read the accelerometer data from it's characteristic in the motion service. As above, we follow promises until we get the value which we save into a variable and call updateUI.
Moving to the app.js, you'll see that this file is now only 37 lines of code instead of over 70 which we had before.
We changed our button event code such that we only call the connect function in the hexiwear file.
main.buttonClicked = function () {
if (!isBluetoothEnabled()) {
alert('Bluetooth Not Supported');
return;
}
main.hexiwear.connect();
}
Additional differences include the updateUI function as well as a interval we create.
main.hexiwear.updateUI = () => {
$scope.$apply();
};
Because angular doesn't regularly update the UI, we need to call $apply when ever we need a change. Because hexiwear.js does not include angular, we create this function that has access to the $scope variable which can call $apply. There are many ways to handle this, this how we chose to do it.
setInterval(() => {
if(main.hexiwear && main.hexiwear.connected != undefined){
main.hexiwear.refreshValues();
}
},1000);
We create an interval event that launches every second. Ideally, a device will update us when a value changes. Unfortunately, some devices don't offer notify or indicate on characteristics we may be interested in. That is the case with Hexiwear and their accelerometer data. Since we have to poll the value, we create this interval to stay updated.
To see a more advanced example of Hexiwear that uses it's full scope of features as well as a fully implemented UI, please look at Hexiwear - Full App
Comments
Please log in or sign up to comment.