C and Firmware

이 문서는 Hospital-at-Home 예제에서 C/Firmware가 맡는 device measurement integrity 책임을 설명합니다. C/Firmware는 clinical risk를 판단하지 않고, backend가 신뢰할 수 있는 sample, sequence, quality evidence, firmware artifact를 만드는 경계를 보여줍니다.

C/Firmware 예제는 아래 세 부분으로 나뉩니다.

  • packages/patient-signal-c-core: board-independent C package
  • apps/wearable-firmware: bare-metal firmware app
  • apps/wearable-zephyr-firmware: Zephyr RTOS firmware app

이 구조는 재사용 가능한 domain/application code와 MCU, RTOS, scheduler, sensor, flashing concern을 분리하기 위한 것입니다.

1. Tooling

Area Tooling
Host C package CMake, Ninja, CTest
Bare-metal firmware CMake, Ninja, Arm GNU Toolchain
RTOS firmware Zephyr, west, CMake, Ninja
Formatting/static checks clang-format, clang-tidy, cppcheck
Documentation Doxygen, Markdown
Artifact repository Nexus Conan guide hosted repository, Nexus raw-embedded-guide-hosted repository

C/C++ library package는 Conan 2를 사용하고 Nexus Conan guide hosted repository에 publish합니다. Firmware release artifact는 .elf, .hex, .bin, .map 파일이므로 Nexus raw-embedded-guide-hosted repository에 보관합니다.

Formatting and static-analysis defaults live at source-repo/.clang-format and source-repo/.clang-tidy.

2. Architecture

patient-signal-c-core follows the same boundary rule as the other languages.

  • domain: pure C functions over value structs
  • ports: function pointer table for external clinical metadata
  • usecase: stateless function that calls ports and then pure domain rules
  • tests: unit tests for domain, integration tests with a mock port

Bare-metal and Zephyr apps own the adapters:

  • inbound: scheduler tick, RTOS queue, BLE command, ISR-safe event bridge
  • outbound: sensor, flash, telemetry, clinical metadata cache
  • bootstrap: concrete adapter and port table wiring
  • config: board/device/runtime constants

Port table이 adapter state pointer를 들고 있을 때는 lifetime을 명시적으로 다룹니다. wearable_firmware_bootstrap()wearable_zephyr_bootstrap()은 값 struct를 만들고, 반환된 실제 container 주소를 기준으로 *_bind_ports()가 adapter state를 연결합니다. C에서는 반환 전 local struct 내부 주소를 port state로 저장하면 dangling pointer가 되기 쉽기 때문에 bootstrap과 bind 단계를 분리합니다.

3. Hospital-at-Home 구현 기준

이 시나리오에서 C/Firmware는 실제 PCB에 올라가는 제품 firmware가 아니라, firmware 팀이 참고할 수 있는 device boundary 예제입니다. 따라서 실제 board driver는 포함하지 않고, synthetic sensor와 hardware adapter placeholder를 제공합니다.

3-1. 책임

C/Firmware의 책임은 risk assessment가 아니라 measurement integrity입니다. 장비에서 수집된 생체신호가 backend로 넘어가기 전에 sample, 품질, packet 계약을 안정적으로 만드는 계층입니다.

Firmware가 담당합니다.

  • sampling rate, timestamp, sequence number 관리
  • fixed-size sample window 관리
  • sensor dropout, motion artifact, contact quality flag
  • dropped sample, clipped, saturated, sensor disconnected 같은 packet-level quality flag
  • device status, battery, firmware version, hardware revision
  • calibration metadata boundary
  • backend나 gateway로 보낼 signal packet 생성
  • .elf, .hex, .bin, .map artifact 생성과 추적

Firmware가 담당하지 않습니다.

  • clinical risk scoring
  • model threshold decision
  • patient clinical context 결합
  • final diagnosis 또는 treatment recommendation
  • MQTT broker, HTTP API, Kubernetes runtime policy

3-2. 생체신호 제공 방식

dev-guide는 실제 PCB를 포함하지 않으므로 생체신호는 세 가지 방식으로 제공합니다.

Source Purpose
Deterministic synthetic generator CI와 local demo에서 항상 재현 가능한 기본 signal source
Small fixture dataset Rust/Python/TypeScript가 같은 입력을 기준으로 테스트할 수 있는 sample
Hardware adapter placeholder 실제 PCB driver가 만족해야 할 interface 예시

기본 fixture 위치는 아래처럼 둡니다.

fixtures/signals/
  README.md
  manifest.json
  normal-resting.csv
  motion-artifact.csv
  sensor-dropout.csv
  low-spo2-trend.csv

Bare-metal firmware는 file system을 전제하지 않으므로 fixture CSV를 직접 읽지 않습니다. Firmware app은 synthetic generator로 sample을 만들고, host/Rust/Python/TypeScript 테스트는 fixture를 읽습니다.

