개발자 정보

개요 · 아키텍처

소스를 받아 직접 빌드·수정하는 개발자용 문서입니다. 구조, 데이터 계약, 센서, 기여 규약을 다룹니다.

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/targetUI역할
: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 홀더로 잇습니다.

⌚ WATCH 📱 PHONE UI · 표현 도메인 · 서비스/상태 데이터 · 센서/저장/전송 WearApp Wear Compose UI MainViewModel SessionUiState 보관 ForegroundService 녹화 소유 · wake lock SessionStateHolder StateFlow holder HeartRateSensorManager → Samsung SDK Source mgrs → Sensor · HS · GPS WatchDatabase (Room) ★ source of truth WearDataLayerClient DataClient sender samples StateFlow MobileApp Compose UI MainViewModel sessions · export HeartRateRepository Flows for UI CsvExporter → CSV FileProvider 공유 MobileDatabase (Room) wide ticks · schema v3 MobileDataLayerListenerService WearableListenerService · 수신시각 스탬프 Flows 인프로세스 호출 · Flow 관찰 Wearable Data Layer (DataClient) — 저장 후 전송 · 오프라인 버퍼링 · 자동 재동기화 source of truth — 워치 Room 에 항상 먼저 저장(전송 실패해도 무손실)

워치

  • 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.IO Room 쓰기, ~1초 flush, 세션 생성·마감. ACTION_START/ACTION_STOP 인텐트로 시작·중지를 제어합니다.
  • SessionStateHolder — 프로세스 전역 StateFlow<SessionUiState>. 서비스가 쓰고 UI가 읽습니다.
  • WatchHeartRateRepository · WatchDatabase — local-first. 워치 DB가 source of truth.

  • MobileDataLayerListenerServiceWearableListenerService. 들어온 DataItem을 파싱하고 수신 시각을 찍어 Room에 넣습니다.
  • HeartRateRepository · MobileDatabase — 저장과 UI용 Flow.
  • CsvExporter — CSV 작성 + FileProvider 공유.
  • MainViewModel — 세션·최신 틱·워치 연결(NodeClient)·내보내기.

깨면 안 되는 세 가지

코드 어디서든 지켜야 하는 불변식

  1. 워치 Room이 source of truth. 폰으로 보내기 전에 무조건 로컬에 먼저 씁니다. 전송이 실패해도 데이터는 남아야 합니다.
  2. 저장 단계에서 raw 표본을 거르지 않습니다. bpm 0, off-wrist, 비정상 status, 빈 컬럼 다 그대로 둡니다. 보간 없음. hr <= 0 필터는 시각화에서만 씁니다.
  3. 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-wearable 18.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 디자인 시스템