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 packageapps/wearable-firmware: bare-metal firmware appapps/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,.mapartifact 생성과 추적
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
SignalWindowsummary - invalid sample count와 motion-based quality flag
- dropped sample, clipped, saturated, sensor disconnected, excessive motion flag
- calibration metadata boundary
SignalPacketbuild- 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-firmware와 wearable-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 risk나 diagnosis 같은 risk assessment 용어를 만들기 시작하면 bounded context가 깨집니다. Firmware는 backend가 판단할 수 있는 evidence를 잃지 않고 전달하는 역할에 집중합니다.
3-9. TDD 기준
Firmware TDD는 실제 board를 기다리지 않고 core 의미를 먼저 고정하는 방식으로 진행합니다.
- host unit test로 sample validation과 quality flag rule을 먼저 작성합니다.
- synthetic generator integration test로 scenario별 packet quality를 고정합니다.
- bare-metal host simulation으로 app wiring을 확인합니다.
- 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가 구조 없는 파일 덤프가 되지 않습니다.