AgentDeck · Dashboard Surfaces

# 지원 Dashboard 하드웨어 / OS 사양

daemon hub(port 9120)에 붙어 에이전트 상태를 보여주거나 제어하는 모든 surface — 하드웨어 디스플레이/입력 디바이스 11종 + 소프트웨어 플랫폼(Apple 3 · Android 다수) + 터미널 TUI = 14 surface. 동시 구동 가능.

Host: App Store dashboard macOS 26+ · Node bridge macOS 15+ (Sequoia) · Node.js ≥ 22 · iTerm2 — Windows/Linux 미지원

SSOT: docs/hardware-compatibility.md · 전송 매트릭스: docs/devices.md · 기능 tier: appstore-feature-matrix.md

## 종합 매트릭스

Surface | 종류 | SoC · 플랫폼 | 디스플레이 / 해상도 | 전송 | 연결성 | App Store
--- | --- | --- | --- | --- | --- | ---
IPS 3.5" | ESP32 FW | ESP32-S3 | AXS15231B IPS · 480×320 | USB Serial / WiFi WS | USB-Serial + WiFi | YES
Round AMOLED 1.8" | ESP32 FW | ESP32-S3 | ST77916 AMOLED · 360×360 | USB Serial / WiFi WS | USB-Serial + WiFi | YES
86 Box 4" | ESP32 FW | ESP32-S3 | ST7701 IPS · 480×480 | USB Serial / WiFi WS | USB-Serial + WiFi | YES
TTGO T-Display 1.14" | ESP32 FW | ESP32 (classic) | ST7789 TFT · 135×240 | USB Serial / WiFi WS | USB-Serial + WiFi | YES
IPS 10.1" | ESP32 FW | ESP32-P4 + C6 | JD9365 IPS MIPI-DSI · 800×1280 | USB Serial / WiFi WS | USB-Serial + WiFi | YES
XTeink X3 | ESP32 FW | ESP32-C3 | e-ink · 528×792 (3.7") | WiFi WS | WiFi | EXP7
XTeink X4 | ESP32 FW | ESP32-C3 | e-ink · 800×480 | WiFi WS | WiFi | EXP7
Divoom Pixoo64 | 상용 LED | Divoom (비-ESP32) | RGB LED · 64×64 | HTTP REST :80 | WiFi | YES
InkDeck e-ink | ESP32 FW | XIAO ESP32-S3 Plus | e-ink · 800×480 (Seeed OG, UC8179) | WiFi WS (계획) | WiFi | EXP4
iDotMatrix 32×32 | 상용 BLE | BLE SoC (비-ESP32) | RGB AMOLED · 32×32 | BLE GATT | Bluetooth LE | YES
Divoom Timebox Mini | 상용 BLE | BLE SoC (비-ESP32) | RGB LED · 11×11 | BLE GATT (ISSC) | Bluetooth LE | YES
Ulanzi TC001 | ESP32 FW | ESP32 (classic) | WS2812B · 8×32 (256 LED) | USB Serial / WiFi WS | USB-Serial + WiFi | GAP1
Ulanzi D200H | HID deck | SigmaStar SSD210 | nv3052c LCD · 960×540 (14 keys) | Ulanzi Studio 플러그인 (단일 드라이버; direct-HID retire) | USB | YES
Elgato Stream Deck (15-key) | HID deck | Elgato (내장) | LCD keys 15 (5×3) | WebSocket :9120 | USB → 네트워크 | YES3
Elgato Stream Deck+ | HID deck | Elgato (내장) | LCD keys 8 (4×2) + dials 4 + touch strip | WebSocket :9120 | USB → 네트워크 | YES3
Apple — macOS | SW | Apple Silicon / Intel | 호스트 디스플레이 | 내장 Swift daemon / WS | — | YES
Apple — iOS / iPadOS | SW | A / M-series | 디바이스 디스플레이 | WebSocket :9120 | WiFi (same-LAN) | YES
Android — e-ink | SW | 벤더별 (RK3566 등) | e-ink 디스플레이 | WebSocket (ADB localhost) | USB / ADB | CLI2
Android — 태블릿 | SW | ARM / x86 | 컬러 LCD | WebSocket + mDNS | WiFi (same-LAN) | LAN2
SSE 스트림 | 프로토콜 | 호스트 | 브라우저 / 스크립트 | HTTP SSE :9120 `/sse` | 로컬 / LAN | YES*
TUI Dashboard | 터미널 | 호스트 CPU | 터미널 (truecolor) | WebSocket :9120 | 로컬 | YES

1 GAP = TC001 ⚠는 샌드박스 제약이 아니라 구현 갭. 다른 ESP32 보드와 동일한 serial/WiFi state-JSON으로 자기 UI를 렌더하며 serial entitlement로 이미 커버됨(테스트가 serial board로 보고). Swift 데몬에 `led8x32` 경로 미검증이라 CLI-tier. 과거 "ADB" 표기는 legacy/dead code 였고 2026-06-25 제거됨 — `AdbDeviceClass.ulanziTc001` 와이어 클래스 producer가 없어 ADB 분기는 죽어 있었고 라이브 경로는 serial. `AdbModule.classifyDevice` 분기·enum 케이스·UI 룩업 모두 삭제(라이브 row는 USB-serial 섹션). 남은 ⚠는 Swift `led8x32` 경로 HW 검증만 후속 — 검증 후 ✅ 승격 가능. 2 Android 앱은 mDNS LAN(iOS와 동일) + ADB localhost 양쪽 지원. USB-테더 e-ink는 Mac 앱이 `adb reverse`를 못 띄워 CLI 전용(진짜 제약). 같은 LAN의 태블릿은 mDNS로 App Store Mac 데몬에 직접 접속 가능(LAN). 3 Elgato Stream Deck 앱 별도 설치(플러그인 호스트). 표준 15키(`agentdeck-sd`, DeviceType 0)·SD+(`agentdeck-sdplus`, DeviceType 7)는 동일 플러그인의 두 프로파일이며 mini(1)·XL(2)도 `familyForDeviceType()`로 그리드 자동 산출. · 포트는 가변 — ESP32는 `device_info_request`로 식별.
* SSE 풀 스트리밍은 Node 브리지(`bridge/src/hook-server.ts`, `broadcastSse`) 한정. App Store macOS Swift 데몬(`DaemonServer.swift`)의 `/sse`는 `event: connected` 1회 후 끊는 스텁이라 스트리밍 안 함 — App Store 빌드의 라이브 SSE 대시보드는 외부(Node) 데몬 필요. 실 소비자는 리포 내 `/status`·`/pixoo` HTML 대시보드(인라인 EventSource).
4 InkDeck = experimental(펌웨어 개발 중): 하드웨어는 Seeed TRMNL 7.5" OG DIY Kit — XIAO ESP32-S3 Plus + 800×480 monochrome e-ink(GDEY075T7 / UC8179), 상시 USB 급전. 커스텀 AgentDeck ESP32 펌웨어(`esp32/` PlatformIO env `inkdeck`, 개발 중)로 재플래시해 다른 ESP32 보드처럼 WiFi WS로 데몬에 붙고 데몬이 1-bit 대시보드 프레임을 push, 패널은 부분 리프레시(~0.3s) + 주기적 full refresh로 렌더. 아직 `device_info`/WS 미발신 → 대시보드 미표시. 전송은 다른 ESP32와 동일한 WiFi WS(서브프로세스 없음)라 샌드박스 제약 아님 — 펌웨어가 WS 발신 시작하면 ✅ 승격 가능. 구 "TRMNL" 상용 BYOS pull 통합(Node `bridge/src/trmnl/` + Swift `Trmnl*` 모듈)은 제거됨(Node commit `c71044bd`) → 순정 TRMNL 패널 미지원.
7 XTeink X3/X4 = experimental (등록 구현됨): ESP32-C3 e-ink 리더로, AgentDeck 펌웨어가 아니라 오픈소스 CrossPoint Reader 포크(`crosspoint-agentdeck`, AgentDeck 스택 branch `master`)의 "Decision Card". 하나의 펌웨어가 런타임에 X3/X4 자동 감지 후 `client_register{eink-device}`(→ macOS E-ink rail, 이미 구현) + `device_info{board=xteink_x3/x4}`(→ Node `esp32-wifi`, 추가됨) 이중 등록 → 업데이트 펌웨어 플래시 시 대시보드 표시. 전송은 다른 ESP32와 동일한 WiFi WS(서브프로세스 없음)라 샌드박스 제약 아님. 소유 유닛은 pogo USB-data 패드 사망 → SD `update.bin` 플래시만 가능(→ WiFi OTA 대상 아님), AgentDeck `esp32/`에 board def 없음. ⚠️ 는 e-ink 렌더 개발 중 + 온디바이스 플래시 대기. 포크가 포팅하는 와이어 계약은 `docs/esp32-client-contract.md` 가 SSOT.

## A. ESP32 펌웨어 디스플레이 보드

공통: PlatformIO + Arduino, `LVGL 9.2.0`(TC001·XTeink X3 제외), 115200 baud, flash mode DIO, 이중 경로(USB Serial 기본 + WiFi WebSocket 독립). 출처: `esp32/platformio.ini` · `esp32/boards/board_*.h`.

env 이름 두 층: `friendly명`은 `esp32/scripts/flash.sh`가 받는 canonical 친근명(panel/form + 인치: 3.5→35·1.8→18·1.14→114·1.47→147·10.1→101)이고, `pio env`는 `platformio.ini`의 실제 `[env:...]`(불변). flash.sh가 친근명→pio로 매핑하며 legacy 별칭(`round_amoled`·`ttgo`·`ips10` 등)도 계속 받는다. CI/`pio run -e`는 pio env 사용.

보드 | friendly명 | pio env | 모델 (제조사) | SoC | 해상도 | 디스플레이 IC | 터치 IC | Flash | PSRAM | USB-UART
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
IPS 3.5" | `ips_35` | `ips35` | JC3248W535 (Guition) | ESP32-S3 | 480×320 | AXS15231B (QSPI) | 통합 | 16MB | YES | Native USB JTAG
Round AMOLED 1.8" | `amoled_18` | `amoled` | JC3636W518 (Guition) | ESP32-S3 | 360×360 | ST77916 (QSPI) | CST816S (I2C) | 8MB | YES | Native USB JTAG
86 Box 4" | `box_40` | `box_86`6 | ESP32-S3-4848S040 (Guition) | ESP32-S3 | 480×480 | ST7701 (RGB 16-bit) | GT911 (I2C) | 16MB | YES | CH340
TTGO T-Display 1.14" | `tft_114` | `ttgo` | LilyGO T-Display | ESP32-D0WDQ6 | 135×240 | ST7789 (SPI) | 없음 (버튼 2) | 16MB | NO | CH3404
IPS 10.1" | `ips_101` | `ips10` | JC8012P4A1C (Guition) | ESP32-P4NRW32 (400MHz) | 800×1280 | JD9365 (MIPI-DSI) | GSL3680 (I2C) | 16MB5 | 32MB | CH340
XTeink X37 | — | — | XTeink X3 (XTeink) | ESP32-C3 | 528×792 | e-ink EPD (미확정) | 없음 | 4MB | NO | pogo USB (데이터 사망)

4 TTGO 플래시는 57600 baud + `--no-stub`(~5분). PSRAM 없는 classic이라 정적 DRAM ~10KB → 테라리움 축소 버퍼. 5 P4NRW32 모듈은 16MB Flash + 32MB PSRAM 통합. AgentDeck `ips10` 펌웨어는 2026-07-05 이후 `jc8012p4a1c_ota.csv` 16MB dual-OTA layout(OTA 슬롯 각 6.0MB + SPIFFS). WiFi는 co-processor ESP32-C6-MINI-1U-N4(esp-hosted), TX 20dBm. 6 `box_86`의 legacy 중복 env `rgb48`가 `platformio.ini`의 `default_envs`(같은 `box_86_ota.csv` 16MB dual-OTA 파티션, platform source만 다른 near-dup). 친근명 정리 범위에서 pio env는 유지 — `pio run` 무인자 빌드 시 `rgb48` 선택됨에 주의. 7 XTeink X3 = experimental. AgentDeck `esp32/` 펌웨어 아님 — 오픈소스 CrossPoint Reader 포크(`crosspoint-agentdeck`, AgentDeck 스택 branch `master`)로 "Decision Card" 펌웨어 개발 중이라 `friendly명`/`pio env` 없음, LVGL 미사용(CrossPoint `GfxRenderer`). 전송 WiFi WS(계획). 소유 유닛은 USB-data 패드 사망 → SD `update.bin`만. 종합 매트릭스 각주7 참조.
OTA 실기 검증: 2026-07-05 현재 실험실 `86box`는 USB 마이그레이션 후 daemon에서 `OTA 7.8MB`, `ips_10`은 `OTA 6.0MB`로 감지 완료. 기존 factory/NO_OTA layout 유닛은 최초 1회 USB full flash 필요.

## B. LED 매트릭스 디바이스

#### Divoom Pixoo64

상용 LED 액자 · 비-ESP32

해상도 64×64 RGB
전송 HTTP REST :80
연결 WiFi 2.4G
드라이버 pixoo/ · Swift
특이 PicID 단조증가

#### iDotMatrix 32×32

상용 BLE 픽셀 · BLE SoC

해상도 32×32 AMOLED
전송 BLE GATT
연결 Bluetooth LE
드라이버 sync.py · CoreBT
특이 USB=전원전용

#### Divoom Timebox Mini

상용 BLE 픽셀 · BLE SoC

해상도 11×11 RGB
전송 BLE GATT (ISSC)
연결 Bluetooth LE
드라이버 TimeboxBLE.swift · sync_ble.py
특이 BLE 단일연결

#### Ulanzi TC001

ESP32 펌웨어 · env `led8x32`

해상도 8×32 (256 LED)
SoC ESP32 D0WD
렌더 FastLED 3.7
Flash 8MB · no PSRAM
App Store⚠ 구현 갭(각주3)

## C. 입력 / 제어 하드웨어 (HID 데크)

### Ulanzi D200H "Deck Dock"

stock HID 프로토콜(ADB/펌웨어 변조 없음), daemon-only. Ulanzi Studio 플러그인(`plugin-ulanzi`)이 유일한 지원 드라이버 — D200H 는 Mac Ulanzi Studio 앱을 통해야만 안정 렌더. direct-HID 폴백은 retire(2026-06-21, `380c6510`): 두 데몬 모두 더 이상 HID 로 열지 않음(Node `d200h: false` / Swift `enableD200hDirectHID = false`). 모듈 코드와 stand-down arbitration 은 dormant(보존·재활성 가능)하나 비활성. 출처: `docs/plugin-conventions.md` §D200H, 커밋 `380c6510`.

SoC | SigmaStar SSD210 — dual ARM Cortex-A7 @ 1GHz, 64MB DDR2 SIP
RAM | 33MB total (~16MB free)
패널 | nv3052c LCD, 물리 540×960 (6BIT, 59fps) → 960×540 가로(`rotateScreen:90`), BGRA32
키 | 14 keys (3행×5열, Row2 col3+col4 합체), ~192×180px
OS | FlythingsOS V2.1 (`Zkswe_SSD21X_SPINOR`)
연결 | USB HID — VID `0x2207` / PID `0x0019`, 1024B 패킷, 헤더 `0x7C7C`, ZIP 청킹 (WiFi 없음)
드라이버 | 유일: Ulanzi Studio 플러그인 `plugin-ulanzi/`(daemon WS). direct-HID 모듈(Swift `D200hHidModule.swift` IOKit / Node `bridge/src/d200h/` node-hid)은 retire — dormant(비활성)

### Elgato Stream Deck (표준 15키) · Stream Deck+

하나의 플러그인이 두(이상의) Elgato 하드웨어를 지원 — 액션 코드는 디바이스-불문이며 `familyForDeviceType()`(`plugin/src/actions/session-slot-button.ts`)가 등록 디바이스의 `columns×rows`로 키패드 슬롯(`slot = row*columns + col`) 자동 산출.

항목 | 표준 Stream Deck (15키) | Stream Deck+
--- | --- | ---
프로파일 | `agentdeck-sd` (DeviceType 0) | `agentdeck-sdplus` (DeviceType 7)
키패드 | LCD 키 15개 (5×3) | LCD 키 8개 (4×2)
인코더/스트립 | 없음 | 로터리 인코더 4개 + LCD 터치 스트립
추가 지원 | mini(DeviceType 1) · XL(2)도 `familyForDeviceType()`로 그리드 자동 산출

공통: 호스트 USB → Elgato Stream Deck 앱 플러그인 → daemon WebSocket(:9120) · Stream Deck SDK v2 · UUID `bound.serendipity.agentdeck` · 양방향 BridgeEvent 13종 전부. 인코더 매핑(SD+ 전용): E1 유틸리티 · E2 액션 · E3 터미널 · E4 음성 (음성 takeover 시 E2–E4 wide-canvas).

## D. 모바일 / 데스크톱 소프트웨어 플랫폼

### Apple (iOS · iPadOS · macOS)

SwiftUI Multiplatform 단일 코드베이스, App Store(`bound.serendipity.agent.deck`). 출처: `apple/project.yml`.

항목 | macOS | iOS / iPadOS
--- | --- | ---
최소 OS | macOS 26.0 | iOS / iPadOS 17.0
빌드 | Xcode 26.4 · Swift 6.0 | Xcode 26.4 · Swift 6.0
아키텍처 | Apple Silicon + Intel universal | A-series / M-series
데몬 | in-process Swift daemon (:9120) | WS client → 외부 daemon
샌드박스 | App Store, 서브프로세스 0 (`AGENTDECK_APP_STORE`) | App Store sandbox
엔타이틀먼트 | audio · bluetooth · serial · usb · network · files | 없음 (WS only)

macOS만 하드웨어 모듈(ESP32 serial / D200H USB / Pixoo·iDotMatrix·Timebox BLE) 직접 구동. iOS/iPadOS는 same-LAN WebSocket 뷰어.

### Android (e-ink 리더 · 태블릿)

Jetpack Compose 런처(`dev.agentdeck`) · minSdk 29 / target·compile 34 / JDK 17. 출처: `android/app/build.gradle.kts`.

디바이스 | 칩 / 벤더 | 디스플레이 | EPD API
--- | --- | --- | ---
Crema S | Rockchip RK3566 | B&W e-ink (16-level) | `EinkManager` setMode/sendOneFullFrame
Onyx Boox | Onyx | B&W / 컬러(Kaleido 3) | `BaseDevice.setViewDefaultUpdateMode()`
MOAAN Pantone 6 | Rockchip (rebrand) | 컬러 e-ink (Kaleido 3, 4096색) | `EinkManager` (moaan)
Bigme (Galy / inkNote) | Bigme | 컬러 e-ink (Gallery 3/4) | 컬러 `einkPick()`
Kobo (Android) | Kobo | B&W e-ink | fallback `invalidate()`
일반 태블릿 (Lenovo 등) | ARM / x86 | 컬러 LCD 60fps | 표준 Android 렌더

e-ink 리프레시: GC16(표준) · DU(dither) · A2(fast). 컬러 e-ink는 1/4 해상도 컬러 / full B&W. 연결: Android 앱은 mDNS LAN(iOS와 동일) + ADB localhost 양쪽 지원 — USB-테더 e-ink는 `adb reverse` 필요(CLI 전용), 같은 LAN 태블릿은 mDNS로 App Store Mac 데몬 직접 접속(각주⁴).

왜 Android 렌더링이 iOS(SwiftUI)와 다른가 — 근본적 차이, 통일 이득 없음. Android는 e-ink 리더 자체가 타깃(Crema/Onyx/Kobo/Pantone/Bigme)이라 벤더별 EPD 제어 인프라(`EinkDetector.kt`·`EinkRenderer.kt` 16-level 그레이 양자화·`EinkRefreshZone.kt` GC16/DU/A2 존)를 갖는다. iOS는 컬러 LCD/OLED 전용이라 SwiftUI `Canvas`+`TimelineView` 60fps면 충분하고 EPD 리프레시 모드 개념이 OS에 없어 분기 불가·불요. 데이터/프로토콜 계층은 이미 공유(`shared/src/protocol.ts`, 양쪽 동일 WS dialect) — 갈라지는 건 렌더 파이프라인 + 디스커버리 플러밍(`NsdManager`/OkHttp vs `NWBrowser`/`URLSessionWebSocketTask`)뿐이고 둘 다 플랫폼-네이티브가 안정적이라 의도된 최적화. 더 통일하면 iOS에 무의미한 no-op e-ink shim이 생겨 유지보수가 오히려 나빠짐 — 현 구조가 옳다.

## E. 터미널 Surface

### TUI Dashboard `agentdeck dashboard`

요건 | truecolor 지원 터미널 (의존성 0)
연결 | WS client → daemon (`findDaemonPort()`: daemon.json → sessions.json fallback)
렌더 | raw ANSI escape · braille 테라리움 · 3단 반응형 (wide 120+ / standard 80–119 / narrow 60–79)
이벤트 | BridgeEvent 13종 (push-only 뷰)

## 평가 노트 — App Store 게이팅이 최선인가

매트릭스의 ❌/⚠ 가 샌드박스 근본 제약인지 구현/분류 갭인지 구분 (코드 근거).

항목 | 표기 | 실제 사유 | 최선 여부 / 개선안
--- | --- | --- | ---
TC001 | GAP | 구현 갭. serial entitlement로 커버되는 self-rendering ESP32(테스트가 serial board로 보고). 과거 "ADB" 표기는 producer 없는 dead code(`AdbDeviceClass.ulanziTc001`) 였음 | 표기 정정 + dead-code 제거 완료(2026-06-25, matrix·Swift 5곳). 후속: HW 검증 후 ✅ 승격
Android 태블릿 | LAN | 과분류. mDNS로 App Store Mac 데몬 직접 접속이 코드상 가능 | 개선 권장: e-ink(USB/adb)와 태블릿(LAN/mDNS) tier 분리
Android e-ink | CLI | 진짜 제약. USB-테더 + `adb reverse` 서브프로세스 필요 → sandbox 불가 | 현 구조 합리적(e-ink WiFi 불안정·USB 전제). 변경 불요
Timebox / iDotMatrix | YES | CoreBluetooth 네이티브 | 최선. 단 데몬당 BLE 단일 연결 — 동시 1대만. (구 Timebox-SPP serial 변종은 제거됨)
SSE 스트림 | YES* | 구현 갭. 풀 스트리밍(`broadcastSse`, 멀티클라이언트+하트비트)은 Node 브리지(`hook-server.ts`)만 구현. App Store Swift 데몬(`DaemonServer.swift`)의 `/sse`는 `event: connected` 1회 후 끊는 스텁 | 개선 권장: 샌드박스 제약 아님(같은 HTTP 서버에 SSE 가능). Swift 데몬에 멀티클라이언트 SSE 구현 시 ✅ 승격. 현 App Store 빌드는 라이브 SSE에 외부 Node 데몬 필요
D200H | YES | (갭 아님) Ulanzi Studio 플러그인이 단일 드라이버. direct-HID 폴백은 retire(2026-06-21, `380c6510`) — Node `d200h:false`/Swift `enableD200hDirectHID=false`, 모듈은 dormant 보존 | 최선. 플러그인 없이 HID 직구동 시 화면 깨짐 → 의도적 비활성. 재활성은 두 flip-point만 되돌리면 됨
XTeink X3/X4 | EXP | 등록 구현됨(experimental, 샌드박스 제약 아님). ESP32-C3 e-ink + 외부 CrossPoint 포크 펌웨어(`crosspoint-agentdeck`)가 `client_register{eink-device}` → macOS E-ink rail, `device_info{board}` → Node `esp32-wifi` 이중 등록. 전송은 다른 ESP32와 동일 WiFi WS(서브프로세스 없음) | 개선 예정: e-ink 렌더 개발 중 + 업데이트 펌웨어 SD 플래시 대기라 ⚠️. 플래시 후 표시

요약: 진짜 sandbox 제약은 e-ink(adb) 경로뿐이며 CLI 대체 경로가 정당하다. TC001·Android 태블릿의 ❌ 는 구현/분류 갭이라 ⚠로 정정했고, SSE ✅* 는 Swift 데몬 SSE 미구현이라는 구현 갭(샌드박스 제약 아님)이라 별도 표기 — upstream `appstore-feature-matrix.md`·`AdbModule.swift`·`AgentState.swift`·`TopologyRail.swift` 의 ADB 오기를 정정하고 legacy dead-code 는 2026-06-25 제거 완료(TC001 의 ✅ 승격만 HW 검증 후속).