activityService.ts
Purpose
This module orchestrates the full activity sensing pipeline: starting and stopping the accelerometer, computing CPM from the live sensor buffer on a 1-minute tick, persisting each result to the CPM database, and exposing a 40-minute rolling average for downstream core temperature inference.
Its responsibilities include:
- Initializing the CPM database before starting the sensor
- Starting accelerometer streaming at 30 Hz
- Waiting for a full 60-second window to accumulate before the first CPM persist
- Scheduling steady 1-minute ticks once the initial window is ready
- Persisting each valid CPM value to the database with its timestamp
- Skipping ticks when the sensor window is insufficient, with debug logging
- Exposing a 40-minute rolling CPM average with freshness and coverage guards
- Cancelling all timers and stopping the sensor cleanly on service stop
Invariants
Lifecycle Version Guard
A monotonically incrementing lifecycleVersion counter is checked after every async init step.
If stopActivityService is called while startActivityService is awaiting DB init, the stale start is abandoned silently rather than proceeding to start the sensor and timers.
Window Sufficiency Check
A CPM value is only persisted when the accelerometer buffer spans at least MIN_WINDOW_SPAN_MS = SAMPLE_WINDOW_MS − SENSOR_INTERVAL_MS (≈59,966 ms).
Ticks with insufficient window coverage log a debug message and skip the database write without error.
Initial Window Polling
The first tick is scheduled after SAMPLE_WINDOW_MS (60 seconds). If the window is still building at that point, the timer re-schedules itself at SENSOR_INTERVAL_MS (34 ms) intervals until a full window is available.
Initial wait logs are throttled to one emission per 10 seconds to avoid log flooding.
Steady Tick Non-Duplication
scheduleSteadyMinuteTicks is a no-op if tickTimer is already set.
scheduleInitialMinuteTick is a no-op if either startupTimer or tickTimer is already set.
This prevents duplicate timers from being created if startActivityService is called more than once.
CPM40 Freshness Guard
getActivityCpm40Average returns null if the most recent persisted CPM row is older than 2 minutes.
This prevents a stale CPM value from being used in a prediction after the sensor has stopped or the app has been backgrounded.
CPM40 Coverage Guard
getActivityCpm40Average requires at least CPM_WINDOW_MIN_ROWS = 35 rows within the 40-minute window.
Fewer rows return null rather than an unrepresentative average.
Constants
| Constant | Value | Description |
|---|---|---|
SENSOR_RATE_HZ | 30 | Accelerometer sample rate |
SAMPLE_WINDOW_MS | 60,000 | CPM compute window |
TICK_INTERVAL_MS | 60,000 | Steady CPM persist interval |
CPM_WINDOW_MINUTES | 40 | Rolling average window for CPM40 |
CPM_WINDOW_TARGET_ROWS | 40 | Target number of CPM rows to fetch for averaging |
CPM_WINDOW_MIN_ROWS | 35 | Minimum rows required to return a CPM40 average |
CPM_LATEST_MAX_AGE_MS | 120,000 | Maximum age of most recent CPM row for CPM40 |
INITIAL_WAIT_LOG_INTERVAL_MS | 10,000 | Throttle interval for initial window wait logs |
Exports
CpmDetails
type CpmDetails = {
ok: boolean;
reason?: string;
cpm: number;
windowSeconds: number;
samples: number;
};
Describes the result of a single CPM computation attempt. ok: false indicates the window was insufficient or inference failed.
computeCurrentCpmDetails()
function computeCurrentCpmDetails(): CpmDetails
Reads the current accelerometer buffer and attempts a CPM computation. Returns a CpmDetails with ok: true on success or ok: false with a reason string on failure. Called synchronously.
startActivityService()
async function startActivityService(): Promise<void>
Initializes the CPM database, starts the accelerometer, and schedules the CPM tick pipeline. Throws if DB initialization fails.
stopActivityService()
function stopActivityService(): void
Cancels all timers, stops the accelerometer, and resets all service state. Safe to call when the service is not running.
getActivityCpm40Average()
async function getActivityCpm40Average(): Promise<number | null>
Returns the arithmetic mean of CPM rows from the last 40 minutes, or null if coverage or freshness requirements are not met.