Firmware synthetic generator는 아래 scenario를 순환합니다.

Scenario Purpose Expected device evidence
normal 정상 packet 기준선 quality=good
low-spo2 clinical concern은 있지만 측정 자체는 유효한 sample quality=good, low SpO2 value
motion-artifact 움직임과 접촉 품질 저하 quality=degraded, excessive_motion=true
sensor-dropout sensor contact loss quality=invalid, sensor_disconnected=true

이 구분이 중요합니다. C/Firmware는 low SpO2를 clinical risk로 판단하지 않습니다. 대신 low SpO2 sample을 유효한 measurement로 보존하고, motion/dropout처럼 측정 신뢰도를 낮추는 근거를 packet quality로 표시합니다.

3-3. Core implementation 기준

patient-signal-c-core는 단순 struct 예제가 아니라 host에서 test 가능한 signal core여야 합니다.

필수 구현 기준은 아래와 같습니다.

  • SignalSample 생성과 range validation
  • fixed-size SignalWindow summary
  • invalid sample count와 motion-based quality flag
  • dropped sample, clipped, saturated, sensor disconnected, excessive motion flag
  • calibration metadata boundary
  • SignalPacket build
  • firmware app의 deterministic synthetic sensor

이 기준을 통해 firmware 팀은 실제 PCB driver가 어떤 core contract를 만족해야 하는지 확인할 수 있습니다.

1차 예제는 board-independent C core에 fixed-size window summary와 packet build를 둡니다. 실제 ring buffer, timestamp source, ISR-safe buffer, DMA, BLE/MQTT transport는 board adapter 또는 2차 firmware 확장 범위입니다.

실제 PCB driver가 참고할 field mapping, packet quality mapping, timing/buffering, review checklist는 Firmware Driver Boundary에 둡니다. C/Firmware app은 이 기준을 만족하는 adapter를 core usecase에 연결하는 예제입니다.

Driver PR은 단순히 build 성공만으로 충분하지 않습니다. Timestamp source, sequence policy, raw channel scale, calibration, overflow/backpressure, quality scenario evidence를 남겨야 합니다. 이 기준이 있어야 실제 PCB driver가 붙어도 Rust preprocessing과 Python risk workflow가 measurement integrity 변화를 같은 의미로 해석할 수 있습니다.

3-4. Packet contract

C core가 만드는 packet은 device boundary의 source of truth입니다. 이 packet은 MQTT payload나 file fixture로 변환될 수 있지만, core package는 MQTT를 알지 않습니다.

예시 sample shape는 아래와 같습니다.

typedef struct PatientSignalSample {
  unsigned long long timestamp_ms;
  unsigned int sequence_number;
  int ppg_raw;
  int ecg_raw;
  unsigned int spo2_permille;
  unsigned int heart_rate_bpm;
  unsigned int motion_mg;
  unsigned int contact_quality;
} PatientSignalSample;

품질 정보는 hardware 관점의 integrity flag를 표현합니다.

typedef struct {
    uint32_t dropped_samples;
    bool sensor_disconnected;
    bool clipped;
    bool saturated;
    bool excessive_motion;
} PatientSignalPacketQuality;

C core는 firmware가 관찰할 수 있는 integrity flag를 packet에 담습니다. Python/Rust preprocessing은 이 값을 missing_ratio, artifact_score, signal_quality 계산에 사용합니다. 즉 C/Firmware는 risk를 판단하지 않고, backend가 판단할 수 있는 품질 근거를 안정적으로 제공하는 역할입니다.

3-5. MQTT와 firmware 경계

MQTT는 firmware domain이 아니라 transport adapter입니다. 실제 PCB firmware가 MQTT를 직접 publish할 수도 있지만, dev-guide 1단계에서는 firmware core가 packet을 만들고 별도 device simulator 또는 gateway가 MQTT publish를 담당합니다.

권장 흐름은 아래와 같습니다.

C synthetic generator
  -> SignalPacket
  -> device simulator / gateway
  -> MQTT publish
  -> backend ingestion adapter

초기 topic은 아래를 기준으로 둡니다.

tirosh/sites/{site_id}/devices/{device_id}/signals/v1
tirosh/sites/{site_id}/devices/{device_id}/status/v1
tirosh/sites/{site_id}/devices/{device_id}/events/v1

3-6. Test strategy

Firmware test는 hardware 없이도 의미 있어야 합니다.

Test Scope
Host unit test buffer, packet, quality flag, calibration
Host integration test synthetic generator -> packet -> expected quality
Zephyr native_sim RTOS thread, queue, synthetic sensor, packet flow
Driver evidence review timestamp, sequence, raw scale, calibration, overflow policy

