Skip to content

[Part 6/6] feat(settings): battery status, Anubis branding, wheel picker for session length - #6

Open
ETolboom wants to merge 7 commits into
trioneer-dev:mainfrom
ETolboom:feat/battery-status
Open

ETolboom wants to merge 7 commits into
trioneer-dev:mainfrom
ETolboom:feat/battery-status

Conversation

@ETolboom

@ETolboom ETolboom commented Sep 24, 2026 •

Copy link
Copy Markdown

Stacked on #5; only the top commit is new.

  • Battery status in settings: a "Battery ›" row (hidden until the first battery read) showing High / Low / Replace Now, derived from Voltage B against the 2750/2700 mV thresholds. It pushes a new standalone Battery screen (Voltage A/B, resistance, temperature, last checked, with low/very-low footers), split out of Transmitter Details.
  • Anubis branding: a transmitter reporting the extended 180-day lifetime shows "Anubis" as its model name, and the settings header shows the Anubis artwork (asset ported from DexKit).
  • Session length as wheel picker + Save: replaces the slider. The wheel only stages a value; Save applies it, so scrolling cannot fire intermediate writes into persistent state. Disabled until the value actually changes.
  • Simulator seeds a healthy battery and a 180-day session, so all of the above is visible in the simulator.

Screenshots

Battery row (settings) Battery details Session length (wheel) Anubis header + model
image image image image

@ETolboom
ETolboom marked this pull request as ready for review September 24, 2026 15:07
Copilot AI lite review requested due to automatic review settings September 24, 2026 15:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

OSLog.log handed the [CVarArg] array to os_log for 6+ arguments, which
reads garbage pointers for each %@ and segfaults in
_os_log_fmt_flatten_object_impl. The seven-argument "Backfill
acknowledged" line hit this on every backfill, crash-looping the app.

The fallback now asserts in debug and logs the pre-rendered line as a
single argument in release. The backfill ack line is collapsed to one
argument.
…ensor work in the simulator

Extract the pending-command logic into a pure CommandQueue value type
(supersede-on-enqueue, 5-minute stale-calibration drop, raw-value
persistence round-trip) with unit tests, and mirror didComplete into
enqueue on the simulator, where no link will ever drain the queue.

Also: stop simulated readings once no session is active, and mark
Locked.mutate @discardableResult (both copies).
…ard, discovery timeout

PeripheralManager.didUpdateValueFor: bind the condition's characteristic
and compare with == instead of shadowing the parameter (any
characteristic's update could satisfy a pending command's condition),
and forward unclaimed notifications to the delegate even while a
command is in flight (backfill frames were dropped mid-command).

didComplete(.stopSensor): emit a .sensorEnd persisted event before
clearing the session dates; the simulator drain path mirrors it.

transmitterExpirationDate stays nil until the first version read: the
fallback lifetime is a guess and a 180-day Anubis would alert as
expired at day 90.

Service discovery timeout 2s -> 10s. Battery re-arm reads the persisted
state.lastBatteryReadDate instead of a transient ivar that was nil after
every relaunch. UInt16(clamping:) for the persisted expiry-days Int
fallback, matching G6CGMManagerState.
…smitter model in core

The old thresholds (..<(-3) style) put a boundary rate like exactly
-3.0 mg/dL/min on the single-down arrow where the official apps and
CGMBLEKit put it on the steeper one, and mapped implausible rates
(|rate| > 8, reachable over the wire as Int8 tenths) to a triple arrow
where the apps show none.

The bucketing now lives in G6SensorCore as G6TrendArrow (LoopKit-free,
raw values matching GlucoseTrend for a one-line bridge) so it is covered
by swift test, and all three call sites (glucoseTrend(for:),
G6GlucoseDisplay.trendType, settings trend symbol) share it.

Also moves the transmitter-model prefix classification (8.. G6, 5../C..
ONE) into core as G6TransmitterModel with TransmitterID.model, so the
mapping is unit-tested; deviceModel keeps returning the display string.
…warn on large deviations

The calibration screen now shows the current sensor reading next to the
entry field and only allows sending a calibration when it can be
compared fairly: the latest reading must be fresh (one missed 5-minute
reading closes the gate, with 30 s of jitter tolerance) and the trend
must be flat (|rate| < 1 mg/dL/min, via the shared G6TrendArrow
bucketing). Blocked states explain themselves with a callout; the Send
button greys out.

A change of 40 mg/dL or more from the current reading asks for
confirmation before sending.

A new G6CalibrationViewModel observes manager state so the reading
updates live; a 30 s tick lets the freshness gate expire while the
screen is open (state updates only arrive with a reading). Entry
parsing is now a single computed property shared by the deviation math
and submit, so the warned-about value is exactly the value sent.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants