- 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
LOG.atLevel(logLevel).log(...) in NativeFITMessage.getFieldDefinition
uses the SLF4J 2.0 fluent API, which logback-android 3.0.0 does not
implement. This throws AbstractMethodError at runtime, crashing the
FitAsyncProcessor thread and aborting the entire Garmin device sync
when a .FIT file triggers the type/size mismatch path.
Replace with a switch dispatching to LOG.debug/info/warn.
Health Connect enforces hard min/max bounds on each record-type field; a
single out-of-range value caused the record constructor to throw and
killed the entire batch insert. The trigger was a Garmin Venu 3
producing HRV > 200 ms, but several syncers shared the same gap (no
upper bound, only > 0 or no validation at all).
Add explicit per-syncer numeric guards matching the bounds enforced by
HC's record constructors, drop out-of-range samples, log them with the
existing [HC_SYNC] prefix, and continue the sync.
Bounds applied (per-record HC enforcement):
- HRV: 1..200 ms
- VO2Max: 0..100 ml/kg/min
- Weight: 0..1000 kg
- BloodGlucose: 0..900.91 mg/dL (= 50 mmol/L) -- previously unvalidated
- RespiratoryRate: 0..1000 breaths/min
- HeartRate / RestingHR: 1..300 / 0..300 bpm (widened from 20..250 to
match HC's contract; downstream consumers can apply physiological
filters)
- SpO2: 0..100 %
- Steps: 1..1000000 per record
- ActiveCalories: 0..1000000 kcal per record
- Distance: 0..1000000 m per record
- Skin temperature delta: -30..30 °C
- Body temperature: existing physiological clamp 25..45 °C kept (HC has
no enforced bound)
Add Logger.skipOutOfRange extension in syncers/SyncerLogging.kt and
relax HC_SYNC_TAG visibility from private to internal so syncers can
share it.
HeartRateSync aggregates dropped-sample counts into one summary log
line per slice rather than per-sample, to avoid log spam from glitchy
firmware.
convertSample visibility relaxed from protected to internal in the two
abstract base syncers so the new boundary tests can call it directly.
30 new unit tests in SyncerRangeValidationTest cover boundary
conditions per syncer.
ExerciseRoute requires strictly increasing timestamps; duplicate-second
points from device GPS parsers (e.g. Xiaomi Smart Band 9 Pro outdoor
cycling) caused IllegalArgumentException at construction time, aborting
the workout sync entirely.
Extract route construction into buildSanitisedRoute, which:
- drops points outside the workout time window
- drops points with non-finite or out-of-range lat/lng (HC's Location
constructor enforces lat in [-90, 90], lng in [-180, 180])
- drops points with non-finite or negative hdop/vdop accuracy (HC
rejects negative accuracy)
- deduplicates points by Instant
- returns null if fewer than two usable points remain
- catches IllegalArgumentException as a safety net so future HC
invariants degrade gracefully (workout saves without route rather
than the whole sync aborting)
Logs dropped points with the [HC_SYNC] prefix so users can grep.
The Androidx annotations are frequently used whereas Jetbrains annotations are infrequently used.
Where possible, use Androidx instead of Jetbrains annotations.
Adds the capability to set and dismiss alarms from third party apps through an Intent API, fixes#5553
**This PR was co-authored by ChatGPT and José Rebelo (passively):**
- Scaffold for the core component `DeviceAlarmReceiver` was taken from José's `DeviceSettingsReceiver`
- The core implementation was written by me by hand, ChatGPT was used to review and find potential issues and bugs
- The `DeviceAlarmReceiverTest` unit tests were written by ChatGPT and reviewed by me
**About these changes:**
- Adds a new developer settings entry for the device that the user needs to enable, so other apps can use the Intent API for such device
- Supports app blacklisting as it is done in the `DeviceSettingsReceiver`
- `ConfigureAlarms.addMissingAlarms()` was migrated to `DBHelper.fillMissingAlarms()` to provide this abstraction globally
- `AlarmReceiver` was migrated to `SunriseSunsetAlarmReceiver` to avoid ambiguity with this feature; the formerly called `AlarmReceiver` was related to setting calendar events based on the sunrise/sunset via an Intent API
- The API is registered in the `DeviceCommunicationService`
- The `ConfigureAlarms` activity updates its list after setting alarms from a third party app by broadcasting `ACTION_SAVE_ALARMS`
- It is possible to set alarms of a device that is offline, it should sync when the device connects next time, unless it downloads the alarms from the device when connecting
- However, this device-to-GadgetBridge alarm sync API is not commonly abstracted, so we cannot respect existing alarms on the devices when changing alarms within the new Intent API; the database is the single source of truth and connecting the device after using the API will reset alarms
- ~~The API was designed to align with the `AlarmClock` API from Android, but~~ there is no feature parity to improve the design and interaction; after all the `AlarmClock` API is interacted with `startActivity`, but `GadgetBridge` uses `sendBroadcast` receivers, which is recommend for background tasks
- Errors are not raised to the caller (i.e. providing unknown or invalid Mac Address of device or trying to set an alarm on a nonexisting slot); these errors are logged though
**EDIT: Additional changes during the development:**
- Since slot IDs are unpredictable, the flow is title-driven (there is no way to provide an ID)
- Dismissing an alarm through the API removes its title
- Only alarms without a title can be overwritten and a title should be set through this API to retain the ability to dismiss it without dismissing all alarms of the user
- Dismissing all alarms regardless of the title is still possible
- The title is exposed in the UI not just when the device supports it, but also when this API is enabled in the developer settings of the device itself
**Example usage:**
- https://gitlab.com/martin-braun/warpclock-plus/-/blob/feat/gb/app/src/main/java/com/antonok/warpclock/AlarmIntentService.kt demonstrates dismissing all alarms and setting multiple alarms (search for `sendBroadcast`)
- `DeviceAlarmReceiverTest` should cover all cases for a more complete overview of the capabilities of the API
Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/6088
originally the 100ms sleep after writes was introduced to fix a bug where sending an appmessage right after ACKing a previous appmessage cuased the outgoing packet to be NACK'd by the 3.0+ firmware. 4fe498efc
Keeping because 100ms for most situations doesn't matter, but definitely worth avoiding when uploading new firmware to the watch.