Skip to content

Commit

Permalink
updated Zephyr Heart Rate monitor sample to work with current API
Browse files Browse the repository at this point in the history
  • Loading branch information
petele committed Oct 11, 2013
1 parent 23e2089 commit 852a258
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 133 deletions.
4 changes: 2 additions & 2 deletions zephyr_hxm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
Very simple Zephyr HXM heart rate monitor driver. This sample uses the bluetooth API to fetch heart rate data from a Zephyr HXM device

## Caveats:
- The bluetooth API is only available on dev-channel Chrom(e|ium)OS
- Resource clean-up isn't happening properly yet: you will likely have to disable/enable bluetooth between runs of the program or the connection will fail
- The bluetooth API is only available on dev-channel


## APIs

Expand Down
30 changes: 27 additions & 3 deletions zephyr_hxm/background.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
// Defines the profile used by the Zephyr heart rate monitor
var HXM_PROFILE = {
uuid: '00001101-0000-1000-8000-00805f9b34fb',
name: 'Zephyr HXM Heart Rate Monitor'
};

chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index.html', {
bounds: {
width: 640,
height: 480
}
width: 600,
height: 350
},
singleton: true,
id: "bluetoothhxm"
}, function(win) {
// Add the profile to the list of profiles we support
chrome.bluetooth.addProfile(HXM_PROFILE, function(r) {
console.log("Profile added");
});

// Make the profile available in the main content window.
win.contentWindow.HXM_PROFILE = HXM_PROFILE;
});
});

function removeProfile() {
console.log("Removing Zephyr HXM profile");
chrome.bluetooth.removeProfile(HXM_PROFILE, function(r) {
console.log("Profile removed");
});
}

