Ollee Watch One is a Nordic-UART (nRF) analog smartwatch. This adds a new
all-Kotlin `ollee` device family providing:
- time + location sync
- a single alarm (weekday repetition; one-shot mapped to its next-fire
weekday, as the watch has no true single-fire mode)
- world time (one zone, with the world-time face enable and offset register)
- notification count badge
- battery voltage
- activity step sync (on-watch record drain -> activity samples)
The device family follows the existing all-Kotlin coordinator/support
pattern: OlleeDeviceCoordinator (capabilities + discovery), a thin
OlleeDeviceSupport, and per-feature pure-logic files (frame codec, alarm
record builder, weekday/world-time register composer, faces table, activity
records codec), each covered by JVM unit tests.
The wire protocol was worked out clean-room from HCI snoop logs and BLE
probes of my own watch and companion watch faces; nothing here derives from
decompiled or proprietary sources.
Hardware-verified on an Ollee Watch One (firmware 01.05.0000.01.10):
connect/init, battery voltage, firmware version, time+location sync,
activity step sync, world time, and the alarm (set, acknowledged by the
watch, shown on the Alarm face, and physically ringing at the set time).
The frozen-clientRecordId registry already makes HC upserts idempotent, so
the cursor machinery built to avoid re-processing is redundant. Drop the
persisted finalized flag, the sessionStart-1s cursor-hold, the completed-only
cursor derivation, and the prune-horizon coupling.
Sleep now advances a plain forward cursor (max end synced in slice) like every
other data type. A backward-re-segmenting night re-enters the existing 24h
look-back, overlap-matches its frozen id, and upserts onto the same record, so
advancing the cursor cannot orphan it. Skip the HC write when a re-detected
night's span is unchanged.
Add the missing HealthConnectSleepSession cleanup in
AbstractDeviceCoordinator#deleteDevice.
planSleepSessions: 5 params/two loops -> 3 params/one loop. SleepSessionRow
drops finalized. Schema stays 134 (table new in this PR, no migration).
A sleep session's start moves earlier across syncs because SleepAnalysis is
stateless and re-segments as samples arrive. Both the sync cursor and the
clientRecordId were derived from that unstable start, so a night could be
dropped entirely from Health Connect while the device chart still showed it.
Concretely, from issue #6297 (Xiaomi Smart Band 9 Pro, times UTC): one pass
saw only a single-sample fragment at 01:02 and advanced the cursor to
01:01:59. Seconds later the real night re-segmented to 23:08 -> 07:25, whose
start now sat before the cursor, so the slice-ownership rule skipped it -
permanently, since every later slice starts even later. The 7h28m night never
reached HC; only the bogus 1-minute fragment did, stuck under its own id.
Decouple session identity from the start: persist a per-device registry
(HealthConnectSleepSession) of clientRecordId + grown span + finalized flag.
Match each freshly detected session to a stored row by temporal overlap and
reuse its frozen id, so the single HC record grows in place instead of
spawning a new one. This makes HC's upsert idempotent and lets the
slice-ownership rule be removed. Derive the cursor only from completed
sessions (end older than the 6h in-progress threshold), never from an open
one, so a still-growing night is never orphaned by the forward-only cursor.
The registry is short-lived, not a history log: rows are pruned once their end
predates the next scan window (cursor - 24h look-back) and can no longer be
re-detected, which also reclaims open rows stranded by deleted samples. It is
persisted atomically (delete + re-insert in one transaction) so a failed write
cannot wipe the registry and cause id re-minting and duplicate HC records.
In the #6297 case the night's samples stay within the 24h look-back window,
so the dropped session is recovered automatically on the next sync.
Schema 131 -> 132 (new table, created by createAllTables, no migration).
- fix: use milliseconds instead of seconds to align with most other samples
- DB upgrade copies existing data from GARMIN_RESTING_METABOLIC_RATE_SAMPLE, GENERIC_TRAINING_LOAD_ACUTE_SAMPLE and GENERIC_TRAINING_LOAD_CHRONIC_SAMPLE to GENERIC_METRIC_SAMPLE
- new data is saved to both the specific table as well as GENERIC_METRIC_SAMPLE
- add GenericMetricSampleProvider with service functions
- add GENERIC_MAXIMUM_OXYGEN_UPTAKE parsing for Garmin
This extracts the following metrics and stores them in a new GenericMetricSample / GENERIC_METRIC_SAMPLE table:
- endurance score
- functional threshold power
- hill endurance
- hill score
- hill strength
- MET max VO2
- running lactate threshold power
- training readiness
Visualisation and charts aren't part of this PR.
# laxasfit support
With reference to issue #5640 here comes the initial implementation of the first supported laxasfit device:
- Q11 smartwatch
The used protocol is a derivate of the FitPro protocol, using
- different UUIDs
- different message ID's
- additional CRC
Protocol details are documented in the related issue #5640
Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/5774
Co-authored-by: Erwin Scheuch-Heilig <fr3ts0n@gmx.at>
Co-committed-by: Erwin Scheuch-Heilig <fr3ts0n@gmx.at>