브라우저 기반 ESP32 펌웨어 플래시 시스템
Docker 컨테이너 아키텍처 기술 분석 보고서
ESP32 Web Flasher는 ESP32 계열 마이크로컨트롤러에 펌웨어를 플래시하는 작업을 웹 브라우저에서 수행할 수 있게 해주는 Docker 기반 웹 애플리케이션입니다. Arduino IDE, esptool.py 등 별도 소프트웨어 설치 없이 Chrome/Edge 브라우저와 USB 케이블만으로 동작합니다.
http://localhost:3100| 칩 | USB 방식 | 비고 |
|---|---|---|
| ESP32-S3 | 내장 USB-OTG (드라이버 불필요) | 권장 |
| ESP32-S2 | 내장 USB-OTG | 지원 |
| ESP32-C3 | USB-CDC | 지원 |
| ESP32 | USB-UART 변환기 (CP2102 등) | 변환칩 필요 |
두 개의 Docker 컨테이너가 Docker Compose로 오케스트레이션됩니다. Nginx가 리버스 프록시 역할을 하여 정적 파일 서빙과 API 라우팅을 모두 담당합니다.
| 컨테이너 | 이미지 | 내부 포트 | 외부 포트 | 역할 |
|---|---|---|---|---|
esp32-flasher-frontend |
nginx:alpine | 80 | 3100 | 정적 파일 서빙 + 리버스 프록시 |
esp32-flasher-backend |
node:20-alpine | 3000 | 비공개 (내부망) | REST API + 파일 관리 |
location /api/ → proxy_pass http://backend:3000/api/ location /firmware/ → proxy_pass http://backend:3000/firmware/ location / → try_files $uri /index.html (SPA 라우팅)
백엔드는 외부에 포트를 노출하지 않으며, Docker 내부 DNS(backend:3000)를 통해서만 접근됩니다.
Express.js 기반 REST API 서버. Multer로 멀티파트 업로드 처리, UUID로 펌웨어 식별자 생성, 파일시스템 기반 JSON 메타데이터 관리. esp-web-tools가 요구하는 manifest.json을 동적으로 생성하여 플래싱 파라미터를 제공합니다.
Vanilla JavaScript로 작성된 SPA 로직. Web Serial API로 ESP32 연결을 확인하고, XHR 기반 업로드 진행률 추적, 드래그&드롭 파일 선택, esp-web-install-button 웹 컴포넌트 manifest 동적 주입을 담당합니다.
SPA 라우팅(try_files), API 역방향 프록시, 펌웨어 파일 서빙을 하나의 Nginx 설정으로 처리합니다. 백엔드는 Docker DNS를 통해 참조됩니다.
backend/frontend 두 서비스를 정의합니다. backend 헬스체크(30초 간격), frontend는 backend depends_on, 펌웨어는 Named Volume으로 영속 저장됩니다.
| 구성요소 | 기술 | 버전 | 용도 |
|---|---|---|---|
| 컨테이너 런타임 | Docker Engine | 20.10+ | 컨테이너 빌드·실행 |
| 오케스트레이션 | Docker Compose | v2 | 멀티서비스 관리 |
| 스토리지 | Docker Named Volume | — | 펌웨어 바이너리 영속 저장 |
| 메타데이터 DB | JSON 파일 (_metadata.json) | — | 펌웨어 정보 레지스트리 |
multipart/form-data 방식으로 최대 3개의 .bin 파일(firmware, bootloader, partitions)을 동시 업로드합니다.
각 파일은 타임스탬프-랜덤4바이트.bin 형태로 저장되고,
UUID 기반 메타데이터가 JSON 파일에 기록됩니다.
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Product v1.0",
"version": "1.0.0",
"chipFamily": "ESP32-S3",
"createdAt": "2025-05-17T10:00:00.000Z",
"parts": [
{ "file": "1747123456-ab12.bin", "offset": "0x0000", "label": "Bootloader" },
{ "file": "1747123456-cd34.bin", "offset": "0x8000", "label": "Partition Table" },
{ "file": "1747123456-ef56.bin", "offset": "0x10000", "label": "Application" }
]
}
백엔드가 저장된 메타데이터를 esp-web-tools 표준 manifest 형식으로 변환합니다. offset은 16진수 문자열에서 정수로 자동 변환됩니다.
GET /api/firmware/:id/manifest
{
"name": "My Product v1.0",
"version": "1.0.0",
"new_install_prompt_erase": true,
"builds": [{
"chipFamily": "ESP32-S3",
"parts": [
{ "path": "/firmware/files/xxxx.bin", "offset": 0 },
{ "path": "/firmware/files/yyyy.bin", "offset": 32768 },
{ "path": "/firmware/files/zzzz.bin", "offset": 65536 }
]
}]
}
플래시 전에 ESP32가 올바르게 연결되었는지 VID/PID로 확인합니다.
Espressif의 USB Vendor ID는 0x303A로, 이 값을 감지하면 ESP32 장치로 판단합니다.
| VID | 장치 | 비고 |
|---|---|---|
0x303A | Espressif ESP32 (내장 USB) | Espressif 확인 |
0x10C4 | Silicon Labs CP210x | USB-UART 변환기 |
0x1A86 | CH340/CH341 | USB-UART 변환기 |
| 기타 | 알 수 없는 장치 | 경고 표시 |
DataTransfer API를 이용한 드래그&드롭 인터페이스와 XHR 기반 업로드 진행률 추적을 구현합니다.
fetch()는 업로드 진행률을 지원하지 않으므로 XMLHttpRequest를 직접 사용합니다.
| 타입 | CSS 클래스 | 색상 | 용도 |
|---|---|---|---|
| ok | line-ok | teal (#00c896) | 성공 메시지 |
| err | line-err | red (#f85149) | 오류 메시지 |
| warn | line-warn | yellow (#e3b341) | 경고 메시지 |
| info | line-info | muted (#8b949e) | 정보 메시지 |
| 메서드 | 경로 | 설명 | 응답 |
|---|---|---|---|
| GET | /api/health |
헬스 체크 (Docker 프로브용) | { status: "ok", timestamp } |
| GET | /api/firmware |
펌웨어 목록 조회 | 메타데이터 배열 JSON |
| POST | /api/firmware/upload |
펌웨어 업로드 (multipart) | { success, id, firmware } |
| GET | /api/firmware/:id/manifest |
esp-web-tools manifest 생성 | manifest JSON |
| DELETE | /api/firmware/:id |
펌웨어 삭제 (파일 + 메타데이터) | { success: true } |
| GET | /firmware/files/:filename |
바이너리 파일 정적 서빙 | application/octet-stream |
| 필드명 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
firmware | file (.bin) | 필수 | — | 애플리케이션 바이너리 |
bootloader | file (.bin) | 선택 | — | 부트로더 (offset 0x0000) |
partitions | file (.bin) | 선택 | — | 파티션 테이블 (offset 0x8000) |
name | string | 선택 | 파일명 | 펌웨어 이름 |
version | string | 선택 | 1.0.0 | 버전 문자열 |
chipFamily | string | 선택 | ESP32-S3 | 칩 패밀리 |
flashAddress | string (hex) | 선택 | 0x10000 | 앱 플래시 주소 |
플래싱 단계에서 .bin 파일은 서버에서 브라우저로 다운로드된 후, 브라우저가 직접 Web Serial API를 통해 ESP32에 씁니다. 서버는 플래시 과정에 관여하지 않으므로 네트워크 지연의 영향이 최소화됩니다.
| 항목 | 구현 여부 | 방법 |
|---|---|---|
| 파일 유형 제한 | ✓ 구현 | Multer fileFilter — .bin 확장자만 허용 |
| 파일 크기 제한 | ✓ 구현 | 8 MB 상한 (limits.fileSize) |
| XSS 방지 | ✓ 구현 | escHtml() 함수로 사용자 입력 이스케이프 |
| CORS 제어 | ✓ 구현 | ALLOWED_ORIGIN 환경변수로 설정 가능 |
| 안전한 파일명 | ✓ 구현 | 원본 파일명 미사용, 타임스탬프+랜덤 생성 |
| 인증/인가 | 미구현 | 현재 MVP 단계, 토큰 인증 추가 필요 |
| Rate Limiting | 미구현 | 무차별 업로드 공격 방지 필요 |
| HTTPS | 미구현 | 외부 배포 시 필수 (현재 HTTP) |
# HTTPS (Nginx SSL 설정 예시)
server {
listen 443 ssl;
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;
...
}
# Flash Encryption 활성화 (esptool)
espefuse.py --chip esp32s3 burn_efuse DIS_DOWNLOAD_PLAIN_DECRYPT
| 항목 | 백엔드 | 프론트엔드 |
|---|---|---|
| 베이스 이미지 | node:20-alpine | nginx:alpine |
| 예상 이미지 크기 | ~400 MB | ~40 MB |
| 노출 포트 | 없음 (내부 전용) | 3100:80 |
| 재시작 정책 | unless-stopped | unless-stopped |
| 헬스 체크 | 30초 간격 (3회 재시도) | 없음 |
| 볼륨 | firmware_uploads:/app/uploads | 없음 |
| 변수명 | 기본값 | 설명 |
|---|---|---|
PORT | 3000 | 백엔드 리스닝 포트 |
NODE_ENV | production | Node.js 실행 환경 |
ALLOWED_ORIGIN | * (전체 허용) | CORS 허용 오리진 |
# 빌드 및 시작 docker compose up -d --build # 로그 확인 docker compose logs -f # 재시작 docker compose restart # 중지 (데이터 유지) docker compose down # 완전 초기화 (데이터 삭제) docker compose down -v
| 단계 | 기능 | 우선순위 |
|---|---|---|
| v1.1 | HTTPS 지원 (Nginx SSL/TLS), 업로드 인증 토큰 | 높음 |
| v1.2 | Rate Limiting, 플래시 이력 로깅, MAC 주소 기반 기기 인증 | 중간 |
| v2.0 | 결제 연동 (Stripe), 1회용 플래시 토큰, 라이선스 서버 | 계획 |
| v2.1 | S3 호환 스토리지 연동, CDN 배포, 다국어 UI | 계획 |
| v3.0 | OTA 업데이트 통합, 구독 모델, 관리자 대시보드 | 계획 |
Espressif 공식 esp-web-tools를 기반으로 하므로 기술 안정성이 높습니다. ESP32-S3의 내장 USB-OTG 지원으로 별도 USB-UART 변환 칩 없이 플래싱이 가능하고, 이는 제품 원가 절감으로 이어집니다. MAC 주소 기반 라이선스 바인딩(ESP32의 eFuse MAC은 위변조 난이도 높음)과 결합하면 펌웨어 유료 판매 플랫폼으로 확장할 수 있습니다.
현재 MVP 아키텍처(파일 기반 JSON 메타데이터)는 소규모 운영에 적합하며, 수백~수천 건 이상의 트래픽이 예상되면 PostgreSQL 등 관계형 DB와 S3 호환 스토리지로 마이그레이션이 권장됩니다.
ESP32 Web Flasher 기술 분석 보고서 — 2025년 5월 작성