What the cap measures vs. what we infer
Before we get into how doses get classified, here's the honest
architecture: the cap is a dumb sensor and the server is the brain.
The cap measures a small number of physical signals very well, and
the server is what turns those signals into "you took your dose."
Knowing what the cap can and can't actually see helps explain why
a few things — like "did you take the right pill" — aren't
problems we try to solve with the hardware alone.
What the cap physically measures
Three-axis accelerometer
A tiny chip that tells the cap which way is up. Reads 20 times per second.
Technical detail
InvenSense MPU-6050 IMU. The Z-axis component of acceleration is interpreted against the local gravity vector to determine whether the cap is right-side-up (≈ +9.8 m/s²) or inverted (≈ −9.8 m/s²). Combined with the X and Y components, total acceleration magnitude is used for motion detection.
Three-axis gyroscope
Senses rotation. Captured but not actively used yet — reserved for things like detecting a deliberate twist-off versus a knock-over.
Technical detail
Same MPU-6050 part. Sampled at 20 Hz in 500°/s range. Future revisions will use the gyroscope for rotation-based gesture classification (Claim Group M of the RingoRx patent disclosure).
Wall-clock time
The cap synchronizes its clock with a network time server. Every observation gets a real timestamp.
Technical detail
NTP synchronization via pool.ntp.org with time.nist.gov and time.google.com fallbacks. Local clock drift is tolerated up to 24 hours under offline conditions and reconciled on reconnect.
What we do NOT measure
Worth being explicit. The cap has no camera, no microphone, no
weight sensor, no humidity sensor, no GPS, no fingerprint reader.
Specifically, none of these are inputs to the system:
- Pill weight
- How many pills are in the bottle
- Which specific pill came out
- Who took the pill
- Whether the pill went in the mouth
- Temperature, humidity, or location
- Anything visual — there is no camera
- Anything audible — there is no microphone
This is a deliberate design choice. Fewer sensors means a smaller
cap, longer battery life, lower cost, fewer privacy surfaces, and
clearer engineering. The trade-off is that some questions — "did
the patient actually swallow the pill?" — can't be answered by the
cap alone. We address those with workflow design rather than
hardware: pill-image verification at notification time, caregiver
confirmation flows, and (eventually) optional wearable
integrations for medication-adherence-aware activity context.
What the server infers from those measurements
Everything else is computed by the server from the cap's three
input streams. The cap itself has no concept of "dose,"
"schedule," "compliance," or "tamper" — it only knows orientation,
motion, and time. The server combines those raw signals with the
patient's prescription schedule and the medication's timing-class
rules to produce every classification you see in the app.
Cap removal
We see the cap go upside-down for more than a second. That's "cap off."
Technical detail
A `cap_off` compound event is emitted when the Z-axis acceleration component remains below the inversion threshold (−6.0 m/s²) for at least 1 second of debounce. The inverse condition (≥ +4.0 m/s² for ≥ 1s) emits `cap_on`.
Bottle pickup
The cap notices the bottle is being handled, even before it gets opened.
Technical detail
A `motion` event is emitted when the deviation of the smoothed acceleration magnitude from its rolling average exceeds 4 m/s², subject to a 400 ms cooldown. Motion events in the 2-second window preceding a `cap_off` provide the "intent" signal that distinguishes a deliberate cap-opening from a knocked-over bottle.
A dose was actually taken
The cap came off, stayed off for a brief moment, then went back on — preceded by someone handling the bottle. That's how we infer "the patient did the thing."
Technical detail
A dose is recorded only when (a) a `cap_off` and `cap_on` pair occur within 30 seconds of each other, and (b) the `cap_off` was preceded by a motion event within the prior 2 seconds. Both conditions must be met. Inversions without prior motion are treated as accidental and do not count toward adherence.
Which scheduled dose this counts toward
The server looks up your prescription schedule and finds which scheduled time the dose was closest to.
Technical detail
The server-side classifier computes the minute-of-day for the dose event and finds the nearest configured slot. The dose is credited to that slot if the offset is within the medication's configured tolerance — see the Definitions section above for the on-schedule and early/late windows.
On-schedule vs. early vs. late
Based on how close to the scheduled time you actually took it.
Technical detail
The same `dose_taken` event is emitted regardless, with a `timing` field carrying the classification (`on_schedule`, `early`, or `late`). Downstream consumers (notifier, dashboard) use this field to determine wording, color, and badge placement.
Tamper / off-schedule access
Someone opened the bottle outside any reasonable scheduled-dose window. Could be the patient taking a dose at the wrong time, or someone else accessing the bottle.
Technical detail
A `cap_off` event with no scheduled slot within ±4 hours triggers a `tamper` compound event. Especially relevant for controlled substances (Schedule II–V) where unauthorized access has legal as well as clinical implications.
Cap is online / offline
The cap sends a small heartbeat once a minute. If we don't hear from it for five minutes, we know it's offline.
Technical detail
The cap publishes a `keepalive` event every 60 seconds while it has network connectivity. A server-side watcher thread checks every 30 seconds and flips a cap's `caps_online_state` row to `offline` when more than 300 seconds have elapsed since the last keepalive. The transition triggers a `cap_offline` status event.
Cap is replaying offline events
When the cap comes back online after being out of range, it uploads everything it recorded while offline. We label those events so they're not mistaken for live activity.
Technical detail
On reconnect, the cap drains its local LittleFS event spool to the `ringorx/<cap>/event_backfill` MQTT topic. The server flags each event with `backfilled=1` in the events table and the cap's online state transitions to `replaying`. After 30 seconds of no further backfill traffic, state transitions to `online` and a single summary email is queued to recipients.
Why this split matters: the cap is just a sensor. The
server is the brain. This means we can change the rules without
re-flashing every cap in the field, replace a cap without losing the
patient's history, and add new medication classifications without
firmware changes. Every cap in production runs the same firmware;
every account-specific behavior lives on the server.