-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from raleighlittles/feature/webusb
Add WebUSB implementation
- Loading branch information
Showing
2 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
|
||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>WebUSB Example</title> | ||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" | ||
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> | ||
</head> | ||
|
||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" | ||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" | ||
crossorigin="anonymous"></script> | ||
|
||
<body> | ||
|
||
<nav class="navbar navbar-light bg-light"> | ||
<a class="navbar-brand">PlayStation Camera firmware programmer</a> | ||
<a class="btn btn-light" href="#" role="button">About</a> | ||
<img src="https://raleighlittles.github.io/blog/assets/images/logo.png" width="30" height="30" alt=""> | ||
|
||
</nav> | ||
|
||
<div class="container"> | ||
|
||
<div class="row mt-5"> | ||
<div class="column"> | ||
<h2> Step #1 </h2> | ||
</div> | ||
<div class="column ml-3 mr-2"> | ||
<a class="btn btn-outline-primary" id="connectBtn">Connect to Device</a> | ||
</div> | ||
</div> | ||
|
||
<div class="row mt-5"> | ||
|
||
|
||
<div class="column"> | ||
<h2> Step #2 </h2> | ||
</div> | ||
|
||
<div class="column"> | ||
<div class="mt-5 mb-3"> | ||
<label for="firmwareFileUploadBtnLabel" class="form-label">Select a firmware file to install</label> | ||
<input class="form-control" type="file" id="firmwareFileUploadBtn"> | ||
</div> | ||
|
||
</div> | ||
|
||
</div> | ||
|
||
<!-- to be able to change the progress bar later, the id needs to be on the INNER element, not the outer --> | ||
<div class="progress mt-4 mb-4" role="progressbar" aria-label="Example with label" aria-valuenow="0" | ||
aria-valuemin="0" aria-valuemax="100"> | ||
<div class="progress-bar" id="programmingProgressBar" style="width: 0%"></div> | ||
</div> | ||
|
||
|
||
<div class="row"> | ||
<h3> Debug Log </h3> | ||
</div> | ||
|
||
<div class="row"> | ||
<!-- Empty by default, filled in later --> | ||
<textarea id="debugLogTextArea" name="log" rows="10" cols="100"> </textarea> | ||
</div> | ||
|
||
</div> | ||
|
||
<script> | ||
|
||
let usbDevice; | ||
|
||
function updateDebugLog(newString) { | ||
|
||
let debugLogElement = document.getElementById("debugLogTextArea"); | ||
|
||
let newLogEntry = `${Date().toLocaleString()} | ${newString} \n`; | ||
|
||
debugLogElement.innerText += newLogEntry; | ||
|
||
} | ||
|
||
|
||
function updateProgressBar(value_num, value_denom) { | ||
|
||
let progressBarElement = document.getElementById("programmingProgressBar"); | ||
|
||
let newPercentage = `${Math.round((value_num / value_denom) * 100, 2)}%`; | ||
|
||
progressBarElement.style.width = newPercentage; | ||
} | ||
|
||
|
||
async function uploadFirmwareToDevice(usbDevice, firmwareFileAsBytes) { | ||
|
||
const uint16Max = 65536; | ||
|
||
// USB constants | ||
const maxUsbPacketSize = 512; | ||
|
||
const firmwareFileLen = firmwareFileAsBytes.byteLength; | ||
|
||
let fileByteIdx = 0; | ||
|
||
let wValue = 0; | ||
|
||
let lowerTransactionIdx = 0x14; // 20d | ||
let upperTransactionIdx = 0x15; // 21d | ||
|
||
while (fileByteIdx < firmwareFileLen) { | ||
|
||
let pktSize = Math.min(firmwareFileLen - fileByteIdx, maxUsbPacketSize); | ||
let pktEndIdx = fileByteIdx + pktSize; | ||
|
||
let currTransactionIdx; | ||
|
||
if (fileByteIdx < uint16Max) { | ||
currTransactionIdx = lowerTransactionIdx; | ||
} | ||
else { | ||
currTransactionIdx = upperTransactionIdx; | ||
} | ||
|
||
let transactionData = firmwareFileAsBytes.slice(fileByteIdx, pktEndIdx); | ||
|
||
console.log(transactionData); | ||
|
||
await usbDevice.controlTransferOut({ requestType: "standard", recipient: "device", request: 0x0, value: wValue, index: currTransactionIdx }, transactionData); | ||
|
||
//console.log("Transferred", pktSize, "bytes. Value=", wValue, "index=", currTransactionIdx); | ||
|
||
// Increment for next transaction | ||
fileByteIdx += pktSize; | ||
|
||
if (wValue > uint16Max - pktSize) { | ||
// Can't perform the addition because it would overflow | ||
// How much would it overflow by? That's the new value | ||
wValue = (wValue - (uint16Max - pktSize)); | ||
} else { | ||
// Regular case, no overflow | ||
wValue += pktSize; | ||
} | ||
|
||
// Update progress bar | ||
updateProgressBar(fileByteIdx, firmwareFileLen); | ||
|
||
} // end while | ||
|
||
// now send footer packet | ||
|
||
let footerData = Array([0x5B]).buffer; | ||
await usbDevice.controlTransferOut({ requestType: "standard", recipient: "device", request: 0x0, value: 0x2200, index: 0x8018 }, footerData); | ||
|
||
} // end function | ||
|
||
document.getElementById("connectBtn").addEventListener("click", async () => { | ||
|
||
const vendorID = 0x05a9; | ||
const productID = 0x0580; | ||
|
||
navigator.usb.requestDevice({ filters: [{ vendorId: vendorID, productId: productID }] }) | ||
.then(selectedDevice => { | ||
usbDevice = selectedDevice; | ||
return usbDevice.open(); | ||
}).then(() => { | ||
|
||
let msg = `Connected to USB device with vendorID=${vendorID}, productID=${productID} \n`; | ||
console.log(msg); | ||
//debugLogElement.innerText += msg; | ||
updateDebugLog(msg); | ||
|
||
}) | ||
// Request exclusive control over interface #0, remember, the device only has 1 interface | ||
.then(() => usbDevice.claimInterface(0)) | ||
.catch(error => { console.error("Error connecting to USB device!", error); }); | ||
} | ||
|
||
); // end connect click event listener | ||
|
||
let firmwareFileUploadElement = document.getElementById("firmwareFileUploadBtn"); | ||
|
||
// https://stackoverflow.com/a/32556944/1576548 | ||
firmwareFileUploadElement.addEventListener("change", function () { | ||
|
||
// console.log("Received upload: ", firmwareFileUploadElement.value); | ||
|
||
var reader = new FileReader(); | ||
|
||
var fileByteArray = []; | ||
|
||
reader.readAsArrayBuffer(this.files[0]); | ||
|
||
reader.onloadend = function (evt) { | ||
|
||
if (evt.target.readyState = FileReader.DONE) { | ||
|
||
var arrayBuffer = evt.target.result; | ||
|
||
var array = new Uint8Array(arrayBuffer); | ||
|
||
for (var i = 0; i < array.length; i++) { | ||
fileByteArray.push(array[i]); | ||
} | ||
|
||
let msg = `User uploaded file '${firmwareFileUploadElement.value}' with size '${arrayBuffer.byteLength}' bytes \n`; | ||
|
||
console.log(msg); | ||
//debugLogElement.innerText += msg; | ||
updateDebugLog(msg); | ||
|
||
console.log("array", array.buffer); | ||
|
||
//console.log("fileByteArray", fileByteArray); | ||
|
||
uploadFirmwareToDevice(usbDevice, fileByteArray).then(() => console.log("finished uploading firmware!")); | ||
} | ||
} | ||
|
||
}); // end change file event listener | ||
</script> | ||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
|
||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>WebUSB Example</title> | ||
</head> | ||
|
||
<body> | ||
<button id="connectButton">Connect to Device</button> | ||
|
||
<script> | ||
document.getElementById('connectButton').addEventListener('click', async () => { | ||
|
||
const vendorID = 0x05a9; | ||
const productID = 0x0580; | ||
|
||
let usbDevice; | ||
|
||
navigator.usb.requestDevice({ filters: [{ vendorId: vendorID, productId: productID }] }) | ||
.then(selectedDevice => { | ||
usbDevice = selectedDevice; | ||
return usbDevice.open(); // Begin a session. | ||
}).then(() => console.log("Connected to USB device with vendorID=", vendorID, ", productID=", productID)) | ||
// Request exclusive control over interface #0, remember, the device only has 1 interface | ||
.then(() => usbDevice.claimInterface(0)) | ||
.catch(error => { console.error(error); }); | ||
} | ||
|
||
); // end click event listener | ||
</script> | ||
</body> | ||
|
||
</html> |