Merge pull request #3 from torlando-tech/flasher-version-picker

Add firmware version picker to web flasher
This commit is contained in:
Torlando authored and GitHub committed 2026-02-25 16:58:27 -05:00
commit 7d687ea8a8
2 files changed
+138 -20

No files matched your search

+3
View File
@@ -102,6 +102,9 @@ jobs:
tag_name: ${{ steps.version.outputs.VERSION }}
name: ${{ steps.version.outputs.VERSION }}
files: |
docs/flasher/firmware/bootloader.bin
docs/flasher/firmware/partitions.bin
docs/flasher/firmware/boot_app0.bin
docs/flasher/firmware/firmware.bin
generate_release_notes: true
draft: true
+135 -20
View File
@@ -175,6 +175,44 @@
}
.warning strong { color: var(--warning); }
.version-select-group {
margin-bottom: 1.5rem;
}
.version-select-group label {
display: block;
color: var(--muted);
font-size: 0.875rem;
margin-bottom: 0.5rem;
}
.version-select-group select {
width: 100%;
padding: 0.625rem 1rem;
background: rgba(0,0,0,0.3);
color: var(--text);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 8px;
font-family: inherit;
font-size: 1rem;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1rem center;
}
.version-select-group select:focus {
outline: none;
border-color: var(--primary);
}
.version-note {
font-size: 0.8rem;
color: var(--warning);
margin-top: 0.5rem;
}
footer {
text-align: center;
margin-top: 2rem;
@@ -202,6 +240,14 @@
</div>
</div>
<div class="version-select-group">
<label for="version-select">Firmware Version</label>
<select id="version-select">
<option value="latest">Latest (loading...)</option>
</select>
<div id="version-note" class="version-note hidden"></div>
</div>
<div class="checkbox-group">
<input type="checkbox" id="erase-checkbox">
<label for="erase-checkbox">Full install (required for first install, erases settings & messages)</label>
@@ -266,18 +312,29 @@
const progressContainer = document.getElementById('progress-container');
const progressFill = document.getElementById('progress-fill');
const progressText = document.getElementById('progress-text');
const versionSelect = document.getElementById('version-select');
const versionNote = document.getElementById('version-note');
const FIRMWARE_FILES = {
full: [
{ offset: 0x0, name: 'bootloader.bin', path: 'firmware/bootloader.bin' },
{ offset: 0x8000, name: 'partitions.bin', path: 'firmware/partitions.bin' },
{ offset: 0xe000, name: 'boot_app0.bin', path: 'firmware/boot_app0.bin' },
{ offset: 0x10000, name: 'firmware.bin', path: 'firmware/firmware.bin' }
],
update: [
{ offset: 0x10000, name: 'firmware.bin', path: 'firmware/firmware.bin' }
]
};
const REQUIRED_FULL_ASSETS = ['bootloader.bin', 'partitions.bin', 'boot_app0.bin', 'firmware.bin'];
// Current firmware path prefix — empty string for relative (latest), or a release download URL
let firmwarePathPrefix = '';
// Track whether the selected release has all assets for full install
let releaseHasFullAssets = true;
function makeFirmwareFiles(prefix) {
return {
full: [
{ offset: 0x0, name: 'bootloader.bin', path: prefix + 'bootloader.bin' },
{ offset: 0x8000, name: 'partitions.bin', path: prefix + 'partitions.bin' },
{ offset: 0xe000, name: 'boot_app0.bin', path: prefix + 'boot_app0.bin' },
{ offset: 0x10000, name: 'firmware.bin', path: prefix + 'firmware.bin' }
],
update: [
{ offset: 0x10000, name: 'firmware.bin', path: prefix + 'firmware.bin' }
]
};
}
function log(message, type = 'info') {
logDiv.classList.remove('hidden');
@@ -300,10 +357,72 @@
progressText.textContent = text;
}
// Identical to rnode-flasher's approach
// Version selector logic
let latestVersion = 'dev';
async function loadVersions() {
// Load deployed manifest version first
try {
const manifest = await fetch('manifest-install.json').then(r => r.json());
latestVersion = manifest.version;
document.getElementById('firmware-version').textContent = latestVersion;
} catch (e) {}
// Update the "Latest" option text
versionSelect.options[0].textContent = `Latest (${latestVersion})`;
// Fetch GitHub releases
try {
const resp = await fetch('https://api.github.com/repos/torlando-tech/pyxis/releases');
if (!resp.ok) return;
const releases = await resp.json();
for (const release of releases) {
if (release.draft || release.prerelease) continue;
const assetNames = release.assets.map(a => a.name);
if (!assetNames.includes('firmware.bin')) continue;
const option = document.createElement('option');
const hasAll = REQUIRED_FULL_ASSETS.every(f => assetNames.includes(f));
option.value = release.tag_name;
option.textContent = release.tag_name + (release.name && release.name !== release.tag_name ? ` — ${release.name}` : '');
option.dataset.downloadUrl = `https://github.com/torlando-tech/pyxis/releases/download/${release.tag_name}/`;
option.dataset.hasFullAssets = hasAll ? 'true' : 'false';
versionSelect.appendChild(option);
}
} catch (e) {
// API failures are non-critical — user can still flash latest
}
}
versionSelect.addEventListener('change', function() {
const selected = this.options[this.selectedIndex];
if (this.value === 'latest') {
firmwarePathPrefix = 'firmware/';
releaseHasFullAssets = true;
document.getElementById('firmware-version').textContent = latestVersion;
} else {
firmwarePathPrefix = selected.dataset.downloadUrl;
releaseHasFullAssets = selected.dataset.hasFullAssets === 'true';
document.getElementById('firmware-version').textContent = this.value;
}
// Handle full-install availability
if (!releaseHasFullAssets) {
eraseCheckbox.checked = false;
eraseCheckbox.disabled = true;
versionNote.textContent = 'Full install unavailable for this release (missing bootloader/partition assets)';
versionNote.classList.remove('hidden');
} else {
eraseCheckbox.disabled = false;
versionNote.classList.add('hidden');
}
});
async function flash() {
const useFullInstall = eraseCheckbox.checked;
const files = useFullInstall ? FIRMWARE_FILES.full : FIRMWARE_FILES.update;
const fw = makeFirmwareFiles(firmwarePathPrefix);
const files = useFullInstall ? fw.full : fw.update;
let transport = null;
flashBtn.disabled = true;
@@ -393,13 +512,9 @@
flashBtn.addEventListener('click', flash);
// Load manifest version
fetch('manifest-install.json')
.then(r => r.json())
.then(data => {
document.getElementById('firmware-version').textContent = data.version;
})
.catch(() => {});
// Initialize: set default prefix and load versions
firmwarePathPrefix = 'firmware/';
loadVersions();
</script>
</body>
</html>