Compare commits

...

62 Commits

Author SHA1 Message Date
claus 644cb28e88 Zigbee: opdater husplan med LQI-målinger 20/5 og nye enheder 2026-05-20 20:04:45 +02:00
claus 7cae5a8c8b Varme: gem defaults efter HA genstart 2026.5.1 2026-05-20 18:29:10 +02:00
claus 31c1258760 TODO: Gitea HTTPS via gitea.anneclaus.dk markeret løst 2026-05-20 17:34:42 +02:00
claus 7cb07af473 TODO: nas.anneclaus.dk via NPM markeret løst 2026-05-19 21:39:24 +02:00
claus 25ab4ceeef Stue lys: brug annes_favorit også i aften-perioden (efter 21:00) 2026-05-19 21:05:44 +02:00
claus ba3ba34db8 TODO: Plex markeret som skip 2026-05-19 21:00:18 +02:00
claus c54f583968 Varme: tilføj Bryggers og Køkken sektioner (uden climate-entiteter endnu) 2026-05-19 20:40:57 +02:00
claus 27224a4115 Madplan: opdater Mealie-links til https://mealie.anneclaus.dk 2026-05-18 18:22:03 +02:00
claus ff8324af1f TODO: Mealie HTTPS via NPM markeret løst 2026-05-18 18:19:38 +02:00
claus 6731257b83 Varme: tilføj havesensor inde til Kontor temperatur-graf 2026-05-18 07:29:03 +02:00
claus 8b533ca95a Dokumenter: tilføj zigbee husplan med enhedsplacering og LQI-data 2026-05-17 18:04:29 +02:00
claus b46d820c20 Madplan: tilføj store volumen op/ned knapper til Sonos Køkken 2026-05-17 17:45:26 +02:00
claus f5f085f155 Madplan: tilføj Claus Daily Mix 1-6 knapper - alle 4 familiers daily mix nu tilstede 2026-05-17 17:26:12 +02:00
claus 63b6283fdb Madplan: ret Sonos-kildenavne - Claus→Anne Daily Mix, fjern kommentarer 2026-05-17 17:23:49 +02:00
claus 9e446e0d57 Madplan: tilføj Sonos Køkken media-control + 22 kildeknapper (P3, Family Mix, Danske fav, Rock klassikere, Claus/Andreas/Daniel Daily Mix 1-6) 2026-05-17 17:21:07 +02:00
claus d0e3a6c3e5 Indkørsel: erstat Hue plug med Zigbee stik_indkorsel
- grupper.yaml: fjern light.indkorsel_plug fra Indkørsel og Udendørslamper grupper
- lysindkorsel.yaml: tilføj switch.stik_indkorsel turn_on/off ved alle 6 handlinger
- presence_simulation.yaml: tilføj switch.stik_indkorsel i morgen/aften sekvenser
- andreas_kommer_hjem: tilføj switch.stik_indkorsel turn_on/off
2026-05-17 11:14:38 +02:00
claus 8b2ff535de TODO: marker MQTT og FTP som lukket på router 2026-05-17 07:59:39 +02:00
claus acdc7259a7 TODO: tilføj MQTT og SSH til port forward sikringsliste 2026-05-17 07:57:31 +02:00
claus c297012592 TODO: tilføj opgaver for sikring af port forwards via NPM 2026-05-17 07:54:28 +02:00
claus 52dc97dd3d HA: external_url skiftet til ha.anneclaus.dk 2026-05-17 07:43:28 +02:00
claus 532f42b310 TODO: marker home_charging og Google AI MAX_TOKENS som løst 2026-05-16 20:04:26 +02:00
claus ad5bdf91b9 TODO: Husqvarna Automower BLE markeret løst 2026-05-16 20:02:23 +02:00
claus 0cb3145257 gitignore: ignorer www/snapshots - fjern fra tracking 2026-05-16 19:44:10 +02:00
claus 54144a03b8 TODO: markér aiohttp/400 løst — NPM HTTPS reverse proxy sat op 2026-05-16 19:42:57 +02:00
claus af3779a573 HA: HTTPS via NPM - opdater external_url og trusted_proxies 2026-05-16 18:05:10 +02:00
claus 3094620985 Nginx: porte ændret til 10080/10443 (8080/8443 optaget af Java/Synology) 2026-05-16 16:20:48 +02:00
claus 8057b3abf1 Nginx: use ports 8080/8443 to avoid conflict with Synology DSM on port 443 2026-05-16 16:12:22 +02:00
claus 677debfc27 Nginx: add nginx-proxy-manager service to docker-compose.infrastructure.yml 2026-05-16 16:08:12 +02:00
claus 910de6ed54 Varme: fix shell_command - run python3 directly inside container 2026-05-16 12:45:35 +02:00
claus bc6799c126 Varme: gem komforttemperaturer som defaults via knap på dashboard 2026-05-16 11:59:23 +02:00
claus 9f9de0524a Vanding: fix nedbørsprognose via trigger-template sensor (HA 2024.3+ forecast API) 2026-05-16 11:49:21 +02:00
claus eacd137a7c Gallery: add touch swipe support for iPhone/iPad 2026-05-16 10:37:10 +02:00
claus 63288dbb4b Gallery: add prune button (behold 100), webhook automation, shell_command 2026-05-16 10:35:28 +02:00
claus c394d6b974 Gallery: fix iframe cache-bust by versioning loader URL in dashboard YAML 2026-05-16 10:27:23 +02:00
claus fa64223630 Gallery: show all 192 snapshots (MAX 500), fix shell_command to use docker exec 2026-05-16 10:01:11 +02:00
claus f2ac6064b5 Dashboard views reorganized, mealie/roborock automations, indkorsel snapshots, wavin/sonoff docs, varme/sikkerhed updates 2026-05-16 07:28:28 +02:00
claus bd134bafef docs: tilføj vildtkamera (Reolink Argus 4 Pro + solpanel) på ønskeliste; script vi_laver_mad udvides med Daniel 2026-05-10 19:26:41 +02:00
claus 4e0819d4ff docs: udvid Aqara vindues/dørsensorer fra 6 til 15 stk (forgang, køkken, bryggers, stue, fordør, bagdør, kontor) 2026-05-10 17:48:05 +02:00
claus 0d112fb4d0 docs: tilføj automower BLE fejlfinding til kontekst 2026-05-10 16:16:19 +02:00
claus 067d5c6a63 Vi laver mad script+knap, Ally online, TODO/oensker opdateret, mealie/bilka sync 2026-05-09 06:19:50 +02:00
claus 5a7d25fd3c TODO: tilføj automower BLE auth fejl og AI MAX_TOKENS 2026-05-07 07:14:07 +02:00
claus bee2028f0b Julelys kun i vintersæson uge 42-8, tilføj TODO liste 2026-05-06 07:35:07 +02:00
claus 1525cc0070 Garageport: brug zigbee binary_sensor, animation baseret på last_changed 2026-05-05 19:43:51 +02:00
claus 812199889e Tilføj docker-compose, Touchline manual, netværksgenstart dok, fix ._* ignore 2026-05-05 19:40:05 +02:00
claus 7d5b31f9ab HA version bump, pløneklipper automation, robots scripts, bilka checklist opdatering, mealie 2026-05-05 19:38:12 +02:00
claus bf6f2feba6 Garageport animation, fjern alarm 1899, baghave lys vintersæson uge 42-8 2026-05-05 19:38:01 +02:00
claus ae8b1ea6f8 Opvaskemaskine: kvarter-påmindelser med actionable notification og vedligeholdscheck 2026-05-01 19:44:03 +02:00
claus 16b68da838 Home view: fjern vacation mode fra Modes-sektion (er nu i Ferie-sektionen) 2026-05-01 19:39:24 +02:00
claus 02d63e3d2b Godnat: brug individuelle pærer i stedet for light.sovevaerelse 2026-05-01 07:19:08 +02:00
claus 7b3bf5b375 Daniel motionlys toggle: ret til korrekt automation ID (daniel_lys_via_bevaegelse) 2026-05-01 07:13:33 +02:00
claus da78422f5e Syg: tilføj kommentar om at Andreas ikke har alarmer 2026-05-01 07:04:32 +02:00
claus ba0c34672c Ferie: sæt default start 13.7 kl 10 og slut 27.7 kl 12 2026-04-30 19:19:18 +02:00
claus 1805b4c107 Sonos ungroup: tilføj lille_badevaerelse ved 08:30 2026-04-30 19:00:54 +02:00
claus c366831dd0 Sonos gruppe: tilføj lille_badevaerelse 2026-04-30 18:59:56 +02:00
claus 6310ee4055 Mealie madplan: inkluder titel-entries uden opskrift (fx rester-noter) 2026-04-30 18:52:46 +02:00
claus 0e23bceb79 Ferie: tilføj vacation_start, auto-aktiver ved afrejse, vis start+slut på home view 2026-04-30 18:45:13 +02:00
claus 19dcab272e Badeværelse: Hue Tap Switch tilsidesætter bev.-automatik, 10 min timeout ved manuel tilstand 2026-04-30 06:36:12 +02:00
claus 1fdcf410ed Energi view: tilføj stik_sonos_stue, stik_quooker, stik_fryser, stik_bryggers 2026-04-29 21:19:12 +02:00
claus 83fc33f809 Energi view: tilføj Zigbee stik-sektion med effekt-graf, tile-kort og energistatistik 2026-04-29 21:17:03 +02:00
claus 1e96c41971 Sovevaerelse view: hold-slukning bruger light.sovevaerelse (alle 5 pærer) 2026-04-29 07:24:32 +02:00
claus 6c3def6996 Godnat: brug light.sovevaerelse (alle 5 pærer) i stedet for light.bedroom 2026-04-29 07:22:22 +02:00
claus 68db35cae3 Sovevaerelse: sluk-automation springer over mens godnat_sovevaerelse kører 2026-04-28 21:18:18 +02:00
69 changed files with 3131 additions and 766 deletions
+1 -1
View File
@@ -1 +1 @@
2026.4.3 2026.5.1
+5
View File
@@ -38,6 +38,10 @@ configuration_minimal.yaml
!.HA_VERSION !.HA_VERSION
!customize !customize
# --- Re-ignore macOS metadata files inside whitelisted dirs ---
include/**/.DS_Store
include/**/._*
# --- Whitelist directories --- # --- Whitelist directories ---
!www/ !www/
!include/ !include/
@@ -66,6 +70,7 @@ configuration_minimal.yaml
/oldscripts.yaml /oldscripts.yaml
# --- Local media snapshots and downloads --- # --- Local media snapshots and downloads ---
/www/snapshots/
/www/affalddk/ /www/affalddk/
/www/community/ /www/community/
/www/indkorsel_snapshot.jpg /www/indkorsel_snapshot.jpg
-1
View File
@@ -1,4 +1,3 @@
## Installation of home-assistant on Synology ## Installation of home-assistant on Synology
Follow https://www.home-assistant.io/installation/alternative/: Follow https://www.home-assistant.io/installation/alternative/:
+3 -3
View File
@@ -6,7 +6,7 @@ default_config:
homeassistant: homeassistant:
name: !secret name name: !secret name
external_url: "http://anneclaus.duckdns.org:8123" external_url: "https://ha.anneclaus.dk"
internal_url: "http://dethlefsen:8123" internal_url: "http://dethlefsen:8123"
auth_providers: auth_providers:
- type: homeassistant - type: homeassistant
@@ -27,6 +27,7 @@ http:
trusted_proxies: trusted_proxies:
- 127.0.0.1 - 127.0.0.1
- 10.0.0.142 - 10.0.0.142
- 172.17.0.0/16 # Docker bridge (NPM)
logger: logger:
default: warning default: warning
@@ -38,6 +39,7 @@ logger:
homeassistant.components.discovery: error homeassistant.components.discovery: error
homeassistant.components.dlna_dmr: error homeassistant.components.dlna_dmr: error
async_upnp_client: error async_upnp_client: error
automower_ble: critical
recorder: recorder:
purge_keep_days: 7 purge_keep_days: 7
@@ -110,7 +112,6 @@ cover:
template: !include_dir_merge_list include/templates/ template: !include_dir_merge_list include/templates/
group: !include_dir_merge_named include/groups/ group: !include_dir_merge_named include/groups/
mqtt: !include include/mqtt.yaml
sensor: !include_dir_merge_list include/sensors/ sensor: !include_dir_merge_list include/sensors/
automation: !include_dir_merge_list include/automations/ automation: !include_dir_merge_list include/automations/
binary_sensor: !include_dir_merge_list include/binary_sensors/ binary_sensor: !include_dir_merge_list include/binary_sensors/
@@ -120,7 +121,6 @@ input_number: !include_dir_merge_named include/input/number/
input_select: !include_dir_merge_named include/input/select/ input_select: !include_dir_merge_named include/input/select/
input_boolean: !include_dir_merge_named include/input/boolean/ input_boolean: !include_dir_merge_named include/input/boolean/
input_text: !include_dir_merge_named include/input/text/ input_text: !include_dir_merge_named include/input/text/
command_line: !include_dir_merge_list include/command_line/
light: !include_dir_merge_list include/lights/ light: !include_dir_merge_list include/lights/
panel_iframe: !include_dir_merge_named include/panels/ panel_iframe: !include_dir_merge_named include/panels/
script: !include_dir_merge_named include/scripts/ script: !include_dir_merge_named include/scripts/
+34 -346
View File
@@ -33,275 +33,19 @@ cards:
name: I morgen name: I morgen
icon: mdi:briefcase-outline icon: mdi:briefcase-outline
# 👨‍👩‍👧‍👦 Familien tryk for at toggle syg/rask # 👨‍👩‍👧‍👦 Familien
- type: grid - type: glance
columns: 4 entities:
square: false - entity: person.daniel_schusler_dethlefsen
cards:
- type: custom:button-card
entity: person.daniel_schusler_dethlefsen
name: Daniel name: Daniel
show_name: true - entity: person.claus_dethlefsen
show_state: false
show_label: true
show_icon: false
show_entity_picture: true
label: >
[[[
const s = entity.state;
const sick = states['input_select.daniel_status'] &&
states['input_select.daniel_status'].state === 'syg';
const loc = s === 'home' ? 'Hjemme' : s === 'not_home' ? 'Ude' : s;
return sick ? loc + ' · Syg' : loc;
]]]
styles:
card:
- padding: 8px 4px
- border: >
[[[
return states['input_select.daniel_status'] &&
states['input_select.daniel_status'].state === 'syg'
? '2px solid rgba(220,50,50,0.8)' : '2px solid transparent';
]]]
- border-radius: 12px
entity_picture:
- width: 60px
- height: 60px
- border-radius: 50%
- object-fit: cover
- filter: >
[[[
return states['input_select.daniel_status'] &&
states['input_select.daniel_status'].state === 'syg'
? 'grayscale(100%)' : 'none';
]]]
name:
- font-size: 12px
- font-weight: 600
- padding-top: 6px
- color: >
[[[
return states['input_select.daniel_status'] &&
states['input_select.daniel_status'].state === 'syg'
? 'rgb(220,50,50)' : 'var(--primary-text-color)';
]]]
label:
- font-size: 10px
- color: var(--secondary-text-color)
- padding-bottom: 2px
tap_action:
action: call-service
service: input_select.select_option
service_data:
entity_id: input_select.daniel_status
option: >
[[[
return states['input_select.daniel_status'] &&
states['input_select.daniel_status'].state === 'syg'
? 'hjemme' : 'syg';
]]]
hold_action:
action: more-info
entity: person.daniel_schusler_dethlefsen
- type: custom:button-card
entity: person.claus_dethlefsen
name: Claus name: Claus
show_name: true - entity: person.anne_schusler_dethlefsen
show_state: false
show_label: true
show_icon: false
show_entity_picture: true
label: >
[[[
const s = entity.state;
const sick = states['input_select.claus_status'] &&
states['input_select.claus_status'].state === 'syg';
const loc = s === 'home' ? 'Hjemme' : s === 'not_home' ? 'Ude' : s;
return sick ? loc + ' · Syg' : loc;
]]]
styles:
card:
- padding: 8px 4px
- border: >
[[[
return states['input_select.claus_status'] &&
states['input_select.claus_status'].state === 'syg'
? '2px solid rgba(220,50,50,0.8)' : '2px solid transparent';
]]]
- border-radius: 12px
entity_picture:
- width: 60px
- height: 60px
- border-radius: 50%
- object-fit: cover
- filter: >
[[[
return states['input_select.claus_status'] &&
states['input_select.claus_status'].state === 'syg'
? 'grayscale(100%)' : 'none';
]]]
name:
- font-size: 12px
- font-weight: 600
- padding-top: 6px
- color: >
[[[
return states['input_select.claus_status'] &&
states['input_select.claus_status'].state === 'syg'
? 'rgb(220,50,50)' : 'var(--primary-text-color)';
]]]
label:
- font-size: 10px
- color: var(--secondary-text-color)
- padding-bottom: 2px
tap_action:
action: call-service
service: input_select.select_option
service_data:
entity_id: input_select.claus_status
option: >
[[[
return states['input_select.claus_status'] &&
states['input_select.claus_status'].state === 'syg'
? 'hjemme' : 'syg';
]]]
hold_action:
action: more-info
entity: person.claus_dethlefsen
- type: custom:button-card
entity: person.anne_schusler_dethlefsen
name: Anne name: Anne
show_name: true - entity: person.andreas_schusler_dethlefsen
show_state: false
show_label: true
show_icon: false
show_entity_picture: true
label: >
[[[
const s = entity.state;
const sick = states['input_select.anne_status'] &&
states['input_select.anne_status'].state === 'syg';
const loc = s === 'home' ? 'Hjemme' : s === 'not_home' ? 'Ude' : s;
return sick ? loc + ' · Syg' : loc;
]]]
styles:
card:
- padding: 8px 4px
- border: >
[[[
return states['input_select.anne_status'] &&
states['input_select.anne_status'].state === 'syg'
? '2px solid rgba(220,50,50,0.8)' : '2px solid transparent';
]]]
- border-radius: 12px
entity_picture:
- width: 60px
- height: 60px
- border-radius: 50%
- object-fit: cover
- filter: >
[[[
return states['input_select.anne_status'] &&
states['input_select.anne_status'].state === 'syg'
? 'grayscale(100%)' : 'none';
]]]
name:
- font-size: 12px
- font-weight: 600
- padding-top: 6px
- color: >
[[[
return states['input_select.anne_status'] &&
states['input_select.anne_status'].state === 'syg'
? 'rgb(220,50,50)' : 'var(--primary-text-color)';
]]]
label:
- font-size: 10px
- color: var(--secondary-text-color)
- padding-bottom: 2px
tap_action:
action: call-service
service: input_select.select_option
service_data:
entity_id: input_select.anne_status
option: >
[[[
return states['input_select.anne_status'] &&
states['input_select.anne_status'].state === 'syg'
? 'hjemme' : 'syg';
]]]
hold_action:
action: more-info
entity: person.anne_schusler_dethlefsen
- type: custom:button-card
entity: person.andreas_schusler_dethlefsen
name: Andreas name: Andreas
show_name: true - entity: binary_sensor.family_presence
show_state: false name: Familie
show_label: true
show_icon: false
show_entity_picture: true
label: >
[[[
const s = entity.state;
const sick = states['input_select.andreas_status'] &&
states['input_select.andreas_status'].state === 'syg';
const loc = s === 'home' ? 'Hjemme' : s === 'not_home' ? 'Ude' : s;
return sick ? loc + ' · Syg' : loc;
]]]
styles:
card:
- padding: 8px 4px
- border: >
[[[
return states['input_select.andreas_status'] &&
states['input_select.andreas_status'].state === 'syg'
? '2px solid rgba(220,50,50,0.8)' : '2px solid transparent';
]]]
- border-radius: 12px
entity_picture:
- width: 60px
- height: 60px
- border-radius: 50%
- object-fit: cover
- filter: >
[[[
return states['input_select.andreas_status'] &&
states['input_select.andreas_status'].state === 'syg'
? 'grayscale(100%)' : 'none';
]]]
name:
- font-size: 12px
- font-weight: 600
- padding-top: 6px
- color: >
[[[
return states['input_select.andreas_status'] &&
states['input_select.andreas_status'].state === 'syg'
? 'rgb(220,50,50)' : 'var(--primary-text-color)';
]]]
label:
- font-size: 10px
- color: var(--secondary-text-color)
- padding-bottom: 2px
tap_action:
action: call-service
service: input_select.select_option
service_data:
entity_id: input_select.andreas_status
option: >
[[[
return states['input_select.andreas_status'] &&
states['input_select.andreas_status'].state === 'syg'
? 'hjemme' : 'syg';
]]]
hold_action:
action: more-info
entity: person.andreas_schusler_dethlefsen
# 🪟 Gardiner # 🪟 Gardiner
- type: grid - type: grid
@@ -389,71 +133,36 @@ cards:
icon: mdi:floor-plan icon: mdi:floor-plan
tap_action: tap_action:
action: call-service action: call-service
service: script.turn_on service: button.press
target: target:
entity_id: script.roborock_manuelt_kokken entity_id: button.roborock_s8_pro_ultra_kokken_bryggers
- type: button - type: button
name: Syd name: Syd
icon: mdi:floor-plan icon: mdi:floor-plan
tap_action: tap_action:
action: call-service action: call-service
service: script.turn_on service: button.press
target: target:
entity_id: script.roborock_manuelt_syd entity_id: button.roborock_s8_pro_ultra_syd
- type: button - type: button
name: Mop name: Mop
icon: mdi:floor-plan icon: mdi:floor-plan
tap_action: tap_action:
action: call-service action: call-service
service: script.turn_on service: button.press
target: target:
entity_id: script.roborock_manuelt_mop entity_id: button.roborock_s8_pro_ultra_vac_followed_by_mop
- type: custom:button-card - type: button
entity: vacuum.roborock_s8_pro_ultra name: Gå til dock
name: Start icon: mdi:home-import-outline
icon: mdi:robot-vacuum
tap_action: tap_action:
action: call-service action: call-service
service: script.turn_on service: vacuum.return_to_base
target: target:
entity_id: script.roborock_manuelt_start entity_id: vacuum.roborock_s8_pro_ultra
state:
- value: cleaning
name: Dock
styles:
card:
- background-color: rgba(255, 200, 0, 0.25)
- border: 1px solid rgba(255, 200, 0, 0.8)
tap_action:
action: call-service
service: vacuum.return_to_base
target:
entity_id: vacuum.roborock_s8_pro_ultra
- value: returning
name: Dock
styles:
card:
- background-color: rgba(255, 200, 0, 0.25)
- border: 1px solid rgba(255, 200, 0, 0.8)
tap_action:
action: call-service
service: vacuum.return_to_base
target:
entity_id: vacuum.roborock_s8_pro_ultra
- value: paused
name: Dock
styles:
card:
- background-color: rgba(255, 200, 0, 0.25)
- border: 1px solid rgba(255, 200, 0, 0.8)
tap_action:
action: call-service
service: vacuum.return_to_base
target:
entity_id: vacuum.roborock_s8_pro_ultra
# 🏎️ Plæneklipper # 🏎️ Plæneklipper
- type: horizontal-stack - type: horizontal-stack
@@ -462,25 +171,8 @@ cards:
entity: input_datetime.ploeneklipper_sidst_koert entity: input_datetime.ploeneklipper_sidst_koert
show_icon: false show_icon: false
show_name: true show_name: true
show_state: false show_state: true
show_label: true
name: Sidst klippet name: Sidst klippet
label: >
[[[
const s = entity.state;
if (!s || s === 'unknown') return 'Ukendt';
const d = new Date(s.replace(' ', 'T'));
if (isNaN(d)) return s;
const now = new Date();
const isToday = d.toDateString() === now.toDateString();
const yesterday = new Date(now);
yesterday.setDate(now.getDate() - 1);
const isYesterday = d.toDateString() === yesterday.toDateString();
const t = d.toLocaleTimeString('da-DK', {hour: '2-digit', minute: '2-digit'});
if (isToday) return 'I dag ' + t;
if (isYesterday) return 'I g\u00e5r ' + t;
return d.toLocaleDateString('da-DK', {day: 'numeric', month: 'short'}) + ' ' + t;
]]]
tap_action: tap_action:
action: none action: none
styles: styles:
@@ -490,34 +182,30 @@ cards:
- font-size: 11px - font-size: 11px
- color: var(--secondary-text-color) - color: var(--secondary-text-color)
- padding-bottom: 4px - padding-bottom: 4px
label: state:
- white-space: normal - white-space: normal
- word-break: break-word - word-break: break-word
- line-height: 1.2 - line-height: 1.2
- font-size: 13px - font-size: 13px
- text-align: center - text-align: center
- type: custom:button-card - type: button
entity: lawn_mower.husqvarna_automower
name: Klip name: Klip
icon: mdi:robot-mower icon: mdi:robot-mower
tap_action: tap_action:
action: call-service action: call-service
service: script.turn_on service: lawn_mower.start_mowing
target: target:
entity_id: script.ploeneklipper_manuelt_start entity_id: lawn_mower.husqvarna_automower
state:
- value: mowing - type: button
name: Stop name: Stop
styles: icon: mdi:home-import-outline
card: tap_action:
- background-color: rgba(255, 200, 0, 0.25) action: call-service
- border: 1px solid rgba(255, 200, 0, 0.8) service: lawn_mower.dock
tap_action: target:
action: call-service entity_id: lawn_mower.husqvarna_automower
service: lawn_mower.dock
target:
entity_id: lawn_mower.husqvarna_automower
# 💡 Lys kontrol # 💡 Lys kontrol
- type: horizontal-stack - type: horizontal-stack
+1 -1
View File
@@ -16,7 +16,7 @@ sections:
action: call-service action: call-service
service: light.turn_off service: light.turn_off
service_data: service_data:
entity_id: light.bedroom entity_id: light.sovevaerelse
styles: styles:
card: card:
- height: 64px - height: 64px
+603 -19
View File
@@ -18,9 +18,9 @@ cards:
[[[ [[[
var slug = states['sensor.dagens_aftensmad_slug'].state; var slug = states['sensor.dagens_aftensmad_slug'].state;
if (slug && slug !== '' && slug !== 'unknown') { if (slug && slug !== '' && slug !== 'unknown') {
return 'http://anneclaus.dk:9925/g/home/r/' + slug; return 'https://mealie.anneclaus.dk/g/home/r/' + slug;
} }
return 'http://anneclaus.dk:9925'; return 'https://mealie.anneclaus.dk';
]]] ]]]
styles: styles:
card: card:
@@ -41,7 +41,7 @@ cards:
- color: white - color: white
- padding-top: 4px - padding-top: 4px
# 🎵 Musik i køkken + Der er mad # 🎵 Musik i køkken + Vi laver mad + Der er mad
- type: grid - type: grid
columns: 2 columns: 2
square: false square: false
@@ -57,6 +57,13 @@ cards:
data: data:
source: "1 Family Mix" source: "1 Family Mix"
- type: button
name: Vi laver mad
icon: mdi:chef-hat
tap_action:
action: call-service
service: script.vi_laver_mad
- type: button - type: button
name: Der er mad! name: Der er mad!
icon: mdi:silverware-fork-knife icon: mdi:silverware-fork-knife
@@ -79,7 +86,7 @@ cards:
{%- set slug = recipe.slug if recipe else '' -%} {%- set slug = recipe.slug if recipe else '' -%}
{%- set label = 'I dag' if offset == 0 else days[day.weekday()] -%} {%- set label = 'I dag' if offset == 0 else days[day.weekday()] -%}
{%- if slug -%} {%- if slug -%}
{%- set ns.rows = ns.rows + "| **" + label + "** | [" + name + "](http://anneclaus.dk:9925/g/home/r/" + slug + ") |\n" -%} {%- set ns.rows = ns.rows + "| **" + label + "** | [" + name + "](https://mealie.anneclaus.dk/g/home/r/" + slug + ") |\n" -%}
{%- elif name -%} {%- elif name -%}
{%- set ns.rows = ns.rows + "| **" + label + "** | " + name + " |\n" -%} {%- set ns.rows = ns.rows + "| **" + label + "** | " + name + " |\n" -%}
{%- else -%} {%- else -%}
@@ -92,21 +99,598 @@ cards:
| --- | --- | | --- | --- |
{{ ns.rows }} {{ ns.rows }}
# 🛒 Bilka ToGo - opdater og vis kryds-af liste # 🎵 Sonos Køkken
- type: vertical-stack - type: media-control
entity: media_player.kokken
name: Sonos Køkken
- type: grid
columns: 2
square: false
cards: cards:
- type: markdown - type: custom:button-card
content: | name: Volumen ned
## Bilka ToGo - kryds-af icon: mdi:volume-minus
Tryk på knappen for at hente ingredienser fra ugeplanen (fredagtorsdag).
- type: button
name: Opdater Bilka ToGo-liste nu
icon: mdi:cart-check
tap_action: tap_action:
action: call-service action: perform-action
service: script.mealie_shopping_refresh perform_action: media_player.volume_down
target:
entity_id: media_player.kokken
hold_action:
action: perform-action
perform_action: media_player.volume_down
target:
entity_id: media_player.kokken
styles:
card:
- height: 72px
- font-size: 16px
- background: var(--primary-color)
icon:
- color: white
- width: 36px
name:
- color: white
- font-size: 13px
- type: custom:button-card
name: Volumen op
icon: mdi:volume-plus
tap_action:
action: perform-action
perform_action: media_player.volume_up
target:
entity_id: media_player.kokken
hold_action:
action: perform-action
perform_action: media_player.volume_up
target:
entity_id: media_player.kokken
styles:
card:
- height: 72px
- font-size: 16px
- background: var(--primary-color)
icon:
- color: white
- width: 36px
name:
- color: white
- font-size: 13px
- type: grid
columns: 3
square: false
cards:
- type: custom:button-card
name: DR P3
icon: mdi:radio
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "0 DR P3"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Family Mix
icon: mdi:account-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "1 Family Mix"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Danske fav.
icon: mdi:music-note
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Danske favoritter"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Rock klassikere
icon: mdi:music-note-outline
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Danske rock klassikere"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Anne Mix 1
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Anne Daily Mix 1"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Anne Mix 2
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Anne Daily Mix 2"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Anne Mix 3
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Anne Daily Mix 3"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Anne Mix 4
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Anne Daily Mix 4"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Anne Mix 5
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Anne Daily Mix 5"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Anne Mix 6
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Anne Daily Mix 6"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Claus Mix 1
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Claus Daily Mix 1"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Claus Mix 2
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Claus Daily Mix 2"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Claus Mix 3
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Claus Daily Mix 3"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Claus Mix 4
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Claus Daily Mix 4"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Claus Mix 5
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Claus Daily Mix 5"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Claus Mix 6
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Claus Daily Mix 6"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Andreas Mix 1
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Andreas Daily Mix 1"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Andreas Mix 2
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Andreas Daily Mix 2"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Andreas Mix 3
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Andreas Daily Mix 3"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Andreas Mix 4
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Andreas Daily Mix 4"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Andreas Mix 5
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Andreas Daily Mix 5"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Andreas Mix 6
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Andreas Daily Mix 6"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Daniel Mix 1
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Daniel Daily Mix 1"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Daniel Mix 2
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Daniel Daily Mix 2"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Daniel Mix 3
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Daniel Daily Mix 3"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Daniel Mix 4
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Daniel Daily Mix 4"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Daniel Mix 5
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Daniel Daily Mix 5"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: custom:button-card
name: Daniel Mix 6
icon: mdi:playlist-music
tap_action:
action: perform-action
perform_action: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Daniel Daily Mix 6"
styles:
card:
- height: 52px
- padding: 6px 8px
icon:
- width: 18px
name:
- font-size: 11px
- type: iframe
url: /local/bilka_togo_checklist.html
aspect_ratio: 100%
+215
View File
@@ -0,0 +1,215 @@
title: Vanding
path: vanding
icon: mdi:sprinkler-variant
type: sections
max_columns: 2
sections:
# 💧 Jordfugt målere
- type: grid
cards:
- type: heading
heading: Jordfugt
icon: mdi:water-percent
- type: gauge
entity: sensor.annes_havesensor_soil_moisture_1
name: Højbed 1 Ærter
min: 0
max: 100
needle: true
severity:
green: 40
yellow: 20
red: 0
- type: gauge
entity: sensor.annes_havesensor_soil_moisture_2
name: Højbed 2 Kartofler
min: 0
max: 100
needle: true
severity:
green: 40
yellow: 20
red: 0
- type: gauge
entity: sensor.annes_havesensor_soil_moisture_3
name: Højbed 3 Rabarber
min: 0
max: 100
needle: true
severity:
green: 40
yellow: 20
red: 0
- type: gauge
entity: sensor.annes_havesensor_soil_moisture_4
name: Drivhus
min: 0
max: 100
needle: true
severity:
green: 45
yellow: 25
red: 0
# 📈 Jordfugt historik
- type: grid
cards:
- type: heading
heading: Jordfugt 7 dage
icon: mdi:chart-line
- type: history-graph
title: Højbede (%)
entities:
- entity: sensor.annes_havesensor_soil_moisture_1
name: HB1 Ærter
- entity: sensor.annes_havesensor_soil_moisture_2
name: HB2 Kartofler
- entity: sensor.annes_havesensor_soil_moisture_3
name: HB3 Rabarber
hours_to_show: 168
refresh_interval: 900
- type: history-graph
title: Drivhus (%)
entities:
- entity: sensor.annes_havesensor_soil_moisture_4
name: Drivhus
hours_to_show: 168
refresh_interval: 900
# 🌧️ Regn & vejr
- type: grid
cards:
- type: heading
heading: Regn (Netatmo)
icon: mdi:weather-rainy
- type: tile
entity: sensor.n22_nedbor
name: Nedbør nu
- type: tile
entity: sensor.n22_precipitation_today
name: Nedbør i dag
- type: history-graph
title: Nedbør 7 dage
entities:
- entity: sensor.n22_precipitation_today
name: Nedbør
hours_to_show: 168
refresh_interval: 1800
- type: custom:apexcharts-card
header:
show: true
title: Forventet nedbør næste 7 dage
graph_span: 7d
span:
start: day
apex_config:
chart:
type: bar
height: 200
dataLabels:
enabled: true
formatter: |
EVAL:function(val) { return val ? val + ' mm' : ''; }
xaxis:
type: datetime
labels:
datetimeFormatter:
day: "dd/MM"
yaxis:
min: 0
title:
text: mm
series:
- entity: sensor.vejr_daglig_prognose
name: Nedbør
color: "#4fc3f7"
data_generator: |
return entity.attributes.forecast.map(f => ({
x: new Date(f.datetime).getTime(),
y: f.precipitation ?? 0
}));
# ⏸️ Rain Bird RC2
- type: grid
cards:
- type: heading
heading: Rain Bird RC2
icon: mdi:sprinkler-fire
- type: tile
entity: sensor.annes_vanding_raindelay
name: Regn-forsinkelse status
- type: tile
entity: number.annes_vanding_rain_delay
name: Sæt forsinkelse (dage)
- type: tile
entity: calendar.annes_vanding
name: Vandingsplan
# 🌿 Zonekontrol
- type: grid
cards:
- type: heading
heading: Zoner manuel styring
icon: mdi:water-pump
- type: tile
entity: switch.hojbed_1
name: Højbed 1 Ærter
icon: mdi:sprinkler
- type: tile
entity: switch.hojbed_2
name: Højbed 2 Kartofler
icon: mdi:sprinkler
- type: tile
entity: switch.hojbed_3
name: Højbed 3 Rabarber
icon: mdi:sprinkler
- type: tile
entity: switch.drivhus_drypvanding
name: Drivhus
icon: mdi:greenhouse
# 🔋 Sensorbatterier
- type: grid
cards:
- type: heading
heading: Sensor batterier
icon: mdi:battery
- type: glance
show_name: true
show_icon: true
show_state: true
columns: 4
entities:
- entity: sensor.annes_havesensor_soil_battery_1
name: HB1
icon: mdi:battery
- entity: sensor.annes_havesensor_soil_battery_2
name: HB2
icon: mdi:battery
- entity: sensor.annes_havesensor_soil_battery_3
name: HB3
icon: mdi:battery
- entity: sensor.annes_havesensor_soil_battery_4
name: Drivhus
icon: mdi:battery
+96 -6
View File
@@ -159,6 +159,8 @@ sections:
series: series:
- entity: sensor.kontor_motion_temperatur - entity: sensor.kontor_motion_temperatur
name: Hue name: Hue
- entity: sensor.annes_havesensor_indoor_temperature
name: Havesensor inde
- entity: climate.kontor - entity: climate.kontor
attribute: current_temperature attribute: current_temperature
name: Roth aktuelt name: Roth aktuelt
@@ -357,6 +359,84 @@ sections:
curve: stepline curve: stepline
color: "#ff8800" color: "#ff8800"
- type: grid
cards:
- type: custom:apexcharts-card
graph_span: 24h
header:
show: true
title: Bryggers
show_states: true
colorize_states: true
now:
show: true
label: Nu
apex_config:
chart:
height: 240
grid:
strokeDashArray: 2
xaxis:
type: datetime
labels:
datetimeFormatter:
hour: HH:mm
yaxis:
decimalsInFloat: 1
tickAmount: 6
series:
- entity: sensor.temp_bryggers_temperatur
name: Temperatur
# TODO: tilføj climate-entity når tænd/sluk er monteret
# - entity: climate.bryggers
# attribute: current_temperature
# name: Roth aktuelt
# - entity: climate.bryggers
# attribute: temperature
# name: Roth mål
# stroke_width: 1
# curve: stepline
# color: "#ff8800"
- type: grid
cards:
- type: custom:apexcharts-card
graph_span: 24h
header:
show: true
title: Køkken
show_states: true
colorize_states: true
now:
show: true
label: Nu
apex_config:
chart:
height: 240
grid:
strokeDashArray: 2
xaxis:
type: datetime
labels:
datetimeFormatter:
hour: HH:mm
yaxis:
decimalsInFloat: 1
tickAmount: 6
series:
- entity: sensor.temp_kokken_temperatur
name: Temperatur
# TODO: tilføj climate-entity når tænd/sluk er monteret
# - entity: climate.kokken
# attribute: current_temperature
# name: Roth aktuelt
# - entity: climate.kokken
# attribute: temperature
# name: Roth mål
# stroke_width: 1
# curve: stepline
# color: "#ff8800"
# Indstillinger: Komforttemperaturer og sænkninger # Indstillinger: Komforttemperaturer og sænkninger
- type: grid - type: grid
cards: cards:
@@ -372,6 +452,9 @@ sections:
- entity: input_number.varme_komfort_lille_bad - entity: input_number.varme_komfort_lille_bad
- entity: input_number.varme_komfort_badevarelse - entity: input_number.varme_komfort_badevarelse
- entity: input_number.varme_komfort_stue - entity: input_number.varme_komfort_stue
# TODO: aktiver når climate-entiteter er oprettet
# - entity: input_number.varme_komfort_bryggers
# - entity: input_number.varme_komfort_kokken
- type: entities - type: entities
title: Sænkninger og ferie title: Sænkninger og ferie
@@ -381,8 +464,6 @@ sections:
- entity: input_number.varme_nat_saenkning - entity: input_number.varme_nat_saenkning
- entity: input_number.varme_vaek_saenkning - entity: input_number.varme_vaek_saenkning
- entity: input_number.varme_ferie_temp - entity: input_number.varme_ferie_temp
- entity: input_datetime.vacation_end
name: Hjemkomst (ferie slutter)
- type: button - type: button
name: Genberegn varme nu name: Genberegn varme nu
@@ -391,12 +472,19 @@ sections:
action: call-service action: call-service
service: script.varme_recalculate service: script.varme_recalculate
- type: button
name: Gem temperaturer som standard
icon: mdi:content-save
tap_action:
action: perform-action
perform_action: script.varme_save_defaults
# Ventilposition # Ventilposition
- type: grid - type: grid
cards: cards:
- type: gauge - type: gauge
entity: sensor.fjernvarme_ventil_anbefalet entity: sensor.fjernvarme_ventil_3_ugers_gennemsnit
name: Anbefalet ventilposition (15) name: Anbefalet ventilposition 3 ugers snit (15)
min: 1 min: 1
max: 5 max: 5
needle: true needle: true
@@ -414,9 +502,11 @@ sections:
- type: markdown - type: markdown
content: |- content: |-
**{{ state_attr('sensor.fjernvarme_ventil_anbefalet', 'anbefaling') }}** **Anbefalet stilling (3 ugers snit): {{ states('sensor.fjernvarme_ventil_3_ugers_gennemsnit') | float(0) | round(1) }}**
Udetemperatur: {{ state_attr('sensor.fjernvarme_ventil_anbefalet', 'udetemperatur') }}°C Øjeblikkelig (vejrbaseret): {{ states('sensor.fjernvarme_ventil_anbefalet') }} {{ state_attr('sensor.fjernvarme_ventil_anbefalet', 'anbefaling') }}
Udetemperatur nu: {{ state_attr('sensor.fjernvarme_ventil_anbefalet', 'udetemperatur') }}°C
Gælder for begge manuelle hoveddrejehaner: Gælder for begge manuelle hoveddrejehaner:
- Roth-fordeler (sauna) - Roth-fordeler (sauna)
+352
View File
@@ -0,0 +1,352 @@
title: Sikkerhed
path: sikkerhed
icon: mdi:shield-home
cards:
# ─────────────────────────────────────────────────────────────
# 📷 KAMERAER
# ─────────────────────────────────────────────────────────────
- type: grid
columns: 2
square: false
cards:
- type: picture-entity
entity: camera.terrasse_sub
name: Terasse
camera_view: live
show_state: false
show_name: true
tap_action:
action: fire-dom-event
browser_mod:
service: browser_mod.popup
data:
title: Terasse Live
content:
type: vertical-stack
cards:
- type: picture-entity
entity: camera.terrasse_sub
camera_view: live
show_name: false
show_state: false
tap_action:
action: none
- type: tile
entity: number.terrasse_focus
name: Fokus
icon: mdi:focus-field
features:
- type: numeric-input
style: slider
- type: tile
entity: number.terrasse_zoom
name: Zoom
icon: mdi:magnify
features:
- type: numeric-input
style: slider
- type: picture-entity
entity: camera.indkoersel_sub
name: Indkørsel
camera_view: live
show_state: false
show_name: true
tap_action:
action: fire-dom-event
browser_mod:
service: browser_mod.popup
data:
title: Indkørsel Live
content:
type: vertical-stack
cards:
- type: picture-entity
entity: camera.indkoersel_sub
camera_view: live
show_name: false
show_state: false
tap_action:
action: none
- type: tile
entity: number.indkoersel_focus
name: Fokus
icon: mdi:focus-field
features:
- type: numeric-input
style: slider
- type: tile
entity: number.indkoersel_zoom
name: Zoom
icon: mdi:magnify
features:
- type: numeric-input
style: slider
# ─────────────────────────────────────────────────────────────
# 🛡️ SIKKERHEDSSTATUS
# ─────────────────────────────────────────────────────────────
- type: heading
heading: Sikkerhedsstatus
heading_style: title
- type: grid
columns: 2
square: false
cards:
# 👥 Tilstedeværelse
- type: custom:mushroom-template-card
entity: binary_sensor.family_presence
primary: >
{{ 'Nogen hjemme' if is_state('binary_sensor.family_presence', 'on') else 'Ingen hjemme' }}
secondary: >
{% set persons = [
('Claus', 'person.claus_dethlefsen'),
('Anne', 'person.anne_schusler_dethlefsen'),
('Andreas', 'person.andreas_schusler_dethlefsen'),
('Daniel', 'person.daniel_schusler_dethlefsen')
] %}
{% set ns = namespace(home=[]) %}
{% for name, eid in persons %}
{% if is_state(eid, 'home') %}{% set ns.home = ns.home + [name] %}{% endif %}
{% endfor %}
{{ ns.home | join(', ') if ns.home else 'Alle ude' }}
icon: >
{{ 'mdi:home-account' if is_state('binary_sensor.family_presence', 'on') else 'mdi:home-outline' }}
icon_color: >
{{ 'green' if is_state('binary_sensor.family_presence', 'on') else 'blue' }}
tap_action:
action: none
# 💡 Lys
- type: custom:mushroom-template-card
entity: light.alle_lys
primary: >
{{ 'Lys er tændt' if is_state('light.alle_lys', 'on') else 'Alt lys slukket' }}
secondary: ""
icon: >
{{ 'mdi:lightbulb-on' if is_state('light.alle_lys', 'on') else 'mdi:lightbulb-off' }}
icon_color: >
{{ 'yellow' if is_state('light.alle_lys', 'on') else 'grey' }}
tap_action:
action: navigate
navigation_path: /lovelace/lys
# 🪟 Vinduer og terrassedør
- type: custom:mushroom-template-card
multiline_secondary: true
primary: >
{% set sensors = [
'binary_sensor.andreas_vindue',
'binary_sensor.daniel_vindue',
'binary_sensor.sovevaerelse_vindue',
'binary_sensor.badevaerelse_vindue',
'binary_sensor.lille_bad_vindue',
'binary_sensor.terrassedor'
] %}
{% set ns = namespace(open=0) %}
{% for s in sensors %}{% if is_state(s, 'on') %}{% set ns.open = ns.open + 1 %}{% endif %}{% endfor %}
{{ 'Alle vinduer lukket' if ns.open == 0 else ns.open | string + ' vindue(r) åben' }}
secondary: >
{% set sensor_map = {
'binary_sensor.andreas_vindue': 'Andreas',
'binary_sensor.daniel_vindue': 'Daniel',
'binary_sensor.sovevaerelse_vindue': 'Soveværelse',
'binary_sensor.badevaerelse_vindue': 'Badeværelse',
'binary_sensor.lille_bad_vindue': 'Lille bad',
'binary_sensor.terrassedor': 'Terrassedør'
} %}
{% set ns = namespace(aabne=[]) %}
{% for s, n in sensor_map.items() %}{% if is_state(s, 'on') %}{% set ns.aabne = ns.aabne + [n] %}{% endif %}{% endfor %}
{{ ns.aabne | join(', ') if ns.aabne else '' }}
icon: >
{% set sensors = [
'binary_sensor.andreas_vindue',
'binary_sensor.daniel_vindue',
'binary_sensor.sovevaerelse_vindue',
'binary_sensor.badevaerelse_vindue',
'binary_sensor.lille_bad_vindue',
'binary_sensor.terrassedor'
] %}
{% set ns = namespace(open=0) %}
{% for s in sensors %}{% if is_state(s, 'on') %}{% set ns.open = ns.open + 1 %}{% endif %}{% endfor %}
{{ 'mdi:window-open-variant' if ns.open > 0 else 'mdi:window-closed-variant' }}
icon_color: >
{% set sensors = [
'binary_sensor.andreas_vindue',
'binary_sensor.daniel_vindue',
'binary_sensor.sovevaerelse_vindue',
'binary_sensor.badevaerelse_vindue',
'binary_sensor.lille_bad_vindue',
'binary_sensor.terrassedor'
] %}
{% set ns = namespace(open=0) %}
{% for s in sensors %}{% if is_state(s, 'on') %}{% set ns.open = ns.open + 1 %}{% endif %}{% endfor %}
{{ 'red' if ns.open > 0 else 'green' }}
tap_action:
action: none
# 🚗 Garage
- type: custom:mushroom-template-card
entity: binary_sensor.garageport
primary: >
{{ 'Garage åben' if is_state('binary_sensor.garageport', 'on') else 'Garage lukket' }}
secondary: >
Sidst ændret: {{ relative_time(states.binary_sensor.garageport.last_changed) }} siden
icon: >
{{ 'mdi:garage-open-variant' if is_state('binary_sensor.garageport', 'on') else 'mdi:garage-variant' }}
icon_color: >
{{ 'orange' if is_state('binary_sensor.garageport', 'on') else 'green' }}
tap_action:
action: call-service
service: cover.toggle
target:
entity_id: cover.anne
# 🏖️ Ferietilstand
- type: custom:mushroom-template-card
entity: input_boolean.vacation_mode
primary: >
{{ 'Ferie aktiv' if is_state('input_boolean.vacation_mode', 'on') else 'Normal tilstand' }}
secondary: >
{% if is_state('input_boolean.vacation_mode', 'on') %}
{% set end = states('input_datetime.vacation_end') %}
{% if end not in ['unknown', 'unavailable', ''] %}Slutter {{ as_datetime(end).strftime('%-d. %b') }}{% endif %}
{% endif %}
icon: >
{{ 'mdi:beach' if is_state('input_boolean.vacation_mode', 'on') else 'mdi:home' }}
icon_color: >
{{ 'cyan' if is_state('input_boolean.vacation_mode', 'on') else 'grey' }}
tap_action:
action: more-info
# 🤖 AI-overvågning (indkørsel)
- type: custom:mushroom-template-card
primary: >
{% set pause = states('input_datetime.ai_indkorsel_ai_pause_until') %}
{% set paused = pause not in ['unknown', 'unavailable', ''] and as_timestamp(pause) > as_timestamp(now()) %}
{{ 'AI-overvågning pauset' if paused else 'AI-overvågning aktiv' }}
secondary: >
{% set pause = states('input_datetime.ai_indkorsel_ai_pause_until') %}
{% set paused = pause not in ['unknown', 'unavailable', ''] and as_timestamp(pause) > as_timestamp(now()) %}
{% if paused %}Genoptages kl. {{ as_datetime(pause).strftime('%H:%M') }}{% else %}Indkørsel overvåges{% endif %}
icon: >
{% set pause = states('input_datetime.ai_indkorsel_ai_pause_until') %}
{% set paused = pause not in ['unknown', 'unavailable', ''] and as_timestamp(pause) > as_timestamp(now()) %}
{{ 'mdi:robot-off' if paused else 'mdi:robot' }}
icon_color: >
{% set pause = states('input_datetime.ai_indkorsel_ai_pause_until') %}
{% set paused = pause not in ['unknown', 'unavailable', ''] and as_timestamp(pause) > as_timestamp(now()) %}
{{ 'orange' if paused else 'green' }}
tap_action:
action: more-info
entity: input_datetime.ai_indkorsel_ai_pause_until
# 🔒 Terrassedør (separat overblik)
- type: custom:mushroom-template-card
entity: binary_sensor.terrassedor
primary: >
{{ 'Terrassedør åben' if is_state('binary_sensor.terrassedor', 'on') else 'Terrassedør lukket' }}
secondary: >
Sidst ændret: {{ relative_time(states.binary_sensor.terrassedor.last_changed) }} siden
icon: >
{{ 'mdi:door-open' if is_state('binary_sensor.terrassedor', 'on') else 'mdi:door-closed' }}
icon_color: >
{{ 'red' if is_state('binary_sensor.terrassedor', 'on') else 'green' }}
tap_action:
action: more-info
# 📡 Bevægelse i indkørslen lige nu
- type: custom:mushroom-template-card
entity: binary_sensor.indkorsel_sensor_motion
primary: >
{{ 'Bevægelse registreret!' if is_state('binary_sensor.indkorsel_sensor_motion', 'on') else 'Ingen bevægelse' }}
secondary: >
Sidst: {{ relative_time(states.binary_sensor.indkorsel_sensor_motion.last_changed) }} siden
icon: >
{{ 'mdi:motion-sensor' if is_state('binary_sensor.indkorsel_sensor_motion', 'on') else 'mdi:motion-sensor-off' }}
icon_color: >
{{ 'red' if is_state('binary_sensor.indkorsel_sensor_motion', 'on') else 'grey' }}
tap_action:
action: none
# ─────────────────────────────────────────────────────────────
# 📋 SENESTE BEVÆGELSE INDKØRSEL (AI-log)
# ─────────────────────────────────────────────────────────────
- type: heading
heading: Seneste bevægelse Indkørsel
heading_style: title
# Seneste snapshot gemt af AI-overvågningsscriptet
- type: picture-entity
entity: camera.indkorsel_snapshot
show_name: false
show_state: false
tap_action:
action: fire-dom-event
browser_mod:
service: browser_mod.popup
data:
title: Seneste bevægelse Indkørsel
content:
type: picture-entity
entity: camera.indkorsel_snapshot
show_name: false
show_state: false
tap_action:
action: none
# Seneste AI-beskrivelse
- type: custom:button-card
entity: input_text.last_notification_message
show_name: false
show_icon: true
show_state: true
icon: mdi:robot
styles:
card:
- padding: 14px 16px
- text-align: left
grid:
- grid-template-areas: '"i s"'
- grid-template-columns: 44px 1fr
- grid-template-rows: auto
icon:
- width: 32px
- height: 32px
- color: var(--primary-color)
- align-self: flex-start
- margin-top: 2px
state:
- white-space: normal
- word-break: break-word
- font-size: 13px
- text-align: left
- line-height: "1.5"
tap_action:
action: none
# ─────────────────────────────────────────────────────────────
# 📸 SENESTE PERSON-SNAPSHOT INDKØRSEL
# ─────────────────────────────────────────────────────────────
- type: heading
heading: Seneste person i indkørsel
heading_style: subtitle
# Klik åbner galleri med alle tidligere snapshots
- type: picture
image: /local/snapshots/indkorsel/latest.jpg
tap_action:
action: navigate
navigation_path: /lovelace/indkorsel-snapshots
# Logbog over bevægelseshændelser (48 timer)
- type: logbook
entities:
- binary_sensor.indkorsel_sensor_motion
hours_to_show: 48
title: Bevægelseslog (48 timer)
@@ -0,0 +1,9 @@
title: Snapshots Indkørsel
path: indkorsel-snapshots
icon: mdi:camera-burst
panel: true
cards:
- type: iframe
url: /local/snapshots/indkorsel_loader.html?v=20260516103651
aspect_ratio: 100%
+122 -1
View File
@@ -63,7 +63,128 @@ sections:
name: Ladehastighed name: Ladehastighed
# 💡 Automations / kontrol # Zigbee stik med strømmåling
- type: grid
cards:
- type: heading
heading: Zigbee stik
# Aktuel effekt alle stik samlet
- type: history-graph
title: Effekt lige nu (W)
entities:
- entity: sensor.stik_alrum_effekt
name: Alrum
- entity: sensor.stik_kontor_effekt
name: Kontor
- entity: sensor.stik_bad_effekt
name: Badeværelse
- entity: sensor.stik_lillebad_effekt
name: Lille bad
- entity: sensor.stik_sonos_stue_effekt
name: Sonos stue
- entity: sensor.stik_quooker_effekt
name: Quooker
- entity: sensor.stik_fryser_effekt
name: Fryser
- entity: sensor.stik_bryggers_effekt
name: Bryggers
hours_to_show: 6
refresh_interval: 30
# Individuelle stik med tænd/sluk + effekt
- type: tile
entity: switch.stik_alrum
name: Alrum
secondary_info: sensor.stik_alrum_effekt
icon: mdi:power-plug
features:
- type: toggle
- type: tile
entity: switch.stik_kontor
name: Kontor
secondary_info: sensor.stik_kontor_effekt
icon: mdi:power-plug
features:
- type: toggle
- type: tile
entity: switch.stik_bad
name: Badeværelse
secondary_info: sensor.stik_bad_effekt
icon: mdi:power-plug
features:
- type: toggle
- type: tile
entity: switch.stik_lillebad
name: Lille bad
secondary_info: sensor.stik_lillebad_effekt
icon: mdi:power-plug
features:
- type: toggle
- type: tile
entity: switch.stik_sonos_stue
name: Sonos stue
secondary_info: sensor.stik_sonos_stue_effekt
icon: mdi:power-plug
features:
- type: toggle
- type: tile
entity: switch.stik_quooker
name: Quooker
secondary_info: sensor.stik_quooker_effekt
icon: mdi:power-plug
features:
- type: toggle
- type: tile
entity: switch.stik_fryser
name: Fryser
secondary_info: sensor.stik_fryser_effekt
icon: mdi:power-plug
features:
- type: toggle
- type: tile
entity: switch.stik_bryggers
name: Bryggers
secondary_info: sensor.stik_bryggers_effekt
icon: mdi:power-plug
features:
- type: toggle
# Akkumuleret energiforbrug (kWh) pr. stik
- type: statistics-graph
title: Energiforbrug (kWh)
entities:
- entity: sensor.stik_alrum_summation_delivered
name: Alrum
- entity: sensor.stik_kontor_summation_delivered
name: Kontor
- entity: sensor.stik_bad_summation_delivered
name: Badeværelse
- entity: sensor.stik_lillebad_summation_delivered
name: Lille bad
- entity: sensor.stik_sonos_stue_summation_delivered
name: Sonos stue
- entity: sensor.stik_quooker_summation_delivered
name: Quooker
- entity: sensor.stik_fryser_summation_delivered
name: Fryser
- entity: sensor.stik_bryggers_summation_delivered
name: Bryggers
period: day
days_to_show: 7
chart_type: bar
stat_types:
- change
# 💡 Automations / kontrol
- type: grid - type: grid
cards: cards:
- type: heading - type: heading
+13
View File
@@ -0,0 +1,13 @@
services:
home-assistant:
container_name: homeassistant
image: homeassistant/home-assistant:latest
volumes:
- /volume1/homeassistant:/config
- /volume1/docker/homeassistant/backups:/backups
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
network_mode: host
restart: always
environment:
- TZ=Europe/Copenhagen
+20
View File
@@ -37,6 +37,26 @@ services:
retries: 10 retries: 10
start_period: 20s start_period: 20s
nginx-proxy-manager:
container_name: nginx-proxy-manager
image: jc21/nginx-proxy-manager:latest
restart: unless-stopped
ports:
- "10080:80" # HTTP (inkl. Let's Encrypt HTTP-01 challenge) router forwarder 80→10080
- "10443:443" # HTTPS router forwarder 443→10443
- "10.0.0.142:81:81" # Admin UI kun tilgængeligt fra LAN
volumes:
- ${DOCKER_ROOT:-/volume1/docker}/nginx-proxy-manager/data:/data
- ${DOCKER_ROOT:-/volume1/docker}/nginx-proxy-manager/letsencrypt:/etc/letsencrypt
extra_hosts:
- "host.docker.internal:host-gateway" # Gør det muligt at proxye til HA på host-netværket
healthcheck:
test: ["CMD", "/usr/bin/check-health"]
interval: 10s
timeout: 3s
retries: 3
start_period: 20s
gitea: gitea:
container_name: gitea container_name: gitea
image: gitea/gitea:${GITEA_IMAGE_TAG:-latest} image: gitea/gitea:${GITEA_IMAGE_TAG:-latest}
+45 -27
View File
@@ -1,30 +1,48 @@
# TODO - Pending Tasks # TODO - Pending Tasks
## Home Assistant - Åbne opgaver
### HA-fejl der skal fikses
- [x] **Mealie shopping merge timeout** — udgået, merge med Google Keep droppes. Shopping-liste køres direkte i Mealie hver onsdag morgen.
- [x] **aiohttp 400 Bad Request fra ekstern IP** — Løst: Nginx Proxy Manager sat op som HTTPS reverse proxy (`anneclaus.duckdns.org`). Let's Encrypt cert udstedt, Force HTTPS aktiveret. Port 8123 lukket på routeren. HA tilgås nu udelukkende via HTTPS på port 443.
- [x] **switch.home_charging mangler** — Bilen oplades fint (16. maj 2026). Ikke et reelt problem.
- [x] **climate.badevarelse** — Danfoss Ally TRV monteret og online (7. maj 2026).
- [x] **Husqvarna Automower BLE — genopsæt parring** — Kørte fint. Problemet var at telefon-app'en kørte i baggrunden og holdt BLE-forbindelsen, selvom klipperen var slettet fra appen.
- [x] **Google AI MAX_TOKENS i AI-indkørsel automation** — Ingen fejl observeret i loggen. Fjernet fra aktiv liste.
### HA - Kendte ikke-fejl (ingen handling nødvendig)
- `husqvarna_automower_ble` BLE fejl — normalt når plæneklipperen klipper (men se auth fail-fejl ovenfor)
- `light.spejl1/spejl2` mangler — strøm slukket på kontakt, OK
## Sikring af port forwards via NPM + subdomæner
**Baggrund:** Diverse tjenester er direkte eksponeret via port-forwards på routeren uden kryptering. Disse bør flyttes bag NPM med HTTPS og subdomæner under `anneclaus.dk`.
**Tjenester fra routeren (per 17. maj 2026):**
| Tjeneste | WAN-port | Intern port | Forslag til subdomain |
|---|---|---|---|
| Mealie | 9925 | 9925 | `mealie.anneclaus.dk` |
| Plex | 32400 | 32400 | `plex.anneclaus.dk` (Plex har egen HTTPS — lavere prioritet) |
| Synology DSM | 5000/5001 | 5000/5001 | `nas.anneclaus.dk` |
| Unifi | 8443 | 8443 | (Unifi har egen HTTPS — lavere prioritet) |
| FTP | 21 | 21 | FTP er usikkert — overvej om den skal lukkes |
| MQTT | 1883 | 1883 | MQTT i klartekst — høj risiko |
| SSH | 2222 | 2222 | SSH er krypteret, men tiltrækker brute-force |
**For hver tjeneste der flyttes:**
1. Tilføj CNAME-record hos One.com: `<subdomain>``anneclaus.duckdns.org`
2. Opret proxy host i NPM med SSL-cert og Force HTTPS
3. Luk den direkte port-forward på routeren
- [x] **MQTT port 1883** — lukket på routeren (17. maj 2026). MQTT bruges kun internt af Aqara-knapper til ringklokke — ingen ekstern adgang nødvendig.
- [x] **FTP port 21** — lukket på routeren (17. maj 2026).
- [x] **Mealie**`mealie.anneclaus.dk` via NPM med HTTPS (18. maj 2026). Port 9925 lukket på routeren.
- [x] **Synology DSM**`nas.anneclaus.dk` via NPM med HTTPS (19. maj 2026). Port 5000/5001 lukket på routeren.
- [ ] **SSH port 2222** — overvej at begrænse til nøgle-login og deaktivere password-login
- [~] **Plex** — springer over. Plex krypterer selv sin trafik, og alle klientenheder (telefoner, iPad, Apple TV) skulle opdateres manuelt. Ikke umagen værd.
- [ ] **Unifi** — lavere prioritet, Unifi har allerede HTTPS
---
## Gitea External HTTPS Access ## Gitea External HTTPS Access
**Status:** Partial - content accessible but SSL certificate warning - [x] **gitea.anneclaus.dk via NPM** — CNAME oprettet hos One.com, NPM proxy host med Let's Encrypt + Force SSL, `.env.infrastructure` opdateret, container genstartet. HTTPS 200 OK (20. maj 2026).
### Task 1: SSL Certificate Binding
- [ ] Access Synology DSM Control Panel
- [ ] Navigate to Security → Certificate
- [ ] Bind the SSL certificate to `gitea.anneclaus.synology.me` in the reverse proxy configuration
- [ ] If certificate missing: Obtain new Let's Encrypt certificate for the hostname
- [ ] Test: Verify no SSL warning when accessing https://gitea.anneclaus.synology.me/
### Task 2: Update Gitea Configuration for External URL
- [ ] Edit `.env.infrastructure` file
- [ ] Update the following variables:
```
GITEA_DOMAIN=gitea.anneclaus.synology.me
GITEA_ROOT_URL=https://gitea.anneclaus.synology.me/
GITEA_SSH_DOMAIN=gitea.anneclaus.synology.me
```
- [ ] Restart Gitea container:
```bash
docker compose --env-file .env.infrastructure -f docker-compose.infrastructure.yml up -d gitea
```
- [ ] Test: Verify Git clone links show external URL
### Notes
- DNS routing and reverse proxy already working (content is accessible)
- Only certificate binding and Gitea configuration update remaining
- These changes will enable full external functionality for Git operations
+85
View File
@@ -0,0 +1,85 @@
# Procedure for fuld netværksgenstart
Brug denne procedure ved fejlfinding, strømafbrydelse eller planlagt vedligehold.
Formål: sikre at alle enheder starter i korrekt rækkefølge og at Home Assistant
kan forbinde til alle integrationer ved opstart.
---
## NEDLUKNING
Luk i denne rækkefølge — afhængige enheder lukkes **før** infrastruktur.
1. Luk NAS (Synology) ned via DSM eller SSH
2. Sluk Sonos-enheder i alle rum og tag stikket ud
3. Sluk PlayStation 5
4. Sluk printer og Sonos S1 på kontakten
5. Luk Mac mini og gamer-PC ned
6. Sluk Ubiquiti Access Points (kontor og stue)
7. Sluk Roth Touchline controller
8. Sluk gardin-controller (Hunter Douglas hub)
9. Sluk Denon forstærker
10. Sluk Google Nest Mini
11. Sluk Netatmo central enhed
12. Sluk Hue bridge
13. Sluk Ubiquiti switch
14. Sluk router
15. Sluk bredbåndsmodem (fiber)
---
## OPSTART
Start i denne rækkefølge — infrastruktur **før** afhængige enheder.
### Netværk (fundament)
1. **Bredbåndsmodem** — vent **5 min** til synkronisering
2. **Router** — vent **5 min**
3. **Ubiquiti switch** — vent **2 min**
4. **Ubiquiti Access Points** (stue og kontor) — vent **2 min**
### Enheder HA afhænger af ved opstart — tænd ALLE før NAS
5. **Hue bridge**
6. **Roth Touchline controller**
7. **Gardin-controller** (Hunter Douglas hub)
8. **Netatmo central enhed**
9. **Sonos Port** (central Sonos-enhed i stuen) — vent til den er online
10. **Sonos-enheder** — tænd én ad gangen, startende tættest på Sonos Port
11. **Denon receiver**
12. **Google Nest Mini** *(smart speaker/Google Assistant)*
13. **Printer og Sonos S1**
### NAS og Home Assistant
14. **NAS (Synology)** — tænd og vent **10 min**
- Home Assistant Docker-container starter automatisk
- HA bruger 35 min på at initialisere alle integrationer
- Tjek at HA er oppe: åbn `http://homeassistant.local:8123`
### Verificer Home Assistant
15. Tjek HA-loggen for fejl:
- Gå til **Indstillinger → System → Log** i HA
- Forventede (acceptable) fejl ved opstart:
- `husqvarna_automower_ble` — plæneklipper ikke i paringstilstand (normalt)
- `cover.terrasse_dor` / `cover.hojre` — Hunter Douglas timing (forsvinder efter et par min)
- `light.spejl1`, `light.spejl2` — kun tilgængelige når manuel kontakt er tændt
- Fejl der **kræver handling**:
- `Can not write request body for https://10.0.0.154` → Hue bridge reagerer ikke, genstartden igen
- `touchline` timeout hvert minut → Touchline CGI API stadig nede, prøv genstart af Touchline
### Øvrige enheder (ikke styret af HA)
16. **Mac mini, PS5, gamer-PC**
---
## KENDTE PROBLEMER OG STATUS (maj 2026)
| Integration | Problem | Løsning |
|---|---|---|
| Roth Touchline | CGI API (`/cgi-bin/ILRReadValues.cgi`) returnerer HTTP 000 | Netværksgenstart hjælper typisk |
| Hue bridge (10.0.0.154) | "Can not write request body" ved RAM-udtømning | HA-restart eller Hue-genstart |
| Zigbee USB | Periodisk `NcpFailure: ERROR_EXCEEDED_MAXIMUM_ACK_TIMEOUT_COUNT` | Acceptabelt, genoprettes automatisk |
| RAM | 3.7 GiB total — RAM-opgradering bestilt | Installer ny RAM når den ankommer |
---
*Opdateret: maj 2026*
+73 -10
View File
@@ -1,6 +1,25 @@
# Ønskeliste Nyt udstyr til Home Assistant # Ønskeliste Nyt udstyr til Home Assistant
*Sidst opdateret: april 2026* *Sidst opdateret: maj 2026*
---
## Netværk / UniFi
### IoT VLAN-segmentering (trin 4 fra UniFi check 10. maj 2026)
**Formål:** Isolere IoT-enheder fra primært hjemmenetværk af sikkerhedshensyn.
**Plan:**
- Nyt netværk: `IoT` med VLAN 20, subnet `192.168.20.0/24`
- Genaktivér eksisterende `sonoff`-SSID og tilknyt til IoT-netværk
- Firewall-regler:
- IoT → LAN: **BLOKERET**
- Home Assistant → IoT: **TILLADT** (nødvendigt for styring)
- IoT → Internet: **TILLADT**
- Enheder der skal flyttes til IoT-netværk: Sonoff, Zaptec, Tesla-lader, Wavin, øvrige IoT
**Kræver:** Manuel re-tilslutning af WiFi-enheder til nyt SSID. Kablede enheder tildeles VLAN via switch-port.
--- ---
@@ -10,7 +29,7 @@
| Antal | Rum | Beskrivelse | Status | | Antal | Rum | Beskrivelse | Status |
|---|---|---|---| |---|---|---|---|
| 1 | Badeværelse | Danfoss Ally TRV (Zigbee) | 🔧 To be fixed (kan ikke skrues ordentligt fast) | | 1 | Badeværelse | Danfoss Ally TRV (Zigbee) | ✅ Monteret og online (7. maj 2026) |
**Bekræftet ventiltype:** Danfoss RA (snap-on clips) Ally passer direkte med medfølgende RA-adapter. **Bekræftet ventiltype:** Danfoss RA (snap-on clips) Ally passer direkte med medfølgende RA-adapter.
@@ -46,22 +65,42 @@
--- ---
### Gulvvarme: Wavin bryggers + køkken Sonoff ZBMINI-L2 + temp-sensorer
| Antal | Enhed | Beskrivelse | Status |
|---|---|---|---|
| 2 | Sonoff ZBMINI-L2 | Zigbee relæ, erstatter Wavin RF-modtager | ⬜ Ønsket |
| 2 | SONOFF SNZB-02D | Zigbee temperatur/fugt sensor | ⬜ Ønsket |
**Baggrund:** Bryggers og køkken har i dag en dumb Wavin RF-modtager (JT6/3003-boksen) med to relækanaler (X = bryggers, Y = køkken) der styres af simple trådløse Wavin-termostater. Ingen smart protokol.
**Plan:** ZBMINI-L2 sættes i serie med fasen ud til aktuatoren (kanalernes brune ledning ud) inde i Wavin-boksen. Temp-sensor per rum. HA `generic_thermostat` samler dem til climate-entiteter der integreres med `script.varme_recalculate` som de øvrige rum.
**Se:** `dokumenter/wavin_sonoff_installation.md` for komplet installationsguide.
**Pris:** ~260 kr (ZBMINI) + ~200 kr (temp-sensorer) = **~460 kr total**
---
## Normal prioritet ## Normal prioritet
### Vindues-/dørsensorer Aqara ### Vindues-/dørsensorer Aqara
| Antal | Placering | Beskrivelse | Status | | Antal | Placering | Beskrivelse | Status |
|---|---|---|---| |---|---|---|---|
| 2 | Køkken | Aqara kontaktsensor (vindue) | ⬜ Ønsket | | 4 | Køkken | Aqara kontaktsensor (vindue) | ⬜ Ønsket |
| 1 | Bryggers | Aqara kontaktsensor (vindue) | ⬜ Ønsket | | 2 | Bryggers | Aqara kontaktsensor (vindue) | ⬜ Ønsket |
| 2 | Forgang | Aqara kontaktsensor (vindue/dør) | ⬜ Ønsket | | 4 | Forgang | Aqara kontaktsensor (vindue/dør) | ⬜ Ønsket |
| 1 | Stue | Aqara kontaktsensor (vindue) | ⬜ Ønsket | | 2 | Stue | Aqara kontaktsensor (vindue) | ⬜ Ønsket |
| 1 | Fordør | Aqara kontaktsensor (dør) | ⬜ Ønsket |
| 1 | Bagdør | Aqara kontaktsensor (dør) | ⬜ Ønsket |
| 1 | Kontor | Aqara kontaktsensor (vindue) | ⬜ Ønsket |
**Total:** 6 stk. Aqara Door & Window Sensor (Zigbee) **Total:** 15 stk. Aqara Door & Window Sensor (Zigbee) — 6 eksisterende + 9 nye
**Formål:** Automatisk stop af gulvvarme i rum med åbent vindue — samme logik som eksisterende sensorer i Andreas, Daniel, Soveværelse og Lille bad. Kobles direkte ind i `script.varme_recalculate` med if-betingelse per rum. **Formål:** Automatisk stop af gulvvarme i rum med åbent vindue — samme logik som eksisterende sensorer i Andreas, Daniel, Soveværelse og Lille bad. Kobles direkte ind i `script.varme_recalculate` med if-betingelse per rum.
**Bemærk:** Forgang-sensorer dækker sandsynligvis yderdøren + en vinduesramme — afklar placering inden køb. **Bemærk:** Afklar præcis placering (vinduesramme vs. dørfals) inden køb, særligt forgang og fordør/bagdør.
--- ---
@@ -69,11 +108,11 @@
| Antal | Beskrivelse | Status | | Antal | Beskrivelse | Status |
|---|---|---| |---|---|---|
| 1 stk | Crucial 4GB DDR4-2666 SODIMM — CT4G4SFS8266 | ⬜ Ønsket | | 1 stk | Crucial 16GB DDR4-2666 SODIMM — CT16G4SFD8266 | ✅ Installeret 13. maj 2026 |
**Baggrund:** OOM-kill (exit 137) 23. april 2026 — Synology løb tør for RAM og dræbte mosquitto, gitea, gitea-db og DokuWiki på samme tid. HA selv overlevede men crashede urent. **Baggrund:** OOM-kill (exit 137) 23. april 2026 — Synology løb tør for RAM og dræbte mosquitto, gitea, gitea-db og DokuWiki på samme tid. HA selv overlevede men crashede urent.
**Model:** DS920+ har 4GB loddet + 1 ledig SODIMM-slot. Crucial CT4G4SFS8266 giver 4+4=8GB total. Pris ca. 150200 kr. hos Komplett/Dustin. **Resultat:** DS920+ kører nu med 4GB (loddet) + 16GB SODIMM = **20GB total RAM**. Bekræftet i DSM Info Center. Sundhedscheck 13. maj 2026: 3,4GB brugt af 19GB tilgængeligt, swap-brug 0B — ingen memory-pressure. Alle 8 Docker containere kører healthy.
--- ---
@@ -98,6 +137,30 @@
| Luftkvalitetssensor | VOC / PM2.5 | Udvidelse af eksisterende CO₂-måling | | Luftkvalitetssensor | VOC / PM2.5 | Udvidelse af eksisterende CO₂-måling |
| Energimåler (CT-clamp) | Realtids strømmåling pr. kredsløb | Supplement til Eloverblik | | Energimåler (CT-clamp) | Realtids strømmåling pr. kredsløb | Supplement til Eloverblik |
---
## Vildtkamera
**Krav:**
- Batteri-drevet (ingen strøm på placeringen)
- WiFi-upload — ingen SD-kortafhentning
- Integration med Home Assistant og/eller Synology NAS
**Valgt: Reolink Argus 4 Pro + Reolink solpanel**
- Batteri + solpanel → aldrig behov for genopladning
- WiFi 6 — upload direkte til NAS/cloud uden SD-kort
- Officiel HA-integration (bevægelses-`binary_sensor` + `camera`-entitet med live stream og snapshot)
- Virker med Synology Surveillance Station via RTSP
- Farve-natvisning (spotlight)
| Enhed | Antal | Status |
|---|---|---|
| Reolink Argus 4 Pro | 1 | ⬜ Ønsket |
| Reolink solpanel (kompatibelt) | 1 | ⬜ Ønsket |
--- ---
## Indkøbt ✅ ## Indkøbt ✅
+252
View File
@@ -0,0 +1,252 @@
# Gulvvarme: Wavin bryggers + køkken → HA styring
## Idiot-sikker installationsguide
**Formål:** Erstatte den dumme RF-modtager (Wavin JT6/3003-boksen) med to Sonoff ZBMINI Zigbee-relæer,
så Home Assistant kan styre bryggers og køkken-gulvvarme præcis som de andre rum.
---
## Del 1: Indkøb
| Vare | Antal | Pris ca. | Link/søg |
|------|-------|----------|----------|
| **Sonoff ZBMINI-L2** (Zigbee relæ, ingen nul-ledning) | 2 | ~130 kr/stk | Aliexpress, Elgiganten |
| **SONOFF SNZB-02D** Zigbee temp/fugt sensor | 2 | ~100 kr/stk | Aliexpress |
> **Vigtigt:** Vælg ZBMINI-**L2** (eller ZBMINI Extreme) den kræver **ikke** en nuleder (N).
> Wavin-boksen har måske ikke nuleder fremme til brug for et relæ.
---
## Del 2: Forståelse af Wavin-boksen
Når du kigger på det grønne printplade med låget af:
```
MAINS IND (fra stikkontakt i væggen):
Brun = FASE (L) "det farlige"
Blå = NUL (N)
KANAL X (til aktuator 1, fx bryggers):
Brun = FASE UD til aktuator
KANAL Y (til aktuator 2, fx køkken):
Brun = FASE UD til aktuator
Aktuatorerne får NUL fra boksen via blå ledning.
```
Boksen virker som et simpelt on/off relæ per kanal:
- Når termostaten sender "varm op" → relæet lukker → 230V fase sendes ud til aktuatoren → ventil åbner
- Sonoff ZBMINI erstatter præcis dette relæ
---
## Del 3: Installation trin for trin
### ⚠️ STOP Sluk strøm FØR du rører noget
1. Find den sikring eller kontakt der forsyner Wavin-boksen
2. Sluk den
3. Brug en spændingsprøver/-tester på de brune ledninger inde i boksen bekræft at der er 0V
---
### Trin 1: Fotografér ledningerne i boksen FØR du piller noget
Tag et billede med din telefon. Du vil gerne huske hvad der sidder hvor.
---
### Trin 2: Identificér de 4 relevante ledninger
I Wavin-boksen sidder:
- **Brun ind** = Fase fra væggen (fælles for begge kanaler)
- **Blå ind** = Nul fra væggen (fælles)
- **Brun ud X** = Fase ud til aktuator bryggers
- **Brun ud Y** = Fase ud til aktuator køkken
(De blå ledninger der går ud er nuleder direkte til aktuatorerne de ændres ikke)
---
### Trin 3: Monter Sonoff ZBMINI-L2 nr. 1 (bryggers)
ZBMINI-L2 har disse klemmer:
```
[ L in ] [ L out ] [ S1 ] [ S2 ]
```
Tilslut:
- **L in** ← Brun fase ind fra væggen (eller tag en aftapning fra eksisterende brun)
- **L out** → Brun fase ud til bryggers-aktuatoren (den ledning der tidligere sad i X-relæet)
- **S1/S2** = bruges kun hvis du vil have en fysisk kontakt lad dem sidde tomme
Sonoff ZBMINI-L2 kræver ikke N (nuleder) det er pointen med L2-modellen.
---
### Trin 4: Monter Sonoff ZBMINI-L2 nr. 2 (køkken)
Identisk som trin 3, men brug Y-kanalens udgang:
- **L in** ← Brun fase ind (kan sidde på samme aftapning som nr. 1)
- **L out** → Brun fase ud til køkken-aktuatoren
---
### Trin 5: Wavin RF-modtagerboksen
Den eksisterende boks kobles nu **forbi** dens relæer bruges ikke længere.
Du kan enten:
- Efterlade den hængende (ufarlig, bare strøm ind og tomme udgange)
- Klippe strømmen til den (tag brun og blå ind ud af klemmerne og tape enderne)
Den gamle Wavin termostat på væggen virker stadig men gør intet du kan efterlade den eller tage den ned.
---
### Trin 6: Gendan strøm og test
1. Sæt strøm til igen
2. Begge Sonoff-enheder bør lyse rødt (venter på pairing)
---
## Del 4: Zigbee-pairing i Home Assistant
1. Gå til **Indstillinger → Enheder → Zigbee2MQTT** (eller ZHA hvis du bruger det)
2. Klik **Tillad tilslutning / Permit join** (60 sekunder)
3. Hold knappen på Sonoff ZBMINI nede i 5 sekunder til LED blinker hurtigt
4. Enheden dukker op navngiv den `bryggers_relæ` og `kokken_relæ`
5. Gentag for temp-sensorerne (tryk lille knap på siden for at parre)
---
## Del 5: Home Assistant konfiguration
### 5a: generic_thermostat (climate entity)
Tilføj til `configuration.yaml` (eller en inkluderet fil):
```yaml
climate:
- platform: generic_thermostat
name: Bryggers
unique_id: generic_thermostat_bryggers
heater: switch.bryggers_relae # Sonoff enhedens switch entity
target_sensor: sensor.bryggers_temp_sensor_temperature
min_temp: 15
max_temp: 28
target_temp: 20
cold_tolerance: 0.3
hot_tolerance: 0.3
min_cycle_duration:
minutes: 5
ac_mode: false
- platform: generic_thermostat
name: Køkken
unique_id: generic_thermostat_kokken
heater: switch.kokken_relae
target_sensor: sensor.kokken_temp_sensor_temperature
min_temp: 15
max_temp: 28
target_temp: 20
cold_tolerance: 0.3
hot_tolerance: 0.3
min_cycle_duration:
minutes: 5
ac_mode: false
```
> Tilpas entity-navnene til hvad Zigbee2MQTT faktisk kalder dem efter pairing.
### 5b: input_number til komforttemperaturer
Tilføj til `include/input/number/varme.yaml`:
```yaml
varme_komfort_bryggers:
name: Komfort - Bryggers
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 20
icon: mdi:thermometer
varme_komfort_kokken:
name: Komfort - Køkken
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 20
icon: mdi:thermometer
```
### 5c: Tilføj til varme_recalculate scriptet
De to nye rum skal med i `include/scripts/varme_styring.yaml``varme_recalculate`
på samme måde som badeværelse og stue (Danfoss Ally-mønsteret):
```yaml
# ---- Bryggers generic_thermostat ----
- if:
- condition: template
value_template: "{{ true }}" # ingen vinduessensor endnu
then:
- service: climate.set_temperature
target:
entity_id: climate.bryggers
data:
hvac_mode: heat
temperature: >
{% set k = states('input_number.varme_komfort_bryggers') | float(20) %}
{% if vacation %} {{ ferie_temp }}
{% elif night %} {{ [k - nat_sænk, 15] | max }}
{% elif not home %} {{ [k - vaek_sænk, 15] | max }}
{% else %} {{ k }}
{% endif %}
# ---- Køkken generic_thermostat ----
- if:
- condition: template
value_template: "{{ true }}"
then:
- service: climate.set_temperature
target:
entity_id: climate.kokken
data:
hvac_mode: heat
temperature: >
{% set k = states('input_number.varme_komfort_kokken') | float(20) %}
{% if vacation %} {{ ferie_temp }}
{% elif night %} {{ [k - nat_sænk, 15] | max }}
{% elif not home %} {{ [k - vaek_sænk, 15] | max }}
{% else %} {{ k }}
{% endif %}
```
---
## Del 6: Verificering
Når alt er sat op:
1. Gå til **Udviklerværktøjer → Tjenester**
2. Kald `climate.set_temperature``climate.bryggers` med `temperature: 25`
3. Lyt efter at aktuatoren klikker (kan høres eller mærkes) inden for 1-2 minutter
4. Sæt tilbage til normal komforttemperatur
---
## Resumé: Hvad du køber
| | |
|---|---|
| 2× Sonoff ZBMINI-L2 | ~260 kr |
| 2× Sonoff SNZB-02D temp-sensor | ~200 kr |
| **Total** | **~460 kr** |
Ingen elektriker, ingen nye kabler til aktuatorerne, ingen cloud-afhængighed.
+91
View File
@@ -0,0 +1,91 @@
# Zigbee husplan og netværksnoter
## Fysisk layout
```
NORD/INDKØRSEL-SIDE
══════════════════════════════════════════════════════════════════
GARAGE
[stik_fryser] ←── fjern ende (mod nord)
[garageport sensor] [stik_indkørsel] ←── tæt ende (mod kontor/syd)
══════════════ GARAGEMUR (beton - dæmper signal markant) ══════════
[stik_bryggers] [køkken] [forgang] [stik_lillebad] [stik_kontor]
↑ indkørslen løber langs denne side (bryggers → køkken → forgang → lille bad → badeværelse → kontor)
══════════════════════════════════════════════════════════════════
[badeværelse] [stik_bad]
[stik_sonos_stue] [stik_quooker] [stue]
[stik_daniel] (Daniels værelse - nabo til Andreas og over for bad)
[stik_soveværelse] (nabo til Daniel og kontor)
[stik_andreas] (Andreas værelse)
[stik_alrum]
SYD/STUE-SIDE
KOORDINATOR: Sonoff ZBDongle-E sidder på loftet over stue/Andreas-siden
```
## Zigbee enheder
| Enhed | Type | Placering |
|-------|------|-----------|
| SONOFF ZBDongle-E | Coordinator | Loft over stue/Andreas |
| stik_indkørsel | Router (TS011F) | Garage, tæt ende mod kontor |
| stik_fryser | Router (TS011F) | Garage, fjern ende mod nord |
| stik_kontor | Router (TS011F) | Kontor |
| stik_bryggers | Router (TS011F) | Bryggers |
| stik_lillebad | Router (TS011F) | Lille bad |
| stik_bad | Router (TS011F) | Badeværelse |
| stik_daniel | Router (TS011F) | Daniels værelse |
| stik_soveværelse | Router (TS011F) | Soveværelse |
| stik_andreas | Router (TS011F) | Andreas' værelse |
| stik_alrum | Router (TS011F) | Alrum |
| stik_sonos_stue | Router (TS011F) | Stue (Sonos) |
| stik_quooker | Router (TS011F) | Køkken (Quooker) |
| garageport | EndDevice (3RDTS01056Z) | Garage, tæt ende - tiltssensor på garageport |
| badevarelse | EndDevice | Badeværelse |
| stue | EndDevice | Stue |
| temp_bryggers | EndDevice | Bryggers (temperatursensor) |
| temp_køkken | EndDevice | Køkken (temperatursensor) |
| LUMI magnetsensorer (×6) | EndDevice | Spredt i huset |
## LQI-målinger over tid
| Enhed | 18/5 (morgen) | 18/5 (aften) | 20/5 | Bemærkning |
|-------|--------------|-------------|------|------------|
| stik_alrum | 184192 | 188192 | 184 | Stærk, tæt på koordinator |
| stik_andreas | 184192 | 188192 | 184 | Stærk, tæt på koordinator |
| stik_soveværelse | 76192 | 76188 | 84 | Svingende — route-afhængig |
| stik_quooker | 144152 | 124148 | 144 | God |
| stik_sonos_stue | 108152 | 108152 | 148 | God |
| stik_lillebad | 112136 | 112136 | 144 | OKGod |
| stik_daniel | — | 100120 | 92 | OK |
| stik_bryggers | 100132 | 100132 | 108 | OK |
| stik_bad | 40104 | 96144 | 144 | Forbedret |
| stik_kontor | 5296 | 7296 | 96 | Forbedret |
| stik_fryser | 5684 | 5684 | 84 | Forbedret |
| stik_indkørsel | 4480 | 56124 | 84 | OK efter genstart |
| garageport sensor | 4092 | 5292 | 84 | Bedste måling! |
| badevarelse | 108136 | 108136 | 144 | God |
| stue | 84116 | 84116 | 112 | OK |
| temp_bryggers | — | — | 148 | Ny 20/5 |
| temp_køkken | — | — | 152 | Ny 20/5 |
## Kendte problemer
- **Garagemuren** dæmper signalet markant — alle enheder bag muren har LQI 4080
- **Garageport-sensoren** er tæt på grænsen og har tidligere været unavailable
- Koordinatoren sidder i den modsatte ende af huset fra garagen
## Optimeringforslag
1. **Flyt koordinatoren til midten** (forgang/køkken-loftet) — størst effekt, kræver blot USB-forlænger
2. **Tilsæt router i forgang/gang** tæt på garagemur — bedre mellemled til garagen
3. stik_fryser er acceptabelt svag hvis fryseren blot er til strømmåling
## Entiteter der styrer indkørselslys
- `switch.stik_indkorsel` — Zigbee plug (erstattede `light.indkorsel_plug` fra Hue, maj 2026)
- `light.indkorsel_2` — Hue gruppe (garage venstre + højre + bryggersdør)
- `scene.indkorsel_bright` / `scene.indkorsel_dimmed` — Hue scener
- Automationer: `lysindkorsel.yaml`, `presence_simulation.yaml`, `andreas_kommer_hjem_taend_lys.yaml`
-28
View File
@@ -214,31 +214,3 @@
- service: homeassistant.turn_off - service: homeassistant.turn_off
entity_id: switch.sonos_alarm_298 entity_id: switch.sonos_alarm_298
- alias: 'Turn on alarms Badeværelse Afsted'
trigger:
platform: time
at: '20:07:10'
condition:
- condition: state
entity_id: binary_sensor.arbejdsdagimorgen
state: 'on'
- condition: template
value_template: >-
{{ not is_state('input_select.anne_status', 'syg') and
not is_state('input_select.claus_status', 'syg') }}
action:
- service: homeassistant.turn_on
entity_id: switch.sonos_alarm_1899
- alias: 'Turn off alarms Badeværelse Afsted'
trigger:
platform: time
at: '20:06:20'
condition:
- condition: state
entity_id: binary_sensor.arbejdsdagimorgen
state: 'off'
action:
- service: homeassistant.turn_off
entity_id: switch.sonos_alarm_1899
@@ -29,10 +29,13 @@
action: action:
- variables: - variables:
lights: lights: >
- light.indkorsel_2 {% set base = ['light.indkorsel_2', 'light.garage'] %}
- light.extended_color_light_1 {% if now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 %}
- light.garage {{ (base + ['light.extended_color_light_1']) | list }}
{% else %}
{{ base }}
{% endif %}
lights_to_turn_on: > lights_to_turn_on: >
{{ lights | select('is_state','off') | list }} {{ lights | select('is_state','off') | list }}
@@ -45,9 +48,17 @@
target: target:
entity_id: "{{ lights_to_turn_on }}" entity_id: "{{ lights_to_turn_on }}"
- service: switch.turn_on
data:
entity_id: switch.stik_indkorsel
- delay: "00:10:00" - delay: "00:10:00"
- service: light.turn_off - service: light.turn_off
target: target:
entity_id: "{{ lights_to_turn_on }}" entity_id: "{{ lights_to_turn_on }}"
- service: switch.turn_off
data:
entity_id: switch.stik_indkorsel
+1
View File
@@ -14,6 +14,7 @@
data: data:
group_members: group_members:
- media_player.badevaerelse - media_player.badevaerelse
- media_player.lille_badevaerelse
- media_player.sovevaerelse - media_player.sovevaerelse
- media_player.stue - media_player.stue
- media_player.alrum - media_player.alrum
+10
View File
@@ -10,6 +10,8 @@
- condition: state # from sunset until sunrise - condition: state # from sunset until sunrise
entity_id: sun.sun entity_id: sun.sun
state: 'below_horizon' state: 'below_horizon'
- condition: template # Vintersæson uge 42-8
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
action: action:
- service: light.turn_on - service: light.turn_on
data: data:
@@ -19,6 +21,9 @@
trigger: trigger:
platform: sun platform: sun
event: sunrise event: sunrise
condition:
- condition: template # Vintersæson uge 42-8
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
action: action:
- service: light.turn_off - service: light.turn_off
data: data:
@@ -31,6 +36,8 @@
condition: condition:
- condition: time - condition: time
before: '21:30:00' before: '21:30:00'
- condition: template # Vintersæson uge 42-8
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
action: action:
- service: light.turn_on - service: light.turn_on
data: data:
@@ -40,6 +47,9 @@
trigger: trigger:
platform: time platform: time
at: "22:00:00" at: "22:00:00"
condition:
- condition: template # Vintersæson uge 42-8
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
action: action:
- service: light.turn_off - service: light.turn_off
data: data:
+112 -2
View File
@@ -1,3 +1,27 @@
- id: badevaerelse_startup_sluk
alias: Badeværelse lys sluk ved HA opstart
description: >
Slukker badeværelsets lys ved genstart hvis bevægelsessensoren er inaktiv.
Sikrer mod lys der sidder tændt efter strømudfald eller HA-genstart.
mode: single
trigger:
- platform: homeassistant
event: start
action:
- delay:
seconds: 30
- condition: state
entity_id: binary_sensor.badevaerelse_bevaegelse
state: "off"
- service: light.turn_off
target:
area_id: badevaerelse
- service: input_boolean.turn_off
target:
entity_id: input_boolean.badevaerelse_manuel_tilstand
- id: badevaerelse_motion_lys - id: badevaerelse_motion_lys
alias: Badeværelse lys via bevægelse alias: Badeværelse lys via bevægelse
mode: restart mode: restart
@@ -7,6 +31,12 @@
entity_id: binary_sensor.badevaerelse_bevaegelse entity_id: binary_sensor.badevaerelse_bevaegelse
to: "on" to: "on"
condition:
# Spring over hvis manuel tilstand er aktiv Hue-knap styrer lyset
- condition: state
entity_id: input_boolean.badevaerelse_manuel_tilstand
state: "off"
action: action:
- choose: - choose:
# Arbejdsdag dagtid (06:0022:00) # Arbejdsdag dagtid (06:0022:00)
@@ -62,11 +92,91 @@
action: action:
- delay: - delay:
minutes: > minutes: >
{{ states('input_number.badevaerelse_timeout_day') | int if is_dag {% if is_state('input_boolean.badevaerelse_manuel_tilstand', 'on') %}
else states('input_number.badevaerelse_timeout_night') | int }} 10
{% elif is_dag %}
{{ states('input_number.badevaerelse_timeout_day') | int }}
{% else %}
{{ states('input_number.badevaerelse_timeout_night') | int }}
{% endif %}
- condition: state - condition: state
entity_id: binary_sensor.badevaerelse_bevaegelse entity_id: binary_sensor.badevaerelse_bevaegelse
state: "off" state: "off"
- service: light.turn_off - service: light.turn_off
target: target:
area_id: badevaerelse area_id: badevaerelse
- service: input_boolean.turn_off
target:
entity_id: input_boolean.badevaerelse_manuel_tilstand
- id: badevaerelse_hue_knap
alias: Badeværelse Hue knap
description: >
Hue Tap Switch sætter manuel tilstand og tænder valgt scene.
Bevægelses-automatik springes over så længe manuel tilstand er aktiv.
Knap 4 slukker lyset og nulstiller til automatisk styring.
mode: restart
trigger:
- platform: state
entity_id: event.hue_tap_switch_1_button_1
id: knap_1
- platform: state
entity_id: event.hue_tap_switch_1_button_2
id: knap_2
- platform: state
entity_id: event.hue_tap_switch_1_button_3
id: knap_3
- platform: state
entity_id: event.hue_tap_switch_1_button_4
id: knap_4
action:
- choose:
# Knap 1 Nat/dæmpet lys
- conditions:
- condition: trigger
id: knap_1
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.badevaerelse_manuel_tilstand
- service: scene.turn_on
target:
entity_id: scene.badevaerelse_nat_2_lys
# Knap 2 Fuld lys (klar til brug)
- conditions:
- condition: trigger
id: knap_2
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.badevaerelse_manuel_tilstand
- service: scene.turn_on
target:
entity_id: scene.badevaerelse_klar
# Knap 3 Blomstrende forår (medium)
- conditions:
- condition: trigger
id: knap_3
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.badevaerelse_manuel_tilstand
- service: scene.turn_on
target:
entity_id: scene.badevaerelse_blomstrende_forar
# Knap 4 Sluk lys + nulstil til automatisk
- conditions:
- condition: trigger
id: knap_4
sequence:
- service: input_boolean.turn_off
target:
entity_id: input_boolean.badevaerelse_manuel_tilstand
- service: light.turn_off
target:
area_id: badevaerelse
@@ -173,6 +173,9 @@
- condition: time - condition: time
after: '06:30:00' after: '06:30:00'
before: '22:00:00' before: '22:00:00'
- condition: state
entity_id: script.godnat_sovevaerelse
state: 'off'
action: action:
- service: homeassistant.turn_off - service: homeassistant.turn_off
data: data:
+31 -5
View File
@@ -24,7 +24,8 @@
{% set t = now().strftime('%H%M') | int %} {% set t = now().strftime('%H%M') | int %}
{% if 600 <= t < 1600 %}morgen {% if 600 <= t < 1600 %}morgen
{% elif 1600 <= t < 1900 %}eftermiddag {% elif 1600 <= t < 1900 %}eftermiddag
{% elif 1900 <= t %}aften {% elif 1900 <= t < 2100 %}aften_lys
{% elif 2100 <= t %}aften
{% else %}nat{% endif %} {% else %}nat{% endif %}
timeout_min: > timeout_min: >
{% set t = now().strftime('%H%M') | int %} {% set t = now().strftime('%H%M') | int %}
@@ -32,7 +33,9 @@
{{ states('input_number.stue_timeout_morgen') | int }} {{ states('input_number.stue_timeout_morgen') | int }}
{% elif 1600 <= t < 1900 %} {% elif 1600 <= t < 1900 %}
{{ states('input_number.stue_timeout_eftermiddag') | int }} {{ states('input_number.stue_timeout_eftermiddag') | int }}
{% elif 1900 <= t %} {% elif 1900 <= t < 2100 %}
{{ states('input_number.stue_timeout_aften') | int }}
{% elif 2100 <= t %}
{{ states('input_number.stue_timeout_aften') | int }} {{ states('input_number.stue_timeout_aften') | int }}
{% else %} {% else %}
{{ states('input_number.stue_timeout_nat') | int }} {{ states('input_number.stue_timeout_nat') | int }}
@@ -50,6 +53,15 @@
{{ states('sensor.stue_belysningsstyrke') | int < lux_limit }} {{ states('sensor.stue_belysningsstyrke') | int < lux_limit }}
sequence: sequence:
- choose: - choose:
# Gæster: altid Annes favorit uanset tidspunkt
- conditions:
- condition: state
entity_id: input_boolean.gaester
state: "on"
sequence:
- service: scene.turn_on
target:
entity_id: scene.stue_annes_favorit
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ dagperiode == 'morgen' }}" value_template: "{{ dagperiode == 'morgen' }}"
@@ -64,6 +76,20 @@
- service: scene.turn_on - service: scene.turn_on
target: target:
entity_id: scene.stue_annes_favorit entity_id: scene.stue_annes_favorit
- conditions:
- condition: template
value_template: "{{ dagperiode == 'aften_lys' }}"
sequence:
- service: scene.turn_on
target:
entity_id: scene.stue_annes_favorit
- conditions:
- condition: template
value_template: "{{ dagperiode == 'aften' }}"
sequence:
- service: scene.turn_on
target:
entity_id: scene.stue_annes_favorit
default: default:
- service: scene.turn_on - service: scene.turn_on
target: target:
@@ -76,7 +102,7 @@
id: motion_off id: motion_off
- condition: template - condition: template
value_template: > value_template: >
{{ dagperiode != 'aften' or {{ dagperiode not in ('aften','aften_lys') or
is_state('media_player.samsung_s95ca_55_3', 'off') }} is_state('media_player.samsung_s95ca_55_3', 'off') }}
sequence: sequence:
- delay: - delay:
@@ -86,7 +112,7 @@
state: "off" state: "off"
- condition: template - condition: template
value_template: > value_template: >
{{ dagperiode != 'aften' or {{ dagperiode not in ('aften','aften_lys') or
is_state('media_player.samsung_s95ca_55_3', 'off') }} is_state('media_player.samsung_s95ca_55_3', 'off') }}
- service: light.turn_off - service: light.turn_off
target: target:
@@ -97,7 +123,7 @@
- condition: trigger - condition: trigger
id: tv_off id: tv_off
- condition: template - condition: template
value_template: "{{ dagperiode == 'aften' }}" value_template: "{{ dagperiode in ('aften','aften_lys') }}"
sequence: sequence:
- delay: - delay:
minutes: "{{ timeout_min }}" minutes: "{{ timeout_min }}"
+21 -3
View File
@@ -16,6 +16,9 @@
- service: scene.turn_on - service: scene.turn_on
data: data:
entity_id: scene.indkorsel_bright entity_id: scene.indkorsel_bright
- service: switch.turn_on
data:
entity_id: switch.stik_indkorsel
- alias: 'Sluk lys indkørsel når der er lys nok' - alias: 'Sluk lys indkørsel når der er lys nok'
trigger: trigger:
@@ -30,6 +33,9 @@
- service: light.turn_off - service: light.turn_off
data: data:
entity_id: light.indkorsel_2 entity_id: light.indkorsel_2
- service: switch.turn_off
data:
entity_id: switch.stik_indkorsel
- alias: 'Tænd lys indkørsel aften' - alias: 'Tænd lys indkørsel aften'
trigger: trigger:
@@ -43,6 +49,9 @@
- service: scene.turn_on - service: scene.turn_on
data: data:
entity_id: scene.indkorsel_bright entity_id: scene.indkorsel_bright
- service: switch.turn_on
data:
entity_id: switch.stik_indkorsel
- alias: 'Sluk lys indkørsel aften' - alias: 'Sluk lys indkørsel aften'
trigger: trigger:
@@ -52,6 +61,9 @@
- service: light.turn_off - service: light.turn_off
data: data:
entity_id: light.indkorsel_2 entity_id: light.indkorsel_2
- service: switch.turn_off
data:
entity_id: switch.stik_indkorsel
- alias: 'Tænd lys indkørsel ved bevægelse' - alias: 'Tænd lys indkørsel ved bevægelse'
@@ -78,6 +90,9 @@
- service: scene.turn_on - service: scene.turn_on
data: data:
entity_id: scene.indkorsel_bright entity_id: scene.indkorsel_bright
- service: switch.turn_on
data:
entity_id: switch.stik_indkorsel
- alias: 'Sluk lys indkørsel 15 min efter bevægelse' - alias: 'Sluk lys indkørsel 15 min efter bevægelse'
trigger: trigger:
@@ -97,8 +112,11 @@
# entity_id: sun.sun # entity_id: sun.sun
# state: below_horizon # state: below_horizon
action: action:
service: light.turn_off - service: light.turn_off
data: data:
entity_id: light.indkorsel_2 entity_id: light.indkorsel_2
- service: switch.turn_off
data:
entity_id: switch.stik_indkorsel
+13 -7
View File
@@ -19,7 +19,10 @@
entity_id: entity_id:
- light.drivhus - light.drivhus
- light.paradis - light.paradis
- light.extended_color_light_1 - condition: template
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
- service: homeassistant.turn_on
entity_id: light.extended_color_light_1
- alias: 'Sluk lys ved garage' - alias: 'Sluk lys ved garage'
trigger: trigger:
@@ -29,12 +32,15 @@
for: for:
minutes: 10 minutes: 10
action: action:
service: homeassistant.turn_off - service: homeassistant.turn_off
data: data:
entity_id: entity_id:
- light.drivhus - light.drivhus
- light.paradis - light.paradis
- light.extended_color_light_1 - condition: template
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
- service: homeassistant.turn_off
entity_id: light.extended_color_light_1
+11 -10
View File
@@ -1,13 +1,3 @@
- id: mealie_update_mealplan
alias: "Mealie opdater madplan"
trigger:
- platform: homeassistant
event: start
- platform: time_pattern
minutes: "/30"
action:
- service: shell_command.mealie_update
- id: mealie_generate_bilka_checklist_wednesday - id: mealie_generate_bilka_checklist_wednesday
alias: "Mealie indkøbsliste - onsdag morgen" alias: "Mealie indkøbsliste - onsdag morgen"
trigger: trigger:
@@ -19,3 +9,14 @@
- wed - wed
action: action:
- service: script.mealie_shopping_refresh - service: script.mealie_shopping_refresh
- id: mealie_update_mealplan
alias: "Mealie opdater madplan"
trigger:
- platform: homeassistant
event: start
- platform: time_pattern
minutes: "/30"
action:
- service: shell_command.mealie_update
+60 -11
View File
@@ -94,11 +94,14 @@
trigger: trigger:
- platform: time_pattern - platform: time_pattern
minutes: "0" minutes: "/15"
condition: condition:
- condition: template - condition: template
value_template: "{{ now().hour >= 19 and now().hour <= 21 }}" value_template: "{{ now().hour >= 19 and now().hour <= 21 }}"
- condition: state
entity_id: input_boolean.dishwasher_reminder_snoozed
state: "off"
- condition: template - condition: template
value_template: "{{ is_state('sensor.dishwasher_status_2', 'Off') }}" value_template: "{{ is_state('sensor.dishwasher_status_2', 'Off') }}"
- condition: or - condition: or
@@ -108,22 +111,68 @@
state: "off" state: "off"
- condition: template - condition: template
value_template: "{{ not is_state('binary_sensor.dishwasher_dor', 'off') }}" value_template: "{{ not is_state('binary_sensor.dishwasher_dor', 'off') }}"
- condition: template
value_template: "{{ is_state('binary_sensor.dishwasher_info_2', 'on') }}"
- condition: template
value_template: "{{ is_state('binary_sensor.dishwasher_svigt', 'on') }}"
- condition: template
value_template: "{{ states('sensor.dishwasher_salt_level') | float(999) < 20 }}"
- condition: template
value_template: "{{ states('sensor.dishwasher_rinse_aid_level') | float(999) < 20 }}"
- condition: template
value_template: "{{ states('sensor.dishwasher_powerdisk_level') | float(999) < 20 }}"
action: action:
- variables: - variables:
reminder_time: "{{ now().strftime('%d-%m %H:%M') }}" reminder_time: "{{ now().strftime('%H:%M') }}"
remote_off: "{{ is_state('binary_sensor.dishwasher_fjernbetjening', 'off') }}" remote_off: "{{ is_state('binary_sensor.dishwasher_fjernbetjening', 'off') }}"
door_not_closed: "{{ not is_state('binary_sensor.dishwasher_dor', 'off') }}" door_not_closed: "{{ not is_state('binary_sensor.dishwasher_dor', 'off') }}"
low_salt: "{{ states('sensor.dishwasher_salt_level') | float(999) < 20 }}"
low_rinse: "{{ states('sensor.dishwasher_rinse_aid_level') | float(999) < 20 }}"
low_powerdisk: "{{ states('sensor.dishwasher_powerdisk_level') | float(999) < 20 }}"
info_on: "{{ is_state('binary_sensor.dishwasher_info_2', 'on') }}"
svigt: "{{ is_state('binary_sensor.dishwasher_svigt', 'on') }}"
issue_text: > issue_text: >
{% set issues = [] %} {% set issues = [] %}
{% if remote_off %} {% if remote_off %}{% set issues = issues + ['remote control er ikke slået til'] %}{% endif %}
{% set issues = issues + ['remote control er ikke slået til'] %} {% if door_not_closed %}{% set issues = issues + ['døren er ikke lukket'] %}{% endif %}
{% endif %} {% if low_salt %}{% set issues = issues + ['salt er lavt (' ~ states('sensor.dishwasher_salt_level') ~ '%)'] %}{% endif %}
{% if door_not_closed %} {% if low_rinse %}{% set issues = issues + ['afspændingsmiddel er lavt (' ~ states('sensor.dishwasher_rinse_aid_level') ~ '%)'] %}{% endif %}
{% set issues = issues + ['døren er ikke lukket'] %} {% if low_powerdisk %}{% set issues = issues + ['powerdisk er lav (' ~ states('sensor.dishwasher_powerdisk_level') ~ '%)'] %}{% endif %}
{% endif %} {% if info_on %}{% set issues = issues + ['info-advarsel aktiv'] %}{% endif %}
{{ issues | join(' og ') }} {% if svigt %}{% set issues = issues + ['maskinsvigt'] %}{% endif %}
{{ issues | join(', ') }}
- service: notify.mobile_app_claus_iphone_15pro - service: notify.mobile_app_claus_iphone_15pro
data: data:
title: "Slå fjernbetjening til på opvaskemaskinen" title: "⚠️ Opvaskemaskine - tjek inden natkørsel"
message: "[{{ reminder_time }}] Opvaskemaskinen er planlagt til natkørsel, men {{ issue_text }}." message: "[{{ reminder_time }}] {{ issue_text | capitalize }}."
data:
actions:
- action: "DISHWASHER_DONE"
title: "✅ Gjort det"
- action: "DISHWASHER_IGNORE"
title: "🔕 Ignorer i aften"
- id: dishwasher_reminder_action_ignore
alias: Opvaskemaskine - ignorer påmindelser i aften
mode: single
trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: DISHWASHER_IGNORE
action:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.dishwasher_reminder_snoozed
- id: dishwasher_reminder_snooze_reset
alias: Opvaskemaskine - nulstil snooze ved midnat
mode: single
trigger:
- platform: time
at: "00:00:00"
action:
- service: input_boolean.turn_off
target:
entity_id: input_boolean.dishwasher_reminder_snoozed
+61 -1
View File
@@ -124,12 +124,72 @@
message: "Klipperen er sendt hjem - {{ trigger.to_state.attributes.friendly_name }} kom hjem." message: "Klipperen er sendt hjem - {{ trigger.to_state.attributes.friendly_name }} kom hjem."
- alias: 'Plæneklipper - reset manuelt startet flag' - alias: 'Plæneklipper - reset manuelt startet flag'
description: 'Nulstil manuelt-startet flag når klipperen dokker' description: 'Nulstil manuelt-startet flag når klipperen dokker efter kl. 20 (i dagtimerne håndteres genstarten af genstart-automation)'
trigger: trigger:
- platform: state - platform: state
entity_id: lawn_mower.husqvarna_automower entity_id: lawn_mower.husqvarna_automower
to: 'docked' to: 'docked'
condition:
- condition: time
after: '20:00:00'
action: action:
- service: input_boolean.turn_off - service: input_boolean.turn_off
target: target:
entity_id: input_boolean.ploeneklipper_manuelt_startet entity_id: input_boolean.ploeneklipper_manuelt_startet
- alias: 'Plæneklipper - genstart efter opladning (manuelt startet)'
description: 'Genstart klipperen 75 min efter den er dokket, hvis den er manuelt startet og det er før kl. 20'
trigger:
- platform: state
entity_id: lawn_mower.husqvarna_automower
to: 'docked'
condition:
- condition: state
entity_id: input_boolean.ploeneklipper_manuelt_startet
state: 'on'
- condition: time
before: '20:00:00'
action:
- delay: '01:15:00'
- condition: state
entity_id: input_boolean.ploeneklipper_manuelt_startet
state: 'on'
- condition: time
before: '20:00:00'
- condition: state
entity_id: lawn_mower.husqvarna_automower
state: 'docked'
- service: lawn_mower.start_mowing
target:
entity_id: lawn_mower.husqvarna_automower
- service: notify.mobile_app_claus_iphone_15pro
data:
title: "Plæneklipper"
message: "Klipperen er genstartet efter opladning."
- alias: 'Plæneklipper - stop kl. 20 ved manuel start'
description: 'Stop manuelt startet klipper kl. 20 og nulstil flag'
trigger:
- platform: time
at: '20:00:00'
condition:
- condition: state
entity_id: input_boolean.ploeneklipper_manuelt_startet
state: 'on'
action:
- choose:
- conditions:
- condition: state
entity_id: lawn_mower.husqvarna_automower
state: 'mowing'
sequence:
- service: lawn_mower.dock
target:
entity_id: lawn_mower.husqvarna_automower
- service: input_boolean.turn_off
target:
entity_id: input_boolean.ploeneklipper_manuelt_startet
- service: notify.mobile_app_claus_iphone_15pro
data:
title: "Plæneklipper"
message: "Klipperen er stoppet - kl. 20 grænse nået."
@@ -54,6 +54,10 @@
- delay: - delay:
seconds: "{{ range(30,180) | random }}" seconds: "{{ range(30,180) | random }}"
- service: switch.turn_on
data:
entity_id: switch.stik_indkorsel
- delay: - delay:
minutes: "{{ range(10,30) | random }}" minutes: "{{ range(10,30) | random }}"
@@ -66,6 +70,10 @@
- delay: - delay:
seconds: "{{ range(20,120) | random }}" seconds: "{{ range(20,120) | random }}"
- service: switch.turn_off
data:
entity_id: switch.stik_indkorsel
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
@@ -146,6 +154,10 @@
- delay: - delay:
seconds: "{{ range(20,120) | random }}" seconds: "{{ range(20,120) | random }}"
- service: switch.turn_on
data:
entity_id: switch.stik_indkorsel
- delay: - delay:
minutes: "{{ range(15,60) | random }}" minutes: "{{ range(15,60) | random }}"
@@ -158,6 +170,10 @@
- delay: - delay:
seconds: "{{ range(20,120) | random }}" seconds: "{{ range(20,120) | random }}"
- service: switch.turn_off
data:
entity_id: switch.stik_indkorsel
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
+7 -10
View File
@@ -88,13 +88,13 @@
target: target:
entity_id: button.roborock_s8_pro_ultra_kokken_bryggers entity_id: button.roborock_s8_pro_ultra_kokken_bryggers
- delay: "00:00:20" - wait_template: "{{ is_state('vacuum.roborock_s8_pro_ultra', 'cleaning') }}"
timeout: "00:02:00"
- choose: - choose:
- conditions: - conditions:
- condition: state - condition: template
entity_id: vacuum.roborock_s8_pro_ultra value_template: "{{ wait.completed }}"
state: "cleaning"
sequence: sequence:
- service: input_number.increment - service: input_number.increment
target: target:
@@ -109,17 +109,14 @@
}} min. }} min.
- conditions: - conditions:
- condition: not - condition: template
conditions: value_template: "{{ not wait.completed }}"
- condition: state
entity_id: vacuum.roborock_s8_pro_ultra
state: "cleaning"
sequence: sequence:
- service: notify.mobile_app_claus_iphone_15pro - service: notify.mobile_app_claus_iphone_15pro
data: data:
title: "⚠️ Roborock start fejlede" title: "⚠️ Roborock start fejlede"
message: > message: >
Startkommando sendt, men den begyndte ikke at køre. Startkommando sendt, men den begyndte ikke at køre inden for 2 min.
State: {{ states('vacuum.roborock_s8_pro_ultra') }}. State: {{ states('vacuum.roborock_s8_pro_ultra') }}.
Status: {{ state_attr('vacuum.roborock_s8_pro_ultra', 'status') | default('ukendt', true) }}. Status: {{ state_attr('vacuum.roborock_s8_pro_ultra', 'status') | default('ukendt', true) }}.
Error: {{ state_attr('vacuum.roborock_s8_pro_ultra', 'error') | default('ingen', true) }}. Error: {{ state_attr('vacuum.roborock_s8_pro_ultra', 'error') | default('ingen', true) }}.
@@ -0,0 +1,39 @@
- alias: 'Indkorsel: Slet gamle snapshots (behold 100)'
description: Køres via webhook fra galleriet sletter alle undtagen de 100 nyeste snapshots og regenererer galleriet.
trigger:
- platform: webhook
webhook_id: indkorsel_prune_100
allowed_methods: [POST]
local_only: true
action:
- action: shell_command.indkorsel_prune_keep_100
- delay: '00:00:02'
- action: shell_command.indkorsel_generate_gallery
mode: single
- alias: 'Snapshot ved person i indkorsel'
description: >
Gemmer et tidsstemplet snapshot + opdaterer latest.jpg + regenererer HTML-galleri,
hver gang binary_sensor.indkoersel_person skifter til 'on'.
trigger:
- platform: state
entity_id: binary_sensor.indkoersel_person
to: 'on'
condition: []
action:
- variables:
ts: "{{ now().strftime('%Y-%m-%d_%H-%M-%S') }}"
# Gem tidsstemplet kopi
- action: camera.snapshot
data:
entity_id: camera.indkoersel_sub
filename: "/config/www/snapshots/indkorsel/{{ ts }}.jpg"
# Overskriv latest.jpg (bruges af local_file-kamera i dashboardet)
- action: camera.snapshot
data:
entity_id: camera.indkoersel_sub
filename: "/config/www/snapshots/indkorsel/latest.jpg"
# Regenerer HTML-galleriet
- action: shell_command.indkorsel_generate_gallery
mode: queued
max: 5
+2 -2
View File
@@ -7,7 +7,8 @@
################################################## ##################################################
# ---- Andreas ---- # ---- Andreas ----
# Andreas har ingen Sonos-alarmer, så der er intet at slå fra/til ved sygdom.
# Motion-lys håndteres via condition i lys_andreas.yaml (tjekker andreas_status != syg).
# ---- Daniel ---- # ---- Daniel ----
@@ -60,7 +61,6 @@
entity_id: entity_id:
- switch.sonos_alarm_1782 # Soft wakeup - switch.sonos_alarm_1782 # Soft wakeup
- switch.sonos_alarm_298 # Badeværelse - switch.sonos_alarm_298 # Badeværelse
- switch.sonos_alarm_1899 # Badeværelse afsted
- alias: "Syg - Soveværelse - genaktiver alarmer" - alias: "Syg - Soveværelse - genaktiver alarmer"
id: syg_sovevaerelse_genaktiver_alarmer id: syg_sovevaerelse_genaktiver_alarmer
+2
View File
@@ -10,6 +10,8 @@
action: action:
- service: media_player.unjoin - service: media_player.unjoin
entity_id: media_player.badevaerelse entity_id: media_player.badevaerelse
- service: media_player.unjoin
entity_id: media_player.lille_badevaerelse
- service: media_player.unjoin - service: media_player.unjoin
entity_id: media_player.sovevaerelse entity_id: media_player.sovevaerelse
+11
View File
@@ -75,6 +75,17 @@
action: action:
- service: script.varme_recalculate - service: script.varme_recalculate
- alias: "Varme - Ferietilstand: Aktiver ved afrejse"
id: varme_ferie_aktiver
description: "Slår vacation_mode til automatisk på afrejsetidspunktet (vacation_start)"
trigger:
- platform: time
at: input_datetime.vacation_start
action:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.vacation_mode
- alias: "Varme - Ferieopvarmning: Start 2 dage før hjemkomst" - alias: "Varme - Ferieopvarmning: Start 2 dage før hjemkomst"
id: varme_ferie_forvarm id: varme_ferie_forvarm
description: > description: >
+1 -1
View File
@@ -9,7 +9,7 @@
id: varme_vindue_trigger id: varme_vindue_trigger
description: "Kalder varme_recalculate når et vindue eller terrassedøren skifter tilstand" description: "Kalder varme_recalculate når et vindue eller terrassedøren skifter tilstand"
mode: queued mode: queued
max: 3 max: 10
trigger: trigger:
- platform: state - platform: state
entity_id: entity_id:
+3
View File
@@ -0,0 +1,3 @@
badevaerelse_manuel_tilstand:
name: Badeværelse manuel tilstand
initial: off
+3
View File
@@ -0,0 +1,3 @@
gaester:
name: "Gæster hjemme"
icon: mdi:account-group
+4
View File
@@ -1,3 +1,7 @@
vis_alle_vedligehold: vis_alle_vedligehold:
name: Vis alle vedligehold name: Vis alle vedligehold
icon: mdi:eye-outline icon: mdi:eye-outline
dishwasher_reminder_snoozed:
name: Opvaskemaskine - påmindelser snoozed
icon: mdi:bell-sleep
+7
View File
@@ -1,5 +1,12 @@
vacation_start:
name: Vacation Start
has_date: true
has_time: true
initial: "2026-07-13 10:00:00"
vacation_end: vacation_end:
name: Vacation End name: Vacation End
has_date: true has_date: true
has_time: true has_time: true
initial: "2026-07-27 12:00:00"
+1 -1
View File
@@ -4,7 +4,7 @@ shelly_bagdor_event_cnt:
max: 99999 max: 99999
step: 1 step: 1
mode: box mode: box
initial: -1 initial: 67
shelly_fordor_event_cnt: shelly_fordor_event_cnt:
name: Shelly fordoer event count name: Shelly fordoer event count
+7 -7
View File
@@ -23,7 +23,7 @@ varme_komfort_sovevaerelse:
max: 28 max: 28
step: 0.5 step: 0.5
unit_of_measurement: "°C" unit_of_measurement: "°C"
initial: 18 initial: 20
icon: mdi:thermometer icon: mdi:thermometer
varme_komfort_kontor: varme_komfort_kontor:
@@ -32,7 +32,7 @@ varme_komfort_kontor:
max: 28 max: 28
step: 0.5 step: 0.5
unit_of_measurement: "°C" unit_of_measurement: "°C"
initial: 19 initial: 20
icon: mdi:thermometer icon: mdi:thermometer
varme_komfort_gang: varme_komfort_gang:
@@ -41,7 +41,7 @@ varme_komfort_gang:
max: 28 max: 28
step: 0.5 step: 0.5
unit_of_measurement: "°C" unit_of_measurement: "°C"
initial: 19 initial: 20
icon: mdi:thermometer icon: mdi:thermometer
varme_komfort_forgang: varme_komfort_forgang:
@@ -50,7 +50,7 @@ varme_komfort_forgang:
max: 28 max: 28
step: 0.5 step: 0.5
unit_of_measurement: "°C" unit_of_measurement: "°C"
initial: 22 initial: 24
icon: mdi:thermometer icon: mdi:thermometer
varme_komfort_lille_bad: varme_komfort_lille_bad:
@@ -59,7 +59,7 @@ varme_komfort_lille_bad:
max: 28 max: 28
step: 0.5 step: 0.5
unit_of_measurement: "°C" unit_of_measurement: "°C"
initial: 22 initial: 24
icon: mdi:thermometer icon: mdi:thermometer
varme_komfort_badevarelse: varme_komfort_badevarelse:
@@ -68,7 +68,7 @@ varme_komfort_badevarelse:
max: 28 max: 28
step: 0.5 step: 0.5
unit_of_measurement: "°C" unit_of_measurement: "°C"
initial: 20 initial: 21.5
icon: mdi:thermometer icon: mdi:thermometer
varme_komfort_stue: varme_komfort_stue:
@@ -77,7 +77,7 @@ varme_komfort_stue:
max: 28 max: 28
step: 0.5 step: 0.5
unit_of_measurement: "°C" unit_of_measurement: "°C"
initial: 24 initial: 25
icon: mdi:thermometer icon: mdi:thermometer
# Globale sænkninger # Globale sænkninger
-2
View File
@@ -105,7 +105,6 @@
name: Indkørsel name: Indkørsel
unique_id: lys_indkorsel unique_id: lys_indkorsel
entities: entities:
- light.indkorsel_plug
- light.udendors_forgang - light.udendors_forgang
- light.hue_ambiance_lamp_1_2 - light.hue_ambiance_lamp_1_2
- light.hue_ambiance_lamp_1_3 - light.hue_ambiance_lamp_1_3
@@ -116,7 +115,6 @@
unique_id: lys_udenfor unique_id: lys_udenfor
entities: entities:
- light.garage - light.garage
- light.indkorsel_plug
- light.fordoer - light.fordoer
- light.julelys - light.julelys
+24 -4
View File
@@ -4,28 +4,48 @@ godnat_sovevaerelse:
sequence: sequence:
- service: light.turn_on - service: light.turn_on
target: target:
entity_id: light.bedroom entity_id:
- light.claus
- light.anne
- light.sov_dor
- light.sov_midt
- light.sov_vindue
data: data:
brightness_pct: 40 brightness_pct: 40
transition: 300 transition: 300
- delay: "00:05:00" - delay: "00:05:00"
- service: light.turn_on - service: light.turn_on
target: target:
entity_id: light.bedroom entity_id:
- light.claus
- light.anne
- light.sov_dor
- light.sov_midt
- light.sov_vindue
data: data:
brightness_pct: 10 brightness_pct: 10
transition: 300 transition: 300
- delay: "00:05:00" - delay: "00:05:00"
- service: light.turn_on - service: light.turn_on
target: target:
entity_id: light.bedroom entity_id:
- light.claus
- light.anne
- light.sov_dor
- light.sov_midt
- light.sov_vindue
data: data:
brightness_pct: 1 brightness_pct: 1
transition: 300 transition: 300
- delay: "00:05:00" - delay: "00:05:00"
- service: light.turn_off - service: light.turn_off
target: target:
entity_id: light.bedroom entity_id:
- light.claus
- light.anne
- light.sov_dor
- light.sov_midt
- light.sov_vindue
data: data:
transition: 5 transition: 5
+102
View File
@@ -1,3 +1,105 @@
vi_laver_mad:
alias: Vi laver mad
sequence:
- choose:
- conditions:
- condition: state
entity_id: person.andreas_schusler_dethlefsen
state: home
sequence:
- service: notify.mobile_app_andreas_iphone_12
data:
message: >-
{% set meal = states('sensor.dagens_aftensmad') %}
{% if meal and meal not in ['unknown','unavailable','Ingen planlagt'] %}
Vi laver mad! I dag: {{ meal }}
{% else %}
Vi laver mad!
{% endif %}
- choose:
- conditions:
- condition: state
entity_id: person.daniel_schusler_dethlefsen
state: home
sequence:
- service: notify.mobile_app_daniels_iphone_13_mini
data:
message: >-
{% set meal = states('sensor.dagens_aftensmad') %}
{% if meal and meal not in ['unknown','unavailable','Ingen planlagt'] %}
Vi laver mad! I dag: {{ meal }}
{% else %}
Vi laver mad!
{% endif %}
- choose:
- conditions:
- condition: state
entity_id: person.andreas_schusler_dethlefsen
state: home
sequence:
- service: sonos.snapshot
data:
entity_id: media_player.andreas
with_group: true
- service: media_player.media_stop
target:
entity_id: media_player.andreas
- service: media_player.volume_set
target:
entity_id: media_player.andreas
data:
volume_level: 0.35
- service: tts.speak
target:
entity_id: tts.google_ai_tts
data:
media_player_entity_id: media_player.andreas
message: >-
{% set meal = states('sensor.dagens_aftensmad') %}
{% if meal and meal not in ['unknown','unavailable','Ingen planlagt'] %}
Vi laver mad! I dag spiser vi {{ meal }}
{% else %}
Vi laver mad!
{% endif %}
- delay: "00:00:08"
- service: sonos.restore
data:
entity_id: media_player.andreas
- choose:
- conditions:
- condition: state
entity_id: person.daniel_schusler_dethlefsen
state: home
sequence:
- service: sonos.snapshot
data:
entity_id: media_player.daniel
with_group: true
- service: media_player.media_stop
target:
entity_id: media_player.daniel
- service: media_player.volume_set
target:
entity_id: media_player.daniel
data:
volume_level: 0.35
- service: tts.speak
target:
entity_id: tts.google_ai_tts
data:
media_player_entity_id: media_player.daniel
message: >-
{% set meal = states('sensor.dagens_aftensmad') %}
{% if meal and meal not in ['unknown','unavailable','Ingen planlagt'] %}
Vi laver mad! I dag spiser vi {{ meal }}
{% else %}
Vi laver mad!
{% endif %}
- delay: "00:00:08"
- service: sonos.restore
data:
entity_id: media_player.daniel
mad_announcement: mad_announcement:
alias: Der er mad alias: Der er mad
sequence: sequence:
+2 -2
View File
@@ -4,5 +4,5 @@ mealie_shopping_refresh:
- service: shell_command.mealie_shopping_merge - service: shell_command.mealie_shopping_merge
- service: notify.mobile_app_claus_iphone_15pro - service: notify.mobile_app_claus_iphone_15pro
data: data:
title: "Bilka ToGo liste opdateret" title: "Indkøbsliste opdateret i Mealie"
message: "Mealie-indkøb (fredag til torsdag) er flettet med Keep-basislisten." message: "Indkøbslisten 'Bilka ToGo' er opdateret med opskrifter fra fredag til torsdag."
+4
View File
@@ -15,6 +15,10 @@ overvaagning:
device_id: cf4f218aae515c84aea9f37f190dcfd5 device_id: cf4f218aae515c84aea9f37f190dcfd5
enabled: true enabled: true
action: camera.snapshot action: camera.snapshot
- action: homeassistant.update_entity
data:
entity_id: camera.indkorsel_snapshot
enabled: true
- delay: - delay:
hours: 0 hours: 0
minutes: 0 minutes: 0
+10
View File
@@ -47,3 +47,13 @@ ploeneklipper_manuelt_start:
- service: lawn_mower.start_mowing - service: lawn_mower.start_mowing
target: target:
entity_id: lawn_mower.husqvarna_automower entity_id: lawn_mower.husqvarna_automower
ploeneklipper_manuelt_stop:
alias: "Plæneklipper: Stop manuelt"
sequence:
- service: input_boolean.turn_off
target:
entity_id: input_boolean.ploeneklipper_manuelt_startet
- service: lawn_mower.dock
target:
entity_id: lawn_mower.husqvarna_automower
+15
View File
@@ -223,3 +223,18 @@ varme_recalculate:
data: data:
hvac_mode: heat hvac_mode: heat
temperature: "{{ ferie_temp }}" temperature: "{{ ferie_temp }}"
varme_save_defaults:
alias: Gem varme-standardværdier
icon: mdi:content-save
sequence:
- action: shell_command.varme_save_defaults
response_variable: result
- action: persistent_notification.create
data:
title: Varme-standardværdier gemt
message: >
{{ result.stdout if result.returncode == 0
else 'Fejl: ' ~ result.stderr }}
notification_id: varme_save_defaults
+13
View File
@@ -0,0 +1,13 @@
##################################################
# Glidende gennemsnit af anbefalet fjernvarme-ventilposition
# Beregner mean over de seneste 21 dage, så anbefalingen
# bevæger sig sæsonmæssigt (uger) i stedet for dagligt.
##################################################
- platform: statistics
name: "Fjernvarme ventil 3 ugers gennemsnit"
entity_id: sensor.fjernvarme_ventil_anbefalet
state_characteristic: mean
sampling_size: 2000
max_age:
days: 21
+2
View File
@@ -0,0 +1,2 @@
indkorsel_generate_gallery: "/usr/local/bin/docker exec homeassistant python3 /config/python_scripts/generate_indkorsel_gallery.py"
indkorsel_prune_keep_100: "ls -1 /config/www/snapshots/indkorsel/*.jpg | grep -v '/latest.jpg$' | sort -r | tail -n +101 | xargs rm -f"
+1
View File
@@ -0,0 +1 @@
varme_save_defaults: "python3 /config/python_scripts/save_varme_defaults.py"
+3 -12
View File
@@ -41,21 +41,12 @@
- default_entity_id: switch.daniel_motionlys_toggle - default_entity_id: switch.daniel_motionlys_toggle
name: "Daniel motionlys" name: "Daniel motionlys"
unique_id: daniel_motionlys_toggle unique_id: daniel_motionlys_toggle
state: >- state: "{{ is_state('automation.daniel_lys_via_bevaegelse', 'on') }}"
{{ is_state('automation.lys_daniel_dag_arbejdsdag', 'on')
and is_state('automation.lys_daniel_dag_ikke_arbejdsdag', 'on')
and is_state('automation.sluk_lys_i_daniel', 'on') }}
turn_on: turn_on:
action: automation.turn_on action: automation.turn_on
target: target:
entity_id: entity_id: automation.daniel_lys_via_bevaegelse
- automation.lys_daniel_dag_arbejdsdag
- automation.lys_daniel_dag_ikke_arbejdsdag
- automation.sluk_lys_i_daniel
turn_off: turn_off:
action: automation.turn_off action: automation.turn_off
target: target:
entity_id: entity_id: automation.daniel_lys_via_bevaegelse
- automation.lys_daniel_dag_arbejdsdag
- automation.lys_daniel_dag_ikke_arbejdsdag
- automation.sluk_lys_i_daniel
+18
View File
@@ -0,0 +1,18 @@
- trigger:
- platform: homeassistant
event: start
- platform: time_pattern
hours: "/1"
action:
- action: weather.get_forecasts
target:
entity_id: weather.norgardsvej
data:
type: daily
response_variable: daily
sensor:
- name: "Vejr daglig prognose"
unique_id: vejr_daglig_prognose
state: "{{ daily['weather.norgardsvej'].forecast | length }}"
attributes:
forecast: "{{ daily['weather.norgardsvej'].forecast }}"
+6
View File
@@ -45,3 +45,9 @@ Vigtige detaljer om min opsaetning:
- Telefoner: notify.mobile_app_claus_iphone_15pro, notify.mobile_app_annes_iphone_14_pro - Telefoner: notify.mobile_app_claus_iphone_15pro, notify.mobile_app_annes_iphone_14_pro
- Sonos hoejtalere: media_player.alrum, media_player.lille_badevaerelse, m.fl. - Sonos hoejtalere: media_player.alrum, media_player.lille_badevaerelse, m.fl.
- Git repo: gitea.anneclaus.synology.me (SSH) - Git repo: gitea.anneclaus.synology.me (SSH)
- Husqvarna Automower (lawn_mower.husqvarna_automower): BLE-baseret via husqvarna_automower_ble integration
- MAC: C4:64:E3:B1:16:14, BLE-proxy: ESP32 paa D0:CF:13:0D:01:16
- BLE tillader kun EN aktiv forbindelse ad gangen - Husqvarna-appen paa telefon SKAL lukkes helt
(ogsaa selvom moweren er slettet i appen - appen kan stadig holde BLE-session aaben i baggrunden)
- Fejlsymptom: "could not find device with address C4:64:E3:B1:16:14" i log, "mower returned 1" i UI
- Fix: Luk Husqvarna-appen helt paa alle enheder -> genstart integrationen i HA
@@ -0,0 +1,204 @@
#!/usr/bin/env python3
"""Generate HTML gallery for indkorsel person-detection snapshots.
Called via shell_command after each new snapshot is saved.
Output: /config/www/snapshots/indkorsel_gallery.html
"""
import os
import glob
from datetime import datetime
SNAPSHOT_DIR = "/config/www/snapshots/indkorsel"
OUTPUT_FILE = "/config/www/snapshots/indkorsel_gallery.html"
LOADER_FILE = "/config/www/snapshots/indkorsel_loader.html"
MAX_SNAPSHOTS = 500
def parse_timestamp(filename):
base = os.path.basename(filename).replace(".jpg", "")
try:
dt = datetime.strptime(base, "%Y-%m-%d_%H-%M-%S")
return dt.strftime("%-d. %b %Y %H:%M:%S")
except Exception:
return base
os.makedirs(SNAPSHOT_DIR, exist_ok=True)
files = sorted(
[f for f in glob.glob(os.path.join(SNAPSHOT_DIR, "*.jpg"))
if os.path.basename(f) != "latest.jpg"],
reverse=True
)[:MAX_SNAPSHOTS]
items_html = ""
images_js = [] # [{src, ts}, ...] for JS navigation
for f in files:
rel = "/local/snapshots/indkorsel/" + os.path.basename(f)
ts = parse_timestamp(f)
idx = len(images_js)
items_html += f"""
<div class="thumb" onclick="openModal({idx})">
<img src="{rel}" loading="lazy" alt="{ts}"/>
<div class="ts">{ts}</div>
</div>"""
images_js.append({"src": rel, "ts": ts})
import json as _json
images_js_str = _json.dumps(images_js)
version = datetime.now().strftime("%Y%m%d%H%M%S")
html = f"""<!DOCTYPE html>
<html lang="da"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Indkorsel snapshots</title>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<style>
.prune-btn{{display:inline-block;margin-left:12px;padding:3px 10px;font-size:11px;border:1px solid #c44;background:transparent;color:#c88;border-radius:12px;cursor:pointer;vertical-align:middle;transition:background .15s,color .15s}}
.prune-btn:hover{{background:#c44;color:#fff}}
.prune-btn:disabled{{opacity:.4;cursor:default}}
*{{box-sizing:border-box;margin:0;padding:0}}
body{{background:#111;color:#ddd;font-family:sans-serif;padding:10px}}
h2{{padding:8px 0 14px;font-size:13px;opacity:.5;font-weight:normal}}
.grid{{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:6px}}
.thumb{{cursor:pointer;border-radius:7px;overflow:hidden;background:#222;transition:transform .12s,opacity .12s}}
.thumb:hover{{transform:scale(1.02);opacity:.9}}
.thumb img{{width:100%;aspect-ratio:16/9;object-fit:cover;display:block}}
.ts{{font-size:10px;padding:4px 6px;opacity:.5;text-align:center}}
.modal{{display:none;position:fixed;inset:0;background:rgba(0,0,0,.93);z-index:9999;flex-direction:column;justify-content:center;align-items:center}}
.modal.show{{display:flex}}
.modal img{{max-width:96vw;max-height:82vh;border-radius:8px;object-fit:contain;box-shadow:0 4px 40px rgba(0,0,0,.6)}}
.modal-ts{{margin-top:12px;font-size:14px;opacity:.6;letter-spacing:.3px}}
.modal-counter{{margin-top:4px;font-size:11px;opacity:.35;letter-spacing:.3px}}
.close{{position:absolute;top:14px;right:18px;font-size:32px;cursor:pointer;opacity:.5;line-height:1;user-select:none}}
.close:hover{{opacity:1}}
.nav{{position:absolute;top:50%;transform:translateY(-50%);font-size:44px;cursor:pointer;opacity:.35;user-select:none;padding:0 18px;line-height:1}}
.nav:hover{{opacity:.9}}
.nav.prev{{left:0}} .nav.next{{right:0}}
.nav.disabled{{opacity:.08;cursor:default;pointer-events:none}}
.empty{{padding:40px;text-align:center;opacity:.4;font-size:14px}}
.reload-badge{{position:fixed;bottom:14px;right:14px;background:#1a73e8;color:#fff;padding:6px 14px;border-radius:20px;font-size:12px;cursor:pointer;display:none;z-index:9998}}
</style>
</head><body>
<h2>Viser {len(files)} person-snapshots &ndash; Indkorsel
<button class="prune-btn" id="pruneBtn" onclick="pruneSnapshots()" title="Slet alle undtagen de 100 nyeste">Behold sidste 100</button>
</h2>
{"<div class='grid'>" + items_html + "</div>" if files else "<div class='empty'>Ingen snapshots endnu.</div>"}
<div id="reload-badge" class="reload-badge" onclick="window.location.reload(true)">Nye billeder &#8211; tryk for at opdatere</div>
<div class="modal" id="modal">
<span class="close" onclick="closeModal()">&#x2715;</span>
<span class="nav prev" id="navPrev" onclick="navigate(-1)">&#x2039;</span>
<img id="mimg" src="" alt=""/>
<span class="nav next" id="navNext" onclick="navigate(1)">&#x203a;</span>
<div class="modal-ts" id="mts"></div>
<div class="modal-counter" id="mcounter"></div>
</div>
<script>
const IMGS = {images_js_str};
const VERSION = '{version}';
let cur = 0;
function openModal(idx){{
cur = idx;
render();
document.getElementById('modal').classList.add('show');
}}
function render(){{
const item = IMGS[cur];
document.getElementById('mimg').src = item.src;
document.getElementById('mts').textContent = item.ts;
document.getElementById('mcounter').textContent = (cur+1) + ' / ' + IMGS.length;
document.getElementById('navPrev').classList.toggle('disabled', cur === 0);
document.getElementById('navNext').classList.toggle('disabled', cur === IMGS.length - 1);
}}
function navigate(dir){{
const next = cur + dir;
if(next >= 0 && next < IMGS.length){{ cur = next; render(); }}
}}
function closeModal(){{
document.getElementById('modal').classList.remove('show');
document.getElementById('mimg').src = '';
}}
document.addEventListener('keydown', e => {{
if(!document.getElementById('modal').classList.contains('show')) return;
if(e.key === 'ArrowLeft') navigate(-1);
if(e.key === 'ArrowRight') navigate(1);
if(e.key === 'Escape') closeModal();
}});
document.getElementById('modal').addEventListener('click', function(e){{
if(e.target === this) closeModal();
}});
// Touch swipe support (iPhone/iPad)
let _tx = null;
document.getElementById('modal').addEventListener('touchstart', e => {{
_tx = e.changedTouches[0].clientX;
}}, {{passive: true}});
document.getElementById('modal').addEventListener('touchend', e => {{
if(_tx === null) return;
const dx = e.changedTouches[0].clientX - _tx;
_tx = null;
if(Math.abs(dx) < 40) return; // ignore taps
navigate(dx < 0 ? 1 : -1); // swipe left = næste, swipe right = forrige
}}, {{passive: true}});
function pruneSnapshots(){{
if(!confirm('Slet alle undtagen de 100 nyeste billeder?')) return;
const btn = document.getElementById('pruneBtn');
btn.disabled = true;
btn.textContent = 'Sletter...';
fetch('/api/webhook/indkorsel_prune_100', {{method:'POST'}})
.then(() => {{
btn.textContent = 'Færdig genindlæser...';
setTimeout(() => window.location.reload(true), 3500);
}})
.catch(() => {{
btn.disabled = false;
btn.textContent = 'Fejl prøv igen';
}});
}}
// Tjek hvert 60 sek om der er en nyere version af galleriet
setInterval(() => {{
fetch('/local/snapshots/indkorsel_loader.html?_=' + Date.now())
.then(r => r.text())
.then(html => {{
const m = html.match(/[?]v=(\\d+)/);
if(m && m[1] !== VERSION) document.getElementById('reload-badge').style.display = 'block';
}}).catch(() => {{}});
}}, 60000);
</script>
</body></html>"""
with open(OUTPUT_FILE, "w", encoding="utf-8") as fh:
fh.write(html)
# Write a tiny loader page that always redirects to gallery with current timestamp
# so the iframe in HA never serves a cached version
cache_bust = version
loader = f"""<!DOCTYPE html>
<html><head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta http-equiv="refresh" content="0; url=/local/snapshots/indkorsel_gallery.html?v={cache_bust}">
</head><body></body></html>"""
with open(LOADER_FILE, "w", encoding="utf-8") as fh:
fh.write(loader)
print(f"Gallery updated: {len(files)} snapshots -> {OUTPUT_FILE}")
# Update dashboard YAML iframe URL version so browser always fetches fresh loader
DASHBOARD_VIEW = "/config/dashboards/views/06c_indkorsel_snapshots.yaml"
try:
import re as _re
with open(DASHBOARD_VIEW, "r", encoding="utf-8") as fh:
dash = fh.read()
updated = _re.sub(
r'(url: /local/snapshots/indkorsel_loader\.html)(\?v=\d+)?',
rf'\1?v={version}',
dash
)
if updated != dash:
with open(DASHBOARD_VIEW, "w", encoding="utf-8") as fh:
fh.write(updated)
except Exception as _e:
print(f"Warning: could not update dashboard YAML: {_e}")
+20 -11
View File
@@ -20,17 +20,26 @@ else:
try: try:
req = urllib.request.Request(url, headers={"Authorization": token}) req = urllib.request.Request(url, headers={"Authorization": token})
raw = json.loads(urllib.request.urlopen(req, timeout=10).read()) raw = json.loads(urllib.request.urlopen(req, timeout=10).read())
items = [ items = []
{ for i in raw.get("items", []):
"date": i["date"], recipe = i.get("recipe")
"recipe": { title = i.get("title") or ""
"name": i.get("recipe", {}).get("name", ""), if recipe:
"slug": i.get("recipe", {}).get("slug", ""), items.append({
}, "date": i["date"],
} "recipe": {
for i in raw.get("items", []) "name": recipe.get("name", ""),
if i.get("recipe") "slug": recipe.get("slug", ""),
] },
})
elif title:
items.append({
"date": i["date"],
"recipe": {
"name": title,
"slug": "",
},
})
data = {"count": len(items), "items": items} data = {"count": len(items), "items": items}
except Exception: except Exception:
data = {"count": 0, "items": []} data = {"count": 0, "items": []}
+1 -5
View File
@@ -273,11 +273,7 @@ def main() -> None:
shopping_list_id = reset_shopping_list(MEALIE_BASE_URL, token, TARGET_SHOPPING_LIST_NAME, shopping_list_id) shopping_list_id = reset_shopping_list(MEALIE_BASE_URL, token, TARGET_SHOPPING_LIST_NAME, shopping_list_id)
recipe_ids = get_mealplan_recipe_ids(MEALIE_BASE_URL, token, start_date, end_date) recipe_ids = get_mealplan_recipe_ids(MEALIE_BASE_URL, token, start_date, end_date)
added_recipes = add_recipes_to_shopping_list(MEALIE_BASE_URL, token, shopping_list_id, recipe_ids) add_recipes_to_shopping_list(MEALIE_BASE_URL, token, shopping_list_id, recipe_ids)
mealie_items = fetch_mealie_items(MEALIE_BASE_URL, token, shopping_list_id=shopping_list_id)
items = build_items(mealie_items)
write_outputs(root, items, start_date, end_date)
print( print(
'OK: ' 'OK: '
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""
Gem nuværende varme-indstillinger som nye 'initial' standardværdier i YAML-filen.
Køres inde i homeassistant Docker-containeren.
"""
import re
import json
import urllib.request
YAML_FILE = "/config/include/input/number/varme.yaml"
SECRETS_FILE = "/config/secrets.yaml"
HA_URL = "http://localhost:8123"
ENTITIES = [
"varme_komfort_andreas",
"varme_komfort_daniel",
"varme_komfort_sovevaerelse",
"varme_komfort_kontor",
"varme_komfort_gang",
"varme_komfort_forgang",
"varme_komfort_lille_bad",
"varme_komfort_badevarelse",
"varme_komfort_stue",
"varme_nat_saenkning",
"varme_vaek_saenkning",
"varme_ferie_temp",
]
def get_token():
with open(SECRETS_FILE) as f:
for line in f:
if line.startswith("ha_token:"):
return line.split(":", 1)[1].strip()
raise ValueError("ha_token ikke fundet i secrets.yaml")
def get_states(token):
req = urllib.request.Request(
f"{HA_URL}/api/states",
headers={"Authorization": f"Bearer {token}"},
)
with urllib.request.urlopen(req) as resp:
return {d["entity_id"]: d["state"] for d in json.loads(resp.read())}
def format_value(state_str):
val = float(state_str)
return str(int(val)) if val == int(val) else str(val)
def update_initial(content, entity_name, new_value):
"""Erstat initial-værdien for en given entity i YAML-indholdet."""
pattern = rf"(^{re.escape(entity_name)}:\n(?: [^\n]*\n)*? initial: )\S+"
new_content, count = re.subn(
pattern, rf"\g<1>{new_value}", content, flags=re.MULTILINE, count=1
)
if count == 0:
print(f" ADVARSEL: {entity_name} ikke fundet i YAML")
return new_content
def main():
token = get_token()
states = get_states(token)
with open(YAML_FILE) as f:
content = f.read()
saved = []
for name in ENTITIES:
entity_id = f"input_number.{name}"
state = states.get(entity_id)
if state in (None, "unavailable", "unknown"):
print(f" SPRING OVER {entity_id}: {state}")
continue
val_str = format_value(state)
content = update_initial(content, name, val_str)
saved.append(f"{entity_id} = {val_str}")
with open(YAML_FILE, "w") as f:
f.write(content)
print(f"Gemt {len(saved)} standardværdier -> {YAML_FILE}")
for line in saved:
print(f" {line}")
if __name__ == "__main__":
main()
+5
View File
@@ -37,6 +37,11 @@ synology_password: boss3LEY8ogh!saub
## old nas was 10.0.0.189 ## old nas was 10.0.0.189
# ######################################## # ########################################
npm_url: http://10.0.0.142:81
npm_email: claus.dethlefsen@gmail.com
npm_password: Hwli03yitw
# ########################################
hue_ip: 10.0.0.154 hue_ip: 10.0.0.154
# ######################################## # ########################################
+43 -56
View File
@@ -18,96 +18,83 @@
</head> </head>
<body> <body>
<h1>🛒 Bilka ToGo</h1> <h1>🛒 Bilka ToGo</h1>
<p class="sub">Plan 24/04 30/04 &nbsp;·&nbsp; 82 varer</p> <p class="sub">Plan 15/05 21/05 &nbsp;·&nbsp; 69 varer</p>
<table> <table>
<tr><th colspan="2" class="cat">Andet</th></tr> <tr><th colspan="2" class="cat">Andet</th></tr>
<tr><td class="cb"><input type="checkbox"></td><td>0,50 squash</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>0,50 dl hvidvin</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>0,50 tsk mediumstærk karry</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>0,50 tsk stødt spidskommen</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>0,50 tsk tørret rosmarin</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 æg</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 dl cremefraiche 38%</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 æggehvider</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 dl grøntsagsbouillon</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 dl cremefraiche 18 %</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 dl pickles</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 dl mælk</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 dl rødvin, eller grøntsagsboullion</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 dl rødvin, eller grøntsagsboullion</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 dl tør hvidvin</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 fed hvidløg, presset</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 fed hvidløg, presset</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 gulerødder, i tern</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 håndfuld persille</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 kg kartofler</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 knivspids muskatnød, fintrevet</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 knivspids muskatnød, fintrevet</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 knivspids røget paprika</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 knivspids sød paprika</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 knivspids sød paprika</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 løg, i tern</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 rødløg</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk dijon sennep</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk hampefrø</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk honning</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk majsstivelse</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk smør, til stegning</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 spsk smør, til stegning</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk solsikkekerner</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 squash, groftrevet</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 squash, groftrevet</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 tsk sød paprika</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 tsk tørret timian</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 tsk tørret timian</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>10 g smør, til stegning</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>100 g rejer</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>100 g parmesan, fintrevet</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>100 g stenbiderrogn</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>12 tarteletter</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>2 spsk kapers (valgfrit)</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 æg</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 dl hønsebouillon</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 hønsebryst</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 laurbærblade</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk grov sennep</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk rosiner</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk smør</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>2 spsk smør</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 tsk tørret oregano</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>2 tsk tørret oregano</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>200 g aspargessnitter</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>200 g lasagneplader</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>200 g lasagneplader</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>25 g smør, til stegning</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>3 dl grøntsagsbouillon</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>3 dl mælk</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>3 dl mælk</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>3 gulerødder, groftrevet</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>4 grønne asparges</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>300 g torskefilet</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>4 dl mælk</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>4 fed hvidløg, fintrevet</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>4 gulerødder, groftrevet</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>4 gulerødder, groftrevet</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>40 g smør</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>4 hvide asparges</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>4 skiver brød</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>5 stængler bladselleri, groftrevet</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>5 stængler bladselleri, groftrevet</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>50 g rasp</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>75 g smør</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>8 rødspættefilet</td></tr>
<tr><th colspan="2" class="cat">Frost</th></tr> <tr><th colspan="2" class="cat">Frost</th></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 dl piskefløde</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk mayonnaise</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>2 spsk mayonnaise</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2,50 dl piskefløde</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>2,50 dl piskefløde</td></tr>
<tr><th colspan="2" class="cat">Frugt & Grønt</th></tr> <tr><th colspan="2" class="cat">Frugt & Grønt</th></tr>
<tr><td class="cb"><input type="checkbox"></td><td>0,50 citron, saft herfra</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 citron saft og fintrevet skal</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>0,50 øko citron</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 citron, 1 spsk saft herfra</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>15 g koncentreret tomatpuré</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 citron, skåret i både</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 æble, groftrevet</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>12 skiver agurk</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 banan, skåret i skiver på ca. 1 cm</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk koncentreret tomatpuré</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>4 håndfulde grøn salat, til anretning</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>50 g koncentreret tomatpuré</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>50 g koncentreret tomatpuré</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>75 g soltørrede tomater i olie, finthakket</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>70 g blandet salat</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>800 g hakkede tomater på dåse</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>800 g hakkede tomater på dåse</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>Grøntsager eller salat</td></tr>
<tr><th colspan="2" class="cat">Kolonial</th></tr> <tr><th colspan="2" class="cat">Kolonial</th></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 håndfuld frisk basilikum</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 dl mild chilipasta</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 håndfuld frisk dild</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 spsk hvedemel</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk olivenolie</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk olivenolie, til stegning</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 spsk olivenolie, til stegning</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 tsk olivenolie</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 spsk smør eller olie til stegning</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 håndfulde frisk dild, til pynt</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk finvalset havregryn</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk hvedemel</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>2 spsk hvedemel</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk olivenolie</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>2 spsk olivenolie</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>25 g hvedemel</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>3 dl basmati ris</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>3 dl basmati ris, kogt efter anvisning på emballagen</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>3 spsk olivenolie, til stegning</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>30 g hvedemel</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>50 g saltede peanuts</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>40 g hvedemel</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>Ris eller kartofler</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>400 g pasta</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>flagesalt</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>salt og friskkværnet peber</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>salt og friskkværnet peber</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>Salt og peber</td></tr>
<tr><th colspan="2" class="cat">Kød & Fisk</th></tr> <tr><th colspan="2" class="cat">Kød & Fisk</th></tr>
<tr><td class="cb"><input type="checkbox"></td><td>0,50 dl frisk estragon, finthakket</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 fed hvidløg, finthakket</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>1 løg, finthakket</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>1 løg, finthakket</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 løg, finthakket</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>2 løg, finthakket</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>300 g laks, uden skind</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>2 spsk frisk persille, hakket</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk persille, finthakket</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>4 fed hvidløg, finthakket</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>4 fed hvidløg, finthakket</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>4 kyllingebryst</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>4 laksefileter med skind (ca. 150 g pr. stk)</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>400 g hakket oksekød</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>400 g hakket oksekød</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>600 g kyllingebryst</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>500 g hakket svinekød</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>600 g kyllingebryst, skåret i grove tern</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>75 g bacon, i skiver</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>75 g bacon, i skiver</td></tr>
<tr><th colspan="2" class="cat">Mejeri & Æg</th></tr> <tr><th colspan="2" class="cat">Mejeri & Æg</th></tr>
<tr><td class="cb"><input type="checkbox"></td><td>125 g frisk mozzarella</td></tr> <tr><td class="cb"><input type="checkbox"></td><td>125 g frisk mozzarella</td></tr>
<tr><td class="cb"><input type="checkbox"></td><td>400 g haricots verts, fra frost</td></tr>
</table> </table>
</body> </body>
</html> </html>
+84 -136
View File
@@ -1,96 +1,60 @@
{ {
"count": 82, "count": 69,
"items": [ "items": [
{ {
"name": "0,50 squash", "name": "0,50 dl hvidvin",
"category": "andet" "category": "andet"
}, },
{ {
"name": "0,50 tsk mediumstærk karry", "name": "0,50 tsk stødt spidskommen",
"category": "andet" "category": "andet"
}, },
{ {
"name": "0,50 tsk tørret rosmarin", "name": "1 æg",
"category": "andet" "category": "andet"
}, },
{ {
"name": "1 dl cremefraiche 38%", "name": "1 æggehvider",
"category": "andet" "category": "andet"
}, },
{ {
"name": "1 dl grøntsagsbouillon", "name": "1 dl cremefraiche 18 %",
"category": "andet" "category": "andet"
}, },
{ {
"name": "1 dl pickles", "name": "1 dl mælk",
"category": "andet" "category": "andet"
}, },
{ {
"name": "1 dl rødvin, eller grøntsagsboullion", "name": "1 dl rødvin, eller grøntsagsboullion",
"category": "andet" "category": "andet"
}, },
{
"name": "1 dl tør hvidvin",
"category": "andet"
},
{ {
"name": "1 fed hvidløg, presset", "name": "1 fed hvidløg, presset",
"category": "andet" "category": "andet"
}, },
{
"name": "1 gulerødder, i tern",
"category": "andet"
},
{
"name": "1 håndfuld persille",
"category": "andet"
},
{
"name": "1 kg kartofler",
"category": "andet"
},
{ {
"name": "1 knivspids muskatnød, fintrevet", "name": "1 knivspids muskatnød, fintrevet",
"category": "andet" "category": "andet"
}, },
{
"name": "1 knivspids røget paprika",
"category": "andet"
},
{ {
"name": "1 knivspids sød paprika", "name": "1 knivspids sød paprika",
"category": "andet" "category": "andet"
}, },
{
"name": "1 løg, i tern",
"category": "andet"
},
{
"name": "1 rødløg",
"category": "andet"
},
{
"name": "1 spsk dijon sennep",
"category": "andet"
},
{
"name": "1 spsk hampefrø",
"category": "andet"
},
{
"name": "1 spsk honning",
"category": "andet"
},
{
"name": "1 spsk majsstivelse",
"category": "andet"
},
{ {
"name": "1 spsk smør, til stegning", "name": "1 spsk smør, til stegning",
"category": "andet" "category": "andet"
}, },
{ {
"name": "1 spsk solsikkekerner", "name": "1 squash, groftrevet",
"category": "andet" "category": "andet"
}, },
{ {
"name": "1 squash, groftrevet", "name": "1 tsk sød paprika",
"category": "andet" "category": "andet"
}, },
{ {
@@ -98,39 +62,15 @@
"category": "andet" "category": "andet"
}, },
{ {
"name": "10 g smør, til stegning", "name": "100 g rejer",
"category": "andet" "category": "andet"
}, },
{ {
"name": "100 g parmesan, fintrevet", "name": "100 g stenbiderrogn",
"category": "andet" "category": "andet"
}, },
{ {
"name": "12 tarteletter", "name": "2 spsk kapers (valgfrit)",
"category": "andet"
},
{
"name": "2 æg",
"category": "andet"
},
{
"name": "2 dl hønsebouillon",
"category": "andet"
},
{
"name": "2 hønsebryst",
"category": "andet"
},
{
"name": "2 laurbærblade",
"category": "andet"
},
{
"name": "2 spsk grov sennep",
"category": "andet"
},
{
"name": "2 spsk rosiner",
"category": "andet" "category": "andet"
}, },
{ {
@@ -141,40 +81,16 @@
"name": "2 tsk tørret oregano", "name": "2 tsk tørret oregano",
"category": "andet" "category": "andet"
}, },
{
"name": "200 g aspargessnitter",
"category": "andet"
},
{ {
"name": "200 g lasagneplader", "name": "200 g lasagneplader",
"category": "andet" "category": "andet"
}, },
{
"name": "25 g smør, til stegning",
"category": "andet"
},
{
"name": "3 dl grøntsagsbouillon",
"category": "andet"
},
{ {
"name": "3 dl mælk", "name": "3 dl mælk",
"category": "andet" "category": "andet"
}, },
{ {
"name": "3 gulerødder, groftrevet", "name": "4 grønne asparges",
"category": "andet"
},
{
"name": "300 g torskefilet",
"category": "andet"
},
{
"name": "4 dl mælk",
"category": "andet"
},
{
"name": "4 fed hvidløg, fintrevet",
"category": "andet" "category": "andet"
}, },
{ {
@@ -182,7 +98,11 @@
"category": "andet" "category": "andet"
}, },
{ {
"name": "40 g smør", "name": "4 hvide asparges",
"category": "andet"
},
{
"name": "4 skiver brød",
"category": "andet" "category": "andet"
}, },
{ {
@@ -190,8 +110,16 @@
"category": "andet" "category": "andet"
}, },
{ {
"name": "1 dl piskefløde", "name": "50 g rasp",
"category": "frost" "category": "andet"
},
{
"name": "75 g smør",
"category": "andet"
},
{
"name": "8 rødspættefilet",
"category": "andet"
}, },
{ {
"name": "2 spsk mayonnaise", "name": "2 spsk mayonnaise",
@@ -202,19 +130,31 @@
"category": "frost" "category": "frost"
}, },
{ {
"name": "0,50 citron, saft herfra", "name": "1 citron saft og fintrevet skal",
"category": "frugt & grønt" "category": "frugt & grønt"
}, },
{ {
"name": "0,50 øko citron", "name": "1 citron, 1 spsk saft herfra",
"category": "frugt & grønt" "category": "frugt & grønt"
}, },
{ {
"name": "15 g koncentreret tomatpuré", "name": "1 citron, skåret i både",
"category": "frugt & grønt" "category": "frugt & grønt"
}, },
{ {
"name": "2 æble, groftrevet", "name": "12 skiver agurk",
"category": "frugt & grønt"
},
{
"name": "2 banan, skåret i skiver på ca. 1 cm",
"category": "frugt & grønt"
},
{
"name": "2 spsk koncentreret tomatpuré",
"category": "frugt & grønt"
},
{
"name": "4 håndfulde grøn salat, til anretning",
"category": "frugt & grønt" "category": "frugt & grønt"
}, },
{ {
@@ -222,7 +162,7 @@
"category": "frugt & grønt" "category": "frugt & grønt"
}, },
{ {
"name": "75 g soltørrede tomater i olie, finthakket", "name": "70 g blandet salat",
"category": "frugt & grønt" "category": "frugt & grønt"
}, },
{ {
@@ -230,15 +170,15 @@
"category": "frugt & grønt" "category": "frugt & grønt"
}, },
{ {
"name": "1 håndfuld frisk basilikum", "name": "Grøntsager eller salat",
"category": "frugt & grønt"
},
{
"name": "1 dl mild chilipasta",
"category": "kolonial" "category": "kolonial"
}, },
{ {
"name": "1 håndfuld frisk dild", "name": "1 spsk hvedemel",
"category": "kolonial"
},
{
"name": "1 spsk olivenolie",
"category": "kolonial" "category": "kolonial"
}, },
{ {
@@ -246,7 +186,15 @@
"category": "kolonial" "category": "kolonial"
}, },
{ {
"name": "1 tsk olivenolie", "name": "1 spsk smør eller olie til stegning",
"category": "kolonial"
},
{
"name": "2 håndfulde frisk dild, til pynt",
"category": "kolonial"
},
{
"name": "2 spsk finvalset havregryn",
"category": "kolonial" "category": "kolonial"
}, },
{ {
@@ -258,27 +206,19 @@
"category": "kolonial" "category": "kolonial"
}, },
{ {
"name": "25 g hvedemel", "name": "3 dl basmati ris",
"category": "kolonial" "category": "kolonial"
}, },
{ {
"name": "3 dl basmati ris, kogt efter anvisning på emballagen", "name": "3 spsk olivenolie, til stegning",
"category": "kolonial" "category": "kolonial"
}, },
{ {
"name": "30 g hvedemel", "name": "50 g saltede peanuts",
"category": "kolonial" "category": "kolonial"
}, },
{ {
"name": "40 g hvedemel", "name": "Ris eller kartofler",
"category": "kolonial"
},
{
"name": "400 g pasta",
"category": "kolonial"
},
{
"name": "flagesalt",
"category": "kolonial" "category": "kolonial"
}, },
{ {
@@ -286,7 +226,11 @@
"category": "kolonial" "category": "kolonial"
}, },
{ {
"name": "0,50 dl frisk estragon, finthakket", "name": "Salt og peber",
"category": "kolonial"
},
{
"name": "1 fed hvidløg, finthakket",
"category": "kød & fisk" "category": "kød & fisk"
}, },
{ {
@@ -298,7 +242,11 @@
"category": "kød & fisk" "category": "kød & fisk"
}, },
{ {
"name": "300 g laks, uden skind", "name": "2 spsk frisk persille, hakket",
"category": "kød & fisk"
},
{
"name": "2 spsk persille, finthakket",
"category": "kød & fisk" "category": "kød & fisk"
}, },
{ {
@@ -306,7 +254,7 @@
"category": "kød & fisk" "category": "kød & fisk"
}, },
{ {
"name": "4 kyllingebryst", "name": "4 laksefileter med skind (ca. 150 g pr. stk)",
"category": "kød & fisk" "category": "kød & fisk"
}, },
{ {
@@ -314,7 +262,11 @@
"category": "kød & fisk" "category": "kød & fisk"
}, },
{ {
"name": "600 g kyllingebryst", "name": "500 g hakket svinekød",
"category": "kød & fisk"
},
{
"name": "600 g kyllingebryst, skåret i grove tern",
"category": "kød & fisk" "category": "kød & fisk"
}, },
{ {
@@ -324,10 +276,6 @@
{ {
"name": "125 g frisk mozzarella", "name": "125 g frisk mozzarella",
"category": "mejeri & æg" "category": "mejeri & æg"
},
{
"name": "400 g haricots verts, fra frost",
"category": "mejeri & æg"
} }
] ]
} }
+10 -14
View File
@@ -13,15 +13,16 @@ Plan-vindue: 2026-04-24 til 2026-04-30
- [ ] 1 dl rødvin, eller grøntsagsboullion (Mealie) - [ ] 1 dl rødvin, eller grøntsagsboullion (Mealie)
- [ ] 1 dl tør hvidvin (Mealie) - [ ] 1 dl tør hvidvin (Mealie)
- [ ] 1 fed hvidløg, presset (Mealie) - [ ] 1 fed hvidløg, presset (Mealie)
- [ ] 1 gulerødder, Groftrevet (Mealie) - [ ] 1 gulerødder, i tern (Mealie)
- [ ] 1 håndfuld persille (Mealie)
- [ ] 1 kg kartofler (Mealie) - [ ] 1 kg kartofler (Mealie)
- [ ] 1 knivspids muskatnød, fintrevet (Mealie) - [ ] 1 knivspids muskatnød, fintrevet (Mealie)
- [ ] 1 knivspids sød paprika (Mealie) - [ ] 1 knivspids sød paprika (Mealie)
- [ ] 1 løg, i tern (Mealie)
- [ ] 1 rødløg (Mealie) - [ ] 1 rødløg (Mealie)
- [ ] 1 spsk dijon sennep (Mealie) - [ ] 1 spsk dijon sennep (Mealie)
- [ ] 1 spsk hampefrø (Mealie) - [ ] 1 spsk hampefrø (Mealie)
- [ ] 1 spsk honning (Mealie) - [ ] 1 spsk honning (Mealie)
- [ ] 1 spsk ingefær, fintrevet (Mealie)
- [ ] 1 spsk majsstivelse (Mealie) - [ ] 1 spsk majsstivelse (Mealie)
- [ ] 1 spsk smør, til stegning (Mealie) - [ ] 1 spsk smør, til stegning (Mealie)
- [ ] 1 spsk solsikkekerner (Mealie) - [ ] 1 spsk solsikkekerner (Mealie)
@@ -29,27 +30,30 @@ Plan-vindue: 2026-04-24 til 2026-04-30
- [ ] 1 tsk tørret timian (Mealie) - [ ] 1 tsk tørret timian (Mealie)
- [ ] 10 g smør, til stegning (Mealie) - [ ] 10 g smør, til stegning (Mealie)
- [ ] 100 g parmesan, fintrevet (Mealie) - [ ] 100 g parmesan, fintrevet (Mealie)
- [ ] 12 tarteletter (Mealie)
- [ ] 2 æg (Mealie) - [ ] 2 æg (Mealie)
- [ ] 2 dl hønsebouillon (Mealie) - [ ] 2 dl hønsebouillon (Mealie)
- [ ] 2 hønsebryst (Mealie)
- [ ] 2 laurbærblade (Mealie)
- [ ] 2 spsk grov sennep (Mealie) - [ ] 2 spsk grov sennep (Mealie)
- [ ] 2 spsk rosiner (Mealie) - [ ] 2 spsk rosiner (Mealie)
- [ ] 2 spsk smør (Mealie) - [ ] 2 spsk smør (Mealie)
- [ ] 2 tsk tørret oregano (Mealie) - [ ] 2 tsk tørret oregano (Mealie)
- [ ] 200 g aspargessnitter (Mealie)
- [ ] 200 g lasagneplader (Mealie) - [ ] 200 g lasagneplader (Mealie)
- [ ] 25 g smør, til stegning (Mealie) - [ ] 25 g smør, til stegning (Mealie)
- [ ] 3 dl grøntsagsbouillon (Mealie)
- [ ] 3 dl mælk (Mealie) - [ ] 3 dl mælk (Mealie)
- [ ] 3 gulerødder, groftrevet (Mealie) - [ ] 3 gulerødder, groftrevet (Mealie)
- [ ] 30 forårsrulleplader (Mealie)
- [ ] 300 g torskefilet (Mealie) - [ ] 300 g torskefilet (Mealie)
- [ ] 35 g glasnudler (Mealie) - [ ] 4 dl mælk (Mealie)
- [ ] 4 fed hvidløg, fintrevet (Mealie) - [ ] 4 fed hvidløg, fintrevet (Mealie)
- [ ] 4 gulerødder, groftrevet (Mealie) - [ ] 4 gulerødder, groftrevet (Mealie)
- [ ] 40 g smør (Mealie)
- [ ] 5 stængler bladselleri, groftrevet (Mealie) - [ ] 5 stængler bladselleri, groftrevet (Mealie)
- [ ] vand til pensling (Mealie)
## Frost ## Frost
- [ ] 1 dl piskefløde (Mealie) - [ ] 1 dl piskefløde (Mealie)
- [ ] 1 spsk fishsauce (Mealie)
- [ ] 2 spsk mayonnaise (Mealie) - [ ] 2 spsk mayonnaise (Mealie)
- [ ] 2,50 dl piskefløde (Mealie) - [ ] 2,50 dl piskefløde (Mealie)
@@ -65,13 +69,9 @@ Plan-vindue: 2026-04-24 til 2026-04-30
## Kolonial ## Kolonial
- [ ] 1 håndfuld frisk basilikum (Mealie) - [ ] 1 håndfuld frisk basilikum (Mealie)
- [ ] 1 håndfuld frisk dild (Mealie) - [ ] 1 håndfuld frisk dild (Mealie)
- [ ] 1 liter fritureolie (Mealie)
- [ ] 1 spsk olivenolie (Mealie) - [ ] 1 spsk olivenolie (Mealie)
- [ ] 1 spsk olivenolie, til stegning (Mealie) - [ ] 1 spsk olivenolie, til stegning (Mealie)
- [ ] 1 spsk soja (Mealie)
- [ ] 1 tsk olivenolie (Mealie) - [ ] 1 tsk olivenolie (Mealie)
- [ ] 1 tsk sesamolie, eller anden olie til stegning (Mealie)
- [ ] 1 tsk sukker (Mealie)
- [ ] 2 spsk hvedemel (Mealie) - [ ] 2 spsk hvedemel (Mealie)
- [ ] 2 spsk olivenolie (Mealie) - [ ] 2 spsk olivenolie (Mealie)
- [ ] 25 g hvedemel (Mealie) - [ ] 25 g hvedemel (Mealie)
@@ -85,15 +85,11 @@ Plan-vindue: 2026-04-24 til 2026-04-30
## Kød & Fisk ## Kød & Fisk
- [ ] 0,50 dl frisk estragon, finthakket (Mealie) - [ ] 0,50 dl frisk estragon, finthakket (Mealie)
- [ ] 1 løg, finthakket (Mealie) - [ ] 1 løg, finthakket (Mealie)
- [ ] 1 tsk rød chili, finthakket (Mealie)
- [ ] 2 løg, finthakket (Mealie) - [ ] 2 løg, finthakket (Mealie)
- [ ] 250 g champignon, finthakket (Mealie)
- [ ] 3 fed hvidløg, finthakket (Mealie)
- [ ] 300 g laks, uden skind (Mealie) - [ ] 300 g laks, uden skind (Mealie)
- [ ] 4 fed hvidløg, finthakket (Mealie) - [ ] 4 fed hvidløg, finthakket (Mealie)
- [ ] 4 kyllingebryst (Mealie) - [ ] 4 kyllingebryst (Mealie)
- [ ] 400 g hakket oksekød (Mealie) - [ ] 400 g hakket oksekød (Mealie)
- [ ] 400 g hakket svinekød (Mealie)
- [ ] 600 g kyllingebryst (Mealie) - [ ] 600 g kyllingebryst (Mealie)
- [ ] 75 g bacon, i skiver (Mealie) - [ ] 75 g bacon, i skiver (Mealie)
+10 -14
View File
@@ -13,15 +13,16 @@ Plan-vindue: 2026-04-24 til 2026-04-30
- [ ] 1 dl rødvin, eller grøntsagsboullion - [ ] 1 dl rødvin, eller grøntsagsboullion
- [ ] 1 dl tør hvidvin - [ ] 1 dl tør hvidvin
- [ ] 1 fed hvidløg, presset - [ ] 1 fed hvidløg, presset
- [ ] 1 gulerødder, Groftrevet - [ ] 1 gulerødder, i tern
- [ ] 1 håndfuld persille
- [ ] 1 kg kartofler - [ ] 1 kg kartofler
- [ ] 1 knivspids muskatnød, fintrevet - [ ] 1 knivspids muskatnød, fintrevet
- [ ] 1 knivspids sød paprika - [ ] 1 knivspids sød paprika
- [ ] 1 løg, i tern
- [ ] 1 rødløg - [ ] 1 rødløg
- [ ] 1 spsk dijon sennep - [ ] 1 spsk dijon sennep
- [ ] 1 spsk hampefrø - [ ] 1 spsk hampefrø
- [ ] 1 spsk honning - [ ] 1 spsk honning
- [ ] 1 spsk ingefær, fintrevet
- [ ] 1 spsk majsstivelse - [ ] 1 spsk majsstivelse
- [ ] 1 spsk smør, til stegning - [ ] 1 spsk smør, til stegning
- [ ] 1 spsk solsikkekerner - [ ] 1 spsk solsikkekerner
@@ -29,27 +30,30 @@ Plan-vindue: 2026-04-24 til 2026-04-30
- [ ] 1 tsk tørret timian - [ ] 1 tsk tørret timian
- [ ] 10 g smør, til stegning - [ ] 10 g smør, til stegning
- [ ] 100 g parmesan, fintrevet - [ ] 100 g parmesan, fintrevet
- [ ] 12 tarteletter
- [ ] 2 æg - [ ] 2 æg
- [ ] 2 dl hønsebouillon - [ ] 2 dl hønsebouillon
- [ ] 2 hønsebryst
- [ ] 2 laurbærblade
- [ ] 2 spsk grov sennep - [ ] 2 spsk grov sennep
- [ ] 2 spsk rosiner - [ ] 2 spsk rosiner
- [ ] 2 spsk smør - [ ] 2 spsk smør
- [ ] 2 tsk tørret oregano - [ ] 2 tsk tørret oregano
- [ ] 200 g aspargessnitter
- [ ] 200 g lasagneplader - [ ] 200 g lasagneplader
- [ ] 25 g smør, til stegning - [ ] 25 g smør, til stegning
- [ ] 3 dl grøntsagsbouillon
- [ ] 3 dl mælk - [ ] 3 dl mælk
- [ ] 3 gulerødder, groftrevet - [ ] 3 gulerødder, groftrevet
- [ ] 30 forårsrulleplader
- [ ] 300 g torskefilet - [ ] 300 g torskefilet
- [ ] 35 g glasnudler - [ ] 4 dl mælk
- [ ] 4 fed hvidløg, fintrevet - [ ] 4 fed hvidløg, fintrevet
- [ ] 4 gulerødder, groftrevet - [ ] 4 gulerødder, groftrevet
- [ ] 40 g smør
- [ ] 5 stængler bladselleri, groftrevet - [ ] 5 stængler bladselleri, groftrevet
- [ ] vand til pensling
## Frost ## Frost
- [ ] 1 dl piskefløde - [ ] 1 dl piskefløde
- [ ] 1 spsk fishsauce
- [ ] 2 spsk mayonnaise - [ ] 2 spsk mayonnaise
- [ ] 2,50 dl piskefløde - [ ] 2,50 dl piskefløde
@@ -65,13 +69,9 @@ Plan-vindue: 2026-04-24 til 2026-04-30
## Kolonial ## Kolonial
- [ ] 1 håndfuld frisk basilikum - [ ] 1 håndfuld frisk basilikum
- [ ] 1 håndfuld frisk dild - [ ] 1 håndfuld frisk dild
- [ ] 1 liter fritureolie
- [ ] 1 spsk olivenolie - [ ] 1 spsk olivenolie
- [ ] 1 spsk olivenolie, til stegning - [ ] 1 spsk olivenolie, til stegning
- [ ] 1 spsk soja
- [ ] 1 tsk olivenolie - [ ] 1 tsk olivenolie
- [ ] 1 tsk sesamolie, eller anden olie til stegning
- [ ] 1 tsk sukker
- [ ] 2 spsk hvedemel - [ ] 2 spsk hvedemel
- [ ] 2 spsk olivenolie - [ ] 2 spsk olivenolie
- [ ] 25 g hvedemel - [ ] 25 g hvedemel
@@ -85,15 +85,11 @@ Plan-vindue: 2026-04-24 til 2026-04-30
## Kød & Fisk ## Kød & Fisk
- [ ] 0,50 dl frisk estragon, finthakket - [ ] 0,50 dl frisk estragon, finthakket
- [ ] 1 løg, finthakket - [ ] 1 løg, finthakket
- [ ] 1 tsk rød chili, finthakket
- [ ] 2 løg, finthakket - [ ] 2 løg, finthakket
- [ ] 250 g champignon, finthakket
- [ ] 3 fed hvidløg, finthakket
- [ ] 300 g laks, uden skind - [ ] 300 g laks, uden skind
- [ ] 4 fed hvidløg, finthakket - [ ] 4 fed hvidløg, finthakket
- [ ] 4 kyllingebryst - [ ] 4 kyllingebryst
- [ ] 400 g hakket oksekød - [ ] 400 g hakket oksekød
- [ ] 400 g hakket svinekød
- [ ] 600 g kyllingebryst - [ ] 600 g kyllingebryst
- [ ] 75 g bacon, i skiver - [ ] 75 g bacon, i skiver
+1 -1
View File
@@ -1 +1 @@
{"count": 6, "items": [{"date": "2026-04-28", "recipe": {"name": "Lasagne", "slug": "lasagne"}}, {"date": "2026-04-27", "recipe": {"name": "Kylling i cremet sennepssauce", "slug": "kylling-i-cremet-sennepssauce"}}, {"date": "2026-04-26", "recipe": {"name": "Lasagne", "slug": "lasagne"}}, {"date": "2026-04-24", "recipe": {"name": "Marry Me Chicken", "slug": "marry-me-chicken"}}, {"date": "2026-04-22", "recipe": {"name": "Kylling i cremet sennepssauce", "slug": "kylling-i-cremet-sennepssauce"}}, {"date": "2026-04-23", "recipe": {"name": "K\u00e5lfad med hakket oksek\u00f8d", "slug": "kalfad-med-hakket-oksekod"}}]} {"count": 7, "items": [{"date": "2026-05-23", "recipe": {"name": "Ingen hjemme", "slug": ""}}, {"date": "2026-05-24", "recipe": {"name": "Kylling i cremet sennepssauce", "slug": "kylling-i-cremet-sennepssauce"}}, {"date": "2026-05-22", "recipe": {"name": "M\u00f8rbradb\u00f8ffer med bl\u00f8de l\u00f8g og fl\u00f8desauce", "slug": "morbradboffer-med-blode-log-og-flodesauce"}}, {"date": "2026-05-21", "recipe": {"name": "Frikadeller", "slug": "frikadeller"}}, {"date": "2026-05-20", "recipe": {"name": "Rester fra mandag (Lasagne)", "slug": ""}}, {"date": "2026-05-19", "recipe": {"name": "Rester fra s\u00f8ndag (Flyvende Jacob)", "slug": ""}}, {"date": "2026-05-18", "recipe": {"name": "Lasagne", "slug": "lasagne"}}]}