# 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`): ```c // 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): ```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 ```bash 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 ```typescript 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 ```bash 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 ''` | Evaluate JS expression di device | | `status` | Cek ADB, CDP, ACM0, IDF, Node | ### 8.2 Remote Debug Chrome Android ```bash # 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: 1. **Firmware** — `main.c:29-30`: ACK INIT dikirim oleh `binary_parser_process_packet()` (`binary_parser.c:70`) SEBELUM `serial_bridge_stop()` dipanggil. Saat re-deploy dari SERIAL_BRIDGE, serial bridge masih aktif dan NimBLE mbuf pool (12 blocks) bisa habis → ACK silent drop di `ble_service.c:277-278`. 2. **Webapp** — `ble-deployer.ts:278-284`: END flow melakukan `stopNotifications()` (CCCD=0) lalu `startNotifications()` fire-and-forget yang race dengan `readValue()` poll → bisa gagal → CCCD tertinggal 0 → ACK INIT notification tidak di-deliver. **Fix:** - `main.c`: `serial_bridge_stop()` SEBELUM `binary_parser_process_packet()` - `ble_service.c`: Retry mbuf alloc 3x + error log jika gagal - `sdkconfig.defaults`: `CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=24` - `serial_bridge.c`: `vTaskDelay(1)` yield di large-packet loop - `binary_parser.c`: Hapus `memset(hex_buffer, 0, MAX_HEX_SIZE)` — block BLE thread ~25ms tidak perlu - `ble-deployer.ts`: `deployHex()` stop serial monitor + `await startNotifications()` ensure CCCD=1 sebelum INIT - `ble-deployer.ts`: Hapus `stopNotifications()` dari END flow (penyebab CCCD=0) - `DeployTab.svelte`: `serialActive = false` di handleDeploy ### 10.2 Serial Terminal Always Visible + Baud Dropdown - Serial terminal render saat `isPaired` (bukan `deployState === '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.c` intercept CMD_SET_BAUD → `usb_host_set_baud_rate(baud)` - `usb_host.c`: configurable baud via `serial_baud` static 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_t` callback di `usbh_initialize(0, ESP_USB_FS0_BASE, usb_event_handler)` - Listen `USBH_EVENT_INTERFACE_START` (connect) + `USBH_EVENT_DEVICE_DISCONNECTED` - Forward via `xTaskNotifyGive` ke `usb_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-bluetooth` atau ignore. Error ini pre-existing. - Lesson terkunci (prerequisite belum selesai) → backend kosongkan `initial_code_arduino`, tapi `active_tabs` tetap di-return. Tab Deploy tetap muncul. - `idf.py monitor` membutuhkan 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.