← Hub

Roomfit B2B · 트레이너 주도 수업 플로우

SessionConsole 허브 중심의 멀티-세트 워크플로우 + v1 도메인 완전 포팅 (2026-04-21 PR #65/66/67)
B2B Studio SessionConsole MCU BLE (HM-10) 드롭셋 6 모드 ROM 리셋 201/201 tests PR #61 → #66

0용어 정리 — "시나리오"가 맞는가?

엄밀히는 아닙니다. 우리가 만든 건 하나의 유스케이스와 그 안을 흐르는 플로우이고, 시나리오는 그 플로우의 특정 경로(happy path / emergency / disconnect 등)를 가리킬 때 써야 정확합니다.

용어정의지금 만든 게 이것인가
유스케이스 (use case)하나의 goal — "트레이너가 회원을 수업시킨다"✅ 전체를 감싸는 단위
플로우 (flow)화면/상태 전이 시퀀스✅ ExerciseSelect ↔ Console ↔ Live
시나리오 (scenario)한 유스케이스 안의 특정 경로: 정상 완료 / 비상정지 / 연결 끊김 / 운동 교체 …△ 플로우를 포함한 복합 개념 — 단독 호칭으론 부적절

→ 앞으로 정식 문서에선 "UC-01: 트레이너 주도 수업""S1~S4 시나리오"로 분리하는 쪽이 정확합니다. 캐주얼 대화에서 "기본운동 시나리오"로 쓰는 건 v1 관용어이므로 통합니다.

1플로우 다이어그램

허브 패턴 — 세트 간 휴식/조정이 일어나는 상주 화면은 SessionConsole. LiveWorkoutScreen은 세트 실행 동안만 잠깐 push되었다가 pop되면서 다시 콘솔로 돌아옴.

ExerciseSelect
운동 / 그립 선택
(첫 진입 1회 + "다른 운동" 시)
SessionConsole HUB
세트 기록 · preset 조정
세션 타이머 · 수업 종료
↓ "세트 시작"
LiveWorkoutScreen LIVE
세트 실행 · reps live · 비상정지
PopScope: 뒤로 차단
↓ "세트 완료"
PostSetFeedbackSheet
RPE / RIR 입력 (optional)
↩ 허브로 복귀
↻ 반복 (progressive overload: preset이 마지막 세트 결과로 자동 재시드)
SessionConsole
"수업 종료" 탭
SessionFeedbackSheet → SessionSummary
세션 평가 + 요약 영구 저장

2화면별 책임

각 화면이 MCU에 쏘는 명령 + 사용자가 조작하는 컨트롤. v1 gym 앱의 workout_setup / workout_progress를 v2 패키지 구조로 재구현한 매핑.

ExerciseSelect Transient

운동 카테고리 → 개별 운동 → 그립 선택 (필요 시). 첫 진입 시 1회 + "다른 운동" 시 재호출.

  • MCU 명령: 없음
  • 리턴: {exercise, grip?}
  • cancel → 첫 진입이면 flow abort, 재호출이면 유지

SessionConsole Hub PR #66 완전 재작성

트레이너가 세션 내내 상주하는 허브. v1 workout_setup 의 control surface 를 모두 포팅 — minimal viable 상태에서 기능 완전체로.

  • AppBar: 세션 타이머 (1Hz tick) · 운동명 · "다른 운동" · "수업 종료"
  • ConfigStatusBox (read-only 요약): 세트타입 / 목표 reps / 그립 / 무게 (Single = 각, Double = 각+총) / 음성 토글 / 좌·우 ROM
  • ControlBox 상단: 가동범위 초기화 (→ ResetRangeCommand) · 세트 설정 (→ SetConfigBottomSheet)
  • ControlBox 중단 (v1 3-column): [+0.5/-0.5] | [모드 버튼] | [+5/-5] — 모드 버튼 탭 시 WeightModeBottomSheet 열림
  • SetConfigBottomSheet: 세트타입 (일반/드롭) · 목표 reps CupertinoPicker · 드롭셋 1~3단계 + 단계별 감소량/추가 reps
  • WeightModeBottomSheet: 6 모드 chip (normal/eccentric/elastic/isokinetic/hydraulic/vibration) + 모드별 세부 panel (Ecc level, Iso Vtarget/DecayRate/Fmin/Fmax, Hydro Vmax/n/minRatio, Vib Freq/Amplitude/MaxRatio)
  • Live-push: stepper / 모드 / 드롭셋 변경 시 즉시 MCU 명령 송출 — 트레이너가 케이블 변화 즉시 체감
  • Eccentric ↔ Drop 상호배제: 양방향 enforced (sealed exception)
  • 재시드: 직전 세트 결과가 target 달성 + RPE ≤ 7 이면 +2.5 kg 제안
  • PopScope: 뒤로 완전 차단 — 종료는 "수업 종료"로만

LiveWorkoutScreen Live

세트 실행 동안만 push. 뒤로가기 버튼 자체 제거 + PopScope 완전 차단 — v1 gym workout_progress와 동일.

  • AppBar: 운동명(nameKo) · "Set N" 인디케이터
  • Body: RepCounter · WeightSideBadge · RepFeedbackCard · LiveMetricsPanel
  • SetControlBar: 무게 조정 (AdjustWeightCommand ±0.5/±5) · 비상정지 (EmergencyStopCommand) · "세트 완료"
  • 탈출: "세트 완료" (정상) / "비상정지" (emergency) — 그 외 모든 경로 차단

PostSetFeedbackSheet / SessionFeedbackSheet Modal

세트/세션 완료 직후 띄우는 평가 시트. 건너뛰기 허용 — RPE 없이도 세트는 기록됨.

  • PostSet: RPE (1–10) / RIR (0–5)
  • Session: Session RPE · Mood (great/good/ok/tired/bad)
  • MCU 명령: 없음 (순수 UI)

3MCU 명령 시퀀스 (v1 parity, PR #66 최신)

SessionConsole._startSet()의 BLE 명령 순서. v1 startWithFirstSet() 의 7-step 시퀀스로 완전 정렬. 순서 바뀌면 device 가 sync 안 됨.

startSet (세트 시작) — v1 완전 parity

GetSystemInfo SetAutoWeight(0) SetTargetWeight(kg) SetWeightMode [Mode 파라미터] SetWeightPower(1) StartReport

NEW 모드별 세부 파라미터 송출 — Eccentric → SetEccLevel, Iso/Hydro/Vib → SetModeParam (0xF5 0x05 sub-id별 N개 프레임).   NEW GetSystemInfo handshake 선두 — device state 초기 시드.

SetModeParam — 신규 Command (PR #66)

펌웨어 protocol.c0xF5 0x05 [paramId] [u16 value] 한 프레임 = 한 파라미터. 13개 헬퍼 ctor (단위 변환 캡슐화).

Hydraulic · 0x02 Vmax · 0x03 n · 0x04 minRatio
Isokinetic · 0x10 Vtarget · 0x11 Kp · 0x12 Ki · 0x13 DecayRate · 0x14 Fmin · 0x15 Fmax
Vibration · 0x30 Freq · 0x31 Amplitude · 0x32 MaxRatio
Eccentric · SetEccLevelCommand (별개 0x69)

completeSet (세트 완료)

SetWeightPower(0) StopReport

Mid-set 조정 (콘솔/라이브 공통)

SetTargetWeight(kg) SetWeightMode SetEccLevel SetModeParam ResetRange EmergencyStop

콘솔의 ±5/±0.5 stepper / 모드 버튼 / 드롭셋 세팅 / ROM 초기화 · 라이브의 비상정지 — 모두 즉시 송출. 드롭셋 자동 전이는 DropSetStepAdvancer 가 rep count 감지 시 SetTargetWeight(new kg) 발송.

4시나리오 (한 유스케이스의 4가지 경로)

같은 "트레이너 주도 수업" 유스케이스 내에서 분기하는 구체 경로. 그 중 S1·S3는 integ로 이미 검증, S2·S4는 수동 QA 또는 후속 integ 대상.

S1. 정상 — 연속 세트 + progressive overload integ ✅

가장 흔한 happy path. RPE ≤ 7 + target 달성 → 다음 세트 +2.5 kg.

ExerciseSelect → Console → "Set 1 시작" (20 kg) → LiveWorkout → reps → "세트 완료"
→ PostSet (RPE 7) → Console (history +1, preset 22.5 kg)
→ "Set 2 시작" → LiveWorkout → ... → Console → "수업 종료" → SessionSummary

S2. 운동 교체 (같은 세션 내) 수동 QA

같은 수업 안에서 운동을 바꾸는 경로. 세션은 살아있고 새 ExerciseEntry만 추가.

Console → "다른 운동" → ExerciseSelect → 새 운동 선택
→ Console 재렌더 (새 운동 · setIndex 리셋 · 이전 entries 유지)
→ 세트 진행 재개

S3. 비상정지 (mid-set) 수동 QA

세트 진행 중 위험 상황. EmergencyStopCommand 즉시 발송, 부분 기록은 폐기.

LiveWorkout → "비상정지" 탭 → EmergencyStopCommand 발송
→ SetWeightPower(0) + StopReport → Console 복귀
→ 세트 미기록 (실행 중이던 데이터 drop)

S4. BLE 연결 끊김 복구 수동 QA

중간에 BLE transport가 끊기면 active set을 cutoffTrigger=timeout으로 마감하고 다이얼로그 제시.

LiveWorkout 진행 중 → transport disconnected 감지
setExecutor.forceComplete() → 모든 push 화면 pop
→ AlertDialog: "지금까지 기록 저장" / "수업 종료"
→ 저장 시 SessionSummary, 종료 시 flow abort

5테스트 커버리지

모든 suite 녹색. 도메인/프로토콜은 pure Dart 유닛, UI 는 Flutter widget, e2e 는 MockDeviceGateway + 실 Supabase.

스위트레벨개수결과주 타깃
roomfit_protocol/test/commands_testUnit29+신규 SetModeParamCommand 단위 변환 (×1/×10/×100) · 헬퍼 13개
roomfit_protocol 전체Unit113113/113 ✅프로토콜 codec · 26 Commands · 17 Responses
roomfit_workout/test/domain/drop_set_configUnit1111/11 ✅DropSetConfig 누적 감량 / validation / advance / shouldAdvance
drop_set_step_advancer_testUnit66/6 ✅rep 도달 시 자동 advance · multi-step jump · reset
next_set_preset_modes_testUnit99/9 ✅withMode/withSetType · Eccentric ↔ Drop 상호배제 · intensity 교체
roomfit_workout 전체Unit4545/45 ✅workout 도메인 전부
design_system/testWidget44/4 ✅WeightStepperColumn — Single/Double grip 라벨 · 탭 콜백 · disabled
roomfit_device/testUnit + Widget3939/39 ✅MockDeviceGateway · projectResponse · RepCycleGenerator · buildReportResponse
mcu_simulation_test.dartInteg (Mock)99/9 ✅S1 SetTargetWeight 투영 · S2 SetWeightMode · S3 8 reps · S4 RestDetector · S5 EmergencyStop · S6 ResetRange · S7 드롭셋 자동 전이 · S8 SetModeParam · S9 live stepper
app_e2e_test.dartInteg (Mock)77/7 ✅Console flow · preset 20→22.5kg · RestDetector 다이얼로그 · emergency · 수업 종료
auth_role_gate_e2e_testInteg (실 Supabase)55/5 ✅role routing · display_name 렌더
Total (패키지 unit + widget)201201/201 ✅workout 45 + protocol 113 + design_system 4 + device 39

실행: flutter test (각 패키지) · e2e 는 DEVICE_ID=<id> scripts/run-b2b-e2e.sh integration_test/<file>

6v1 gym 대비 parity 현황

v1 workout_setup/workout_progress의 기능을 v2 패키지 구조로 이식. 완료/일부/미완 단계적으로.

v1 기능v2 구현 위치상태
세트 시작 lifecycle (StartReport → SetWeightMode → SetTargetWeight → SetWeightPower)SetExecutor.startSet
무게 ±0.5 / ±5 kg 실시간 조정SetControlBar + AdjustWeightCommand
비상정지EmergencyStopCommand
세트 간 무게/모드/side preset 조정SessionConsole + NextSetPreset
세션 타이머 (1Hz tick)sessionElapsedProvider
뒤로가기 차단 (mid-set)PopScope(canPop:false) + automaticallyImplyLeading:false
운동명 한글화 (nameKo)AppBar title
Progressive overload 제안NextSetPreset.fromLastSet (RPE ≤ 7)
Eccentric 레벨 1~100 slider (0.5 kg 단위)WeightModeBottomSheet + EccentricIntensity.level + SetEccLevelCommand
6 모드 세부 파라미터 (Iso / Hydro / Vib)WeightModeBottomSheet + ModeIntensity sealed + SetModeParamCommand (0xF5 0x05 신규)
드롭세트 (1~3 단계 + 자동 전이)DropSetConfig · DropSetStepAdvancer · SetConfigBottomSheet
Eccentric ↔ Drop 상호배제 룰NextSetPreset.withMode/withSetTypeIncompatibleModeAndSetTypeError
Range of Motion 재측정 (리셋)콘솔 "가동범위 초기화" 버튼 → ResetRangeCommand(side:0)
ConfigStatusBox 요약 패널 (설정 한눈에 보기)design_system/molecules/config_status_box.dart
3-column 스테퍼 레이아웃 (v1 workout_control_box)WeightStepperColumn × 2 + 가운데 모드 버튼
Single/Double grip 무게 표시 (각 / 총)ConfigStatusBox 에서 자동 분기
음성 가이드 토글 UIConfigStatusBox switch (실 TTS 연결 후속)
좌/우 독립 무게 (SetWeightCommand(L,LMode,R,RMode))현재는 단일 weightKg 유지 — 표시만 각/총 분리. 실제 L/R 독립은 후속
ROM 캘리브레이션 신규 등록 플로우 (리셋 말고 신규)후속 PR
드롭셋 진행률 표시 (LiveWorkoutScreen)콘솔 설정 후속 PR
코치마크 / 첫 사용자 가이드후속 PR
BLE 연결 끊김 복구 다이얼로그LiveWorkoutFlowScreen._handleBleDisconnect
세션 abandoned 복구MainShellScreen._checkAbandonedSessions

7레퍼런스

핵심 파일

  • Protocol: packages/roomfit_protocol/lib/src/commands/command.dart (SetModeParamCommand)
  • Domain: packages/roomfit_workout/lib/src/domain/models/{next_set_preset, drop_set_config, mode_intensity, set_type}.dart
  • Use case: packages/roomfit_workout/lib/src/domain/use_cases/drop_set_step_advancer.dart
  • Shared UI: packages/design_system/lib/src/components/molecules/{config_status_box, weight_stepper_column, set_config_bottom_sheet, weight_mode_bottom_sheet}.dart
  • App wiring: apps/b2b/lib/features/workout/presentation/screens/{session_console_screen, live_workout_flow_screen, live_workout_screen}.dart
  • E2E: apps/b2b/integration_test/{app_e2e_test, mcu_simulation_test}.dart