feat: support merged.bin, boot_app0, upload mode toggle + flash guide
- server.js: add boot_app0 field at 0xe000, raise file limit 8→32 MB - index.html: add 병합/분리 mode toggle, boot_app0 drop zone, numbered split zones - app.js: dynamic mode logic, remove hardcoded flashAddress 0x10000, server now auto-selects 0x0 (merged) or 0x10000 (split) - flash-guide.html: step-by-step Korean flash guide with file table, method A/B walkthrough, flash_args explanation, troubleshooting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -110,14 +110,32 @@
|
||||
<div class="alert alert-info" style="margin-bottom:16px;">
|
||||
<span>ℹ</span>
|
||||
<div>
|
||||
Arduino IDE <strong>Sketch → Export Compiled Binary</strong> 로 생성한 .bin 파일을 업로드하세요.
|
||||
<br>병합 바이너리(merged.bin)는 '펌웨어' 하나만 업로드해도 됩니다.
|
||||
Arduino IDE <strong>Sketch → Export Compiled Binary</strong> 후 빌드 폴더에서 파일을 선택하세요.
|
||||
<br><small style="color:var(--text-muted);">
|
||||
분리 파일 사용 시: bootloader(0x0000) + partition-table(0x8000) + app(0x10000)
|
||||
병합 바이너리: <code>*.merged.bin</code> 하나만 업로드 (권장) |
|
||||
분리 파일: bootloader + partitions + boot_app0 + app
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 업로드 방식 선택 -->
|
||||
<div style="display:flex;gap:10px;margin-bottom:18px;">
|
||||
<label id="lbl-merged" style="flex:1;display:flex;align-items:center;gap:10px;padding:12px 16px;border:2px solid var(--accent);border-radius:8px;cursor:pointer;background:rgba(0,200,150,.06);">
|
||||
<input type="radio" name="upload-mode" id="mode-merged" value="merged" checked style="accent-color:var(--accent);">
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:600;">병합 바이너리 <span style="color:var(--accent);font-size:11px;">권장</span></div>
|
||||
<div style="font-size:11px;color:var(--text-muted);">*.merged.bin 하나 업로드 — offset 0x0</div>
|
||||
</div>
|
||||
</label>
|
||||
<label id="lbl-split" style="flex:1;display:flex;align-items:center;gap:10px;padding:12px 16px;border:2px solid var(--border);border-radius:8px;cursor:pointer;">
|
||||
<input type="radio" name="upload-mode" id="mode-split" value="split" style="accent-color:var(--accent);">
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:600;">분리 파일</div>
|
||||
<div style="font-size:11px;color:var(--text-muted);">bootloader + partitions + boot_app0 + app</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<form id="upload-form">
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
@@ -161,40 +179,49 @@
|
||||
</div>
|
||||
|
||||
<!-- 드롭존: 펌웨어 (필수) -->
|
||||
<div class="form-group">
|
||||
<label>펌웨어 바이너리 <span style="color:var(--danger);">*</span></label>
|
||||
<div class="form-group" id="zone-firmware">
|
||||
<label id="lbl-firmware">펌웨어 바이너리 <span style="color:var(--danger);">*</span></label>
|
||||
<div class="drop-zone" id="drop-firmware">
|
||||
<input type="file" id="file-firmware" accept=".bin" />
|
||||
<div class="icon">📦</div>
|
||||
<div class="drop-label">
|
||||
펌웨어 .bin 파일을 드래그하거나 클릭하세요
|
||||
<div style="font-size:11px;margin-top:4px;color:var(--text-muted);">(최대 8 MB)</div>
|
||||
<div class="drop-label" id="firmware-hint">
|
||||
<span id="firmware-hint-text">*.merged.bin 파일을 드래그하거나 클릭하세요</span>
|
||||
<div style="font-size:11px;margin-top:4px;color:var(--text-muted);">(최대 32 MB)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 분리 파일 섹션 (선택) -->
|
||||
<details style="margin-bottom:14px;">
|
||||
<summary style="cursor:pointer;font-size:13px;color:var(--text-muted);margin-bottom:8px;">
|
||||
▸ 분리 바이너리 파일 추가 (선택)
|
||||
</summary>
|
||||
<div style="padding-top:10px;display:flex;flex-direction:column;gap:12px;">
|
||||
<div class="form-group" style="margin:0;">
|
||||
<label>부트로더 (bootloader.bin) — offset 0x0000</label>
|
||||
<div class="drop-zone" id="drop-bootloader" style="padding:16px;">
|
||||
<input type="file" id="file-bootloader" accept=".bin" />
|
||||
<div class="drop-label">부트로더 .bin (선택사항)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin:0;">
|
||||
<label>파티션 테이블 (partition-table.bin) — offset 0x8000</label>
|
||||
<div class="drop-zone" id="drop-partitions" style="padding:16px;">
|
||||
<input type="file" id="file-partitions" accept=".bin" />
|
||||
<div class="drop-label">파티션 테이블 .bin (선택사항)</div>
|
||||
</div>
|
||||
<!-- 분리 파일 섹션 (mode-split 선택 시 표시) -->
|
||||
<div id="split-files" style="display:none;flex-direction:column;gap:12px;margin-bottom:14px;">
|
||||
<div class="form-group" style="margin:0;">
|
||||
<label>① 부트로더 — offset 0x0000 <span style="color:var(--danger);">*</span></label>
|
||||
<div class="drop-zone" id="drop-bootloader" style="padding:16px;">
|
||||
<input type="file" id="file-bootloader" accept=".bin" />
|
||||
<div class="drop-label">*.bootloader.bin 드래그 또는 클릭</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<div class="form-group" style="margin:0;">
|
||||
<label>② 파티션 테이블 — offset 0x8000 <span style="color:var(--danger);">*</span></label>
|
||||
<div class="drop-zone" id="drop-partitions" style="padding:16px;">
|
||||
<input type="file" id="file-partitions" accept=".bin" />
|
||||
<div class="drop-label">*.partitions.bin 드래그 또는 클릭</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin:0;">
|
||||
<label>③ Boot App0 — offset 0xe000 <span style="color:var(--text-muted);font-size:11px;">(OTA 사용 시 필요)</span></label>
|
||||
<div class="drop-zone" id="drop-boot-app0" style="padding:16px;">
|
||||
<input type="file" id="file-boot-app0" accept=".bin" />
|
||||
<div class="drop-label">boot_app0.bin 드래그 또는 클릭</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin:0;">
|
||||
<label>④ 애플리케이션 — offset 0x10000 <span style="color:var(--danger);">*</span></label>
|
||||
<div class="drop-zone" id="drop-app" style="padding:16px;">
|
||||
<input type="file" id="file-app" accept=".bin" />
|
||||
<div class="drop-label">*.ino.bin 드래그 또는 클릭</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="upload-progress" style="display:none;margin-bottom:14px;">
|
||||
<div class="progress-wrap"><div class="progress-bar" id="upload-bar"></div></div>
|
||||
|
||||
@@ -190,29 +190,71 @@ function setupDropZone(zoneId, inputId) {
|
||||
setupDropZone('drop-firmware', 'file-firmware');
|
||||
setupDropZone('drop-bootloader', 'file-bootloader');
|
||||
setupDropZone('drop-partitions', 'file-partitions');
|
||||
setupDropZone('drop-boot-app0', 'file-boot-app0');
|
||||
setupDropZone('drop-app', 'file-app');
|
||||
|
||||
// ── 업로드 모드 토글 ─────────────────────────────────────────
|
||||
const modeMerged = $('#mode-merged');
|
||||
const modeSplit = $('#mode-split');
|
||||
const splitFiles = $('#split-files');
|
||||
const lblMerged = $('#lbl-merged');
|
||||
const lblSplit = $('#lbl-split');
|
||||
const firmwareHintText = $('#firmware-hint-text');
|
||||
|
||||
function applyUploadMode() {
|
||||
const isMerged = modeMerged.checked;
|
||||
splitFiles.style.display = isMerged ? 'none' : 'flex';
|
||||
lblMerged.style.border = isMerged ? '2px solid var(--accent)' : '2px solid var(--border)';
|
||||
lblMerged.style.background = isMerged ? 'rgba(0,200,150,.06)' : '';
|
||||
lblSplit.style.border = isMerged ? '2px solid var(--border)' : '2px solid var(--accent)';
|
||||
lblSplit.style.background = isMerged ? '' : 'rgba(0,200,150,.06)';
|
||||
firmwareHintText.textContent = isMerged
|
||||
? '*.merged.bin 파일을 드래그하거나 클릭하세요'
|
||||
: '앱 바이너리 (*.ino.bin)를 드래그하거나 클릭하세요';
|
||||
}
|
||||
|
||||
modeMerged.addEventListener('change', applyUploadMode);
|
||||
modeSplit.addEventListener('change', applyUploadMode);
|
||||
|
||||
uploadForm.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
|
||||
const fwFile = $('#file-firmware').files[0];
|
||||
if (!fwFile) {
|
||||
alert('펌웨어(.bin) 파일을 선택하세요.');
|
||||
return;
|
||||
const isMerged = modeMerged.checked;
|
||||
const fwFile = $('#file-firmware').files[0];
|
||||
|
||||
if (isMerged) {
|
||||
if (!fwFile) { alert('merged.bin 파일을 선택하세요.'); return; }
|
||||
} else {
|
||||
const appFile = $('#file-app').files[0];
|
||||
if (!fwFile && !appFile) { alert('앱 바이너리(.bin) 파일을 선택하세요.'); return; }
|
||||
// 분리 모드에서는 app 드롭존 파일을 firmware로 사용
|
||||
if (!fwFile && appFile) {
|
||||
const dt = new DataTransfer();
|
||||
dt.items.add(appFile);
|
||||
$('#file-firmware').files = dt.files;
|
||||
}
|
||||
}
|
||||
|
||||
const finalFwFile = $('#file-firmware').files[0] || $('#file-app').files[0];
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('name', $('#fw-name').value || fwFile.name.replace('.bin',''));
|
||||
fd.append('version', $('#fw-version').value || '1.0.0');
|
||||
fd.append('name', $('#fw-name').value || finalFwFile.name.replace(/\.bin$/i,''));
|
||||
fd.append('version', $('#fw-version').value || '1.0.0');
|
||||
fd.append('description', $('#fw-desc').value);
|
||||
fd.append('chipFamily', $('#fw-chip').value);
|
||||
fd.append('flashAddress', '0x10000');
|
||||
fd.append('firmware', fwFile);
|
||||
fd.append('chipFamily', $('#fw-chip').value);
|
||||
fd.append('firmware', finalFwFile);
|
||||
// flashAddress 미전송 → 서버가 bootloader 유무로 자동 결정 (merged:0x0 / split:0x10000)
|
||||
|
||||
const blFile = $('#file-bootloader').files[0];
|
||||
if (blFile) fd.append('bootloader', blFile);
|
||||
if (!isMerged) {
|
||||
const blFile = $('#file-bootloader').files[0];
|
||||
if (blFile) fd.append('bootloader', blFile);
|
||||
|
||||
const ptFile = $('#file-partitions').files[0];
|
||||
if (ptFile) fd.append('partitions', ptFile);
|
||||
const ptFile = $('#file-partitions').files[0];
|
||||
if (ptFile) fd.append('partitions', ptFile);
|
||||
|
||||
const baFile = $('#file-boot-app0').files[0];
|
||||
if (baFile) fd.append('boot_app0', baFile);
|
||||
}
|
||||
|
||||
progressWrap.style.display = 'block';
|
||||
progressBar.style.width = '0%';
|
||||
|
||||
Reference in New Issue
Block a user