7 changes: 3 additions & 4 deletions zephyr_hxm/graph.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<meta http-equiv="X-WebKit-CSP" content="script-src 'self' chrome-extension: http://code.highcharts.com http://ajax.googleapis.com">
<title>
Google Visualization API Sample
</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js" type="text/javascript"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="http://code.highcharts.com/modules/exporting.js"></script>
</head>
Expand Down
12 changes: 7 additions & 5 deletions zephyr_hxm/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<html>
<head>
</head>
<body>
<iframe id="graph" src="graph.html" seamless="seamless" width="580px" height="420px"></iframe>
<input type="button" id="close" value="Quit"></input>
<!-- Uncomment for logging
<pre id="log"></pre>
-->
<iframe id="graph" src="graph.html" seamless="seamless" width="580px" height="300px"></iframe>
<input type="button" id="butQuit" value="Quit"></input>
<input type="button" id="butConnect" value="Connect"></input>
<input type="button" id="butDisconnect" value="Disconnect"></input>
<span id="hr"></span>
</body>
<script src="main.js"></script>
</html>
270 changes: 155 additions & 115 deletions zephyr_hxm/main.js
Original file line number Diff line number Diff line change
@@ -1,135 +1,175 @@
function log(msg) {
var msg_str = (typeof(msg) == 'object') ? JSON.stringify(msg) : msg;
console.log(msg_str);
var READ_INTERVAL = 1000;

var l = document.getElementById('log');
if (l) {
l.innerText += msg_str + '\n';
}
var _socket = null;
var _readIntervalId = null;

function init() {
console.log("Starting Zephyr HXM demo...");

document.getElementById("butDisconnect")
.addEventListener("click", disconnect);
document.getElementById("butConnect")
.addEventListener("click", findAndConnect);
document.getElementById("butQuit").addEventListener("click", closeApp);

// Add the listener to deal with our initial connection
chrome.bluetooth.onConnection.addListener(onConnected);

// Check the paired Bluetooth devices then connect to the HXM
findAndConnect();
}

function updateHeartRate(value) {
document.getElementById('graph').contentWindow.postMessage(
{heartrate: value}, '*');
/*
HXM Specific Function
*/

function updateHeartRate(heartrate) {
document.getElementById("hr").innerText = heartrate;
document.getElementById("graph")
.contentWindow.postMessage({heartrate: heartrate}, "*");
}

var kUUID = '00001101-0000-1000-8000-00805f9b34fb';
var readIntervalId;
var readInterval = function (socket) {
return function() {
chrome.bluetooth.read({socket: {id: socket.id}}, function(data) {
if (chrome.apps.lastError) {
log('Read error:');
log(chrome.runtime.lastError);
window.clearInterval(readIntervalId);
} else {
// Data parsing is based on the code in the openzephyr library:
// http://code.google.com/p/zephyropen/source/browse/zephyropen/src/zephyropen/device/zephyr/ZephyrUtils.java
if (data) {
if ((data.byteLength % 60) != 0) {
log('Payload is wrong size (' + data.byteLength +
'). Discarding.');
return;
}

var offset = 0;
while (offset + 60 <= data.byteLength) {
var data_view = new Uint8Array(data, offset);
offset += 60;

if (data_view[0] != 2) {
log('Check failed data[0] = ' + data_view[0]);
continue;
}

if (data_view[1] != 38) {
log('Check failed data[1] = ' + data_view[1]);
continue;
}

if (data_view[2] != 55) {
log('Check failed data[2] = ' + data_view[2]);
continue;
}

if (data_view[59] != 3) {
log('Check failed data[59] = ' + data_view[59]);
continue;
}

var heartrate = data_view[12];
if (heartrate < 30 || heartrate > 240) {
log('Heartrate out of range (' + heartrate + '). Discarding.');
return;
}

updateHeartRate(heartrate);
log('HR=' + heartrate);
}
}
}
});
}
function closeApp() {
console.log("Close app.");
disconnect();
window.close();
}

function startReads(socket) {
log('Starting reads');
readIntervalId = window.setInterval(readInterval(socket), 1000);
function findAndConnect() {
// Get's the device list, and passes that list to the connectToHXM
// function as a callback.
getDeviceList(connectToHXM);
}

var socketId_;
var connectCallback = function(socket) {
function onConnected(socket) {
console.log("onConnected", socket);
if (socket) {
log('Connected! Socket ID is: ' + socket.id + ' on service ' +
socket.serviceUuid);
startReads(socket);
socketId_ = socket.id;
_socket = socket;
_readIntervalId = window.setInterval(function() {
// Reads the data from the socket and passes it to parseHXMData
chrome.bluetooth.read({socket: _socket}, parseHXMData);
}, READ_INTERVAL);
} else {
log('Failed to connect.');
console.error("Failed to connect.");
}
};
}

function parseHXMData(data) {

// Data parsing is based on the code in the openzephyr library:
// http://code.google.com/p/zephyropen/source/browse/zephyropen/src/zephyropen/device/zephyr/ZephyrUtils.java
if ((data) && ((data.byteLength % 60) === 0)) {
var offset = 0;
while (offset + 60 <= data.byteLength) {
var data_view = new Uint8Array(data, offset);
offset += 60;

if (data_view[0] != 2) {
console.log('Check failed data[0] = ' + data_view[0]);
continue;
}

if (data_view[1] != 38) {
console.log('Check failed data[1] = ' + data_view[1]);
continue;
}

var connectToDevice = function(result) {
if (chrome.runtime.lastError) {
log('Error searching for a device to connect to.');
return;
if (data_view[2] != 55) {
console.log('Check failed data[2] = ' + data_view[2]);
continue;
}

if (data_view[59] != 3) {
console.log('Check failed data[59] = ' + data_view[59]);
continue;
}

var heartrate = data_view[12];
if (heartrate < 30 || heartrate > 240) {
console.log('Heartrate out of range (' + heartrate + '). Discarding.');
return;
}

updateHeartRate(heartrate);
}
} else {
console.error("Error parsing data.", data, data.byteLength);
}
if (result.length == 0) {
log('No devices found to connect to.');
return;
}

function connectToHXM(deviceList) {
console.log("connectToHXM", deviceList);
if (deviceList !== null) {
// Iterates through the device list looking for a device that starts with
// HXM as it's name then tries to connect to that device.
for (var i in deviceList) {
var device = deviceList[i];
if (device.name.indexOf("HXM") === 0) {
console.log("Connecting to HXM", device);
connect(device.address, HXM_PROFILE);
}
}
}
for (var i in result) {
var device = result[i];
if (device.name.indexOf("HXM") === 0) {
log('Connecting to device: ' + device.name + ' @ ' + device.address);
chrome.bluetooth.connect(
{device: {address: device.address}, profile: {uuid: kUUID}}, connectCallback);
}


/*
Generic BlueTooth
*/

function getDeviceList(callback) {
console.log("Searching for bluetooth devices...");
var deviceList = [];

// Get the BlueTooth adapter state and verify it's ready
chrome.bluetooth.getAdapterState(function(adapterState) {
if (adapterState.available && adapterState.powered) {

// Gets the list of paired devices and adds each one to the deviceList
// array, when done calls the callback with the list of devices.
chrome.bluetooth.getDevices({
deviceCallback: function(device) { deviceList.push(device); }
}, function () {
console.log("Devices found", deviceList);
if (callback) {
callback(deviceList);
} else {
console.error("No callback specified.");
}
});
} else {
// If the bluetooth adapter wasn't ready or is unavailble, return null
console.log("Bluetooth adapter not ready or unavailable.");
callback(null);
}
});
}

function connect(deviceAddress, profile) {
console.log("Connecting to device", deviceAddress, profile);
chrome.bluetooth.connect({
device: {address: deviceAddress},
profile: profile
}, function() {
if (chrome.runtime.lastError) {
console.error("Error on connection.", chrome.runtime.lastError.message);
}
});
}

function disconnect() {
console.log("End session.");
if (_readIntervalId !== null) {
console.log("Clearing interval.");
clearInterval(_readIntervalId);
}
};

log('Starting Zephyr HXM demo...');


var kSimulate = false;
if (kSimulate) {
window.setInterval(function() {
updateHeartRate(60 + Math.floor((Math.random()*10)+1));
}, 1000);
} else {
var devicesFound = [];
chrome.bluetooth.getDevices({
profile: {uuid: kUUID},
deviceCallback: function(device) { devicesFound.push(device); }
}, function() {
connectToDevice(devicesFound);
if (_socket !== null) {
console.log("Disconnecting socket.");
chrome.bluetooth.disconnect({socket: _socket}, function() {
console.log("Socket closed.");
_socket = null;
});
}
}

document.getElementById('close').addEventListener('click',
function() {
if (!kSimulate) {
chrome.bluetooth.disconnect({socket: {id: socketId_}});
}
window.close();
});
init();
9 changes: 5 additions & 4 deletions zephyr_hxm/manifest.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
{
"name": "Heart Rate Monitor Integration Sample",
"description": "Demo of Zephyr HXM Heart Rate Monitor interaction",
"version": "1.1",
"version": "1.2",
"manifest_version": 2,
"minimum_chrome_version": "23",
"minimum_chrome_version": "30",
"app": {
"background": {
"scripts": ["background.js"]
}
},
"permissions": [
"bluetooth"
{"bluetooth":[{"uuid": "00001101-0000-1000-8000-00805f9b34fb"}]}
],
"sandbox": {
"pages": ["graph.html"]
}
},
"offline_enabled": true
}

0 comments on commit 852a258

Please sign in to comment.