Skip to content

Commit 1495a86

Browse files
committed
bs-select
1 parent c370e72 commit 1495a86

File tree

4 files changed

+156
-1
lines changed

4 files changed

+156
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ __pycache__/
44
*.pyc
55
.pdm-python
66
pdm.lock
7+
8+
# Bitstream archives (large binary files)
9+
bitstreams/*.tar.gz

bitstreams/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Bitstreams Directory
2+
3+
Place your .tar.gz bitstream archives in this directory.
4+
They will be copied to build/bitstreams/ during the build process and made available for selection in the web interface.
5+
6+
Example:
7+
- bitstreams/my-bitstream-r4.tar.gz
8+
- bitstreams/bootloader-r3.tar.gz

scripts/server.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ def build_application():
1818
(build_dir / "tiliqua" / "build").mkdir(parents=True)
1919
(build_dir / "rs" / "manifest" / "src").mkdir(parents=True)
2020

21+
# Create bitstreams directory
22+
(build_dir / "bitstreams").mkdir(parents=True)
23+
2124
files_to_copy = [
2225
("src/index.html", "index.html"),
2326
("src/coi-serviceworker.js", "coi-serviceworker.js"),
@@ -49,6 +52,41 @@ def build_application():
4952
(build_dir / "rs" / "manifest" / "__init__.py").touch()
5053
(build_dir / "rs" / "manifest" / "src" / "__init__.py").touch()
5154

55+
# Copy bitstream archives from bitstreams/ directory
56+
bitstreams_src = project_root / "bitstreams"
57+
bitstreams_dest = build_dir / "bitstreams"
58+
bitstreams_list = []
59+
60+
if bitstreams_src.exists():
61+
copied_count = 0
62+
for bitstream_file in sorted(bitstreams_src.glob("*.tar.gz")):
63+
shutil.copy2(bitstream_file, bitstreams_dest / bitstream_file.name)
64+
print(f"Copied bitstream: {bitstream_file.name}")
65+
66+
# Track bitstream info for JS generation
67+
bitstreams_list.append({
68+
'name': bitstream_file.name,
69+
'size': bitstream_file.stat().st_size,
70+
'url': f'bitstreams/{bitstream_file.name}'
71+
})
72+
copied_count += 1
73+
74+
if copied_count == 0:
75+
print("No .tar.gz bitstreams found in bitstreams/ directory")
76+
else:
77+
print("No bitstreams/ directory found - skipping bitstream copy")
78+
79+
# Generate bitstreams.js with the list of available bitstreams
80+
bitstreams_js_content = f"""// Auto-generated list of available bitstreams
81+
// This file is generated during the build process
82+
83+
export const AVAILABLE_BITSTREAMS = {bitstreams_list};
84+
"""
85+
86+
bitstreams_js_path = build_dir / "bitstreams.js"
87+
bitstreams_js_path.write_text(bitstreams_js_content)
88+
print(f"Generated bitstreams.js with {len(bitstreams_list)} bitstream(s)")
89+
5290
print(f"Build completed successfully in {build_dir}")
5391

5492

src/index.html

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,13 @@ <h1>Tiliqua Flash Tool</h1>
108108
</div>
109109

110110
<div class="controls">
111+
<select id="bitstream-source" disabled>
112+
<option value="file">Upload File</option>
113+
<option value="server">Select from Server</option>
114+
</select>
111115
<label for="archive" class="disabled" id="archive-label">Choose File</label>
112116
<input type="file" id="archive" accept=".tar.gz" disabled>
117+
<select id="server-bitstreams" class="disabled" disabled style="display:none;"></select>
113118
<select id="slot" disabled>
114119
<option value="null">Bootloader</option>
115120
<option value="0" selected>Slot 0</option>
@@ -128,6 +133,7 @@ <h1>Tiliqua Flash Tool</h1>
128133

129134
<script type="module">
130135
import { runOpenFPGALoader, Exit } from 'https://cdn.jsdelivr.net/npm/@yowasp/openfpgaloader/gen/bundle.js';
136+
import { AVAILABLE_BITSTREAMS } from './bitstreams.js';
131137

132138
// Global state
133139
let pyodide = null;
@@ -257,8 +263,13 @@ <h1>Tiliqua Flash Tool</h1>
257263
statusEl.textContent = `Connected: ${productName}`;
258264
statusEl.style.color = '#4CAF50';
259265

266+
// Enable bitstream source selection
267+
document.getElementById('bitstream-source').disabled = false;
268+
269+
// Enable file upload by default
260270
document.getElementById('archive').disabled = false;
261271
document.getElementById('archive-label').classList.remove('disabled');
272+
262273
return tiliquaHwVersion;
263274

264275
} catch (error) {
@@ -281,6 +292,56 @@ <h1>Tiliqua Flash Tool</h1>
281292
}
282293
}
283294

295+
function handleBitstreamSourceChange() {
296+
const sourceSelect = document.getElementById('bitstream-source');
297+
const fileLabel = document.getElementById('archive-label');
298+
const fileInput = document.getElementById('archive');
299+
const serverSelect = document.getElementById('server-bitstreams');
300+
const slotSelect = document.getElementById('slot');
301+
const flashButton = document.getElementById('flash');
302+
303+
// Reset selection
304+
selectedFile = null;
305+
slotSelect.disabled = true;
306+
flashButton.disabled = true;
307+
308+
if (sourceSelect.value === 'file') {
309+
// Show file upload, hide server select
310+
fileLabel.style.display = '';
311+
fileInput.style.display = '';
312+
fileLabel.classList.remove('disabled');
313+
fileInput.disabled = false;
314+
serverSelect.style.display = 'none';
315+
serverSelect.disabled = true;
316+
fileLabel.textContent = 'Choose File';
317+
} else {
318+
// Show server select, hide file upload
319+
fileLabel.style.display = 'none';
320+
fileInput.style.display = 'none';
321+
serverSelect.style.display = '';
322+
serverSelect.disabled = false;
323+
serverSelect.classList.remove('disabled');
324+
populateServerBitstreams();
325+
}
326+
}
327+
328+
function populateServerBitstreams() {
329+
const serverSelect = document.getElementById('server-bitstreams');
330+
serverSelect.innerHTML = '<option value="">Select a bitstream...</option>';
331+
332+
for (const bitstream of AVAILABLE_BITSTREAMS) {
333+
const option = document.createElement('option');
334+
option.value = bitstream.url;
335+
const sizeKB = (bitstream.size / 1024).toFixed(1);
336+
option.textContent = `${bitstream.name} (${sizeKB} KB)`;
337+
serverSelect.appendChild(option);
338+
}
339+
340+
if (AVAILABLE_BITSTREAMS.length === 0) {
341+
serverSelect.innerHTML = '<option value="">No bitstreams available</option>';
342+
}
343+
}
344+
284345
async function handleFileSelect(event) {
285346
const file = event.target.files[0];
286347
if (file) {
@@ -292,6 +353,36 @@ <h1>Tiliqua Flash Tool</h1>
292353
}
293354
}
294355

356+
async function handleServerBitstreamSelect(event) {
357+
const url = event.target.value;
358+
if (!url) {
359+
selectedFile = null;
360+
document.getElementById('slot').disabled = true;
361+
document.getElementById('flash').disabled = true;
362+
return;
363+
}
364+
365+
try {
366+
log(`Loading bitstream from server: ${url}`);
367+
const response = await fetch(url);
368+
if (!response.ok) {
369+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
370+
}
371+
372+
selectedFile = await response.arrayBuffer();
373+
const fileName = url.split('/').pop();
374+
log(`Loaded: ${fileName} (${selectedFile.byteLength} bytes)`);
375+
376+
document.getElementById('slot').disabled = false;
377+
document.getElementById('flash').disabled = false;
378+
} catch (error) {
379+
log(`\x1b[31mFailed to load bitstream: ${error.message}\x1b[0m`);
380+
selectedFile = null;
381+
document.getElementById('slot').disabled = true;
382+
document.getElementById('flash').disabled = true;
383+
}
384+
}
385+
295386
async function handleFlash() {
296387
if (!selectedFile || tiliquaHwVersion === null) {
297388
log('\x1b[31mNo file or device\x1b[0m');
@@ -324,7 +415,20 @@ <h1>Tiliqua Flash Tool</h1>
324415
archive_buffer = io.BytesIO(bytes(archive_bytes))
325416
files = {}
326417
327-
with tarfile.open(fileobj=archive_buffer, mode='r:gz') as tar:
418+
# Try to detect if it's gzipped or plain tar
419+
# Check for gzip magic bytes (0x1f 0x8b)
420+
archive_buffer.seek(0)
421+
first_two = archive_buffer.read(2)
422+
archive_buffer.seek(0)
423+
424+
if first_two == b'\\x1f\\x8b':
425+
# Gzipped tar
426+
tar_mode = 'r:gz'
427+
else:
428+
# Plain tar (might happen if browser/server decompressed it)
429+
tar_mode = 'r'
430+
431+
with tarfile.open(fileobj=archive_buffer, mode=tar_mode) as tar:
328432
for member in tar.getmembers():
329433
if member.isfile():
330434
f = tar.extractfile(member)
@@ -446,7 +550,9 @@ <h1>Tiliqua Flash Tool</h1>
446550
initTerminal();
447551
initPyodide();
448552
document.getElementById('connect').addEventListener('click', scanForTiliqua);
553+
document.getElementById('bitstream-source').addEventListener('change', handleBitstreamSourceChange);
449554
document.getElementById('archive').addEventListener('change', handleFileSelect);
555+
document.getElementById('server-bitstreams').addEventListener('change', handleServerBitstreamSelect);
450556
document.getElementById('flash').addEventListener('click', handleFlash);
451557
});
452558
</script>

0 commit comments

Comments
 (0)