Gå til indhold
← Alle indlæg

eTilbudsavis i Home Assistant: sorteret efter butik og med besked, når du ankommer

Sådan fik jeg eTilbudsavis ind i Home Assistant: varer sorteret efter butik, prisen i beskrivelsen og en push-besked, når man ankommer til en butik.

9. juni 2026 Verificeret på HA 2026.7
eTilbudsavis i Home Assistant: sorteret efter butik og med besked, når du ankommer

Jeg stod i Netto og vidste ikke, om der var noget på listen herfra. Det var der, men det lå gemt midt i femten varer fra tre andre butikker. Jeg scrollede og fandt dem til sidst. Sådan gik det hver gang.

Ugen før havde nogen i en dansk Facebook-gruppe nævnt, at Home Assistant kunne sende en besked, når man gik ind i en butik med varer på listen. Jeg tænkte over det hele vejen hjem fra Netto.

Integrationen fra CagosDk på GitHub

Udgangspunktet er en HACS-integration lavet af CagosDk. Tilføj den via HACS → Integrationer → Brugerdefinerede repositories, indsæt adressen fra GitHub, og tilføj integrationen. Den opretter en todo-entitet for hver af dine eTilbudsavis-lister, og min hedder todo.min_indkobsliste. Derfra dukker den op som et almindeligt todo-kort i HA og i Companion-appen på telefonen.

Home Assistant dashboard med et indkøbskurv-chip der viser antal varer på listen

Så var problemet med en app mere løst. Men standardvisningen viser kun varenavne: ingen butik, ingen pris, ingen dato. Med femten varer fra fire butikker hjælper det ikke meget, når man har travlt.

Ændringer i todo.py: beskrivelse og sortering

Integrationen gemmer alle data fra eTilbudsavis' API i sin coordinator, blandt andet business.name (butikken), offer.price og offer.validUntil. Som standard lægger todo.py kun butikkens navn i feltet description. For at få pris og dato med tilføjede jeg to hjælpefunktioner og ændrede sorteringen.

Funktionen, der bygger beskrivelsen:

def _build_description(store: str, offer: dict | None, now: datetime) -> str | None:
    if not store:
        return None
    offer = offer or {}
    price = offer.get("price")
    valid_until_str = offer.get("validUntil")
    desc = store
    if price is not None:
        price_str = f"{price:.2f}".replace(".", ",")
        desc += f" - {price_str} kr."
    if valid_until_str:
        try:
            valid_until = datetime.fromisoformat(valid_until_str.replace("Z", "+00:00"))
            if (valid_until - now).total_seconds() > 0:
                desc += f" til {valid_until.day}/{valid_until.month}"
        except ValueError:
            pass
    return desc

Både price og validUntil hentes med .get(). Returnerer API'et dem ikke for varer på indkøbslisten, bliver de bare sprunget over. Resultatet er altid gyldigt, i værste fald kun med butikkens navn.

Den anden hjælpefunktion tager sig af udløbne tilbud. Ligger validUntil i fortiden, får varens titel et præfiks, så det er tydeligt:

def _expiry_prefix(offer: dict | None, now: datetime) -> str:
    if not offer:
        return ""
    valid_until_str = offer.get("validUntil")
    if not valid_until_str:
        return ""
    try:
        valid_until = datetime.fromisoformat(valid_until_str.replace("Z", "+00:00"))
    except ValueError:
        return ""
    if (valid_until - now).days < 0:
        return "UDLOBET!!! "
    return ""

I todo_items kaldes begge funktioner for hver vare, og sorteringen får butikken med:

store = (item.get("business") or {}).get("name") or ""
prefix = _expiry_prefix(item.get("offer"), now)
summary = f"{prefix}{count}x {name}"
description = _build_description(store, item.get("offer"), now)

result.append((store, TodoItem(...)))

