mirror of
https://github.com/thegecko/web-bluetooth-dfu.git
synced 2025-12-11 19:48:12 +08:00
295 lines
10 KiB
HTML
295 lines
10 KiB
HTML
<!--
|
|
Web Bluetooth DFU
|
|
Copyright (c) 2018 Rob Moran
|
|
|
|
The MIT License (MIT)
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
-->
|
|
|
|
<!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>
|
|
html {
|
|
margin: 0;
|
|
padding: 0;
|
|
border: 0;
|
|
}
|
|
body {
|
|
font-family: 'Raleway', sans-serif;
|
|
color: #d7ecfb;
|
|
background-color: #072b44;
|
|
text-align: center;
|
|
}
|
|
h1 {
|
|
font-weight: 400;
|
|
}
|
|
strong {
|
|
font-weight: 600;
|
|
}
|
|
#drop {
|
|
position: relative;
|
|
margin: 20px auto;
|
|
max-width: 500px;
|
|
background-color: rgba(255, 255, 255, 0.10);
|
|
padding: 100px 20px 80px 20px;
|
|
outline: 2px dashed #072b44;
|
|
outline-offset: -10px;
|
|
}
|
|
#drop.hover {
|
|
outline-offset: -10px;
|
|
background-color: rgba(255, 255, 255, 0.15);
|
|
}
|
|
#icon {
|
|
width: 100%;
|
|
fill: #d7ecfb;
|
|
margin-bottom: 30px;
|
|
}
|
|
#github {
|
|
fill: #d7ecfb;
|
|
}
|
|
#github:hover {
|
|
fill: #8bb5ba;
|
|
}
|
|
#file {
|
|
width: 0.1px;
|
|
height: 0.1px;
|
|
opacity: 0;
|
|
z-index: -1;
|
|
}
|
|
#label {
|
|
cursor: pointer;
|
|
}
|
|
#label:hover strong {
|
|
color: #8bb5ba;
|
|
}
|
|
#select {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
visibility: hidden;
|
|
}
|
|
#button {
|
|
font-size: 12px;
|
|
color: inherit;
|
|
margin: 20px auto;
|
|
border: 0px;
|
|
background-color: #1b679c;
|
|
height: 30px;
|
|
padding: 0 30px;
|
|
border-radius: 4px;
|
|
text-transform: uppercase;
|
|
cursor: pointer;
|
|
outline: none;
|
|
}
|
|
#button:hover {
|
|
background: #2674ab;
|
|
}
|
|
#status {
|
|
margin: 20px auto;
|
|
border: 1px solid #d7ecfb;
|
|
width: 70%;
|
|
height: 24px;
|
|
visibility: hidden;
|
|
}
|
|
#bar {
|
|
background: #2674ab;
|
|
width: 0%;
|
|
height: 100%;
|
|
}
|
|
#transfer {
|
|
width: 100%;
|
|
line-height: 24px;
|
|
margin-top: -24px;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<h1>Web Bluetooth Secure DFU</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="select">
|
|
<button id="button">Select Device</button>
|
|
</div>
|
|
|
|
<div id="status">
|
|
<div id="bar"></div>
|
|
<div id="transfer"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<a href="https://github.com/thegecko/web-bluetooth-dfu/">
|
|
<svg id="github" xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 16 16">
|
|
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
|
|
</svg>
|
|
</a>
|
|
|
|
<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="./package.js"></script>
|
|
<script src="../dist/secure-dfu.js"></script>
|
|
|
|
<script>
|
|
let dropEl = document.getElementById("drop");
|
|
let fileEl = document.getElementById("file");
|
|
let selectEl = document.getElementById("select");
|
|
let buttonEl = document.getElementById("button");
|
|
let labelEl = document.getElementById("label");
|
|
let statusEl = document.getElementById("status");
|
|
let transferEl = document.getElementById("transfer");
|
|
let barEl = document.getElementById("bar");
|
|
|
|
let package = null;
|
|
|
|
function setStatus(state) {
|
|
labelEl.textContent = state;
|
|
}
|
|
|
|
function setTransfer(state) {
|
|
if (!state) {
|
|
statusEl.style.visibility = "hidden";
|
|
return;
|
|
}
|
|
selectEl.style.visibility = "hidden";
|
|
statusEl.style.visibility = "visible";
|
|
barEl.style.width = state.currentBytes / state.totalBytes * 100 + '%';
|
|
transferEl.textContent = `${state.currentBytes}/${state.totalBytes} ${state.object} bytes written`;
|
|
}
|
|
|
|
// Load a firmware package
|
|
function setPackage(file) {
|
|
if (!file) return;
|
|
|
|
package = new SecureDfuPackage(file);
|
|
package.load()
|
|
.then(() => {
|
|
setStatus(`Firmware package: ${file.name}`);
|
|
selectEl.style.visibility = "visible";
|
|
})
|
|
.catch(error => {
|
|
selectEl.style.visibility = "hidden";
|
|
statusEl.style.visibility = "hidden";
|
|
setStatus(error);
|
|
});
|
|
}
|
|
|
|
// Choose a device
|
|
function selectDevice() {
|
|
setStatus("Selecting device...");
|
|
setTransfer();
|
|
|
|
const dfu = new SecureDfu(CRC32.buf);
|
|
dfu.addEventListener("log", event => {
|
|
console.log(event.message);
|
|
});
|
|
dfu.addEventListener("progress", event => {
|
|
setTransfer(event);
|
|
});
|
|
|
|
dfu.requestDevice(true)
|
|
.then(device => {
|
|
if (!device) {
|
|
setStatus("DFU mode set, select device again");
|
|
return;
|
|
}
|
|
return update(dfu, device);
|
|
})
|
|
.catch(error => {
|
|
statusEl.style.visibility = "hidden";
|
|
setStatus(error);
|
|
});
|
|
}
|
|
|
|
// Update a device with all firmware from a package
|
|
function update(dfu, device) {
|
|
if (!package) return;
|
|
|
|
Promise.resolve()
|
|
.then(() => package.getBaseImage())
|
|
.then(image => {
|
|
if (image) {
|
|
setStatus(`Updating ${image.type}: ${image.imageFile}...`);
|
|
return dfu.update(device, image.initData, image.imageData);
|
|
}
|
|
})
|
|
.then(() => package.getAppImage())
|
|
.then(image => {
|
|
if (image) {
|
|
setStatus(`Updating ${image.type}: ${image.imageFile}...`);
|
|
return dfu.update(device, image.initData, image.imageData);
|
|
}
|
|
})
|
|
.then(() => {
|
|
setStatus("Update complete!");
|
|
setTransfer();
|
|
fileEl.value = "";
|
|
})
|
|
.catch(error => {
|
|
statusEl.style.visibility = "hidden";
|
|
setStatus(error);
|
|
});
|
|
}
|
|
|
|
fileEl.addEventListener("change", event => {
|
|
setPackage(event.target.files[0]);
|
|
});
|
|
|
|
dropEl.addEventListener("drop", event => {
|
|
setPackage(event.dataTransfer.files[0]);
|
|
});
|
|
|
|
buttonEl.addEventListener("click", selectDevice);
|
|
|
|
["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>
|