A reference pattern for fusing multiple watchOS sensor signals — HealthKit vitals, CoreMotion accelerometer data, optional CoreLocation speed — into a single risk classification, with a debounce/cooldown primitive for the notification side.
Extracted and generalized from the anomaly-detection pipeline built for Ember, a biometric dead man's switch app. This package shares the architecture, not Ember's production configuration — see What this is / isn't below.
Most watchOS "something bad might be happening" features (fall detection, crash detection, panic triggers) end up solving the same three problems:
- Combine several independent, asynchronously-arriving signals into one decision, without letting a single ambiguous signal drown out a single unambiguous one (e.g. a confirmed fall shouldn't be diluted by averaging it against calm vitals).
- Detect a sudden motion anomaly from raw accelerometer data using both a "sustained" check (filters noise) and a "sharp delta" check (catches instantaneous impacts a duration gate would smooth over).
- Debounce the result so the user isn't re-alerted every few seconds while the underlying condition persists.
SignalFusionKit is those three pieces, written as small, pure, unit-tested
types with zero dependency on HealthKit/CoreMotion in the parts that matter —
plus one thin, documented (but untested) adapter showing how to wire them to
real sensors.
Swift Package Manager:
.package(url: "https://github.com/izetg/SignalFusionKit.git", from: "1.0.0")import SignalFusionKit
// 1. Combine signals into a risk level.
let snapshot = SignalSnapshot(
oxygenSaturation: 93,
heartRateVariability: 40,
fallDetected: false,
motionAnomalyDetected: false
)
let assessment = RiskEngine.evaluate(snapshot, config: .example)
// assessment.level -> .high (SpO2 below the example "low" threshold)
// 2. Feed raw accelerometer magnitude samples to detect motion anomalies.
let detector = MotionAnomalyDetector(config: .example)
let anomalyDetected = detector.ingest(magnitudeG: 4.2, at: Date())
// 3. Debounce repeated triggers.
let cooldown = CooldownGate(window: 60) // seconds
if cooldown.attempt() {
// safe to alert / act
}See WatchKitAdapter.swift
for a worked (but untested — see below) example wiring this to real
HealthKit + CoreMotion callbacks in a watchOS app.
Is: a reusable architecture for combining multi-sensor watchOS signals
into a risk decision, with the interesting logic (RiskEngine,
MotionAnomalyDetector, CooldownGate) fully unit-tested and free of Apple
framework dependencies.
Isn't: Ember's production configuration. The threshold values in
RiskEngineConfig.example and MotionAnomalyConfig.example are round,
illustrative numbers picked for readability in tests and docs — not the
tuned, validated values Ember actually ships with. Real thresholds need
real-world data collection, false-positive/negative tradeoffs specific to
your use case, and ideally domain-expert review. Treat the example configs
as the seam where your own calibration plugs in, not as a recommendation.
Isn't tested end-to-end on a Watch. RiskEngine, MotionAnomalyDetector,
and CooldownGate are pure Swift and covered by swift test.
WatchKitAdapter is real HealthKit/CoreMotion glue that needs Xcode and a
device/simulator to exercise — verify it there before relying on it.
swift testRuns on any platform with the Swift toolchain — the tested types don't import HealthKit or CoreMotion.
MIT — see LICENSE.