Report glucose sensor unavailable when data is stale (#53) - #60
Open
proscar87 wants to merge 1 commit into
Open
Conversation
…TST#53) The LibreView API always returns the most recent known reading, even when the CGM has been disconnected for hours, so GlucoseSensor never became unavailable and alert automations kept firing on stale data. Adds a CONF_MAX_DATA_AGE option (minutes), following the existing CONF_SENSOR_DURATION pattern in const.py/config_flow.py, and overrides GlucoseSensor.available to compare the reading's factory_timestamp against it. Defaults to 0 (disabled) to preserve current behaviour for existing installations until they opt in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #53
Problem
The LibreView API always returns the latest known reading — it never signals "no current data".
GlucoseSensorinsensor.pydoesn't overrideavailable, so the entity never becomesunavailable, even if the CGM has been disconnected for hours. In practice this means a glucose alert automation can silently stop working while the entity looks perfectly healthy — a trust failure in a health-related integration, not a cosmetic one.Design (proposed by the maintainer in the issue thread)
This PR implements exactly that.
Implementation
const.py: newCONF_MAX_DATA_AGE = "max_data_age"andDEFAULT_MAX_DATA_AGE = 0, added right next toCONF_SENSOR_DURATIONfollowing the same naming/placement convention.config_flow.py: addedCONF_MAX_DATA_AGEas avol.Required(..., default=...)intfield to both the initial setup step (async_step_options) and the options/reconfigure step (LibreViewOptionsFlowHandler.async_step_init) — mirroringCONF_SENSOR_DURATIONin both defaulting logic and schema placement.sensor.py:GlucoseSensornow takes amax_data_ageconstructor argument (minutes) and overridesavailable:super().availableis still respected (coordinator failure still means unavailable).max_data_ageis0, behaves exactly as before (always available) — see "Default" below.gcm.factory_timestamp(UTC) againstnow; unavailable once the reading is older thanmax_data_ageminutes._measurement_timestampcatchesAttributeError/TypeError/ValueError/IndexErrorfrom the underlyingGlucoseMeasurement.parse_dtand fails open (reports available, logs at debug level), since blowing up a health-data entity over a parsing edge case seemed worse than the alternative.async_setup_entryreads the new option withentry.data.get(CONF_MAX_DATA_AGE, DEFAULT_MAX_DATA_AGE)rather than direct indexing (unlike the other options), because existing config entries created before this PR won't have this key until the user re-saves the options flow — direct indexing wouldKeyErroron every existing installation at startup.strings.json/translations/en.json: added the new field description next tosensor_durationin both the config and options steps. No other languages touched.Default: disabled (
0)Libre sensors report roughly every 1–5 minutes depending on model. A too-short threshold would flip the entity unavailable on every normal sync gap; too-long and it stops protecting anything. Rather than guess a "safe" active default (there's a real range of opinions in the issue thread — 1x vs 3-5x the reporting interval), I defaulted to disabled (
0) so existing installations see zero behavior change until a user explicitly opts in and picks a value that fits their sensor/automation setup. This seemed like the safer default for a health-adjacent integration where an unexpected "unavailable" could itself break an automation. Happy to change the default if the maintainer prefers an active one (e.g. 15 min) instead.What I validated
This repo has no unit test suite (CI is Hassfest + HACS + lint only), so I wrote a standalone script that imports the real
custom_components.libreview.sensormodule and instantiates the realGlucoseSensorclass against a synthetic-but-structurally-realLibreView.models.Connection/GlucoseMeasurement/Sensorobject graph (same field layout as the real 0.3.0 package — I had to stub theLibreViewpackage itself for local testing only, because the published 0.3.0 wheel is currently broken against moderndataclass-wizardreleases due to ajson_fieldimport that no longer exists there; that's a pre-existing, unrelated upstream packaging issue, not something this PR touches). This is not a bundled test — it's a local-only validation script since the repo has no test infra.Confirmed via
git stash:available=True— reproducing the bug.max_data_age=5reportsavailable=False; a 1-minute-old reading with the same setting staysavailable=True;max_data_age=0(default) keeps stale dataavailable=True(no behavior change); a malformed timestamp does not raise and reportsavailable=True(fail-open).black,isort --profile black, andpylint(matching this repo's CI) — clean except for three pre-existing pylint warnings unrelated to this change (verified identical onmain).What I could not validate: real-world CGM disconnect behavior. I don't have a way to simulate a real Libre sensor going out of range for hours and observe what
FactoryTimestampLibreView's API actually returns in that situation (frozen at last-seen time vs. something else). The reporter of this issue has a real FreeStyle Libre and offered to test — that real-world confirmation would be valuable before merging, especially to sanity-check the timestamp field genuinely stops updating (rather than e.g. reflecting the LibreView cloud sync time) when the sensor drops off.🤖 Generated with Claude Code