모듈 의존성 맵
🔵 레이어 구조
┌──────────────────────────── 화면 (Presentation) ────────────────────────────┐
│ spoex/screens remote/screens home/ device/screens │
│ (묠니르, VBT) (리모컨, DEV) (메인홈) (기기설정) │
├──────────────────────────── 비즈니스 로직 (Domain) ──────────────────────────┤
│ vbt_common/ vbt_engine/ spoex/domain remote/domain │
│ (ViewModel) (파이프라인) (묠니르Ctrl) (리모컨Ctrl) │
│ │
│ motion_analysis/ device/domain exercise/ workout/ │
│ (v1 분석, 레거시) (DeviceState) (운동 DB) (세션 관리) │
├──────────────────────────── 데이터/인프라 (Data) ────────────────────────────┤
│ bluetooth/ vbt_common/data spoex/data │
│ (BLE 프로토콜) (DataSource) (BLE 커맨드) │
└─────────────────────────────────────────────────────────────────────────────┘
FW (RoomFitMCU) ←──── Nordic UART BLE ────→ bluetooth/
핵심 모듈 6개
bluetooth BLE 통신 계층
FW와의 유일한 통신 채널. Nordic UART Service 기반.
Commands (앱→FW): weight, mode_change, set_range, report, 0xF5 서브코드
Responses (FW→앱): ReportResponse(v3 17B / Dev 41B), OnOff, WeightMode, FirmwareVersion
핵심 클래스:
Commands (앱→FW): weight, mode_change, set_range, report, 0xF5 서브코드
Responses (FW→앱): ReportResponse(v3 17B / Dev 41B), OnOff, WeightMode, FirmwareVersion
핵심 클래스:
BleBufferServiceImpl (패킷 파싱) → DeviceStateService (상태 저장)
device 기기 상태 서비스
DeviceStateService — GetX 전역 서비스. BLE 응답을 Rx 변수로 변환.positionL/R, forceL/R, speedL/R, motionTime, weightMode 등.
모든 화면이 이 서비스를 통해 기기 상태를 읽음. 단일 진실 소스.
vbt_engine VBT 분석 엔진
순수 Dart 로직 (Flutter 의존 없음). 4단 파이프라인:
34개 유닛테스트. BLE/Sim 무관하게 동일 동작.
Pipeline (LPF+속도) → TurningPointDetector (극점) → RepCounter (렙) → FeedbackService (코칭)34개 유닛테스트. BLE/Sim 무관하게 동일 동작.
vbt_common VBT 공통 계층
VbtDataSource 추상화: BLE/Sim/Touch/Random 4종 구현.VbtWorkoutViewModel: DataSource → Pipeline → Detector → FeedbackService 풀체인 조립.화면(VbtWorkoutScreen 등)은 이 ViewModel만 바라봄.
spoex SPOEX 2026 모듈
묠니르 + AI-VBT 전시 전용.
화면: M0(세팅)→M1(캘리)→M2(카운트다운)→M3(운동)→M4(결과)
MjolnirController: 자동세팅(밴드59kg+리포트시작+저점등록), 무게ON/OFF, 세션관리SpoexBleCommands: FW 명령 래퍼 (devReportOn/Off, sendWeight, sendMode 등)화면: M0(세팅)→M1(캘리)→M2(카운트다운)→M3(운동)→M4(결과)
remote 리모컨 모드
기존 앱의 핵심 운동 화면. 스태프가 태블릿으로 제어.
v4 수정 대상: 6종 모드 탭 UI, ±0.5/±5 조절
RemoteWorkoutController: 무게±, 모드변경, 가동범위, 자동무게, 시작/종료RemoteDevScreen: DEV 리모컨. Dev Report 차트 + VbtEngine 연동.v4 수정 대상: 6종 모드 탭 UI, ±0.5/±5 조절
레거시 vs 신규
| 영역 | 레거시 | 신규 (v4) | 상태 |
|---|---|---|---|
| 속도 계산 | motion_analysis/ position 미분 | vbt_engine/ FW Speed 직접 수신 | 완료 |
| 렙 감지 | repetition_service (Worker 기반) | StreamingRepDetector (실시간, 1-rep 버퍼) | 완료 |
| Report 파싱 | v3 17B 고정 | v3/Dev(41B) 자동 판별 (패킷 길이) | 완료 |
| 무게 모드 | 3종 (Const/Neg/Band) | 6종 (+Iso/Hyd/Vib) | P0 |
| BLE 파라미터 | weight/mode/range만 | +0xF5 0x05 런타임 파라미터 | P0 |
BLE 데이터 파이프라인
FW (MCU)
ISR 10kHz
→
ISR 10kHz
BLE TX
50ms 주기
→
50ms 주기
FlutterBluePlus
Nordic UART
→
Nordic UART
BleBufferService
패킷 조립
→
패킷 조립
ResponseFactory
cmd 코드 분기
→
cmd 코드 분기
DeviceStateService
Rx 변수 갱신
Rx 변수 갱신
패킷 판별 로직
// ResponseFactory (response_factory.dart:67)
case startReportData (0x41):
if (data.length >= 41 && data[2] == 38)
→ ReportResponse (isDevReport = true, 41B) // Dev Report
else
→ ReportResponse (isDevReport = false, 17B) // v3 Normal
case debugReportData (0xF5):
→ DebugReportResponse (28B) // 구버전, DEV 리모컨 전용
Report 필드 비교
| 필드 | v3 (17B) | Dev (41B) | 사용처 |
|---|---|---|---|
| posL/R | ✅ | ✅ | 차트, 가동범위, 렙 감지 |
| forceL/R | ✅ (Iq 기반) | ✅ (Iq 기반) | 무게 표시 |
| speedL/R | ❌ | ✅ (mm/s) | VBT 속도, 인디케이터 |
| accelL/R | ❌ | ✅ (mm/s²) | (미사용, 향후 분석) |
| IcmdL/R | ❌ | ✅ (×1000) | DEV 차트 |
| IfbL/R | ❌ | ✅ (×1000) | DEV 차트 |
| fLoadL/R | ❌ | ✅ (×100 kg) | Power 계산, DEV 차트 |
| modeL/R | ❌ | ✅ | 모드 확인 |
| regionL/R | ❌ | ✅ | 구간 표시 |
| weightSetL/R | ❌ | ✅ (×100 kg) | 설정무게 확인 |
BLE 명령 (앱→FW)
| 명령 | 코드 | 용도 | 주의 |
|---|---|---|---|
| SET_WEIGHT | - | 무게 설정 | 60kg=에러, 59kg 사용 |
| WEIGHT_ON/OFF | - | 모터 구동 | - |
| MODE_CHANGE | - | 무게 모드 변경 (0~5) | 상한 5 |
| SET_RANGE_DIGIT | - | 가동범위 수치 설정 | Weight OFF에서만 |
| ECC_LEVEL | - | 네거티브 부하 | ClampEccWeight 50% |
| 0xF5 0x05 | debug | 모드별 파라미터 런타임 변경 | paramId별 분기 |
| 0xF5 0x20/21 | debug | Dev Report ON/OFF | 진입/이탈 시 전환 |
| START_REPORT | 0x41 | 리포트 시작 | Start_Report() 필수 |
VBT 엔진 파이프라인
BleVbtSource
50ms Timer
→
50ms Timer
VbtPipeline
LPF + 속도
→
LPF + 속도
TurningPointV3
극점 감지
→
극점 감지
StreamingRepDetector
렙 세그먼테이션
→
렙 세그먼테이션
FeedbackService
코칭/리포트
코칭/리포트
VbtPipeline 상세
ingest(timeMs, rawPosition, side, {fwSpeedMmS}) {
// 1. Butterworth LPF (3Hz cutoff, 20Hz sample)
filteredPosition = lpf.filter(rawPosition);
// 2. 속도: FW Speed 우선, 없으면 position 미분
if (fwSpeedMmS != null)
velocity = fwSpeedMmS / 1000.0; // mm/s → mm/ms
else
velocity = (filteredPosition - prev.position) / dt;
// 3. Raw + Filtered 히스토리 저장 (오브젝트 풀링)
// 4. 다운스트림 Stream 전달
}
데이터소스 4종
| 소스 | 용도 | FW Speed | 사용 화면 |
|---|---|---|---|
BleVbtSource | 실기기 BLE | ✅ DeviceStateService.speedL/R | VBT 체험, 묠니르 |
SimVbtSource | sine파 시뮬 | ❌ (미분 fallback) | VBT 시뮬레이터 |
TouchVbtSource | 터치 드래그 | ❌ | 터치 시뮬 위젯 |
RandomVbtSource | 랜덤 운동 패턴 | ❌ | 자동 데모 |
ViewModel 풀체인
VbtWorkoutViewModel.create(source, exerciseName, goal, loadKg) {
// 동일 detector 인스턴스 공유 (중요!)
detector = StreamingRepDetector(concDir, dt: 0.05);
feedbackService = VbtFeedbackService(detector, goal, loadKg);
pipeline = VbtPipeline();
}
// 데이터 흐름:
source.samples.listen(_onSample) {
pipeline.ingest(raw.timeMs, raw.posLMm, Side.left, fwSpeedMmS: raw.speedLMmS);
// velocity: FW Speed 우선
detector.onSample(positionMm, velocityMmS, fMuscle, fMotor);
// → repFeedbacks stream → UI
}
SPOEX 모듈 구조
묠니르 (Mjolnir)
M0 Setup
자동세팅
→
자동세팅
M1 Calibration
고점 등록
→
고점 등록
M2 Countdown
3초
→
3초
M3 Workout
1RM 측정
→
1RM 측정
M4 Result
QR+순위
QR+순위
MjolnirController
settings: side(L/R/Both), weight(59kg 고정)
autoSetup: 밴드모드 설정 → 리포트 시작 → 저점 등록 → 캘리브레이션 진입
MjolnirSession: start→end 시간, maxPosition, Supabase 업로드
BLE 순서: MODE_CHANGE(2) → SET_WEIGHT(59) → START_REPORT → SET_RANGE_LOW
autoSetup: 밴드모드 설정 → 리포트 시작 → 저점 등록 → 캘리브레이션 진입
MjolnirSession: start→end 시간, maxPosition, Supabase 업로드
BLE 순서: MODE_CHANGE(2) → SET_WEIGHT(59) → START_REPORT → SET_RANGE_LOW
AI-VBT 체험
V1 ExercisePick
운동 선택
→
운동 선택
V2 MachineSetup
무게/모드/범위
→
무게/모드/범위
V3 GoalSelect
훈련 목표
→
훈련 목표
V4 Workout
실시간 VBT
→
실시간 VBT
V5 SetReport
세트 결과
→
세트 결과
V6 SessionReport
전체 리포트
전체 리포트
SpoexBleCommands
FW 명령 래퍼. 모든 BLE 명령을 한곳에서 관리.
v4 추가 필요: 6종 모드 파라미터 전송 (0xF5 0x05 확장)
sendWeight(side, kg) · sendWeightMode(mode) · sendWeightOnOff(bool)sendSetRangeLow/High(side) · sendStartReport() · sendStopReport()sendDevReportOn/Off() · sendModeParam(paramId, value)v4 추가 필요: 6종 모드 파라미터 전송 (0xF5 0x05 확장)
리모컨 모듈
기존 리모컨 (RemoteWorkoutScreen)
구조: 3분할 레이아웃 (Chart : Config : Control = 3:2:4)
RemoteWorkoutConfigBox: 무게 표시, 줄 선택, 현재 모드
RemoteWorkoutControlBox:
• 가동범위 설정 / 리셋 (무게ON 시 잠금 🔒)
• ±0.5kg / ±5kg 무게 조절
• 자동 무게 토글
• 무게 모드 선택 (바텀시트)
• 시작 / 일시정지 / 종료
v4 수정: 6종 모드 탭 UI, 모드별 파라미터, Dev Report 차트 채널
RemoteWorkoutConfigBox: 무게 표시, 줄 선택, 현재 모드
RemoteWorkoutControlBox:
• 가동범위 설정 / 리셋 (무게ON 시 잠금 🔒)
• ±0.5kg / ±5kg 무게 조절
• 자동 무게 토글
• 무게 모드 선택 (바텀시트)
• 시작 / 일시정지 / 종료
v4 수정: 6종 모드 탭 UI, 모드별 파라미터, Dev Report 차트 채널
DEV 리모컨 (RemoteDevScreen)
DEV flavor 전용. 고급 디버깅 + 튜닝 도구.
기존 기능:
• VbtEngine 연동 (렙 감지, 속도 인디케이터)
• 무게 모드/가동범위/자동무게 제어
• PI 게인 (Kp, Ki) 슬라이더 (등속성)
• Vtarget 범위 50~2000 mm/s
• Debug/Dev Report 자동 ON/OFF (진입/이탈)
• FW 버전 + 앱 커밋 해시 AppBar 표시
v4 수정: 모드별 탭 전환, 차트 채널 토글, 관측→조작→세부 레이아웃
기존 기능:
• VbtEngine 연동 (렙 감지, 속도 인디케이터)
• 무게 모드/가동범위/자동무게 제어
• PI 게인 (Kp, Ki) 슬라이더 (등속성)
• Vtarget 범위 50~2000 mm/s
• Debug/Dev Report 자동 ON/OFF (진입/이탈)
• FW 버전 + 앱 커밋 해시 AppBar 표시
v4 수정: 모드별 탭 전환, 차트 채널 토글, 관측→조작→세부 레이아웃
컨트롤러 계층
RemoteWorkoutController
├── weightPlus/Minus/PlusFive/MinusFive() → DeviceCommandController
├── toggleAutoWeightActive() → BLE AutoWeight
├── startRemoteWorkoutSession() → BLE StartReport + WeightOn
├── showRangeOfMotionDialog() → BLE SetRange
└── changeWeightMode() → BLE ModeChange
RemoteWorkoutDeviceController
└── changeMode(WeightMode) → 모드별 분기
├── constant → setConstantMode()
├── negative → setNegativeMode() + eccLevel()
├── band → setSqueezeMode()
├── iso/hyd/vib → setConstantMode() (임시!) ← P0 수정 대상
화면 → 모듈 연결도
| 화면 | 경로 | 사용 모듈 | 데이터 소스 |
|---|---|---|---|
| 홈 | home/ | device | BLE 연결 상태 |
| 리모컨 | remote/remote_workout_screen | device bluetooth motion_analysis | DeviceStateService (positionL/R, forceL/R) |
| DEV 리모컨 | remote/remote_dev_screen | device vbt_engine bluetooth | DeviceStateService + VbtEngine (렙감지) |
| 묠니르 세팅 | spoex/mjolnir/mjolnir_setup_screen | spoex device | MjolnirController + SpoexBleCommands |
| 묠니르 운동 | spoex/mjolnir/mjolnir_workout_screen | spoex device camera | DeviceStateService (position), MjolnirSession |
| VBT 머신세팅 | spoex/vbt/vbt_machine_setup_screen | spoex device | SpoexBleCommands (무게/모드/범위) |
| VBT 운동 | spoex/vbt/vbt_workout_screen | vbt_common vbt_engine spoex | BleVbtSource → VbtWorkoutViewModel |
| VBT 세트리포트 | spoex/vbt/vbt_set_report_screen | vbt_common | VbtWorkoutViewModel.completedSets |
| VBT 시뮬레이터 | dev/vbt_simulator_screen | vbt_engine | SimVbtSource / 수동 입력 |
핵심 데이터 흐름
═══ VBT 운동 (V4 WorkoutScreen) ═══
FW ISR(10kHz) → BLE(50ms) → BleBufferService → ReportResponse(41B)
→ DeviceStateService.positionL/R, speedL/R
→ BleVbtSource(50ms Timer) → VbtRawSample{pos, speed}
→ VbtWorkoutViewModel._onSample()
→ VbtPipeline.ingest(pos, fwSpeedMmS: speed) // FW Speed 우선!
→ StreamingRepDetector.onSample(pos, vel, force)
→ VbtFeedbackService → RepFeedback stream
→ UI (렙카운트, 속도바, 차트)
═══ 리모컨 (RemoteWorkoutScreen) ═══
DeviceStateService.motionTime → Worker trigger
→ MotionAnalysisService → position/velocity/force
→ LineChartComponent (차트)
→ RemoteWorkoutConfigBox (무게/속도 표시)
═══ DEV 리모컨 (RemoteDevScreen) ═══
DeviceStateService.positionL/R → VbtEngine.ingest() → 렙 감지
DeviceStateService.speedL/R → 속도 인디케이터 (FW 직접)
DeviceStateService.forceL/R → LineChart (실시간)
PRD v4 항목 → 코드 매핑
| PRD | 항목 | 수정 파일 | 상태 |
|---|---|---|---|
| P0 | 2.1 VBT FW Speed |
vbt_pipeline.dart (fwSpeedMmS)ble_vbt_source.dart (speedL/R 전달)vbt_workout_viewmodel.dart (FW 우선)vbt_workout_screen.dart (devReport ON/OFF)
|
완료 |
| P0 | 2.2 V2 BLE 6종 모드 |
vbt_machine_setup_screen.dart (모드 탭)spoex_ble_commands.dart (sendModeParam 확장)remote_workout_device_controller.dart (case 3/4/5)
|
진행중 |
| P0 | 2.3 M0 UI 깨짐 | mjolnir_setup_screen.dart (Spacer→고정 spacing) |
완료 |
| P1 | 2.4 DEV 리모컨 모드탭 | remote_dev_screen.dart (전면 재설계) |
대기 |
| P1 | 2.5 렙 시각화 | line_chart.dart (vertical line layer)vbt_workout_screen.dart (토글) |
대기 |
| P1 | 2.6 카메라 토글 | workout_camera/ → 독립 위젯 분리 |
대기 |
| P2 | 2.7 Dev Report 차트 | remote_dev_screen.dart (채널 토글) |
대기 |
| P2 | 2.9 바텀시트 fallthrough | remote_workout_device_controller.dart:78-80 |
P0-2.2에서 통합 |