result.sort(key=lambda x: (
    x[1].status == TodoItemStatus.COMPLETED,
    x[0].lower(),   # butik — samler varer fra samme butik
    x[1].summary.lower(),
))

Resultatet: varer, der ikke er krydset af, kommer først, derefter sorteret alfabetisk efter butik og så alfabetisk inden for hver butik.

eTilbudsavis indkøbsliste i Home Assistant companion-appen, sorteret efter butik med pris og dato synlig

To ting er værd at vide, før du retter i filen. For det første ligger todo.py i /config/custom_components/etilbudsavis/todo.py på HA-maskinen, og HACS-opdateringer overskriver den. Ændringerne er så små, at det tager et par minutter at lægge dem ind igen. For det andet kræver ændringer i Python-filer i brugerdefinerede integrationer en fuld genstart af HA. reload_config_entry tømmer ikke Pythons cache af moduler.

Zoner og besked, når man er tæt på

Det er den del, Facebook-kommentaren handlede om. Home Assistant har zoner: man tegner en cirkel rundt om en adresse, og Companion-appen holder øje med, hvornår telefonen er inden for cirklen. Jeg oprettede zoner for de butikker, vi faktisk handler i: Min Købmand, ABC Lavpris, Bilka, Lidl, Føtex og to Netto-butikker.

En automation går i gang, når enten min Pixel 10 eller min kones Pixel 9a ankommer til en af zonerne:

trigger:
  - platform: zone
    entity_id:
      - device_tracker.pixel_10
      - device_tracker.pixel_9a
    zone: zone.bilka
    event: enter
  # ... gentages for hvert butiks-zone

Automationen kobler hver zone til et butiksnavn i en variables-blok. To Netto-adresser peger på det samme navn, så de giver den samme besked, uanset hvilken af butikkerne man ankommer til:

variables:
  butik_lookup:
    zone.min_kobmand: "Min Kobmand"
    zone.abc_lavpris: "ABC Lavpris"
    zone.bilka: "Bilka"
    zone.lidl_vejlevej: "Lidl"
    zone.lidl_mosevej: "Lidl"
    zone.fotex: "Føtex"
    zone.netto_hylkedalvej: "Netto"
    zone.netto_stadionvej: "Netto"
  butik_navn: "{{ butik_lookup[trigger.zone] | default('') }}"

Handlingen henter den aktuelle liste med todo.get_items, filtrerer de varer, hvis description starter med butikkens navn, og sender en push-besked, hvis der er et match:

- target:
    entity_id: todo.min_indkobsliste
  data:
    status: needs_action
  response_variable: todo_svar
  action: todo.get_items
- variables:
    varer: >
      {% set alle = todo_svar['todo.min_indkobsliste']['items'] | default([]) %}
      {% set ns = namespace(liste=[]) %}
      {% for vare in alle %}
        {% if vare.description | default('') | regex_match(butik_navn) %}
          {% set ns.liste = ns.liste + [vare.summary] %}
        {% endif %}
      {% endfor %}
      {{ ns.liste }}
- condition: template
  value_template: "{{ varer | count > 0 }}"
- data:
    title: "🛒 {{ butik_navn }} – {{ varer | count }} vare(r) på listen"
    message: "{{ varer | join('\n') }}"
  action: notify.alle_enheder

regex_match sammenligner fra starten af teksten, så «Netto» passer på «Netto – 12,95 kr. · til 12/6» uden at bruge contains. Erstat notify.alle_enheder med din egen notify-handling. Det er den gruppe, jeg bruger til at ramme begge telefoner på én gang.

Første gang det virkede, stod jeg foran Netto. Telefonen vibrerede. To varer. Jeg vidste godt, hvad der stod på listen, for jeg havde selv skrevet den, men det er noget andet at have den på skærmen i det øjeblik, man går ind ad døren.

Nye indlæg direkte i din RSS-læser: følg med via RSS I Google-søgning: vælg szimnau.dk som foretrukken kilde