Maya Workflow Kit
The whole Maya workflow in Blender, in one install. Free and open source, Blender 4.5 – 5.2.
翻譯準備中
Responsive Home Assistant Lovelace card for CGM data from Dexcom, Nightscout, LibreView, LibreLink, and CareLink, with automatic source and trend detection.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗A custom Lovelace card for Home Assistant that displays CGM (Continuous Glucose Monitor) data. Supports Dexcom, Nightscout, LibreView, LibreLink, and Carelink (Medtronic) with automatic trend detection.
One card and one config in two slots: it takes the shape it is given.
| Integration | Value Entity | Trend Detection |
|---|---|---|
| Dexcom | sensor.*_glucose_value | Auto-detected from *_glucose_trend entity |
| Nightscout | sensor.blood_sugar | Auto-detected from direction attribute |
| LibreView (PTST) | sensor.*_glucose_level | Auto-detected from trend attribute |
| LibreLink (gillesvs) | sensor.*_glucose_measurement | Auto-detected from sibling *_trend entity |
| Carelink (Medtronic) | sensor.*last_glucose_level_mg_dl | Auto-detected from *last_glucose_trend entity |
Just select your glucose sensor — the card figures out the rest automatically.
Community integrations beyond the table work as glucose sources too, with two
limits. Their trend usually lives in a separate entity (*_bg_direction,
*_glucose_trend) rather than an attribute, so point glucose_trend at it
(the card already speaks the Nightscout directions those entities publish);
and none of them reports the measurement time in a shape the card reads, so
the clock falls back to last_updated.
# Minimal — just one entity, trend auto-detected
type: custom:sugartv-card
glucose_value: sensor.jane_glucose_value
# Full config with all options
type: custom:sugartv-card
glucose_value: sensor.jane_glucose_value
glucose_trend: sensor.jane_glucose_trend # optional override
timestamp_attribute: measurement_timestamp # optional override
insulin_value: sensor.jane_active_insulin # optional
show_prediction: true
color_thresholds: true
thresholds:
urgent_low: 54 # mg/dL (or 3.0 mmol/L)
low: 70 # mg/dL (or 3.9 mmol/L)
high: 180 # mg/dL (or 10.0 mmol/L)
urgent_high: 250 # mg/dL (or 13.9 mmol/L)
sensor.jane_* is a placeholder. Your entity ids depend on the integration, so
check Developer Tools, States and match the patterns in the table above. Dexcom,
for example, builds the id from your account username, so it is
sensor.<username>_glucose_value, with no dexcom_ prefix.
| Option | Type | Default | What it does |
|---|---|---|---|
glucose_value | entity id | required | The sensor holding the reading. Everything else is derived from it. |
glucose_trend | entity id | auto-detected | Point at the trend entity when the card cannot find it. YAML only. |
timestamp_attribute | string | auto-detected | Attribute holding the measurement time, for an integration not listed above. |
insulin_value | entity id | none | An active insulin (IOB) sensor, drawn as a line above the reading. |
show_prediction | boolean | true | The line of text under the reading. |
relative_time | boolean | false | Show the reading's age ("14 min ago") in place of the clock. |
dim_by_age | boolean | false | Fade the card once a poll has been missed, ahead of the stale fade. |
color_thresholds | boolean | true | Colour the reading by zone. |
thresholds | object | AGP/TIR for the unit | urgent_low, low, high, urgent_high, in whatever unit your sensor reports. |
locale | string | your HA language | Formats the clock and the number, for example en-GB or ru-RU. YAML only. |
glucose_trend and locale are the two you have to write by hand; the rest are
in the visual editor.
The card takes both from Home Assistant, in this order.
locale, if you set one.The order matters for one common case. Home Assistant's language list has en
and en-GB, and en on its own means American English to a browser, so a
24-hour country running Home Assistant in plain English gets an AM/PM clock
unless something says otherwise. Setting Time format to 24 hours in your profile
is that something; locale: en-GB on the card is another.
They are deliberately not separate card options. A card showing 15:12 beside 8.1 is half German and half English, so the clock, the decimal separator and the digits are decided together or the card contradicts itself.
The card is translated into all 64 languages Home Assistant ships, and a test
fails when Home Assistant adds one the card has not caught up with. Only English
and Russian are maintainer-written; the rest are machine-assisted and have not
been checked by a native speaker, so corrections are genuinely welcome.
src/localize.js holds all of it, one block per language, and
test/localize.test.js will tell you if a key or a {0} slot goes missing.
That covers the card face, the editor's own labels, and the card's name in the picker you add it from. The last two arrive without a Home Assistant object attached, so they read the language off the frontend directly; up to v0.12.0 they were English whatever your language was.
show_prediction puts a line like "Expected to rise 30-45 mg/dL in 15 minutes"
under the reading. It is worth knowing exactly what that is: a plain-language
restatement of the trend arrow your integration already sent, with the range
each arrow conventionally stands for. It is not a computed forecast, it does not
look at your history, and it cannot know about insulin or food. Treat it as the
arrow spelled out, and turn it off if you would rather it were not there.
The seven arrows are the same set whatever your integration calls them: the card normalizes Dexcom's names, Nightscout's directions, LibreView's numbers and Carelink's spellings onto these.
Point the card at an insulin sensor and it draws a line above the reading: "Active insulin 1.25 U", phrased in the card's language like everything else on it. Above, not under the forecast, because it is status rather than projection: what is on board now, ahead of the number the way the time is, while everything derived from the trend (the delta, the forecast) stays below.
type: custom:sugartv-card
glucose_value: sensor.jane_glucose_value
insulin_value: sensor.jane_active_insulin
The entity is the toggle: point at one and the line appears, clear it and the
line goes away. Nothing shows while the entity is missing, unknown or
unavailable.
There is nothing to auto-detect, deliberately. Of the integrations in the
table above only Carelink is a pump integration, and its sensor.*active_insulin is
the entity to point at.
Everywhere else the insulin number lives in a second integration beside the
glucose one, and insulin_value takes any sensor. Where one exists, checked
against each integration's source:
| Source | Insulin entity | Unit |
|---|---|---|
| Carelink (Medtronic) | sensor.*active_insulin | none published |
| Nightscout community integrations | sensor.*insulin_on_board | U |
| Nocturne | sensor.*insulin_on_board | U |
| Glucifer (Juggluco bridge) | sensor.*insulin_on_board, sensor.*active_insulin | U |
| Medtrum EasyView | sensor.*active_insulin | U |
The core Nightscout integration publishes glucose only, so if your glucose
comes from it and you run AAPS, Loop or OpenAPS, the number has to come from
one of the community Nightscout integrations above or from a template sensor
of your own; spellings vary a little between them (nightscout_v3 calls it
Loop IOB).
The amount is formatted in your number format, to two decimals, and the unit
comes off the sensor itself. Carelink publishes no unit at all, so the line
falls back to U. A negative value is passed through rather than clamped: a
temporary basal below zero produces negative insulin on board on some systems,
and hiding that would be worse than showing it.
There is no layout option, on purpose. The card reads the shape of the slot it is given and picks the arrangement that fills it, so it stays right when you rearrange a dashboard instead of needing to be told again. The two arrangements are the pair at the top of this page.
| Slot | Layout |
|---|---|
| Wider than 5:3 | Row: time, reading and trend on one line |
| Narrower than 5:3 | Column: the reading stacked over its trend |
The type scales to fill either one, which is why there are no font sizes to set. 5:3 is not a taste call: it is the point where the column starts giving a larger reading than the row.
To get the vertical layout, give the card a slot taller than it is wide:
The time on the card is when the reading was last confirmed, and it drives the dimming of stale data.
The card resolves it in this order:
timestamp_attribute, if you set one.measurement_timestamp attribute (LibreView).date attribute, but only on a sensor that also carries Nightscout's
direction/delta attributes, since date is a generic name.*last_glucose_update (Carelink) or
*_minutes_since_update (LibreLink).last_updated, then last_changed.Both ISO date strings and epoch values (seconds or milliseconds) are accepted; anything unparseable falls back to the next step.
Accuracy depends on your integration, because Home Assistant only advances
last_updated when the state or one of its attributes actually changes. A poll
that re-reports an identical value with identical attributes leaves both
last_updated and last_changed frozen. Home Assistant records that poll in
last_reported, which it does not send to the frontend, so a card cannot see
it. That is why the card prefers a time the integration itself reports.
| Integration | Reading time |
|---|---|
| LibreView (PTST) | Exact, from measurement_timestamp |
| Nightscout | Exact, from date |
| Carelink | Exact, from the *last_glucose_update entity |
| LibreLink | To the minute while the reading is recent, from *_minutes_since_update |
| Dexcom | Approximate: it publishes no time at all, so a flat value can still look stale |
LibreLink is the weak one. It reports an age rather than a time, and computes it
by subtracting a device-local reading time from the Home Assistant server's
clock, so the number is wrong by the offset between the two timezones
(gillesvs/librelink#27
reports -118 minutes). A wrong age cannot be told apart from an old reading, so
the card only trusts an age below the staleness threshold. Past it, the age and
last_updated both say "stale" anyway.
If your integration reports the measurement time under some other attribute of
the glucose sensor, point the card at it with timestamp_attribute.
On a wall display a clock reading is one subtraction away from the thing you
actually want to know, and next to a real clock it reads as a second clock. Set
relative_time: true and the card shows how old the reading is in that spot
instead: "14 min ago", "14 мин назад", "vor 14 Min". Under a minute it reads
"now", past an hour it counts in hours.
It is one or the other, not both. Every word comes from the browser's own locale data rather than from anything the card ships, so it is right in every language Home Assistant runs in, and it is the same phrasing Home Assistant uses elsewhere in its interface. The stop CLDR puts after an abbreviated unit is dropped in every language, so the card reads "14 min ago" and "vor 14 Min" rather than carrying a full stop in some languages and not others. Hebrew is spelled out instead, since the mark it abbreviates with is what makes the word an abbreviation at all.
Languages differ a lot in how long this runs: "14 min ago" fits anywhere, "14 perccel ezelőtt" is nearly three times the width of the clock it replaces. Since the line cannot wrap, the card measures the phrase and scales it down when it would not fit beside the reading. The time gives way rather than the number, which is the one thing the card exists to show.
Some browsers are built with a trimmed locale data set and have no wording for every language. Rather than dropping to English in the middle of an otherwise translated card, the card keeps showing the clock for those. If you turn this on and still see a clock, that is why.
The fade runs one way: toward staleness. A current reading is drawn at full strength, and the card loses contrast as the reading gets older. Older is never brighter, so you can read the card's health from across a room without reading the time at all.
| Age | What it looks like | Reading it |
|---|---|---|
| Under 1 interval | Full strength, nothing dimmed | Nothing to see here |
| 1 to 3 intervals | The card fades a little | A poll went missing |
| Over 3 intervals | The whole card, plus greyscale | Do not trust this |
All three are measured against your sensor's own update interval, so they mean the same thing whatever it is.
The bottom rung is the card's oldest behaviour and has no setting. A dead sensor has to look dead, so that one is not yours to turn off.
The middle rung is yours: dim_by_age: true turns it on, off by default. It is
a preference rather than a safety signal, since a reading one poll old is
usually still worth trusting.
It does not colour anything. Orange already means "out of range" here, so a second signal reaching for orange would paint two unrelated meanings the same colour, and on the urgent zones it would land on red. Contrast is the one axis still free.
Changed in v0.15.0. Through v0.14.0 this option ran the other way: it dimmed the timestamp while the reading was current and brought it up to full strength as the reading aged. That put the card's only "something is off" signal on the state where nothing is off. If you had it on, you will now see the opposite, and a current reading is no longer dimmed at all.
The card dims once three polls have gone missing. Three of what depends on your sensor, so the card measures rather than assumes: it reads the gaps between your own readings in the recorder history and takes the typical one as the update interval. A one minute CGM therefore dims after three minutes, a five minute one after fifteen.
There is no setting for this, and the interval is not asked for. The card already reads history to compute the delta, so the answer is in data it holds.
Two deliberate limits. It takes the median gap rather than the average or the smallest. The average stretches the window on exactly the flat stretches where a stuck sensor most needs catching, since Home Assistant writes no history entry when a reading repeats. The smallest collapses it the other way: one poll arriving early, after a retry or a restart, would otherwise redefine a five minute sensor as a ninety second one. And a measured interval can only shorten the window, never extend it past fifteen minutes: a live sensor that looks stale is a harmless failure, a dead one that looks live is not. With the recorder disabled, fifteen minutes is what you get.
Color-coded zones are enabled by default using the AGP/TIR international standard thresholds.
| Zone | mg/dL | mmol/L | Style |
|---|---|---|---|
| Urgent Low | < 54 | < 3.0 | Red background, white text |
| Low | 54 – 70 | 3.0 – 3.9 | Orange text |
| In Range | 70 – 180 | 3.9 – 10.0 | Normal |
| High | 180 – 250 | 10.0 – 13.9 | Orange text |
| Urgent High | > 250 | > 13.9 | Red background, white text |
To disable color coding, set color_thresholds: false or use the toggle in the visual editor.
Colors can be customized via CSS custom properties in your HA theme:
# In your HA theme
sugartv-urgent-bg: '#c62828'
sugartv-urgent-text: '#ffffff'
sugartv-warning-text: '#e65100'
The reading shows N/A. The card is pointed at an entity Home Assistant has no value for. Open Developer Tools, States and check the entity id exists and holds a number; ids differ between installs even for the same integration, which is why the table above matches the tail of an id rather than the whole thing.
The trend is a question mark. The card could not find a trend to go with the
reading. It looks for a sibling entity next to your glucose sensor, so a renamed
entity breaks the link. Set glucose_trend explicitly.
The colours look wrong for my unit. Thresholds are stored as plain numbers,
so a set written for mg/dL means something very different against a mmol
reading. The card drops thresholds that exactly match the other unit's defaults,
but it cannot second-guess numbers you chose yourself. Clear thresholds to
fall back to the standard ones for your unit.
The clock is behind, or the card greys out while the sensor is fine. The card can only show a time its integration reports. Dexcom publishes none, so the card falls back to when Home Assistant last saw the value change, and a reading that repeats leaves that frozen. The table under Reading time above says what each integration offers.
I wanted the vertical layout. It follows the shape of the slot, not a setting. See Layout above.
Three strings in this project are external identities. Each one lives somewhere outside the repository, so changing it breaks existing installs rather than producing a rename to review.
| String | Where it lives | What breaks |
|---|---|---|
wiltodelta/homeassistant-sugartv-card | The HACS default plugin list | HACS resolves the card by repository path, so renaming the repository cuts off updates for everyone who installed it |
sugartv-card.js in hacs.json | Release assets | HACS downloads this exact filename from the release |
sugartv-card in customElements.define | Every user's Lovelace config, as type: custom:sugartv-card | Cards stop rendering until each user edits their own dashboard |
The name field in package.json is not one of them: nothing is published to
npm under it, so it is free to change.
If this card is useful to you, please consider supporting its development:
This project is licensed under the MIT License. See the LICENSE file for details.
Made by Victor Kuznetsov.