“Skuret skal ryddes.” “Pæren i bryggerset er sprunget.” “Husk at skifte filter i emhætten.” Den slags opgaver plejede at ende på en seddel på køleskabet eller i hovedet på én person, hvor de så blev glemt. Nu siger man det højt til Home Assistant, og et øjeblik efter ligger opgaven som et issue på GitHub og venter på at blive lukket.
Til daglig arbejder jeg med ServiceNow, så en opgave er for mig en ticket. Den har et nummer, en historik, og nogen lukker den, når den er løst. GitHub Issues giver præcis det, uden at jeg skal drive et helt ticketsystem derhjemme. Telefon-appen er god, labels er gratis, og alt er der stadig om tre år.
Sådan hænger det sammen
- Indbakke: En helt almindelig lokal to-do-liste i Home Assistant,
todo.husopgaver. Den kan alle i huset skrive på via stemmen (“tilføj skift filter til husopgaver”), via HA-appen eller via en lille formular på vægtabletten. - Automation: Hver gang listen ændrer sig, laves alle åbne punkter om til GitHub-issues i et privat repository. Bagefter fjernes de fra listen.
- Visning: En template-sensor henter de åbne og de senest lukkede issues hvert 5. minut, så de kan vises på dashboardet.
- Lukning: Det sker på GitHub, typisk fra telefonen, når opgaven er løst.
To-do-listen er altså kun en indbakke. Opgaverne bor ét sted, nemlig på GitHub, så der aldrig er to lister, der er uenige om, hvad der mangler.
Adgang til GitHub
Lav et fine-grained personal access token på GitHub, der kun har adgang til det ene repository og kun har rettigheden “Issues: Read and write”. Slipper tokenet ud, kan det ikke andet end at rode med husopgaverne. Det lægges i secrets.yaml sammen med ordet Bearer:
github_husopgaver_token: "Bearer github_pat_xxxxxxxxxxxx"REST-kommandoerne
Det hele ligger i én pakkefil, packages/husopgaver.yaml. Først kommandoen, der opretter et issue:
rest_command:
github_create_issue:
url: https://api.github.com/repos/DIN-BRUGER/husopgaver/issues
method: POST
headers:
Authorization: !secret github_husopgaver_token
Accept: application/vnd.github+json
User-Agent: HomeAssistant
content_type: application/json
payload: '{{ {"title": title, "body": body} | to_json }}'
github_list_issues:
url: https://api.github.com/repos/DIN-BRUGER/husopgaver/issues?state=open&per_page=100
method: GET
headers:
Authorization: !secret github_husopgaver_token
Accept: application/vnd.github+json
User-Agent: HomeAssistantTo detaljer, der er værd at kende:
| to_jsonpå hele payloaden. Så bliver anførselstegn og linjeskift i titlen escaped korrekt. Skriver man JSON’en i hånden, går den i stykker første gang nogen skriver “Ret ‘den’ skuffe”.- User-Agent. GitHubs API afviser kald uden en User-Agent. Den kan i øvrigt også give problemer den anden vej: Min egen webhost blokerede Home Assistants standard-User-Agent, så det er en god vane altid at sætte den selv.
Automationen: fra liste til issue
alias: Husopgaver → GitHub issues
mode: queued
max: 10
triggers:
- trigger: state
entity_id: todo.husopgaver
actions:
- action: todo.get_items
target:
entity_id: todo.husopgaver
data:
status: needs_action
response_variable: result
- variables:
oprettet_af: >-
{% set uid = trigger.to_state.context.user_id if trigger.to_state is not none else none %}
{{ states.person | selectattr('attributes.user_id', 'eq', uid)
| map(attribute='name') | first
| default('stemme/ukendt' if uid is none else 'ukendt bruger', true) }}
- repeat:
for_each: "{{ result['todo.husopgaver']['items'] }}"
sequence:
- action: rest_command.github_create_issue
data:
title: "{{ repeat.item.summary }}"
body: >-
{{ repeat.item.description ~ '\n\n' if repeat.item.description is defined
and repeat.item.description else '' }}Oprettet af {{ oprettet_af }} via Home Assistant
response_variable: svar
continue_on_error: true
- if:
- condition: template
value_template: "{{ svar is defined and svar.status == 201 }}"
then:
- action: todo.remove_item
target:
entity_id: todo.husopgaver
data:
item: "{{ repeat.item.uid }}"
- event: husopgaver_opdater
event_data:
number: "{{ svar.content.number }}"Det vigtigste her er rækkefølgen. Punktet fjernes først fra listen, når GitHub har svaret 201 Created. Er nettet nede, eller udløber tokenet, bliver opgaven bare stående på listen og prøves igen ved næste ændring. Intet bliver væk på vejen. Det er det samme princip som i en god integration på arbejdet: Bekræft modtagelsen, før du sletter kilden.
Et par andre greb:
- Hvem oprettede den? State-ændringens
context.user_idslås op iperson-entiteterne. Kommer opgaven fra stemmeassistenten, er der ingen bruger, og så står der “stemme/ukendt”. Teksten “Oprettet af …” havner nederst i issuet. mode: queued. Fjerner automationen et punkt, ændrer listen sig, og automationen trigges igen. Medqueuedkører de efter hinanden i stedet for at falde over hinanden. Den anden kørsel finder bare en tom liste.- Eventet
husopgaver_opdaterfår sensoren til at hente listen med det samme, så det nye issue står på dashboardet med det samme og ikke først om fem minutter.
Sensoren: issues tilbage på dashboardet
template:
- triggers:
- trigger: homeassistant
event: start
- trigger: time_pattern
minutes: "/5"
- trigger: event
event_type: husopgaver_opdater
actions:
- action: rest_command.github_list_issues
response_variable: svar
continue_on_error: true
sensor:
- name: Husopgaver
unique_id: husopgaver_github_open_issues
icon: mdi:hammer-wrench
state: >
{% if svar is defined and svar.status == 200 %}
{{ svar.content | rejectattr('pull_request', 'defined') | list | count }}
{% else %}
{{ this.state }}
{% endif %}
attributes:
issues: >
{% if svar is defined and svar.status == 200 %}
{% set ns = namespace(l=[]) %}
{% for i in svar.content if i.pull_request is not defined %}
{% set ns.l = ns.l + [{'number': i.number, 'title': i.title,
'url': i.html_url, 'created': i.created_at,
'labels': i.labels | map(attribute='name') | list}] %}
{% endfor %}
{{ ns.l }}
{% else %}
{{ this.attributes.issues | default([]) }}
{% endif %}- Pull requests filtreres fra. GitHubs issues-endpoint returnerer også pull requests. Dem kan man kende på feltet
pull_request. this.statesom fallback. Fejler kaldet, beholder sensoren sin sidste værdi i stedet for at hoppe til 0 eller “unavailable”. Et kort GitHub-udfald får altså ikke det til at se ud, som om alle opgaver er løst.- Hos mig henter sensoren også de 10 senest lukkede issues til en liste over klarede opgaver, for det er rart at se, hvad der faktisk bliver lavet.

Formularen på vægtabletten
Ikke alle vil tale til huset. Derfor er der en lille popup på tabletten med to input_text-felter (titel og beskrivelse) og en knap, der kører et script. Scriptet lægger opgaven på todo.husopgaver og tømmer felterne. Resten klarer automationen. Det er en pointe i sig selv: Uanset om opgaven kommer fra stemmen, appen eller formularen, går den samme vej ind. Der er kun én integration at vedligeholde.
Hvad har jeg lært?
- Ét sted for sandheden. Det er fristende at synkronisere begge veje mellem to-do-listen og GitHub, men så skal man håndtere konflikter, sletninger og dubletter. Som ren indbakke er listen langt simplere, og der er aldrig tvivl om, hvor opgaven bor.
- Templates genindlæses ikke altid. Efter en ændring i template-sensoren skete der ingenting, før jeg fyrede eventet manuelt. Tjek altid sensoren bagefter, og genstart HA, hvis den ikke ændrer sig.
- Labels er gratis. Sæt labels på issues på GitHub, så kommer de automatisk med i sensorens attributter og kan bruges til at filtrere eller farve listen på dashboardet. Det kræver ingen ekstra kode i Home Assistant.
Vil du se resten af huset? Her er oversigten over mit Home Assistant-setup.