Files
web-bluetooth-dfu/dist/dfu.js
2016-02-04 10:40:31 -06:00

344 lines
12 KiB
JavaScript

/*
* Protocol from:
* http://developer.nordicsemi.com/nRF51_SDK/nRF51_SDK_v8.x.x/doc/8.1.0/s110/html/a00103.html
*/
// https://github.com/umdjs/umd
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['es6-promise', 'bleat'], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS
module.exports = factory(Promise, require('bleat'));
} else {
// Browser globals with support for web workers (root is window)
root.dfu = factory(Promise, root.navigator.bluetooth);
}
}(this, function(Promise, bluetooth) {
"use strict";
var packetSize = 20;
var notifySteps = 40;
var serviceUUID = "00001530-1212-efde-1523-785feabcd123";
var controlUUID = "00001531-1212-efde-1523-785feabcd123";
var packetUUID = "00001532-1212-efde-1523-785feabcd123";
var versionUUID = "00001534-1212-efde-1523-785feabcd123";
var ImageType = {
None: 0,
SoftDevice: 1,
Bootloader: 2,
SoftDevice_Bootloader: 3,
Application: 4
};
var littleEndian = (function() {
var buffer = new ArrayBuffer(2);
new DataView(buffer).setInt16(0, 256, true);
return new Int16Array(buffer)[0] === 256;
})();
var controlChar = null;
var packetChar = null;
var versionChar = null;
var server = null;
// Hack to see debug info
var resultsEl = document.getElementById("results");
function logFn(message) {
console.log(message);
resultsEl.innerText += message + "\n";
}
function findDevice(filters) {
return bluetooth.requestDevice({
filters: [ filters ],
optionalServices: [serviceUUID]
});
}
function writeMode(device) {
return new Promise(function(resolve, reject) {
// Disconnect event currently not implemented
/*
device.addEventListener("gattserverdisconnected", () => {
logFn("modeData written");
resolve();
});
*/
connect(device)
.then(() => {
logFn("writing modeData...");
controlChar.writeValue(new Uint8Array([1]));
return server.disconnect();
})
.then(() => {
logFn("modeData written");
resolve(device);
}).catch(error => {
error = "writeMode error: " + error;
logFn(error);
reject(error);
});
});
}
function provision(device, arrayBuffer, imageType) {
return new Promise(function(resolve, reject) {
imageType = imageType || ImageType.Application;
connect(device)
.then(() => {
if (versionChar) {
versionChar.readValue()
.then(data => {
var view = new DataView(data);
var major = view.getUint8(0);
var minor = view.getUint8(1);
return transfer(arrayBuffer, imageType, major, minor);
});
} else {
// Default to version 6.0
return transfer(arrayBuffer, imageType, 6, 0);
}
})
.then(() => {
resolve();
})
.catch(error => {
logFn(error);
reject(error);
});
});
}
function connect(device) {
return new Promise(function(resolve, reject) {
var service = null;
// Disconnect event currently not implemented
/*
device.addEventListener("gattserverdisconnected", () => {
logFn("device disconnected");
service = null;
controlChar = null;
packetChar = null;
versionChar = null;
server = null;
});
*/
device.connectGATT()
.then(gattServer => {
// Connected
server = gattServer;
logFn("connected to device");
return server.getPrimaryService(serviceUUID);
})
.then(primaryService => {
logFn("found DFU service");
service = primaryService;
return service.getCharacteristic(controlUUID);
})
.then(characteristic => {
logFn("found control characteristic");
controlChar = characteristic;
return service.getCharacteristic(packetUUID);
})
.then(characteristic => {
logFn("found packet characteristic");
packetChar = characteristic;
service.getCharacteristic(versionUUID)
.then(() => {
logFn("found version characteristic");
versionChar = characteristic;
resolve();
})
.catch(error => {
resolve();
});
})
.catch(error => {
error = "connect error: " + error;
logFn(error);
reject(error);
});
});
};
var interval;
var offset;
function transfer(arrayBuffer, imageType, majorVersion, minorVersion) {
return new Promise(function(resolve, reject) {
logFn('using dfu version ' + majorVersion + "." + minorVersion);
// Set up receipts
interval = Math.floor(arrayBuffer.byteLength / (packetSize * notifySteps));
offset = 0;
controlChar.addEventListener('characteristicvaluechanged', data => {
var view = new DataView(data);
var opCode = view.getUint8(0);
if (opCode === 16) { // response
var resp_code = view.getUint8(2);
if (resp_code !== 1) {
var error = "error from control: " + resp_code;
logFn(error);
return reject(error);
}
var req_opcode = view.getUint8(1);
if (req_opcode === 1 && majorVersion > 6) {
logFn('write null init packet');
controlChar.writeValue(new Uint8Array([2,0]))
.then(() => {
return packetChar.writeValue(new Uint8Array([0]));
})
.then(() => {
return controlChar.writeValue(new Uint8Array([2,1]));
})
.catch(error => {
error = "error writing init: " + error;
logFn(error);
reject(error);
});
} else if (req_opcode === 1 || req_opcode === 2) {
logFn('complete, send packet count');
var buffer = new ArrayBuffer(3);
var view = new DataView(buffer);
view.setUint8(0, 8);
view.setUint16(1, interval, littleEndian);
controlChar.writeValue(view)
.then(() => {
logFn("sent packet count: " + interval);
return controlChar.writeValue(new Uint8Array([3]));
})
.then(() => {
logFn("sent receive");
return writePacket(arrayBuffer, 0);
})
.catch(error => {
error = "error sending packet count: " + error;
logFn(error);
reject(error);
});
} else if (req_opcode === 3) {
logFn('complete, check length');
controlChar.writeValue(new Uint8Array([7]))
.catch(error => {
error = "error checking length: " + error;
logFn(error);
reject(error);
});
} else if (req_opcode === 7) {
var bytecount = view.getUint32(3, littleEndian);
logFn('length: ' + bytecount);
logFn('complete, validate...');
controlChar.writeValue(new Uint8Array([4]))
.catch(error => {
error = "error validating: " + error;
logFn(error);
reject(error);
});
} else if (req_opcode === 4) {
logFn('complete, reset...');
controlChar.writeValue(new Uint8Array([5]))
.then(() => {
resolve();
})
.catch(error => {
error = "error resetting: " + error;
logFn(error);
reject(error);
});
}
} else if (opCode === 17) {
var bytecount = view.getUint32(1, littleEndian);
logFn('transferred: ' + bytecount);
writePacket(arrayBuffer, 0);
}
});
if (!controlChar.properties.notify) {
var error = "controlChar missing notify property";
logFn(error);
return reject(error);
}
logFn("enabling notifications");
controlChar.startNotifications()
.then(() => {
logFn("sending imagetype: " + imageType);
return controlChar.writeValue(new Uint8Array([1, imageType]))
})
.then(() => {
logFn("sent start");
var softLength = (imageType === ImageType.SoftDevice) ? arrayBuffer.byteLength : 0;
var bootLength = (imageType === ImageType.Bootloader) ? arrayBuffer.byteLength : 0;
var appLength = (imageType === ImageType.Application) ? arrayBuffer.byteLength : 0;
var buffer = new ArrayBuffer(12);
var view = new DataView(buffer);
view.setUint32(0, softLength, littleEndian);
view.setUint32(4, bootLength, littleEndian);
view.setUint32(8, appLength, littleEndian);
// Set firmware length
packetChar.writeValue(view)
.then(() => {
logFn("sent buffer size: " + arrayBuffer.byteLength);
})
.catch(error => {
error = "firmware length error: " + error;
logFn(error);
reject(error);
});
})
.catch(error => {
error = "start error: " + error;
logFn(error);
reject(error);
});
});
}
function writePacket(arrayBuffer, offset, count) {
var size = (offset + packetSize > arrayBuffer.byteLength) ? arrayBuffer.byteLength - offset : packetSize;
var packet = arrayBuffer.slice(offset, offset + size);
var view = new Uint8Array(packet);
packetChar.writeValue(view)
.then(() => {
count ++;
offset += packetSize;
if (count < interval && offset < arrayBuffer.byteLength) {
writePacket(arrayBuffer, count);
}
})
.catch(error => {
error = "writePacket error: " + error;
logFn(error);
});
}
return {
ImageType: ImageType,
findDevice: findDevice,
writeMode: writeMode,
provision: provision
};
}));