This commit is contained in:
Roslund
2025-07-31 08:26:23 +00:00
parent 741cb5b427
commit f6f30c0c69
18 changed files with 240 additions and 10 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
[
{
"hwModelSlug": "rak4631",
"architecture": "nrf52",
"displayName": "RAK WisBlock 4631",
"versions": ["2.6.11.60ec05e", "2.6.4.b89355f"]
},
{
"hwModelSlug": "tlora-t3s3-v1",
"architecture": "esp32",
"displayName": "LILYGO T-LoRa T3-S3",
"versions": ["2.6.11.60ec05e53"]
},
{
"hwModelSlug": "heltec-v3",
"architecture": "esp32",
"displayName": "Heltec V3",
"partitionScheme": "8MB",
"versions": ["2.6.11.60ec05e53"]
}
]
Binary file not shown.
+85
View File
@@ -0,0 +1,85 @@
async function flashFirmware(board, version, fullEraseInstall) {
const { ESPLoader, Transport } = await import('https://unpkg.com/esptool-js/bundle.js')
const logBox = document.getElementById('espLog');
// Helper functions
function log(...m) {
logBox.textContent+=m.join(' ')+'\n';logBox.scrollTop=logBox.scrollHeight;
}
function convertToBinaryString(bytes) {
let binaryString = "";
for (let i = 0; i < bytes.length; i++) {
binaryString += String.fromCharCode(bytes[i]);
}
return binaryString;
}
async function fetchBinaryContent(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
}
const buffer = await response.arrayBuffer();
return convertToBinaryString(new Uint8Array(buffer));
}
try {
log(`\n=== ${board.displayName} · ${version} (${fullEraseInstall ? 'full' : 'update'}) ===`);
let port = await navigator.serial.requestPort({});
let transport = new Transport(port, true);
let loader = new ESPLoader({
transport: transport,
baudrate: 460800,
terminal:{ writeLine:log, write:log, clean(){} }
});
await loader.main();
log('Connected.');
let fileArray, eraseAll = false;
if (!fullEraseInstall) {
// UPDATE  write app only
const app = await fetchBinaryContent(`/firmware/${board.hwModelSlug}/${version}/firmware-${board.hwModelSlug}-${version}-update.bin`);
fileArray = [
{ address: 0x10000, data: app }
];
} else {
// FULL ERASE, and firmware + littlefs flash
// ota is share, need to af logic if supporting other devices...
let otaOffset=0x260000, spiffsOffset=0x300000;
if (board.partitionScheme==='8MB'){ otaOffset=0x340000; spiffsOffset=0x670000; }
if (board.partitionScheme==='16MB'){otaOffset=0x650000; spiffsOffset=0xC90000; }
const factory = await fetchBinaryContent(`/firmware/${board.hwModelSlug}/${version}/firmware-${board.hwModelSlug}-${version}.bin`);
const ota = await fetchBinaryContent(`/firmware/bleota-s3.bin`);
const lfs = await fetchBinaryContent(`/firmware/${board.hwModelSlug}/${version}/littlefswebui-${board.hwModelSlug}-${version}.bin`);
fileArray = [
{ address: 0x0000, data: factory },
{ address: otaOffset, data: ota },
{ address: spiffsOffset,data: lfs }
];
eraseAll = true;
}
const flashOptions = {
fileArray: fileArray,
flashSize: 'keep',
eraseAll: eraseAll,
compress: true,
flashMode: 'keep',
flashFreq: 'keep',
};
await loader.writeFlash(flashOptions);
} catch(e){ console.error(e); log('❌ Error:', e); }
}
+117
View File
@@ -0,0 +1,117 @@
let boards = [];
let selectedBoard = {};
let selectedVersion = '';
fetch('/firmware/firmware.json')
.then(r => r.json())
.then(data => {
boards = data;
showFirmwareAccordion(boards);
});
async function showFirmwareAccordion(boards) {
const accordion = document.getElementById('firmwareAccordion');
boards.forEach((board, idx) => {
const items = board.versions.map(version => {
if (board.architecture === 'nrf52') {
return renderNRF52Item(board.hwModelSlug, version);
}
if (board.architecture === 'esp32') {
return renderESP32Item(board.hwModelSlug, version);
}
}).join('');
const extra = (board.architecture === 'nrf52') ? renderNRF52EraseItem() : '';
accordion.insertAdjacentHTML('beforeend', renderAccordionItem(board, items + extra, idx === 0));
});
}
// ── Template renderers ──
function renderNRF52Item(hwModelSlug, version) {
return `
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>${hwModelSlug}-${version}</span>
<span>
<a class="btn btn-sm btn-outline-secondary me-2"
href="/firmware/${hwModelSlug}/${version}/${hwModelSlug}-${version}-ota.zip">Download OTA</a>
<a class="btn btn-sm btn-outline-primary"
href="/firmware/${hwModelSlug}/${version}/${hwModelSlug}-${version}.uf2">Download UF2</a>
</span>
</li>`;
}
function renderESP32Item(hwModelSlug, version) {
return `
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>${version}</span>
<span>
<button class="btn btn-sm btn-outline-primary open-modal-btn"
data-board="${hwModelSlug}" data-version="${version}">Flash
</button>
</span>
</li>`;
}
function renderNRF52EraseItem() {
return `
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>nrf_erase2.uf2</span>
<a class="btn btn-sm btn-outline-danger"
href="/firmware/nrf_erase2.uf2">Download UF2</a>
</li>`;
}
function renderAccordionItem(board, bodyHtml, isOpen) {
return `
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button ${isOpen ? '' : 'collapsed'}" type="button"
data-bs-toggle="collapse" data-bs-target="#body-${board.hwModelSlug}"
aria-expanded="${isOpen}" aria-controls="body-${board.hwModelSlug}">
<strong>${board.displayName}</strong>
</button>
</h2>
<div id="body-${board.hwModelSlug}" class="accordion-collapse collapse ${isOpen ? 'show' : ''}">
<div class="accordion-body">
<ul class="list-group list-group-flush">
${bodyHtml}
</ul>
</div>
</div>
</div>`;
}
// ── Modal logic ──
document.addEventListener('click', ev => {
if (!ev.target.matches('.open-modal-btn')) return;
const modalEl = document.getElementById('flashModal');
const flashModal = new bootstrap.Modal(modalEl);
const titleEl = document.getElementById('flashModalLabel');
const eraseChk = document.getElementById('eraseSwitch');
const logBox = document.getElementById('espLog');
let currentBoardSlug = ev.target.dataset.board;
selectedVersion = ev.target.dataset.version;
selectedBoard = boards.find(b => b.hwModelSlug === currentBoardSlug);
titleEl.textContent = `Flash ${selectedBoard.displayName} ${selectedVersion}`;
eraseChk.checked = false;
logBox.textContent = '';
flashModal.show();
});
// ── Start Flash Button ──
const startBtn = document.getElementById('startFlashBtn');
startBtn.addEventListener('click', async () => {
const fullEraseInstall = document.getElementById('eraseSwitch').checked;
startBtn.disabled = true;
await flashFirmware(selectedBoard, selectedVersion, fullEraseInstall);
startBtn.disabled = false;
});