mirror of
https://github.com/thegecko/web-bluetooth-dfu.git
synced 2025-12-13 12:38:13 +08:00
Added secure web example
This commit is contained in:
51
examples/dfu_node.js
Normal file
51
examples/dfu_node.js
Normal file
@@ -0,0 +1,51 @@
|
||||
var dfu = require('../index').dfu;
|
||||
var hex2bin = require('../index').hex2bin;
|
||||
var fs = require('fs');
|
||||
|
||||
var log = console.log;
|
||||
dfu.addLogger(log);
|
||||
hex2bin.addLogger(log);
|
||||
|
||||
var fileMask = "";
|
||||
var fileName = null;
|
||||
|
||||
var deviceType = process.argv[2];
|
||||
if (!deviceType) {
|
||||
deviceType = "nrf51";
|
||||
log("no device-type specified, defaulting to " + deviceType);
|
||||
}
|
||||
|
||||
switch(deviceType) {
|
||||
case "nrf51":
|
||||
fileMask = "firmware/nrf51_app_{0}.hex";
|
||||
break;
|
||||
case "nrf52":
|
||||
fileMask = "firmware/nrf52_app.hex";
|
||||
break;
|
||||
default:
|
||||
log("unknown device-type: " + deviceType);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
dfu.findDevice({ services: [0x180D] })
|
||||
.then(device => {
|
||||
fileName = fileMask.replace("{0}", device.name === "Hi_Rob" ? "bye" : "hi");
|
||||
log("found device: " + device.name);
|
||||
log("using file name: " + fileName);
|
||||
|
||||
return dfu.writeMode(device);
|
||||
})
|
||||
.then(() => dfu.findDevice({ name: "DfuTarg" }))
|
||||
.then(device => {
|
||||
var file = fs.readFileSync(fileName);
|
||||
var hex = file.toString();
|
||||
var buffer = hex2bin.convert(hex);
|
||||
log("file length: " + buffer.byteLength);
|
||||
|
||||
return dfu.provision(device, buffer);
|
||||
})
|
||||
.then(() => process.exit())
|
||||
.catch(error => {
|
||||
log(error);
|
||||
process.exit();
|
||||
});
|
||||
112
examples/dfu_web.html
Normal file
112
examples/dfu_web.html
Normal file
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>web-bluetooth-dfu</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<style>
|
||||
html, body { width: 100%; padding: 0; margin: 0; font-family: sans-serif; }
|
||||
input { font-size: 20px; }
|
||||
button { font-size: 20px; padding: 0 20px; display: block; margin: 20px auto; }
|
||||
.device-type { width: 160px; margin: 20px auto; white-space: nowrap; }
|
||||
#results { height: 300px; overflow: auto; border: 1px solid lightgray; margin: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="device-type">
|
||||
<input type="radio" name="device-type" id="nrf51" checked>
|
||||
<label for="nrf51">nRF51</label>
|
||||
<input type="radio" name="device-type" id="nrf52">
|
||||
<label for="nrf52">nRF52</label>
|
||||
</div>
|
||||
|
||||
<button onclick="setMode()">Set Mode</button>
|
||||
<button onclick="findDFU()">Transfer</button>
|
||||
<button onclick="both()">Both</button>
|
||||
|
||||
<div id="results"></div>
|
||||
|
||||
<script src="/dist/crc16.js"></script>
|
||||
<script src="/dist/dfu.js"></script>
|
||||
<script src="/dist/hex2bin.js"></script>
|
||||
|
||||
<script>
|
||||
var resultsEl = document.getElementById("results");
|
||||
var urlBase = "//thegecko.github.io/web-bluetooth-dfu/firmware/";
|
||||
|
||||
function log(message) {
|
||||
console.log(message);
|
||||
resultsEl.innerText += message + "\n";
|
||||
resultsEl.scrollTop = resultsEl.scrollHeight;
|
||||
}
|
||||
dfu.addLogger(log);
|
||||
hex2bin.addLogger(log);
|
||||
|
||||
function download(url) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener("load", function() {
|
||||
resolve(this.responseText);
|
||||
});
|
||||
xhr.open("GET", url);
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
function transfer(device) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var url = "";
|
||||
var deviceType = document.querySelector('input[name="device-type"]:checked').id;
|
||||
|
||||
switch(deviceType) {
|
||||
case "nrf51":
|
||||
url = urlBase + "nrf51_app_{0}.hex";
|
||||
url = url.replace("{0}", device.name === "Hi_Rob" ? "bye" : "hi");
|
||||
break;
|
||||
case "nrf52":
|
||||
url = urlBase + "nrf52_app.hex";
|
||||
break;
|
||||
}
|
||||
|
||||
download(url)
|
||||
.then(hex => {
|
||||
var buffer = hex2bin.convert(hex);
|
||||
log("downloaded length: " + buffer.byteLength);
|
||||
|
||||
// dfu.ImageType.Application is the default value for the third parameter
|
||||
// Specify this if you want to upload a softdevice/bootloader
|
||||
return dfu.provision(device, buffer);
|
||||
})
|
||||
.then(() => {
|
||||
log('dfu complete');
|
||||
resolve();
|
||||
})
|
||||
.catch(error => {
|
||||
log(error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setMode() {
|
||||
dfu.findDevice({ services: [0x180D] })
|
||||
.then(device => dfu.writeMode(device))
|
||||
.catch(log);
|
||||
}
|
||||
|
||||
function findDFU() {
|
||||
dfu.findDevice({ name: "DfuTarg" })
|
||||
.then(device => transfer(device))
|
||||
.catch(log);
|
||||
}
|
||||
|
||||
function both() {
|
||||
dfu.findDevice({ services: [0x180D] })
|
||||
.then(device => dfu.writeMode(device))
|
||||
.then(device => transfer(device))
|
||||
.catch(log);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
98
examples/secure_dfu_node.js
Normal file
98
examples/secure_dfu_node.js
Normal file
@@ -0,0 +1,98 @@
|
||||
var fs = require('fs');
|
||||
var bluetooth = require("bleat").webbluetooth;
|
||||
var crc = require("crc-32");
|
||||
var progress = require('progress');
|
||||
var secureDfu = require("../index").secure;
|
||||
var bluetoothDevices = [];
|
||||
|
||||
var dat = fs.readFileSync("test_images_update_nrf52832/dfu_test_app_hrm_s132/nrf52832_xxaa.dat");
|
||||
var bin = fs.readFileSync("test_images_update_nrf52832/dfu_test_app_hrm_s132/nrf52832_xxaa.bin");
|
||||
|
||||
function logError(error) {
|
||||
console.log(error);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('readable', () => {
|
||||
var input = process.stdin.read();
|
||||
if (input === '\u0003') {
|
||||
process.exit();
|
||||
} else {
|
||||
var index = parseInt(input);
|
||||
if (index && index <= bluetoothDevices.length) {
|
||||
process.stdin.setRawMode(false);
|
||||
selectDevice(index - 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function toArrayBuffer(buffer) {
|
||||
var ab = new ArrayBuffer(buffer.length);
|
||||
var view = new Uint8Array(ab);
|
||||
for (var i = 0; i < buffer.length; ++i) {
|
||||
view[i] = buffer[i];
|
||||
}
|
||||
return ab;
|
||||
}
|
||||
|
||||
function selectDevice(index) {
|
||||
var bar = null;
|
||||
var device = bluetoothDevices[index];
|
||||
var dfu = new secureDfu(crc.buf);
|
||||
dfu.addEventListener("progress", event => {
|
||||
if (bar) bar.update(event.currentBytes / event.totalBytes);
|
||||
});
|
||||
|
||||
console.log();
|
||||
|
||||
dfu.connect(device)
|
||||
.then(() => {
|
||||
process.stdout.write("Transferring init packet...");
|
||||
return dfu.transferInit(toArrayBuffer(dat));
|
||||
})
|
||||
.then(data => {
|
||||
console.log("Done");
|
||||
var data = toArrayBuffer(bin);
|
||||
bar = new progress('Transferring firmware [:bar] :percent :etas', {
|
||||
complete: '=',
|
||||
incomplete: ' ',
|
||||
width: 20,
|
||||
total: data.byteLength
|
||||
});
|
||||
return dfu.transferFirmware(data);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("\nComplete!");
|
||||
process.exit();
|
||||
})
|
||||
.catch(logError);
|
||||
}
|
||||
|
||||
function handleDeviceFound(bluetoothDevice) {
|
||||
var discovered = bluetoothDevices.some(device => {
|
||||
return (device.id === bluetoothDevice.id);
|
||||
});
|
||||
if (discovered) return;
|
||||
|
||||
if (bluetoothDevices.length === 0) {
|
||||
process.stdin.setRawMode(true);
|
||||
console.log("Select a device to update:");
|
||||
}
|
||||
|
||||
bluetoothDevices.push(bluetoothDevice);
|
||||
console.log(bluetoothDevices.length + ": " + bluetoothDevice.name);
|
||||
}
|
||||
|
||||
console.log("Scanning for DFU devices...");
|
||||
bluetooth.requestDevice({
|
||||
filters: [{ services: [0xFE59] }],
|
||||
deviceFound: handleDeviceFound
|
||||
})
|
||||
.then(() => {
|
||||
if (bluetoothDevices.length === 0) {
|
||||
console.log("no devices found");
|
||||
process.exit();
|
||||
}
|
||||
})
|
||||
.catch(logError);
|
||||
261
examples/secure_dfu_web.html
Normal file
261
examples/secure_dfu_web.html
Normal file
@@ -0,0 +1,261 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Web Bluetooth Secure DFU</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Raleway:400,600" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
color: #d7ecfb;
|
||||
background-color: #072b44;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
font-weight: 400;
|
||||
}
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
#drop {
|
||||
margin: 40px auto;
|
||||
max-width: 640px;
|
||||
background-color: rgba(255, 255, 255, 0.10);
|
||||
position: relative;
|
||||
padding: 100px 0px 70px;
|
||||
outline: 2px dashed #072b44;
|
||||
outline-offset: -10px;
|
||||
}
|
||||
#drop.hover {
|
||||
outline-offset: -10px;
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
#icon {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
fill: #d7ecfb;
|
||||
display: block;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
#file {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
#label {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
#label:hover strong {
|
||||
color: #8bb5ba;
|
||||
}
|
||||
#update {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
visibility: hidden;
|
||||
}
|
||||
#button {
|
||||
font-size: 12px;
|
||||
color: #d5e8f1;
|
||||
margin: 20px auto;
|
||||
border: 0px;
|
||||
background-color: #1b679c;
|
||||
outline: none;
|
||||
height: 30px;
|
||||
padding: 0 30px;
|
||||
border-radius: 4px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
#button:hover {
|
||||
background: #2674ab;
|
||||
}
|
||||
#status {
|
||||
position: relative;
|
||||
margin: 20px auto;
|
||||
border: 1px solid #d7ecfb;
|
||||
width: 400px;
|
||||
height: 24px;
|
||||
visibility: hidden;
|
||||
}
|
||||
#bar {
|
||||
background: #2674ab;
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
}
|
||||
#transfer {
|
||||
position: absolute;
|
||||
line-height: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Web Bluetooth Secure Device Firmware Update</h1>
|
||||
<div id="drop">
|
||||
<svg id="icon" xmlns="http://www.w3.org/2000/svg" width="50" height="43" viewBox="0 0 50 43">
|
||||
<path d="M48.4 26.5c-.9 0-1.7.7-1.7 1.7v11.6h-43.3v-11.6c0-.9-.7-1.7-1.7-1.7s-1.7.7-1.7 1.7v13.2c0 .9.7 1.7 1.7 1.7h46.7c.9 0 1.7-.7 1.7-1.7v-13.2c0-1-.7-1.7-1.7-1.7zm-24.5 6.1c.3.3.8.5 1.2.5.4 0 .9-.2 1.2-.5l10-11.6c.7-.7.7-1.7 0-2.4s-1.7-.7-2.4 0l-7.1 8.3v-25.3c0-.9-.7-1.7-1.7-1.7s-1.7.7-1.7 1.7v25.3l-7.1-8.3c-.7-.7-1.7-.7-2.4 0s-.7 1.7 0 2.4l10 11.6z"/>
|
||||
</svg>
|
||||
|
||||
<input id="file" type="file" />
|
||||
<label id="label" for="file">
|
||||
<strong>Choose a firmware package</strong>
|
||||
<span>or drag it here</span>
|
||||
</label>
|
||||
|
||||
<div id="update">
|
||||
<button id="button">Update</button>
|
||||
</div>
|
||||
|
||||
<div id="status">
|
||||
<div id="bar" />
|
||||
<div id="transfer" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/crc-32/1.0.2/crc32.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script>
|
||||
<script src="/dist/secure-dfu.js"></script>
|
||||
|
||||
<script>
|
||||
let dropEl = document.getElementById("drop");
|
||||
let fileEl = document.getElementById("file");
|
||||
let updateEl = document.getElementById("update");
|
||||
let labelEl = document.getElementById("label");
|
||||
let statusEl = document.getElementById("status");
|
||||
let transferEl = document.getElementById("transfer");
|
||||
let barEl = document.getElementById("bar");
|
||||
|
||||
let package = null;
|
||||
let manifest = null;
|
||||
|
||||
function updateStatus(state) {
|
||||
labelEl.textContent = state;
|
||||
}
|
||||
|
||||
function updateTransfer(state) {
|
||||
if (!state) {
|
||||
statusEl.style.visibility = "hidden";
|
||||
return;
|
||||
}
|
||||
updateEl.style.visibility = "hidden";
|
||||
statusEl.style.visibility = "visible";
|
||||
barEl.style.width = state.currentBytes / state.totalBytes * 100 + '%';
|
||||
transferEl.textContent = `${state.currentBytes}/${state.totalBytes} bytes written`;
|
||||
}
|
||||
|
||||
function updatePackage(file) {
|
||||
if (!file) return;
|
||||
|
||||
JSZip.loadAsync(file)
|
||||
.then(zipFile => {
|
||||
try {
|
||||
package = zipFile;
|
||||
return zipFile.file("manifest.json").async("string");
|
||||
} catch(e) {
|
||||
throw new Error("Unable to find manifest, is this a proper DFU package?");
|
||||
}
|
||||
})
|
||||
.then(content => {
|
||||
manifest = JSON.parse(content).manifest;
|
||||
updateStatus(`Package: ${file.name}`);
|
||||
updateEl.style.visibility = "visible";
|
||||
})
|
||||
.catch(error => {
|
||||
updateEl.style.visibility = "hidden";
|
||||
statusEl.style.visibility = "hidden";
|
||||
updateStatus(error);
|
||||
});
|
||||
}
|
||||
|
||||
function transfer() {
|
||||
if (!package) return;
|
||||
updateStatus(`Selecting device...`);
|
||||
updateTransfer();
|
||||
|
||||
const dfu = new SecureDfu(CRC32.buf);
|
||||
dfu.addEventListener("log", event => {
|
||||
console.log(event.message);
|
||||
});
|
||||
dfu.addEventListener("progress", event => {
|
||||
updateTransfer(event);
|
||||
});
|
||||
|
||||
dfu.requestDevice()
|
||||
.then(device => {
|
||||
return dfu.connect(device);
|
||||
})
|
||||
.then(() => {
|
||||
for (type of ["softdevice", "bootloader", "softdevice_bootloader"]) {
|
||||
if (manifest[type]) {
|
||||
return transferImage(dfu, manifest[type]);
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (manifest.application) {
|
||||
return transferImage(dfu, manifest.application);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
updateStatus("Update complete!");
|
||||
updateTransfer();
|
||||
})
|
||||
.catch(error => {
|
||||
statusEl.style.visibility = "hidden";
|
||||
updateStatus(error);
|
||||
});
|
||||
}
|
||||
|
||||
function transferImage(dfu, manifest) {
|
||||
return package.file(manifest.dat_file).async("arraybuffer")
|
||||
.then(data => {
|
||||
updateStatus(`Transferring init: ${manifest.dat_file}...`);
|
||||
return dfu.transferInit(data);
|
||||
})
|
||||
.then(() => {
|
||||
return package.file(manifest.bin_file).async("arraybuffer");
|
||||
})
|
||||
.then(data => {
|
||||
updateStatus(`Transferring firmware: ${manifest.bin_file}...`);
|
||||
return dfu.transferFirmware(data);
|
||||
});
|
||||
}
|
||||
|
||||
fileEl.addEventListener("change", event => {
|
||||
updatePackage(event.target.files[0]);
|
||||
});
|
||||
|
||||
dropEl.addEventListener("drop", event => {
|
||||
updatePackage(event.dataTransfer.files[0]);
|
||||
});
|
||||
|
||||
updateEl.addEventListener("click", transfer);
|
||||
|
||||
["drag", "dragstart", "dragend", "dragover", "dragenter", "dragleave", "drop"].forEach(eventName => {
|
||||
dropEl.addEventListener(eventName, event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
});
|
||||
|
||||
["dragover", "dragenter"].forEach(eventName => {
|
||||
dropEl.addEventListener(eventName, event => {
|
||||
dropEl.classList.add("hover");
|
||||
});
|
||||
});
|
||||
|
||||
["dragleave", "dragend", "drop"].forEach(eventName => {
|
||||
dropEl.addEventListener(eventName, event => {
|
||||
dropEl.classList.remove("hover");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user