HIL은 1단계 예제 범위가 아닙니다. 실제 PCB 연결 테스트가 필요해지면 별도 repository, 별도 workflow, manual/self-hosted runner 기준으로 다룹니다.

3-7. Clean Architecture 기준

C/Firmware에서 Clean Architecture는 class나 framework 구조가 아니라, board-independent core와 board-specific adapter를 나누는 기준입니다.

patient-signal-c-core는 scheduler, RTOS, sensor bus, flash, MQTT를 알지 않습니다. Core는 sample/window/packet/quality 계산만 수행하고, 외부 입력은 function pointer port로 받습니다. wearable-firmwarewearable-zephyr-firmware는 이 core를 실제 runtime에 연결하는 app입니다.

단단하게 고정할 것은 SignalSample, SignalPacket, SignalQuality의 의미입니다. 느슨하게 둘 것은 timestamp source, sensor driver, queue, transport, board profile입니다. 이 구분이 있어야 실제 PCB driver가 붙어도 backend contract와 host test가 흔들리지 않습니다.

3-8. DDD 기준

C/Firmware bounded context의 언어는 clinical diagnosis가 아니라 measurement integrity입니다. 따라서 C 코드와 문서에서는 아래 표현을 구분합니다.

Term Meaning
low_spo2 유효한 생체신호 값이 낮은 상태
sensor_disconnected 측정 자체가 무효인 hardware/contact 상태
excessive_motion artifact 가능성이 높은 device evidence
dropped_samples sequence/timing gap으로 관찰된 packet integrity 문제

Firmware가 high riskdiagnosis 같은 risk assessment 용어를 만들기 시작하면 bounded context가 깨집니다. Firmware는 backend가 판단할 수 있는 evidence를 잃지 않고 전달하는 역할에 집중합니다.

3-9. TDD 기준

Firmware TDD는 실제 board를 기다리지 않고 core 의미를 먼저 고정하는 방식으로 진행합니다.

  1. host unit test로 sample validation과 quality flag rule을 먼저 작성합니다.
  2. synthetic generator integration test로 scenario별 packet quality를 고정합니다.
  3. bare-metal host simulation으로 app wiring을 확인합니다.
  4. Zephyr native_sim smoke로 RTOS queue/thread composition이 깨지지 않았는지 확인합니다.

이 순서를 따르면 실제 board adapter가 추가되어도 domain rule 회귀와 runtime wiring 문제를 분리해서 볼 수 있습니다.

4. Commands

make doctor/c
make doctor/firmware
make doctor/zephyr

Host C package:

cmake --preset host-debug -S packages/patient-signal-c-core
cmake --build packages/patient-signal-c-core/build/host-debug
ctest --test-dir packages/patient-signal-c-core/build/host-debug --output-on-failure
make package/build/c

Bare-metal firmware:

cmake --preset arm-none-eabi-debug -S apps/wearable-firmware
cmake --build apps/wearable-firmware/build/arm-none-eabi-debug

Zephyr RTOS firmware:

cd apps
west init -l wearable-zephyr-firmware
west update
west zephyr-export
west build --pristine=always -b native_sim wearable-zephyr-firmware -- -DZEPHYR_TOOLCHAIN_VARIANT=host

5. Artifacts

Host C package should produce a Conan package:

conan create packages/patient-signal-c-core --version 0.1.0 --build=missing -s build_type=Release
conan upload patient-signal-c-core/0.1.0 -r tirosh-conan-guide-hosted --confirm

Bare-metal firmware should produce these files:

wearable_firmware.elf
wearable-firmware.hex
wearable-firmware.bin
wearable-firmware.map
wearable-firmware.release-manifest.json
  • .elf: debug symbols and section metadata
  • .hex: Intel HEX image for common flashing tools
  • .bin: raw image for bootloader or OTA packaging
  • .map: linker memory layout for review and release evidence
  • .release-manifest.json: artifact size, sha256, source revision, firmware version index

Upload them to Nexus raw-embedded-guide-hosted storage with a versioned path:

make firmware/upload PACKAGE_VERSION=0.1.0

Release manifest generation is explicit:

make firmware/manifest PACKAGE_VERSION=0.1.0

이 manifest 자체가 clinical 또는 regulatory report는 아닙니다. 대신 reviewer가 .elf/.hex/.bin/.map 파일을 해당 artifact를 만든 source revision과 package version에 연결해 볼 수 있게 하는 compact release evidence index입니다.

contracts/hospital-at-home/firmware-release-manifest.v1.schema.json가 manifest shape를 고정합니다. make contract/test는 schema example을 검증하고, firmware CI job은 artifact upload 전에 실제 build directory에서 concrete manifest를 생성합니다. 이렇게 해야 raw Nexus upload가 구조 없는 파일 덤프가 되지 않습니다.