17 KiB
05. Velxio BLE Deployer — Dokumentasi Implementasi
Versi: 3.1 Tanggal: 4 Juli 2026 Status: ✅ SELESAI + ENHANCEMENT — Re-deploy fix, serial terminal always visible, baud dropdown, LED patterns, event-based Arduino detection
1. Ringkasan
Integrasi fitur BLE Deployer ke LMS untuk memungkinkan siswa melakukan flashing firmware Arduino langsung dari browser Chrome Android via Web Bluetooth API, menggunakan ESP32-S3 sebagai bridge BLE-to-USB.
Arsitektur:
Webapp (Chrome Android)
| Web Bluetooth API (GATT)
v
ESP32-S3 N16R8 (BLE Peripheral + USB Host)
| STK500v1 over USB CDC (115200 baud)
v
Arduino Uno/Nano (Target)
|
| USB CDC read (9600 baud — match Serial.begin(9600))
v
ESP32-S3 (Serial Bridge via BLE Notify)
|
v
Webapp Serial Monitor
2. Status Implementasi Aktual
| Area | Status | Keterangan |
|---|---|---|
| Frontend SvelteKit | ✅ Selesai | DeployTab.svelte, ble-deployer.ts, deployer.ts — UUID branded, chunks 240, MTU 255 |
| Backend Flask | ✅ Selesai | /velxio-compile return hex_content (tidak berubah) |
| Firmware BLE + Parser | ✅ Selesai | Advertising OK, UUID branded, MTU 255, protocol ACK benar |
| Firmware USB Host | ✅ Selesai | CherryUSB 1.6.1 host CDC, RX claim/unclaim, dual baud rate |
| Firmware STK500v1 | ✅ Selesai | Flash Arduino via STK500v1 (get_sync, signature, prog_page, dll) |
| Firmware Serial Bridge | ✅ Selesai | USB CDC ↔ BLE Notify, throttle 20 pkt/s, flush 500ms |
| Serial Monitor (data flow) | ✅ SELESAI | Data "LED ON"/"LED OFF" dari Arduino tampil di webapp |
| Integrasi Webapp | ✅ Selesai | DeployTab ter-render saat active_tabs include velxio |
| Debug tools | ✅ Selesai | debug-capture.sh — capture CDP + firmware log simultan |
| Re-deploy fix (v3.1) | ✅ FIXED | Stuck di INIT setelah deploy pertama — root cause: ACK sent before serial_bridge_stop + CCCD left at 0 |
| Serial terminal inline (v3.1) | ✅ DONE | Serial terminal tampil saat paired (bukan gated di deploy success) + baud dropdown (9600/19200/38400) |
| LED blink patterns (v3.1) | ✅ DONE | IDLE slow blink (1s), RECEIVING medium (200ms), FLASHING fast (100ms) |
| Arduino event-based (v3.1) | ✅ DONE | Hybrid event+1s poll via CherryUSB usbh_event_handler_t — disconnect ~200ms |
3. Keputusan Desain Final
| Keputusan | Pilihan | Alasan |
|---|---|---|
| UUID BLE | Branded hex 56454C58-494F-0000-... |
UUID lama 0000VELX-IO00 invalid |
| ESP-IDF | v6.0 | v5.1 tidak support ESP32-S3 USB Host dengan benar |
| USB Host | CherryUSB 1.6.1 | TinyUSB tidak stabil di S3 |
| USB Native | GPIO19/20 = Host ke Arduino | Debug log via UART0 |
| Chunk size | 240 byte | Field Len 1-byte (max 255), MTU 255 |
| MTU | 255 | requestMTU(255) di frontend |
| ACK timing END | Setelah STK500v1 flash sukses | Bukan sebelum flash |
| Adv name | "Velxio" (shortened) | Max 31 byte adv data |
| Baud STK500 | 115200 | Arduino bootloader sync (get_sync) |
| Baud Serial Bridge | 9600 | Match Serial.begin(9600) di sketch siswa |
4. UUID BLE (Final)
Service UUID: 56454c58-494f-0000-0000-000000000001
Flashing Char UUID: 56454c58-494f-0000-0000-000000000002
Serial Char UUID: 56454c58-494f-0000-0000-000000000003
Firmware (C, little-endian NimBLE BLE_UUID128_INIT):
// 56454C58-494F-0000-0000-000000000001
static const ble_uuid128_t service_uuid = BLE_UUID128_INIT(
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x4F, 0x49, 0x58, 0x4C, 0x45, 0x56
);
Frontend (TypeScript):
export const BLE_SERVICE_UUID = '56454c58-494f-0000-0000-000000000001';
5. Protokol BLE
Payload: [CMD:1][Index:2 LE][Len:1][Data:N][CRC32:4 LE]
CMD_INIT (0x01) — mulai transfer, data = total CRC (4 byte)
CMD_DATA (0x02) — chunk binary, data = raw hex bytes
CMD_END (0x03) — selesai transfer, data = total CRC (4 byte)
CMD_ACK (0x04) — respon sukses dari firmware
CMD_ERR (0x05) — respon error dari firmware
Alur ACK (Final)
Webapp → INIT → ESP32 kirim ACK segera
Webapp → DATA chunk 0 → ESP32 kirim ACK
Webapp → DATA chunk 1 → ESP32 kirim ACK
...
Webapp → DATA chunk N → ESP32 kirim ACK
Webapp → END → ESP32 TIDAK kirim ACK langsung
ESP32 verifikasi CRC buffer
ESP32 flash via STK500v1
Jika sukses → kirim ACK
Jika gagal → kirim ERR
6. Firmware ESP32-S3
Lokasi: elemes/velxio-deployer-firmware/
6.1 Persyaratan
- ESP-IDF v6.0 (
/home/a2nr/Downloads/lms-c/esp-idf-v6) - Target:
esp32s3 - CherryUSB 1.6.1 (managed component)
6.2 Build & Flash
source /home/a2nr/Downloads/lms-c/esp-idf-v6/export.sh
cd elemes/velxio-deployer-firmware
idf.py build
idf.py -p /dev/ttyACM0 flash
6.3 Konfigurasi (sdkconfig.defaults)
| Key | Value | Keterangan |
|---|---|---|
CONFIG_IDF_TARGET |
esp32s3 |
Target chip |
CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME |
Velxio-Deployer |
Nama BLE |
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU |
255 |
MTU |
CONFIG_SPIRAM |
y |
PSRAM enabled |
CONFIG_SPIRAM_MODE_OCT |
y |
Octal mode (N16R8) |
CONFIG_ESPTOOLPY_FLASHSIZE_16MB |
y |
Flash 16MB |
CONFIG_CHERRYUSB_HOST_CDC_ACM |
y |
USB Host CDC |
CONFIG_ESP_CONSOLE_UART_DEFAULT |
y |
Debug via UART0 |
6.4 Dual Baud Rate Strategy (Kunci Fix Serial Monitor)
Dua baud rate berbeda untuk dua mode operasi:
| Mode | Baud | Fungsi | File |
|---|---|---|---|
| STK500 flashing | 115200 | Arduino bootloader sync | usb_host.c:make_stk500_termios() |
| Serial bridge | 9600 | Match Serial.begin(9600) sketch |
usb_host.c:make_termios() |
Switch terjadi otomatis via usb_host_rx_claim() / usb_host_rx_release():
- Saat flashing:
rx_claim()set 115200 → STK500 sync sukses - Setelah flash:
rx_release()set 9600 → serial bridge cocok dengan baud sketch Arduino
6.5 File Firmware
| File | Fungsi |
|---|---|
main.c |
Entry point, BLE command router |
ble_service.c |
BLE GATT server, advertising, 2 karakteristik |
binary_parser.c |
Parse INIT/DATA/END, CRC32 verification |
state_machine.c |
IDLE → RECEIVING → VERIFYING → FLASHING → SERIAL_BRIDGE → ERROR |
stk500v1.c |
STK500v1 protocol (get_sync, prog_page, dll) |
usb_host.c |
CherryUSB host CDC, RX claim/release, DTR pulse, dual baud |
serial_bridge.c |
USB CDC ↔ BLE Notify bridge, throttle 20 pkt/s |
led_button.c |
LED RGB + tombol Retry |
6.6 Debug
- Serial log: UART0 (CH343 USB-to-UART devkit) via
/dev/ttyACM0 - Tag logging:
BLE_SVC,BIN_PARSER,SM,USB,STK500,SERIAL_BRIDGE - CDP remote debug Chrome Android via ADB +
debug-capture.sh
7. Frontend SvelteKit
7.1 File Terkait
| File | Fungsi |
|---|---|
src/lib/types/deployer.ts |
UUID, constants, types |
src/lib/services/ble-deployer.ts |
BLEHardwareDeployer class |
src/routes/lesson/[slug]/DeployTab.svelte |
UI tab Deploy + Serial Monitor |
src/lib/components/WorkspaceHeader.svelte |
Tab button "Deploy" |
src/lib/components/Footer.svelte |
Version badge |
7.2 Konstanta
export const BLE_SERVICE_UUID = '56454c58-494f-0000-0000-000000000001';
export const BLE_CHAR_FLASHING_UUID = '56454c58-494f-0000-0000-000000000002';
export const BLE_CHAR_SERIAL_UUID = '56454c58-494f-0000-0000-000000000003';
export const CHUNK_SIZE = 240;
export const BLE_TIMEOUT_MS = 5000;
export const MAX_RETRIES = 3;
7.3 Fitur Serial Monitor
- Subscribe karakteristik serial (CCCD notify)
TextDecoder().decode()untuk BLE notify → terminal UI- Auto-scroll ke bawah
- Input form untuk kirim data ke Arduino (Write Without Response)
- Throttle BLE notify dari firmware (max 20 pkt/s)
- Stop serial monitor via unsubscribe
7.4 Build & Deploy
cd elemes/frontend
npm run build
cd elemes
# via container:
./elemes.sh runbuild
8. Debug & Testing Tools
8.1 debug-capture.sh
Lokasi: elemes/debug-capture.sh
Script multifungsi untuk capture log simultan:
| Command | Fungsi |
|---|---|
setup |
Auto-detect ADB + CDP WebSocket URL |
cdp |
Stream console.log Chrome Android via CDP |
firmware |
Source IDF + idf.py monitor pada /dev/ttyACM0 |
all |
CDP + firmware simultan |
sw-clear |
Unregister SW + clear caches + reload page |
eval '<js>' |
Evaluate JS expression di device |
status |
Cek ADB, CDP, ACM0, IDF, Node |
8.2 Remote Debug Chrome Android
# Setup koneksi ADB via Tailscale
adb forward tcp:9222 localabstract:chrome_devtools_remote
# Capture log
./debug-capture.sh setup
./debug-capture.sh all
# On-demand inspect
./debug-capture.sh eval 'document.querySelector(".serial-line")?.textContent'
9. Bug Fix yang Sudah Dilakukan
Firmware
| # | File | Bug | Fix | Sesi |
|---|---|---|---|---|
| B1 | ble_service.c |
UUID invalid (encode "LEXV") | Branded hex little-endian | 22/6 |
| B2 | main.c |
CMD_INIT tidak kirim ACK | binary_parser_process_packet kirim ACK |
22/6 |
| B3 | binary_parser.c |
END ACK dikirim sebelum flash | Hapus ACK dari parser, kirim setelah flash | 23/6 |
| B4 | stk500v1.c |
expect_resp() cuma delay |
Rewrite send_and_expect baca response |
23/6 |
| B5 | usb_host.c |
RX serial bridge vs STK500 bentrok | RX claim/unclaim mechanism | 23/6 |
| B6 | ble_service.c |
conn_handle tidak disimpan |
Simpan dari BLE_GAP_EVENT_CONNECT |
22/6 |
| B7 | ble_service.c |
MTU tidak dinegosiasi | ble_att_set_preferred_mtu(255) |
22/6 |
| B8 | ble_service.c |
serial_char_access_cb kosong |
Forward ke serial_bridge_on_ble_write() |
22/6 |
| B9 | ble_service.c |
Adv tanpa nama | Build explicit adv data | 22/6 |
| B10 | usb_host.c |
TIOCMSET deref alamat 0x6 crash | Pointer ke uint32_t flags |
23/6 |
| B11 | main.c |
CMD_END skip parser → buffer_size=0 |
Panggil binary_parser_process_packet |
23/6 |
| B12 | binary_parser.c |
CRC check duplikat → ERR ganda | Hapus CRC check di parser | 23/6 |
| B13 | usb_host.c |
Baud rate 115200 → null bytes di serial | Dual baud: 115200 STK500 + 9600 serial bridge | 4/7 |
Frontend
| # | File | Bug | Fix | Sesi |
|---|---|---|---|---|
| F1 | deployer.ts |
UUID invalid | Branded hex | 22/6 |
| F2 | ble-deployer.ts |
CHUNK_SIZE=512 tanpa requestMTU() |
240 + requestMTU(255) |
22/6 |
| F3 | ble-deployer.ts |
payload[3]=data.length overflow |
Aman karena chunk ≤ 240 | 22/6 |
| F4 | ble-deployer.ts |
pair() tidak panggil requestMTU |
Tambah requestMTU(255) |
22/6 |
| F5 | ble-deployer.ts |
Race condition ACK | ackResolver BEFORE write | 23/6 |
| F6 | ble-deployer.ts |
DataView byteOffset bug | new Uint8Array(buffer, byteOffset, byteLength) |
23/6 |
| F7 | ble-deployer.ts |
END timeout 5s terlalu pendek | 30s + write fallback | 23/6 |
| F8 | DeployTab.svelte |
Svelte 5 reactivity isConnected |
$state isPaired flag |
22/6 |
| F9 | sw.js |
Service Worker cache stale | Bump cache version | 22/6 |
| F10 | Dockerfile |
Tidak copy package-lock.json |
npm ci + copy lock |
22/6 |
| F11 | ble-deployer.ts |
CCCD subscription hilang saat idle | Refresh stop+startNotifications sebelum END |
30/6 |
| F12 | ble-deployer.ts |
Poll loop resolve di deadline | Hapus resolve() — biarkan ackPromise yg tentukan |
30/6 |
10. Enhancement v3.1 (4 Juli 2026)
10.1 Fix Re-Deploy Stuck di INIT
Root cause: Dua faktor:
- Firmware —
main.c:29-30: ACK INIT dikirim olehbinary_parser_process_packet()(binary_parser.c:70) SEBELUMserial_bridge_stop()dipanggil. Saat re-deploy dari SERIAL_BRIDGE, serial bridge masih aktif dan NimBLE mbuf pool (12 blocks) bisa habis → ACK silent drop dible_service.c:277-278. - Webapp —
ble-deployer.ts:278-284: END flow melakukanstopNotifications()(CCCD=0) lalustartNotifications()fire-and-forget yang race denganreadValue()poll → bisa gagal → CCCD tertinggal 0 → ACK INIT notification tidak di-deliver.
Fix:
main.c:serial_bridge_stop()SEBELUMbinary_parser_process_packet()ble_service.c: Retry mbuf alloc 3x + error log jika gagalsdkconfig.defaults:CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=24serial_bridge.c:vTaskDelay(1)yield di large-packet loopbinary_parser.c: Hapusmemset(hex_buffer, 0, MAX_HEX_SIZE)— block BLE thread ~25ms tidak perluble-deployer.ts:deployHex()stop serial monitor +await startNotifications()ensure CCCD=1 sebelum INITble-deployer.ts: HapusstopNotifications()dari END flow (penyebab CCCD=0)DeployTab.svelte:serialActive = falsedi handleDeploy
10.2 Serial Terminal Always Visible + Baud Dropdown
- Serial terminal render saat
isPaired(bukandeployState === 'success') - Toggle "Mulai"/"Tutup" di serial header
- Auto-start serial setelah deploy success
- Dropdown baud: 9600 / 19200 / 38400 (hanya rate yang aman untuk BLE throughput ~4800 B/s)
CMD_SET_BAUD (0x06)— dikirim via serialChar (writeWithoutResponse)- Firmware
ble_service.cintercept CMD_SET_BAUD →usb_host_set_baud_rate(baud) usb_host.c: configurable baud viaserial_baudstatic variable
10.3 LED Blink Pattern Per-State
| State | Pattern | Interval |
|---|---|---|
| IDLE (advertising) | LED_BLUE_BLINK_SLOW | 1000ms |
| RECEIVING chunks | LED_BLUE_BLINK | 200ms |
| VERIFYING CRC | LED_BLUE solid | — |
| FLASHING STK500 | LED_BLUE_BLINK_FAST | 100ms |
| SERIAL_BRIDGE | LED_GREEN solid | — |
| ERROR_CHECKSUM | LED_RED_BLINK | 100ms |
| ERROR_TARGET | LED_RED solid | — |
10.4 Arduino Connect/Disconnect Event-Based
- CherryUSB
usbh_event_handler_tcallback diusbh_initialize(0, ESP_USB_FS0_BASE, usb_event_handler) - Listen
USBH_EVENT_INTERFACE_START(connect) +USBH_EVENT_DEVICE_DISCONNECTED - Forward via
xTaskNotifyGivekeusb_monitor_task vTaskDelay(1000)→xTaskNotifyWait(0, 0, NULL, pdMS_TO_TICKS(1000))— event OR 1s poll fallback- Constraint: handler jalan di hub thread, hanya boleh notify, tidak boleh
usbh_serial_*
11. Sisa Pekerjaan (Improvement, Bukan Blocker)
| Task | Prioritas | Keterangan |
|---|---|---|
| OTA firmware update | Rencana V2 | Update firmware ESP32 via webapp |
| Multi-board support | Rencana V2 | Target MCU selain ATmega328P |
| Baud rate auto-detect | Rencana V2 | Deteksi baud dari kode siswa |
12. Alur Data End-to-End (Final)
Webapp ESP32-S3 Arduino
| | |
|-- BLE INIT (total CRC) ------->| |
|<-- BLE ACK --------------------| |
|-- BLE DATA (chunk 0..N) ------>| |
|<-- BLE ACK (per chunk) --------| |
|-- BLE END -------------------->| |
| |-- STK500 (115200) --------->|
| | get_sync → signature → |
| | enter_prog → load_addr → |
| | prog_page → leave_prog |
| |<-- flash OK -----------------|
|<-- BLE ACK END (flash OK) -----| |
| |-- switch to 9600 baud |
| | |
|== Serial Monitor mode == | |
|<-- BLE serial Notify ----------|<-- Serial.print (9600) ------|
|-- BLE serial Write ----------->|-- USB CDC write ------------>|
Verifikasi End-to-End (4 Juli 2026)
Dari capture simultan firmware + CDP:
FW: SERIAL_BRIDGE: USB->BLE: 8 bytes, buf=0/240
FW: NimBLE: att_handle=19
CDP: [BLE-SERIAL] RX: 8B "LED ON"
FW: SERIAL_BRIDGE: USB->BLE: 9 bytes, buf=0/240
FW: NimBLE: att_handle=19
CDP: [BLE-SERIAL] RX: 9B "LED OFF"
Data "LED ON" / "LED OFF" dari Arduino muncul di webapp setiap ~1 detik.
13. Catatan Teknis
- Advertising data max 31 byte. Nama "Velxio-Deployer" (15 char) + UUID128 (18 byte) + flags (3 byte) = 38 byte → overflow. Solusi: shorten ke "Velxio" (6 char) → total ~21 byte.
- Frontend scan pakai
namePrefix: 'Velxio'→ tetap match. - Web Bluetooth types (
BluetoothRemoteGATTServer, dll) tidak ada di TypeScript default — perlu@types/web-bluetoothatau ignore. Error ini pre-existing. - Lesson terkunci (prerequisite belum selesai) → backend kosongkan
initial_code_arduino, tapiactive_tabstetap di-return. Tab Deploy tetap muncul. idf.py monitormembutuhkan TTY — tidak bisa dijalankan di bash tool non-interaktif. Alternatif: Python serial raw (python3 -c "import serial; ...").- Debug-capture.sh menggunakan Node.js inline (WebSocket native) untuk CDP — tidak perlu dependency eksternal.