Galaxy Watch4 이상에서 여러 센서를 시간 정렬해 설정된 주기(기본 1 Hz)로 모읍니다. 틱은
워치 Room DB에 먼저 쌓고, 페어링된 안드로이드 폰으로 Wearable Data Layer를 통해 흘려보낸 뒤,
폰에서 세션 단위 CSV로 내보냅니다. 모듈은 둘 — 워치 앱 :wear, 폰 동반 앱
:mobile.
- 심박 — Samsung Health Sensor SDK (
HEART_RATE_CONTINUOUS) - 모션·기압 — Android
SensorManager - 속도·케이던스·걸음 — Wear Health Services
- 위치 — FusedLocationProvider
- SpO₂·ECG·혈압·BIA·스트레스는 읽을 수 없습니다 — on-demand 단발 측정이거나 연속 트래커가 없고, 일부는 파트너 전용 API라 일반 앱에서 접근할 수 없습니다. 피부온도는 Watch5 이상에서만 조건부로 채워집니다 (→ J. 센서 레퍼런스).
모듈 구성
공유 모듈은 두지 않습니다. 모델·TimeUtils·Data Layer 키 상수 정도는 모듈마다
복제합니다. MVP 빌드 그래프를 단순하게 두려는 선택이고, 중복이 부담이 되면 그때 합칩니다.
| 모듈 | 패키지 | min/target | UI | 역할 |
|---|---|---|---|---|
:mobile | ai.atela.heartratecollector.mobile | 26 / 36 | Compose M3 | 폰 동반 앱 — 수신·저장·세션·CSV |
:wear | ai.atela.heartratecollector.wear | 30 / 36 | Wear Compose | 워치 앱 — 센서 수집·로컬 저장·전송 |
아키텍처
계층은 얕습니다 — Activity → ViewModel → Repository → Room. DI 프레임워크도, 클린
아키텍처식 레이어 분리도 없습니다. 워치에선 서비스와 UI 사이를 서비스 바인딩 대신 프로세스
전역 StateFlow 홀더로 잇습니다.
워치
HeartRateSensorManager— Samsung SDK가 닿는 유일한 지점.connect()→startTracking()→ 표본마다onSample(RawHrSample)→stop/disconnect. 검증 못 한 추출은// TODO(SDK)로 남깁니다.- 나머지 센서는
wear/.../sensor/source/*아래입니다 —SensorManager·Health Services·위치. Samsung SDK가 아닙니다. HeartRateForegroundService— 기록을 맡습니다. foreground 알림,PARTIAL_WAKE_LOCK(4시간 캡),Dispatchers.IORoom 쓰기, ~1초 flush, 세션 생성·마감.ACTION_START/ACTION_STOP인텐트로 시작·중지를 제어합니다.SessionStateHolder— 프로세스 전역StateFlow<SessionUiState>. 서비스가 쓰고 UI가 읽습니다.WatchHeartRateRepository·WatchDatabase— local-first. 워치 DB가 source of truth.
폰
MobileDataLayerListenerService—WearableListenerService. 들어온 DataItem을 파싱하고 수신 시각을 찍어 Room에 넣습니다.HeartRateRepository·MobileDatabase— 저장과 UI용 Flow.CsvExporter— CSV 작성 +FileProvider공유.MainViewModel— 세션·최신 틱·워치 연결(NodeClient)·내보내기.
깨면 안 되는 세 가지
코드 어디서든 지켜야 하는 불변식
- 워치 Room이 source of truth. 폰으로 보내기 전에 무조건 로컬에 먼저 씁니다. 전송이 실패해도 데이터는 남아야 합니다.
- 저장 단계에서 raw 표본을 거르지 않습니다. bpm
0, off-wrist, 비정상status, 빈 컬럼 다 그대로 둡니다. 보간 없음.hr <= 0필터는 시각화에서만 씁니다. - wire 포맷과 CSV 계약은 양쪽을 같이 고칩니다. writer
(
WearDataLayerClient)·reader(MobileDataLayerListenerService), CSV 컬럼 순서·파일명은 공개 계약입니다 (→ I. 데이터 명세).
기술 스택
- 언어·UI — Kotlin 2.0.x, Compose(폰 Material3 · 워치 Wear Compose), Coroutines/Flow.
- 빌드 — Gradle Kotlin DSL + 버전 카탈로그(
gradle/libs.versions.toml), AGP 8.11.0, Gradle 8.13. wrapper가 포함돼 있어./gradlew로 바로 빌드됩니다. - JDK 17+. Android Studio 번들 JBR(JDK 21) 권장 — 시스템
java가 JDK 8이면 빌드가 실패합니다. - Room 2.6(KSP). 폰 DB는 v3 —
MIGRATION_1_2(favorite 컬럼)와MIGRATION_2_3(wide ticks 테이블)가 모두 비파괴 마이그레이션. - Data Layer —
play-services-wearable18.x, Wear Compose 1.4. - 지도 — osmdroid + CartoDB Positron 타일. API 키 없음.
- 버전은 카탈로그(
libs.*)에서만 관리합니다. 모듈 빌드 파일에 박지 않습니다.
레포 구조
kict-hr-sensor/
├─ mobile/ # :mobile — 폰 동반 앱
│ └─ src/.../mobile/ui/ # Compose 화면(Live·Sessions·Settings)
├─ wear/ # :wear — 워치 앱
│ ├─ libs/ # samsung-health-sensor-api-1.4.1.aar
│ └─ src/.../sensor/ # HeartRateSensorManager(Samsung) + source/*(그 외)
├─ gradle/libs.versions.toml # 버전 카탈로그(단일 출처)
├─ README.md # 한국어 비개발자 안내
├─ SPEC.md # 기술 명세(동작·데이터·아키텍처)
├─ SENSORS.md # 센서 지원/미지원 근거
└─ DESIGN.md # UI 디자인 시스템