# Mealie i Home Assistant – og fire fejl der aldrig viste sig

- Link: https://www.vogh.dk/mealie-i-home-assistant-og-fire-fejl-der-aldrig-viste-sig/
- Udgivet: 2026-10-10T19:30:43+00:00
- Forfatter: ha-upload

**Mealie** er en selvhostet opskriftssamling med madplan og indkøbsliste. Hos os
kører den som App direkte på Home Assistant, og den officielle Mealie-integration
binder den sammen med resten af huset. Idéen er enkel: Planlæg ugens mad i Mealie–
og så dukker den op i familiens kalender og på mobilen, uden at nogen skal skrive
den to steder.

Da jeg skulle skrive dette indlæg, opdagede jeg dog, at den del, der flytter madplanen
over i kalenderen, havde **fire fejl** – som aldrig var blevet opdaget. Mere om 
det nedenfor.

## Hvad integrationen giver

 * **En kalender pr. måltidstype** – morgenmad, frokost, aftensmad, snack, tilbehør,
   dessert og drikke – med det, der er planlagt i Mealie.
 * **Indkøbslisten som to-do-liste** i Home Assistant, så den kan bruges fra mobilen
   og med stemmen.
 * **Handlinger** til at hente, oprette, ændre og slette madplaner, søge i opskrifter
   og importere en opskrift fra en URL.

## Sådan hænger det sammen

 1. **Dagens og morgendagens måltider**: To automationer kører hver time og ved opstart.
    De henter dagens og morgendagens begivenheder fra Mealie-kalenderne og gemmer dem
    som attributter på en variabel (via HACS-integrationen _Variable_) – én attribut
    pr. måltidstype.
 2. **Mobil-dashboardet** viser to markdown-kort, “Dagens måltider” og “Morgendagens
    måltider”, som løber variablens attributter igennem og kun viser dem, der har noget
    planlagt.
 3. **Familiekalenderen**: Hver nat kl. 00:05 kopierer en automation de næste syv dages
    madplan over i kalenderen “Madplan”, som hele familien har på telefonen.

![](https://www.vogh.dk/wp-content/uploads/2026/10/image-4.png)

## Fire fejl der aldrig viste sig

Synkroniseringen til familiekalenderen så fornuftig ud og kørte hver nat uden fejl.
Men den havde aldrig flyttet en eneste ret – og grunden til, at ingen opdagede det,
var lige så simpel: Madplanen i Mealie havde været tom i flere måneder. En løkke
over en tom liste fejler ikke. Den gør bare ingenting.

Så snart der kom en ret i planen, ville den være gået galt på fire måder:

 1. **Forkerte feltnavne.** Automationen læste `meal_type` og `date`, men Mealie-integrationen
    sender `entry_type` og `mealplan_date`. Datoen ville være tom.
 2. **Datoen er et dato-objekt.** `mealplan_date` er ikke tekst, men et Python-dato-
    objekt. Det får `repeat: for_each` til at afvise hele listen med “must be a list
    of items”. Løsningen er først at lave listen om til simple tekstværdier.
 3. **Ingen dubletkontrol.** Hver nat blev de næste syv dage oprettet igen. En ret 
    planlagt en uge frem ville altså ende med at stå i kalenderen op til syv gange.
    Nu slår automationen først op i kalenderen og springer over, hvis samme dato og
    titel allerede står der.
 4. **Slutdato = startdato.** En heldagsbegivenheds slutdato er _eksklusiv_ – den skal
    være dagen efter. Med samme dato som start har begivenheden ingen længde.

## Sådan testede jeg det

Når planen er tom, kan man ikke se, om noget virker. Integrationen kan heldigvis
både oprette og slette måltider, så testen kan ryddes helt op bagefter:

 1. `mealie.set_mealplan` med en note som titel – “TEST fra Home Assistant” – til aftensmad
    i morgen. Svaret viste de rigtige feltnavne.
 2. Kør synkroniseringen: én heldagsbegivenhed i morgen, med den rigtige slutdato.
 3. Kør den igen: ingen ny begivenhed – dubletkontrollen virker.
 4. Kør “Hent Mealie i morgen”: testretten står under aftensmad.
 5. `mealie.delete_mealplan` med id’et fra trin 1: planen er tom igen.

## Automationen

    ```nord
    alias: Sync Mealie måltider til kalender
    mode: single
    variables:
      meal_icons:
        breakfast: 🌅
        lunch: ☀️
        dinner: 🌙
        snack: 🍎
        side: 🥗
        dessert: 🍰
        drink: 🥤
    triggers:
      - trigger: time
        at: "00:05:00"
    actions:
      - action: mealie.get_mealplan
        data:
          config_entry_id: DIN_MEALIE_CONFIG_ENTRY
          start_date: "{{ now().strftime('%Y-%m-%d') }}"
          end_date: "{{ (now() + timedelta(days=7)).strftime('%Y-%m-%d') }}"
        response_variable: plan
      # mealplan_date er et dato-objekt – lav listen om til tekst først
      - variables:
          maaltider: >-
            {% set ns = namespace(l=[]) %}
            {% for m in plan.mealplan %}
              {% set type = (m.entry_type | string | lower).split('.')[-1] %}
              {% set ns.l = ns.l + [{
                   'dato': m.mealplan_date | string,
                   'type': type,
                   'titel': meal_icons.get(type, '🍽️') ~ ' ' ~ (m.recipe.name if m.recipe else m.title)}] %}
            {% endfor %}
            {{ ns.l }}
      - action: calendar.get_events
        target:
          entity_id: calendar.madplan
        data:
          start_date_time: "{{ now().strftime('%Y-%m-%d') }} 00:00:00"
          end_date_time: "{{ (now() + timedelta(days=8)).strftime('%Y-%m-%d') }} 23:59:59"
        response_variable: eksisterende
      # 'dato|titel' for alt, der allerede står i kalenderen
      - variables:
          kendte: >-
            {% set ns = namespace(l=[]) %}
            {% for e in eksisterende['calendar.madplan'].events %}
              {% set ns.l = ns.l + [(e.start | string)[:10] ~ '|' ~ e.summary] %}
            {% endfor %}
            {{ ns.l }}
      - repeat:
          for_each: "{{ maaltider }}"
          sequence:
            - if:
                - condition: template
                  value_template: "{{ (repeat.item.dato ~ '|' ~ repeat.item.titel) not in kendte }}"
              then:
                - action: calendar.create_event
                  target:
                    entity_id: calendar.madplan
                  data:
                    summary: "{{ repeat.item.titel }}"
                    start_date: "{{ repeat.item.dato }}"
                    # heldagsbegivenhed: slutdatoen er eksklusiv
                    end_date: "{{ (strptime(repeat.item.dato, '%Y-%m-%d') + timedelta(days=1)).strftime('%Y-%m-%d') }}"
                    description: "Fra Mealie ({{ repeat.item.type }})"
    ```

## Det jeg har lært

 * **En tom liste skjuler alt.** En automation, der løber over en liste, kan være
   fuld af fejl og stadig køre “grønt” hver nat, så længe listen er tom.
 * **Kig på det rigtige svar.** Feltnavnene står i handlingens svar – kør den én
   gang i Udviklerværktøjer og se, hvad der faktisk kommer tilbage, før du skriver
   skabelonerne.
 * **Alt, der kører igen og igen, skal kunne køre igen.** En natlig synkronisering
   skal tjekke, hvad der allerede findes – ellers bygger den dubletter op.
 * **Test med data, du kan fjerne igen.** Opret en testpost, kør, verificér og slet–
   så er testen gentagelig og efterlader intet.
