Compare commits

...

105 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
claus 4e13ccf658 Input numbers: sync initial values to current HA state 2026-04-26 11:24:37 +02:00
claus 1063166faa Godnat: trin-for-trin daempning over 15 min (40->10->1->off) 2026-04-26 07:09:06 +02:00
claus 750dee2996 Sovevaerelse: godnat-knap daemper lys over 15 min (hold=sluk straks) 2026-04-25 19:40:26 +02:00
claus 7c6c62cff4 Fordorklokke: ret forgang lys (forgang_sensor/scene, ikke gang) 2026-04-25 19:35:31 +02:00
claus e0ec8391d9 Fordørklokke: tænd forgang lys ved ringning hvis mørkt 2026-04-25 19:33:40 +02:00
claus 6208f6fb49 Stue lys: moderniser til 1 automation (mode:restart), input_number parametre for lux+timeouts 2026-04-25 19:28:13 +02:00
claus 9cb7550f78 Motion-lys: input_number parametre for gang/daniel/kontor, fix badevaerelse timeout, kompakt lys view 2026-04-25 19:17:56 +02:00
claus 56f21f79e1 Docker: saml compose-filer i docker/ undermappe + tilføj unifi; monthly_scene: fjern light.syd 2026-04-25 17:49:57 +02:00
claus 6b302b7c65 Syg: fjern forældede alarm-IDs (445, 1874, 1894, 2273, 1899) 2026-04-25 13:45:41 +02:00
claus a7580b851a Dørklokke: gendan volumen på lille bad efter afspilning 2026-04-25 13:16:03 +02:00
claus 3d2cfe6437 Robots: manuelt start overrider family_presence stop for støvsuger og plæneklipper 2026-04-25 12:52:10 +02:00
claus 40cb13662a Varme: genberegn straks når komforttemperatur ændres 2026-04-25 12:43:54 +02:00
claus 9b833aef16 Vinduer: undgå flapping - kræv 5s stabil tilstand og ignorer unavailable 2026-04-25 07:07:19 +02:00
claus 4030b5995f Docker: tilføj /backups volume mount til homeassistant container 2026-04-24 19:49:30 +02:00
claus cd9bc28e9e Soft wakeup lys: spring over hvis anne eller claus er syg 2026-04-24 19:27:29 +02:00
claus c36e47d2ee Slet 02_person_status view (integreret i home view) 2026-04-24 19:16:10 +02:00
claus 94f0cd4d8a Home view: person-kort med fast billedstørrelse + lokation (Hjemme/Ude/zone) 2026-04-24 19:14:10 +02:00
claus 158beed9c5 Home view: person-billeder med syg-toggle (gråtoner + rød kant ved tryk) 2026-04-24 19:10:13 +02:00
claus 9c184ed0b7 Syg-status: ret 'Syg' → 'syg' (lowercase) i alle filer 2026-04-24 19:07:41 +02:00
claus df734d9259 Syg-status: sluk alarmer+motion lys, kompakt person view 2026-04-24 19:03:13 +02:00
claus 9f1dd20f06 Varme script: hvac off → set_temperature ferie_temp ved åbent vindue (Roth understøtter ikke off) 2026-04-24 17:44:01 +02:00
claus 8bd20bda42 Plæneklipper: opdater sidst klippet ved alle start-kilder (app, auto, HA) 2026-04-24 15:41:03 +02:00
claus 30fffd8651 Home view: plæneklipper datoformat + toggle-knapper til plæneklipper og støvsuger 2026-04-24 15:38:08 +02:00
claus 5a0232eeba Varme: vinduer trigger recalculate på åben+luk, script slukker aktivt ved åbent vindue 2026-04-24 15:32:17 +02:00
claus 3608da8a4c Wishlist: smartplugs + Danfoss stue indkøbt, badeværelse to be fixed 2026-04-24 07:09:28 +02:00
claus 7e38ef2e42 Varme view: gendan + tilføj datetime inputs og genberegn-knap 2026-04-24 07:04:37 +02:00
claus 72e0e583be Varme: gør morgen/aften-tider konfigurerbare via input_datetime
- Ny input_datetime/varme.yaml med varme_morgen_tid og varme_aften_tid
- Automations trigger nu på input_datetime-entiteterne i stedet for hardkodede tider
- Script beregner nat dynamisk ud fra de to tider (string-sammenligning)
- View: tilføj morgen/aften-tider til tabellen + manuel Genberegn-knap
2026-04-24 06:37:15 +02:00
claus 70d01bfabf Wishlist: specify Crucial CT4G4SFS8266 4GB SODIMM for DS920+ 2026-04-23 17:54:20 +02:00
claus 2ae9723fa3 Add window sensors to Ally rooms: badevarelse + stue 2026-04-23 17:48:01 +02:00
claus 9fbc1626d7 Add Danfoss Ally: stue + badevarelse to heating system and dashboard 2026-04-23 15:21:03 +02:00
claus cea0e599f9 Heating charts: add Roth current_temp + setpoint series, remove tile cards 2026-04-23 07:48:48 +02:00
claus b7caa8d158 Heating dashboard: tile cards, input_number sliders, vacation pre-heat 2 days before return 2026-04-23 07:42:14 +02:00
claus 542af023c8 Fix: set preset_mode none before temperature to clear Roth schedule (program_1 was overriding setpoints) 2026-04-23 07:07:29 +02:00
claus 9697dd2925 Heating tweaks: morning 06:00, comfort temps, valve scale 1-5, gauge card 2026-04-22 21:55:25 +02:00
claus fa79ffc0e6 Add automated Roth floor heating control: comfort temps, night/away/vacation setbacks, seasonal valve guidance 2026-04-22 21:43:49 +02:00
claus 5bbf0a5082 Fix 7-day meal plan table: use namespace to build rows, avoid YAML folded-scalar whitespace issue 2026-04-22 19:36:29 +02:00
claus 7b7dc22245 Simplify shopping list: drop Keep, fast reset, HTML output for HA iframe 2026-04-22 17:56:49 +02:00
claus 2c3e5bb540 Add HA YAML automation specialist agent 2026-04-22 16:54:50 +02:00
claus 0f3743c1cf Ignore tmp_backups directory 2026-04-22 16:42:10 +02:00
claus ea97b824e7 Import Valdemarsro favorites into Mealie via API 2026-04-22 16:34:43 +02:00
claus ef9200f65b Fix Mealie shopping refresh flow with bulk recipe import and Bilka outputs 2026-04-22 16:12:23 +02:00
claus 83f8908a3f Add Wednesday Bilka checklist merge flow and Madplan dashboard view 2026-04-22 07:37:37 +02:00
claus 98adadbaaa Update Madplan agent to always import recipes into Mealie 2026-04-22 07:24:52 +02:00
95 changed files with 5618 additions and 1247 deletions
+1 -1
View File
@@ -1 +1 @@
2026.4.3 2026.5.1
+57
View File
@@ -0,0 +1,57 @@
---
name: ha
description: "Use when creating, updating, or reviewing Home Assistant YAML automations, scripts, templates, dashboards, and includes in this repository. Trigger phrases: ha, home assistant, automation, script, yaml, lovelace, include, configuration.yaml."
tools: [read, edit, search, todo, execute]
user-invocable: true
---
You are a Home Assistant specialist for this exact repository.
Your primary goal is to produce reliable, maintainable YAML changes that match this installation's structure and conventions.
## Core Principles
- Target latest Home Assistant behavior and YAML syntax.
- Always prefer YAML-managed configuration over UI-generated storage unless explicitly requested.
- Keep changes minimal, safe, and consistent with nearby files.
- Never move secrets into tracked YAML files; keep secrets in secrets.yaml.
## Repository-Specific Layout
Follow these include rules from configuration.yaml:
- Automations: include/automations/ via include_dir_merge_list
- Scripts: include/scripts/ via include_dir_merge_named
- Shell commands: include/shell_commands/ via include_dir_merge_named
- Templates: include/templates/ via include_dir_merge_list
- Sensors: include/sensors/ via include_dir_merge_list
- Binary sensors: include/binary_sensors/ via include_dir_merge_list
- Inputs live under include/input/*/
When adding new logic:
- Put automations in include/automations/*.yaml (do not edit root automations.yaml unless asked).
- Put scripts in include/scripts/*.yaml (do not edit root scripts.yaml unless asked).
- Put shell commands in include/shell_commands/*.yaml.
- Keep Lovelace in YAML mode and edit existing YAML dashboards/views files.
## Automation Authoring Rules
- Use clear alias names and deterministic triggers.
- Prefer idempotent actions and guard conditions to avoid repeated/duplicate notifications.
- Use choose/conditions for branching instead of duplicated automations.
- Reuse existing helper entities and naming patterns where possible.
- Keep time/date logic explicit (timezone-aware assumptions for Europe/Copenhagen).
## YAML Quality Rules
- Preserve existing indentation and style in the target file.
- Avoid broad refactors when only a local change is needed.
- Keep comments short and only where they clarify non-obvious logic.
- If multiple files are touched, ensure references are consistent across automations/scripts/shell commands/dashboard buttons.
## Validation and Safety
- After edits, run a quick repository sanity check (at minimum inspect YAML structure and obvious syntax issues).
- If executable checks are available, run them and report results.
- Call out assumptions or runtime dependencies (custom components, integrations, mobile notify targets).
- Do not remove or overwrite unrelated user changes.
## Output Expectations
When asked to implement changes:
1. Make the YAML edits directly in the correct include files.
2. Explain exactly which files were changed and why.
3. Summarize any validation performed.
4. Mention any manual Home Assistant reload/restart steps required (automation/script reload vs full restart).
+61
View File
@@ -0,0 +1,61 @@
---
name: Madplan
description: "Use when creating or updating weekly dinner meal plans for this family, importing Valdemarsro recipes into Mealie, avoiding duplicates, and producing a Bilka ToGo shopping list. Trigger phrases: madplan, mealie, Valdemarsro, aftensmad, indkøbsliste, Bilka ToGo, ugeplan."
tools: [read, edit, search, web, todo, execute]
user-invocable: true
---
You are a specialist meal-planning agent for this Home Assistant + Mealie setup.
Your goal is to create practical weekly DINNER plans that match family nutrition needs, reuse leftovers smartly, and keep shopping simple.
## Family Profile
- Family of 4: Anne (49), Claus (54), Andreas (16), Daniel (14).
- Andreas: wants high protein and fewer carbs.
- Daniel: elite swimmer, trains 7x/week, needs high carbs.
- Anne and Claus: weight maintenance.
## Fixed Weekly Structure
- Dinner only.
- Sunday: cook double portion; serve leftovers Tuesday.
- Monday: cook double portion; serve leftovers Wednesday.
- Thursday: light, fast, flexible meal (family may eat at different times).
- Friday: "lækker mad" (for example ribeye-style dinner).
- Saturday: "lækker mad" (for example homemade salmon sushi).
## Recipe Source Rules
- Prefer Valdemarsro recipes.
- Always import planned recipes into Mealie by default.
- Fetch recipe pages and map to Mealie-friendly names/slugs before finalizing the plan.
- Never add duplicate recipes already present in Mealie or existing week plan.
- Keep the plan realistic for one weekly grocery run.
## Shopping Rules
- Build one combined weekly shopping list suitable for Bilka ToGo.
- Group clearly by category (produce, meat/fish, dairy, pantry, frozen, etc.).
- Use plain item names and practical quantities for 4 people, accounting for double-portion days.
- If exact quantities are unknown from source, provide best-effort estimates and mark them clearly.
## Workflow
1. Read current Mealie data and detect already planned dinners and existing imported recipes.
2. Propose or update a week plan that follows the fixed Sunday-Monday leftovers pattern.
3. Validate nutrition balance across the week:
- Ensure protein-forward options exist for Andreas.
- Ensure carb availability for Daniel (sides/add-ons where relevant).
- Keep Anne/Claus portions and sides weight-maintenance friendly.
4. Import all missing planned recipes into Mealie references before publishing the final plan.
5. Produce a Bilka ToGo shopping list for the final plan.
## Output Format
Always return:
1. Weekly dinner plan (Sun-Sat) with leftover links Tue/Wed.
2. Brief nutrition notes per day (protein/carbs adjustment hints per person).
3. "Imported to Mealie" section (added vs skipped as duplicate).
4. Bilka ToGo shopping list grouped by category.
5. Optional prep plan for Friday pickup/delivery and Sunday prep.
## Constraints
- Do not plan breakfast/lunch unless explicitly requested.
- Do not ignore the fixed leftovers pattern.
- Do not leave planned recipes unimported in Mealie unless blocked by missing source data.
- Do not include duplicate recipe imports.
- Keep recommendations practical for a busy training week.
+9
View File
@@ -1,5 +1,6 @@
# Ignore everything globally # Ignore everything globally
.DS_Store .DS_Store
._*
custom_components/ custom_components/
blueprints/ blueprints/
dwains-dashboard/ dwains-dashboard/
@@ -37,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/
@@ -65,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
@@ -75,4 +81,7 @@ configuration_minimal.yaml
# --- Local helper scripts not used in git --- # --- Local helper scripts not used in git ---
/python_scripts/scene_generator.py /python_scripts/scene_generator.py
# --- Temporary backup workspace ---
/tmp_backups/
/python_scripts/update_climate.py /python_scripts/update_climate.py
-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/
-79
View File
@@ -1,79 +0,0 @@
title: Person Status
path: person-status
icon: mdi:account-group
type: sections
max_columns: 1 # én kolonne for fuld bredde
sections:
# 👨‍👩‍👧‍👦 Personer (2x2 grid)
- type: grid
columns: 2
square: false
cards:
- type: entities
title: Daniel
entities:
- entity: person.daniel_schusler_dethlefsen
- entity: input_select.daniel_status
- type: entities
title: Claus
entities:
- entity: person.claus_dethlefsen
- entity: input_select.claus_status
- type: entities
title: Anne
entities:
- entity: person.anne_schusler_dethlefsen
- entity: input_select.anne_status
- type: entities
title: Andreas
entities:
- entity: person.andreas_schusler_dethlefsen
- entity: input_select.andreas_status
# 🗺️ Popup kort nederst
- type: grid
cards:
- type: custom:button-card
name: Åbn kort
icon: mdi:map
show_state: false
styles:
card:
- border-radius: 16px
- padding: 14px
- background: linear-gradient(135deg, rgba(0,0,0,0.05), rgba(0,0,0,0.1))
icon:
- color: var(--primary-color)
name:
- font-size: 14px
- font-weight: 600
tap_action:
action: call-service
service: browser_mod.popup
service_data:
title: 📍 Familien
size: fullscreen
style:
--popup-border-radius: 20px
--popup-background-color: rgba(0,0,0,0.9)
content:
type: map
default_zoom: 12
hours_to_show: 6
aspect_ratio: 1
entities:
- entity: device_tracker.andreas_iphone_12
name: Andreas
- entity: device_tracker.daniels_iphone_7_3
name: Daniel
- entity: device_tracker.annes_iphone_xs_4
name: Anne
- entity: device_tracker.claus_iphone_15pro
name: Claus
+25
View File
@@ -4,6 +4,31 @@ icon: mdi:bed-king-outline
type: sections type: sections
sections: sections:
- type: grid
cards:
- type: custom:button-card
name: Godnat
icon: mdi:weather-night
tap_action:
action: call-service
service: script.godnat_sovevaerelse
hold_action:
action: call-service
service: light.turn_off
service_data:
entity_id: light.sovevaerelse
styles:
card:
- height: 64px
- background-color: "#1a1a2e"
- color: "#c8b8f0"
icon:
- color: "#c8b8f0"
- width: 24px
name:
- font-size: 14px
- color: "#c8b8f0"
- type: grid - type: grid
cards: cards:
- type: heading - type: heading
+633 -69
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
@@ -64,69 +71,626 @@ cards:
action: call-service action: call-service
service: script.mad_announcement service: script.mad_announcement
# 📅 Ugens madplan # 📅 Madplan: i dag + 6 dage
- type: custom:button-card - type: markdown
entity: sensor.mealie_madplan_ugen content: |-
name: Ugens madplan {%- set ns = namespace(rows="") -%}
show_icon: false {%- set items = state_attr('sensor.mealie_madplan_ugen', 'items') or [] -%}
show_name: true {%- set days = ['Mandag','Tirsdag','Onsdag','Torsdag','Fredag','Lørdag','Søndag'] -%}
show_state: false {%- for offset in range(7) -%}
styles: {%- set day = now().date() + timedelta(days=offset) -%}
card: {%- set ms = items | selectattr('date', 'eq', day.isoformat()) | list -%}
- padding: 0 {%- set m = ms[0] if ms else none -%}
- border-radius: 12px {%- set recipe = m.recipe if m else none -%}
name: {%- set name = recipe.name if recipe else '' -%}
- font-size: 16px {%- set slug = recipe.slug if recipe else '' -%}
- font-weight: bold {%- set label = 'I dag' if offset == 0 else days[day.weekday()] -%}
- padding: 12px 12px 4px 12px {%- if slug -%}
- justify-self: start {%- set ns.rows = ns.rows + "| **" + label + "** | [" + name + "](https://mealie.anneclaus.dk/g/home/r/" + slug + ") |\n" -%}
custom_fields: {%- elif name -%}
week: | {%- set ns.rows = ns.rows + "| **" + label + "** | " + name + " |\n" -%}
[[[ {%- else -%}
if (!entity || !entity.attributes || !entity.attributes.items) {%- set ns.rows = ns.rows + "| **" + label + "** | - |\n" -%}
return '<div>Ingen madplan data</div>'; {%- endif -%}
var items = entity.attributes.items; {%- endfor -%}
var dayNames = ['Søndag','Mandag','Tirsdag','Onsdag','Torsdag','Fredag','Lørdag']; ## Næste 7 dage
var today = new Date().toISOString().slice(0,10);
var meals = {}; | Dag | Ret |
for (var j = 0; j < items.length; j++) { | --- | --- |
var it = items[j]; {{ ns.rows }}
if (it.recipe && it.recipe.name) {
meals[it.date] = { name: it.recipe.name, slug: it.recipe.slug || '' }; # 🎵 Sonos Køkken
} - type: media-control
} entity: media_player.kokken
var now = new Date(); name: Sonos Køkken
var dow = now.getDay();
var monday = new Date(now); - type: grid
monday.setDate(now.getDate() - (dow === 0 ? 6 : dow - 1)); columns: 2
var html = '<div style="width:100%">'; square: false
for (var i = 0; i < 7; i++) { cards:
var d = new Date(monday); - type: custom:button-card
d.setDate(monday.getDate() + i); name: Volumen ned
var dateStr = d.toISOString().slice(0,10); icon: mdi:volume-minus
var dayName = dayNames[d.getDay()]; tap_action:
var meal = meals[dateStr]; action: perform-action
var isToday = dateStr === today; perform_action: media_player.volume_down
var bg = isToday ? 'var(--primary-color)' : 'transparent'; target:
var tc = isToday ? 'white' : 'var(--primary-text-color)'; entity_id: media_player.kokken
var dc = isToday ? 'rgba(255,255,255,0.7)' : 'var(--secondary-text-color)'; hold_action:
var fw = isToday ? 'bold' : 'normal'; action: perform-action
var br = isToday ? '8px' : '0'; perform_action: media_player.volume_down
var bb = isToday ? 'none' : '1px solid var(--divider-color)'; target:
var mn = meal ? meal.name : '-'; entity_id: media_player.kokken
var sl = meal ? meal.slug : ''; styles:
var lnk = sl ? 'http://anneclaus.dk:9925/g/home/r/' + sl : ''; card:
var cur = sl ? 'pointer' : 'default'; - height: 72px
var oc = sl ? ' onclick="window.open(\x27' + lnk + '\x27,\x27_blank\x27)"' : ''; - font-size: 16px
html += '<div style="display:flex;align-items:center;padding:10px 12px;background:' + bg + ';border-radius:' + br + ';border-bottom:' + bb + ';cursor:' + cur + '"' + oc + '>'; - background: var(--primary-color)
html += '<div style="width:70px;font-size:12px;color:' + dc + ';text-transform:uppercase;font-weight:600">' + dayName + '</div>'; icon:
html += '<div style="flex:1;font-size:15px;color:' + tc + ';font-weight:' + fw + '">' + mn + '</div>'; - color: white
html += '</div>'; - width: 36px
} name:
html += '</div>'; - color: white
return html; - font-size: 13px
]]]
extra_styles: | - type: custom:button-card
#week { name: Volumen op
width: 100%; 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
+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
+261 -28
View File
@@ -37,10 +37,15 @@ sections:
name: Netatmo name: Netatmo
- entity: sensor.hue_motion_sensor_2_temperature_2 - entity: sensor.hue_motion_sensor_2_temperature_2
name: Hue name: Hue
- entity: climate.andreas
- type: thermostat attribute: current_temperature
entity: climate.andreas name: Roth aktuelt
name: Andreas - entity: climate.andreas
attribute: temperature
name: Roth mål
stroke_width: 1
curve: stepline
color: "#ff8800"
- type: grid - type: grid
cards: cards:
@@ -72,10 +77,15 @@ sections:
name: Netatmo name: Netatmo
- entity: sensor.hue_motion_sensor_2_temperature - entity: sensor.hue_motion_sensor_2_temperature
name: Hue name: Hue
- entity: climate.daniel
- type: thermostat attribute: current_temperature
entity: climate.daniel name: Roth aktuelt
name: Daniel - entity: climate.daniel
attribute: temperature
name: Roth mål
stroke_width: 1
curve: stepline
color: "#ff8800"
- type: grid - type: grid
cards: cards:
@@ -111,10 +121,15 @@ sections:
name: Skab Claus name: Skab Claus
- entity: sensor.temperature - entity: sensor.temperature
name: Skab Anne name: Skab Anne
- entity: climate.sovev_prelse
- type: thermostat attribute: current_temperature
entity: climate.sovev_prelse name: Roth aktuelt
name: Sovevaerelse - entity: climate.sovev_prelse
attribute: temperature
name: Roth mål
stroke_width: 1
curve: stepline
color: "#ff8800"
- type: grid - type: grid
cards: cards:
@@ -144,10 +159,17 @@ sections:
series: series:
- entity: sensor.kontor_motion_temperatur - entity: sensor.kontor_motion_temperatur
name: Hue name: Hue
- entity: sensor.annes_havesensor_indoor_temperature
- type: thermostat name: Havesensor inde
entity: climate.kontor - entity: climate.kontor
name: Kontor attribute: current_temperature
name: Roth aktuelt
- entity: climate.kontor
attribute: temperature
name: Roth mål
stroke_width: 1
curve: stepline
color: "#ff8800"
- type: grid - type: grid
cards: cards:
@@ -177,10 +199,15 @@ sections:
series: series:
- entity: sensor.gang_sensor_temperature - entity: sensor.gang_sensor_temperature
name: Hue name: Hue
- entity: climate.fordelingsgang
- type: thermostat attribute: current_temperature
entity: climate.fordelingsgang name: Roth aktuelt
name: Gang - entity: climate.fordelingsgang
attribute: temperature
name: Roth mål
stroke_width: 1
curve: stepline
color: "#ff8800"
- type: grid - type: grid
cards: cards:
@@ -188,7 +215,7 @@ sections:
graph_span: 24h graph_span: 24h
header: header:
show: true show: true
title: Bad title: Badeværelse
show_states: true show_states: true
colorize_states: true colorize_states: true
now: now:
@@ -210,6 +237,15 @@ sections:
series: series:
- entity: sensor.bad_motion_sensor_temperature - entity: sensor.bad_motion_sensor_temperature
name: Hue name: Hue
- entity: climate.badevarelse
attribute: current_temperature
name: Ally aktuelt
- entity: climate.badevarelse
attribute: temperature
name: Ally mål
stroke_width: 1
curve: stepline
color: "#ff8800"
- type: grid - type: grid
cards: cards:
@@ -239,6 +275,15 @@ sections:
series: series:
- entity: sensor.stue_motion_temperatur - entity: sensor.stue_motion_temperatur
name: Hue name: Hue
- entity: climate.stue
attribute: current_temperature
name: Ally aktuelt
- entity: climate.stue
attribute: temperature
name: Ally mål
stroke_width: 1
curve: stepline
color: "#ff8800"
- type: grid - type: grid
cards: cards:
@@ -268,13 +313,201 @@ sections:
series: series:
- entity: sensor.forgang_sensor_temperature - entity: sensor.forgang_sensor_temperature
name: Hue name: Hue
- entity: climate.forgang
- type: thermostat attribute: current_temperature
entity: climate.forgang name: Roth aktuelt
name: Forgang - entity: climate.forgang
attribute: temperature
name: Roth mål
stroke_width: 1
curve: stepline
color: "#ff8800"
- type: grid - type: grid
cards: cards:
- type: thermostat - type: custom:apexcharts-card
entity: climate.lille_bad graph_span: 24h
name: Lille bad header:
show: true
title: Lille bad
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: climate.lille_bad
attribute: current_temperature
name: Roth aktuelt
- entity: climate.lille_bad
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: 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
- type: grid
cards:
- type: entities
title: Komforttemperaturer
entities:
- entity: input_number.varme_komfort_andreas
- entity: input_number.varme_komfort_daniel
- entity: input_number.varme_komfort_sovevaerelse
- entity: input_number.varme_komfort_kontor
- entity: input_number.varme_komfort_gang
- entity: input_number.varme_komfort_forgang
- entity: input_number.varme_komfort_lille_bad
- entity: input_number.varme_komfort_badevarelse
- 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
title: Sænkninger og ferie
entities:
- entity: input_datetime.varme_morgen_tid
- entity: input_datetime.varme_aften_tid
- entity: input_number.varme_nat_saenkning
- entity: input_number.varme_vaek_saenkning
- entity: input_number.varme_ferie_temp
- type: button
name: Genberegn varme nu
icon: mdi:refresh
tap_action:
action: call-service
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
- type: grid
cards:
- type: gauge
entity: sensor.fjernvarme_ventil_3_ugers_gennemsnit
name: Anbefalet ventilposition 3 ugers snit (15)
min: 1
max: 5
needle: true
segments:
- from: 1
color: "#44aa44" # grøn: lukket/sommer
- from: 2
color: "#aaaa00" # gul: mildt
- from: 3
color: "#dd8800" # orange: køligt
- from: 4
color: "#cc4400" # rød-orange: koldt
- from: 4.5
color: "#aa0000" # rød: frost
- type: markdown
content: |-
**Anbefalet stilling (3 ugers snit): {{ states('sensor.fjernvarme_ventil_3_ugers_gennemsnit') | float(0) | round(1) }}**
Ø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:
- Roth-fordeler (sauna)
- Fjernvarme indstikning (bryggers)
+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
@@ -3,7 +3,7 @@ path: lys
icon: mdi:lightbulb-multiple icon: mdi:lightbulb-multiple
type: sections type: sections
max_columns: 2 max_columns: 3
sections: sections:
- type: grid - type: grid
@@ -150,7 +150,7 @@ sections:
data: data:
entity_id: script.raketloop entity_id: script.raketloop
- type: grid - type: grid
columns: 2 columns: 3
square: false square: false
cards: cards:
- type: custom:mushroom-light-card - type: custom:mushroom-light-card
@@ -464,7 +464,7 @@ sections:
- light.spejl2 - light.spejl2
- light.badevaerelse_2 - light.badevaerelse_2
- type: grid - type: grid
columns: 2 columns: 3
square: false square: false
cards: cards:
- type: custom:mushroom-light-card - type: custom:mushroom-light-card
@@ -569,7 +569,7 @@ sections:
show_color_temp_control: true show_color_temp_control: true
collapsible_controls: true collapsible_controls: true
- type: grid - type: grid
columns: 2 columns: 3
square: false square: false
cards: cards:
- type: custom:mushroom-light-card - type: custom:mushroom-light-card
@@ -647,7 +647,7 @@ sections:
data: data:
entity_id: script.have_color_scene entity_id: script.have_color_scene
- type: grid - type: grid
columns: 2 columns: 3
square: false square: false
cards: cards:
- type: custom:mushroom-light-card - type: custom:mushroom-light-card
@@ -711,7 +711,7 @@ sections:
show_brightness_control: true show_brightness_control: true
collapsible_controls: true collapsible_controls: true
- type: grid - type: grid
columns: 2 columns: 3
square: false square: false
cards: cards:
- type: custom:mushroom-light-card - type: custom:mushroom-light-card
@@ -756,7 +756,7 @@ sections:
- type: heading - type: heading
heading: Ovrige heading: Ovrige
- type: grid - type: grid
columns: 2 columns: 3
square: false square: false
cards: cards:
- type: custom:mushroom-light-card - type: custom:mushroom-light-card
@@ -779,3 +779,64 @@ sections:
use_light_color: true use_light_color: true
show_brightness_control: true show_brightness_control: true
collapsible_controls: true collapsible_controls: true
- type: grid
cards:
- type: heading
heading: Motion-indstillinger
- type: entities
title: Gang
entities:
- entity: input_number.gang_lux_threshold
name: Lux grænse
- entity: input_number.gang_timeout_day
name: Timeout dag
- entity: input_number.gang_timeout_night
name: Timeout nat
- type: entities
title: Badeværelse
entities:
- entity: input_number.badevaerelse_timeout_day
name: Timeout dag
- entity: input_number.badevaerelse_timeout_night
name: Timeout nat
- type: entities
title: Andreas
entities:
- entity: input_number.andreas_lux_threshold
name: Lux grænse
- entity: input_number.andreas_timeout
name: Timeout
- entity: input_number.andreas_brightness
name: Lysstyrke
- type: entities
title: Daniel
entities:
- entity: input_number.daniel_lux_threshold
name: Lux grænse
- entity: input_number.daniel_timeout
name: Timeout
- entity: input_number.daniel_brightness
name: Lysstyrke
- type: entities
title: Kontor
entities:
- entity: input_number.kontor_lux_threshold
name: Lux grænse
- entity: input_number.kontor_timeout_day
name: Timeout dag
- entity: input_number.kontor_timeout_night
name: Timeout nat
- type: entities
title: Stue
entities:
- entity: input_number.stue_lux_threshold
name: Lux grænse
- entity: input_number.stue_timeout_morgen
name: Timeout morgen (06-16)
- entity: input_number.stue_timeout_eftermiddag
name: Timeout eftermiddag (16-19)
- entity: input_number.stue_timeout_aften
name: Timeout aften/TV off (19-00)
- entity: input_number.stue_timeout_nat
name: Timeout nat (00-06)
+1
View File
@@ -4,6 +4,7 @@ services:
image: homeassistant/home-assistant:latest image: homeassistant/home-assistant:latest
volumes: volumes:
- /volume1/homeassistant:/config - /volume1/homeassistant:/config
- /volume1/docker/homeassistant/backups:/backups
devices: devices:
- /dev/ttyUSB0:/dev/ttyUSB0 - /dev/ttyUSB0:/dev/ttyUSB0
network_mode: host network_mode: host
+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
@@ -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}
+8
View File
@@ -0,0 +1,8 @@
services:
unifi:
container_name: jacobalberty-unifi-1
image: jacobalberty/unifi:latest
restart: always
network_mode: host
volumes:
- /volume1/docker/unifi:/var/lib/unifi
+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
+6
View File
@@ -0,0 +1,6 @@
# Google Keep basis-indkøbsliste
# En vare per linje. Linjer der starter med # ignoreres.
# Eksempel:
# Mælk
# Toiletpapir
# Bananer
+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*
+97 -32
View File
@@ -1,44 +1,35 @@
# Ø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.
--- ---
## Høj prioritet ## Høj prioritet
### Zigbee Smart Plugs (med energimåling) ### Zigbee Termostater 1× Danfoss Ally TRV
| Antal | Beskrivelse | Anvendelse | Status |
|---|---|---|---|
| ~8-10 | Zigbee smart plugs med energimåling (fx Nous A1Z, IKEA TRETAKT, Innr SP 242) | Sonos-styring, mesh-udvidelse, strømmåling | ⬜ Ønsket |
**Eksisterende:** 4× Nous A1Z Smart Plug (Zigbee 3.0) + 2× Hue On/Off Plug (udetræer)
**Formål 1 Mesh-udvidelse:**
- Zigbee-plugs fungerer som routere og forstærker mesh-netværket
- Mål: dækning helt ud til postkasse, garage og drivhus
- Placér plugs strategisk som "stepping stones" fra huset og ud
- Sonoff-enhederne (WiFi) hjælper ikke på Zigbee-mesh erstat evt. med Zigbee-plugs over tid
**Formål 2 Sonos genstart-automation:**
- 8 Sonos-højttalere tilsluttes Zigbee smart plugs
- Køkken og soveværelse kræver sandsynligvis indbygget mini-relæ i stikkontakt i stedet for smart plug
- Muliggør automatisk power-cycle i korrekt rækkefølge (sluk alle → vent → tænd én ad gangen)
- Løser det kendte problem med Sonos der "hænger" og kræver strømgenstart
- Automation: script der slukker alle plugs, venter 10 sek, tænder dem sekventielt med delay
**Formål 3 Energimåling:**
- Spor strømforbrug på individuelle enheder
- Nous A1Z understøtter energimåling køb samme model for ensartethed
---
### Zigbee Termostater 2× Danfoss Ally TRV
| Antal | Rum | Beskrivelse | Status | | Antal | Rum | Beskrivelse | Status |
|---|---|---|---| |---|---|---|---|
| 1 | Stue | Danfoss Ally TRV (Zigbee) | ⬜ Ønsket | | 1 | Badeværelse | Danfoss Ally TRV (Zigbee) | ✅ Monteret og online (7. maj 2026) |
| 1 | Badeværelse | Danfoss Ally TRV (Zigbee) | ⬜ Ønsket |
**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.
@@ -74,8 +65,57 @@
--- ---
### 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
| Antal | Placering | Beskrivelse | Status |
|---|---|---|---|
| 4 | Køkken | Aqara kontaktsensor (vindue) | ⬜ Ønsket |
| 2 | Bryggers | Aqara kontaktsensor (vindue) | ⬜ Ønsket |
| 4 | Forgang | Aqara kontaktsensor (vindue/dør) | ⬜ Ø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:** 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.
**Bemærk:** Afklar præcis placering (vinduesramme vs. dørfals) inden køb, særligt forgang og fordør/bagdør.
---
### Mere RAM til Synology NAS
| Antal | Beskrivelse | Status |
|---|---|---|
| 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.
**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.
---
### Idéer til fremtidige udvidelser ### Idéer til fremtidige udvidelser
| Enhed | Beskrivelse | Bemærkninger | | Enhed | Beskrivelse | Bemærkninger |
@@ -97,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 ✅
@@ -105,4 +169,5 @@
| Dato | Enhed | Antal | Bemærkninger | | Dato | Enhed | Antal | Bemærkninger |
|---|---|---|---| |---|---|---|---|
| — | — | | | | April 2026 | Nous A1Z Smart Plug (Zigbee, energimåling) | 8 | Til Sonos + mesh-udvidelse |
| April 2026 | Danfoss Ally TRV (Zigbee) | 1 | Stue installeret |
+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`
+20 -24
View File
@@ -7,6 +7,10 @@
- condition: state - condition: state
entity_id: binary_sensor.arbejdsdagimorgen entity_id: binary_sensor.arbejdsdagimorgen
state: 'on' state: 'on'
- condition: template
value_template: >-
{{ not is_state('input_select.anne_status', 'syg') and
not is_state('input_select.claus_status', 'syg') }}
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
entity_id: switch.sonos_alarm_1782 entity_id: switch.sonos_alarm_1782
@@ -31,6 +35,8 @@
- condition: state - condition: state
entity_id: binary_sensor.arbejdsdagimorgen entity_id: binary_sensor.arbejdsdagimorgen
state: 'on' state: 'on'
- condition: template
value_template: "{{ not is_state('input_select.andreas_status', 'syg') }}"
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
entity_id: switch.sonos_alarm_445 entity_id: switch.sonos_alarm_445
@@ -58,6 +64,8 @@
value_template: '{{ now().date() | string != "2022-12-24" }}' value_template: '{{ now().date() | string != "2022-12-24" }}'
- condition: template - condition: template
value_template: '{{ now().date() | string != "2022-12-31" }}' value_template: '{{ now().date() | string != "2022-12-31" }}'
- condition: template
value_template: "{{ not is_state('input_select.andreas_status', 'syg') }}"
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
entity_id: switch.sonos_alarm_1874 entity_id: switch.sonos_alarm_1874
@@ -78,6 +86,8 @@
- condition: state - condition: state
entity_id: binary_sensor.arbejdsdagimorgen entity_id: binary_sensor.arbejdsdagimorgen
state: 'on' state: 'on'
- condition: template
value_template: "{{ not is_state('input_select.daniel_status', 'syg') }}"
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
entity_id: switch.sonos_alarm_377 entity_id: switch.sonos_alarm_377
@@ -103,6 +113,8 @@
- condition: state - condition: state
entity_id: binary_sensor.arbejdsdagimorgen entity_id: binary_sensor.arbejdsdagimorgen
state: 'on' state: 'on'
- condition: template
value_template: "{{ not is_state('input_select.daniel_status', 'syg') }}"
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
entity_id: switch.sonos_alarm_1894 entity_id: switch.sonos_alarm_1894
@@ -128,6 +140,8 @@
- condition: state - condition: state
entity_id: binary_sensor.arbejdsdagimorgen entity_id: binary_sensor.arbejdsdagimorgen
state: 'on' state: 'on'
- condition: template
value_template: "{{ not is_state('input_select.daniel_status', 'syg') }}"
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
entity_id: switch.sonos_alarm_2273 entity_id: switch.sonos_alarm_2273
@@ -155,6 +169,8 @@
value_template: '{{ now().date() | string != "2022-12-24" }}' value_template: '{{ now().date() | string != "2022-12-24" }}'
- condition: template - condition: template
value_template: '{{ now().date() | string != "2022-12-31" }}' value_template: '{{ now().date() | string != "2022-12-31" }}'
- condition: template
value_template: "{{ not is_state('input_select.daniel_status', 'syg') }}"
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
entity_id: switch.sonos_alarm_3471 entity_id: switch.sonos_alarm_3471
@@ -177,6 +193,10 @@
- condition: state - condition: state
entity_id: binary_sensor.arbejdsdagimorgen entity_id: binary_sensor.arbejdsdagimorgen
state: 'on' state: 'on'
- condition: template
value_template: >-
{{ not is_state('input_select.anne_status', 'syg') and
not is_state('input_select.claus_status', 'syg') }}
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
entity_id: switch.sonos_alarm_298 entity_id: switch.sonos_alarm_298
@@ -194,27 +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'
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
+18 -141
View File
@@ -55,53 +55,12 @@
sequence: sequence:
- parallel: - parallel:
- sequence: - sequence:
- choose: - variables:
- conditions: lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
- condition: time - service: script.spil_paa_lille_bad
after: '20:00:00' data:
before: '06:00:00' lydfil: german-shephard.mp3
sequence: volumen: "{{ lille_bad_volumen }}"
- service: media_player.volume_set
data:
volume_level: 0.5
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/german-shephard.mp3
media_content_type: audio/mpeg
metadata:
title: german-shephard.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
default:
- service: media_player.volume_set
data:
volume_level: 0.8
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/german-shephard.mp3
media_content_type: audio/mpeg
metadata:
title: german-shephard.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
- sequence: - sequence:
- service: media_player.volume_set - service: media_player.volume_set
data: data:
@@ -165,53 +124,12 @@
sequence: sequence:
- parallel: - parallel:
- sequence: - sequence:
- choose: - variables:
- conditions: lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
- condition: time - service: script.spil_paa_lille_bad
after: '20:00:00' data:
before: '06:00:00' lydfil: dog-barking-2-bullmastiff.mp3
sequence: volumen: "{{ lille_bad_volumen }}"
- service: media_player.volume_set
data:
volume_level: 0.5
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/dog-barking-2-bullmastiff.mp3
media_content_type: audio/mpeg
metadata:
title: dog-barking-2-bullmastiff.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
default:
- service: media_player.volume_set
data:
volume_level: 0.8
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/dog-barking-2-bullmastiff.mp3
media_content_type: audio/mpeg
metadata:
title: dog-barking-2-bullmastiff.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
- sequence: - sequence:
- service: tts.speak - service: tts.speak
target: target:
@@ -233,53 +151,12 @@
sequence: sequence:
- parallel: - parallel:
- sequence: - sequence:
- choose: - variables:
- conditions: lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
- condition: time - service: script.spil_paa_lille_bad
after: '20:00:00' data:
before: '06:00:00' lydfil: two-tone-chime.mp3
sequence: volumen: "{{ lille_bad_volumen }}"
- service: media_player.volume_set
data:
volume_level: 0.5
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/two-tone-chime.mp3
media_content_type: audio/mpeg
metadata:
title: two-tone-chime.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
default:
- service: media_player.volume_set
data:
volume_level: 0.8
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/two-tone-chime.mp3
media_content_type: audio/mpeg
metadata:
title: two-tone-chime.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
- sequence: - sequence:
- service: tts.speak - service: tts.speak
target: target:
+43 -189
View File
@@ -44,53 +44,13 @@
sequence: sequence:
- parallel: - parallel:
- sequence: - sequence:
- choose: - variables:
- conditions: lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
- condition: time lille_bad_lydfil: "{{ 'Halloween-doorbell.mp3' if (now().hour >= 20 or now().hour < 6) else 'doorbell.mp3' }}"
after: '20:00:00' - service: script.spil_paa_lille_bad
before: '06:00:00' data:
sequence: lydfil: "{{ lille_bad_lydfil }}"
- service: media_player.volume_set volumen: "{{ lille_bad_volumen }}"
data:
volume_level: 0.5
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/Halloween-doorbell.mp3
media_content_type: audio/mpeg
metadata:
title: Halloween-doorbell.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
default:
- service: media_player.volume_set
data:
volume_level: 0.8
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/doorbell.mp3
media_content_type: audio/mpeg
metadata:
title: doorbell.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
- sequence: - sequence:
- service: tts.speak - service: tts.speak
target: target:
@@ -120,60 +80,36 @@
- delay: 2 - delay: 2
- service: scene.turn_on - service: scene.turn_on
entity_id: scene.before entity_id: scene.before
- sequence:
- condition: template
value_template: >
{{ states('sensor.forgang_sensor_illuminance') | int < 60 }}
- choose:
- conditions:
- condition: time
after: "06:00:00"
before: "23:59:00"
sequence:
- service: scene.turn_on
target:
entity_id: scene.forgang_bright
default:
- service: scene.turn_on
target:
entity_id: scene.forgang_dimmed
- conditions: - conditions:
- condition: template - condition: template
value_template: '{{ trigger.payload_json.event == "SS" }}' value_template: '{{ trigger.payload_json.event == "SS" }}'
sequence: sequence:
- parallel: - parallel:
- sequence: - sequence:
- choose: - variables:
- conditions: lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
- condition: time - service: script.spil_paa_lille_bad
after: '20:00:00' data:
before: '06:00:00' lydfil: german-shephard.mp3
sequence: volumen: "{{ lille_bad_volumen }}"
- service: media_player.volume_set
data:
volume_level: 0.5
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/german-shephard.mp3
media_content_type: audio/mpeg
metadata:
title: german-shephard.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
default:
- service: media_player.volume_set
data:
volume_level: 0.8
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/german-shephard.mp3
media_content_type: audio/mpeg
metadata:
title: german-shephard.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
- sequence: - sequence:
- service: tts.speak - service: tts.speak
target: target:
@@ -195,53 +131,12 @@
sequence: sequence:
- parallel: - parallel:
- sequence: - sequence:
- choose: - variables:
- conditions: lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
- condition: time - service: script.spil_paa_lille_bad
after: '20:00:00' data:
before: '06:00:00' lydfil: dog-barking-2-bullmastiff.mp3
sequence: volumen: "{{ lille_bad_volumen }}"
- service: media_player.volume_set
data:
volume_level: 0.5
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/dog-barking-2-bullmastiff.mp3
media_content_type: audio/mpeg
metadata:
title: dog-barking-2-bullmastiff.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
default:
- service: media_player.volume_set
data:
volume_level: 0.8
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/dog-barking-2-bullmastiff.mp3
media_content_type: audio/mpeg
metadata:
title: dog-barking-2-bullmastiff.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
- sequence: - sequence:
- service: tts.speak - service: tts.speak
target: target:
@@ -263,53 +158,12 @@
sequence: sequence:
- parallel: - parallel:
- sequence: - sequence:
- choose: - variables:
- conditions: lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
- condition: time - service: script.spil_paa_lille_bad
after: '20:00:00' data:
before: '06:00:00' lydfil: two-tone-chime.mp3
sequence: volumen: "{{ lille_bad_volumen }}"
- service: media_player.volume_set
data:
volume_level: 0.5
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/two-tone-chime.mp3
media_content_type: audio/mpeg
metadata:
title: two-tone-chime.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
default:
- service: media_player.volume_set
data:
volume_level: 0.8
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/two-tone-chime.mp3
media_content_type: audio/mpeg
metadata:
title: two-tone-chime.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
- sequence: - sequence:
- service: tts.speak - service: tts.speak
target: target:
+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:
+4
View File
@@ -40,6 +40,10 @@
{{ 1000 <= t <= 2030 }} {{ 1000 <= t <= 2030 }}
{% endif %} {% endif %}
# Ikke syg
- condition: template
value_template: "{{ not is_state('input_select.andreas_status', 'syg') }}"
sequence: sequence:
- service: light.turn_on - service: light.turn_on
+126 -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)
@@ -49,10 +79,104 @@
- platform: state - platform: state
entity_id: binary_sensor.badevaerelse_bevaegelse entity_id: binary_sensor.badevaerelse_bevaegelse
to: "off" to: "off"
for:
minutes: 3 # Standard nat-timeout, kan ændres til 10 for dag, osv. variables:
is_dag: >
{% set t = now().strftime('%H%M') | int %}
{% if is_state('binary_sensor.arbejdsdag', 'on') %}
{{ 600 <= t < 2200 }}
{% else %}
{{ 800 <= t < 2200 }}
{% endif %}
action: action:
- delay:
minutes: >
{% if is_state('input_boolean.badevaerelse_manuel_tilstand', 'on') %}
10
{% elif is_dag %}
{{ states('input_number.badevaerelse_timeout_day') | int }}
{% else %}
{{ states('input_number.badevaerelse_timeout_night') | int }}
{% endif %}
- condition: state
entity_id: binary_sensor.badevaerelse_bevaegelse
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
+53 -87
View File
@@ -1,94 +1,60 @@
# - alias: 'Lys Daniel dag - arbejdsdag - sunrise' - id: daniel_motion_lys
# trigger: alias: Daniel lys via bevaegelse
# platform: time mode: restart
# at: '06:00:00'
# condition:
# - condition: state
# entity_id: binary_sensor.arbejdsdag
# state: 'on'
# action:
# - service: script.sunrise
trigger:
- alias: 'Lys Daniel dag - arbejdsdag' - platform: state
trigger:
platform: state
entity_id: binary_sensor.hue_motion_sensor_2_motion entity_id: binary_sensor.hue_motion_sensor_2_motion
to: 'on' to: "on"
condition: id: motion_on
- condition: numeric_state
entity_id: sensor.hue_motion_sensor_2_illuminance
below: 90
- condition: state
entity_id: binary_sensor.arbejdsdag
state: 'on'
- condition: time
after: '06:30:00'
before: '19:30:00'
action:
- service: homeassistant.turn_on
target:
entity_id: light.daniels_vaerelse
data:
brightness_pct: 100
- alias: 'Lys Daniel dag - ikke arbejdsdag' - platform: state
trigger:
platform: state
entity_id: binary_sensor.hue_motion_sensor_2_motion entity_id: binary_sensor.hue_motion_sensor_2_motion
to: 'on' to: "off"
condition: id: motion_off
- condition: numeric_state
entity_id: sensor.hue_motion_sensor_2_illuminance
below: 90
- condition: state
entity_id: binary_sensor.arbejdsdag
state: 'off'
- condition: time
after: '10:00:00'
before: '19:45:00'
action:
- service: homeassistant.turn_on
target:
entity_id: light.daniels_vaerelse
data:
brightness_pct: 100
# - alias: 'Lys Daniel aften'
# trigger:
# platform: state
# entity_id: binary_sensor.hue_motion_sensor_2_motion
# to: 'on'
# condition:
# - condition: numeric_state
# entity_id: sensor.hue_motion_sensor_2_illuminance
# below: 90
# - condition: time
# after: '20:00:01'
# before: '21:00:00'
# action:
# - service: homeassistant.turn_on
# target:
# entity_id: light.daniels_vaerelse
# data:
# brightness_pct: 50
variables:
lux_limit: "{{ states('input_number.daniel_lux_threshold') | int }}"
brightness: "{{ states('input_number.daniel_brightness') | int }}"
timeout: "{{ states('input_number.daniel_timeout') | int }}"
- alias: 'Sluk lys i Daniel' action:
trigger: - choose:
platform: state
entity_id: binary_sensor.hue_motion_sensor_2_motion
to: 'off'
for:
minutes: 10
condition:
- condition: time
after: '05:00:00'
before: '22:00:00'
action:
- service: homeassistant.turn_off
data:
entity_id:
- light.daniels_vaerelse
# Motion on: taend lys hvis lux lavt og indenfor tidsvindue
- conditions:
- condition: trigger
id: motion_on
- condition: template
value_template: >
{{ states('sensor.hue_motion_sensor_2_illuminance') | int < lux_limit }}
- condition: template
value_template: >
{% set t = now().strftime('%H%M') | int %}
{% if is_state('binary_sensor.arbejdsdag', 'on') %}
{{ 630 <= t < 1930 }}
{% else %}
{{ 1000 <= t < 1945 }}
{% endif %}
- condition: template
value_template: "{{ not is_state('input_select.daniel_status', 'syg') }}"
sequence:
- service: light.turn_on
target:
entity_id: light.daniels_vaerelse
data:
brightness_pct: "{{ brightness }}"
# Motion off: vent timeout, sluk hvis stadig ingen bevaegelse
- conditions:
- condition: trigger
id: motion_off
sequence:
- delay:
minutes: "{{ timeout }}"
- condition: state
entity_id: binary_sensor.hue_motion_sensor_2_motion
state: "off"
- service: light.turn_off
target:
entity_id: light.daniels_vaerelse
+62 -165
View File
@@ -1,171 +1,68 @@
- alias: 'Lys i gang - arbejdsdag - dag' - id: gang_motion_lys
trigger: alias: Gang lys via bevægelse
platform: state mode: restart
entity_id: binary_sensor.gang_sensor_motion
to: 'on'
condition:
- condition: state
entity_id: binary_sensor.arbejdsdag
state: 'on'
- condition: numeric_state
entity_id: sensor.gang_sensor_illuminance
below: '70'
- condition: time
before: '21:30:00'
- condition: time
after: '06:30:00'
action:
- service: scene.turn_on
data:
entity_id: scene.gang_bright
# brightness: 255
# color_temp: 396
- alias: 'Sluk Lys i gang - arbejdsdag - dag'
trigger:
platform: state
entity_id: binary_sensor.gang_sensor_motion
to: 'off'
for:
minutes: 3
condition:
- condition: state
entity_id: binary_sensor.arbejdsdag
state: 'on'
- condition: time
after: '06:30:10'
- condition: time
before: '21:30:00'
action:
service: light.turn_off
data:
entity_id: light.gang
- alias: 'Lys i gang - ikke arbejdsdag - dag' trigger:
trigger: - platform: state
platform: state
entity_id: binary_sensor.gang_sensor_motion entity_id: binary_sensor.gang_sensor_motion
to: 'on' to: "on"
condition: id: motion_on
- condition: state
entity_id: binary_sensor.arbejdsdag
state: 'off'
- condition: numeric_state
entity_id: sensor.gang_sensor_illuminance
below: 70
- condition: time
after: '08:00:00'
- condition: time
before: '22:00:00'
action:
- service: scene.turn_on
data:
entity_id: scene.gang_bright
# brightness: 255
# color_temp: 396
- alias: 'Sluk Lys i gang - ikke arbejdsdag - dag'
trigger:
platform: state
entity_id: binary_sensor.gang_sensor_motion
to: 'off'
for:
minutes: 3
condition:
- condition: state
entity_id: binary_sensor.arbejdsdag
state: 'off'
- condition: time
after: '08:00:10'
- condition: time
before: '22:00:00'
action:
service: light.turn_off
target:
entity_id: light.gang
########## NIGHT - platform: state
entity_id: binary_sensor.gang_sensor_motion
to: "off"
id: motion_off
- alias: 'Lys i gang - arbejdsdag - nat' variables:
trigger: lux_limit: "{{ states('input_number.gang_lux_threshold') | int }}"
platform: state is_dag: >
entity_id: binary_sensor.gang_sensor_motion {% set t = now().strftime('%H%M') | int %}
to: 'on' {% if is_state('binary_sensor.arbejdsdag', 'on') %}
condition: {{ 630 <= t < 2130 }}
- condition: state {% else %}
entity_id: binary_sensor.arbejdsdag {{ 800 <= t < 2200 }}
state: 'on' {% endif %}
- condition: numeric_state
entity_id: sensor.gang_sensor_illuminance
below: '70'
- condition: time
before: '06:30:00'
after: '21:30:00'
action:
- service: scene.turn_on
data:
entity_id: scene.gang_daempet_nat
# brightness_pct: 1
# color_temp: 396
- alias: 'Sluk Lys i gang - arbejdsdag - nat'
trigger:
platform: state
entity_id: binary_sensor.gang_sensor_motion
to: 'off'
for:
minutes: 1
condition:
- condition: state
entity_id: binary_sensor.arbejdsdag
state: 'on'
- condition: time
after: '21:30:10'
before: '06:30:00'
action:
service: light.turn_off
target:
entity_id: light.gang
- alias: 'Lys i gang - ikke arbejdsdag - nat' action:
trigger: - choose:
platform: state
entity_id: binary_sensor.gang_sensor_motion
to: 'on'
condition:
- condition: state
entity_id: binary_sensor.arbejdsdag
state: 'off'
# - condition: numeric_state
# entity_id: sensor.gang_sensor_light_level
# below: '70'
- condition: time
after: '22:00:00'
before: '08:00:00'
action:
- service: scene.turn_on
data:
entity_id: scene.gang_daempet_nat
# brightness_pct: 1
# color_temp: 396
- alias: 'Sluk Lys i gang - ikke arbejdsdag - nat'
trigger:
platform: state
entity_id: binary_sensor.gang_sensor_motion
to: 'off'
for:
minutes: 1
condition:
- condition: state
entity_id: binary_sensor.arbejdsdag
state: 'off'
- condition: time
after: '22:00:10'
before: '08:00:00'
action:
- service: light.turn_off
target:
entity_id: light.gang
# Motion on - dag: taend bright scene hvis lux er lavt nok
- conditions:
- condition: trigger
id: motion_on
- condition: template
value_template: "{{ is_dag }}"
- condition: template
value_template: >
{{ states('sensor.gang_sensor_illuminance') | int < lux_limit }}
sequence:
- service: scene.turn_on
target:
entity_id: scene.gang_bright
# Motion on - nat: taend daempet scene (ingen lux-check)
- conditions:
- condition: trigger
id: motion_on
- condition: template
value_template: "{{ not is_dag }}"
sequence:
- service: scene.turn_on
target:
entity_id: scene.gang_daempet_nat
# Motion off: vent timeout, sluk hvis stadig ingen bevaegelse
- conditions:
- condition: trigger
id: motion_off
sequence:
- delay:
minutes: >
{{ states('input_number.gang_timeout_day') | int if is_dag
else states('input_number.gang_timeout_night') | int }}
- condition: state
entity_id: binary_sensor.gang_sensor_motion
state: "off"
- service: light.turn_off
target:
entity_id: light.gang
+8 -5
View File
@@ -8,9 +8,10 @@
to: "on" to: "on"
condition: condition:
- condition: numeric_state - condition: template
entity_id: sensor.kontor_belysningsstyrke value_template: >
below: 60 {{ states('sensor.kontor_belysningsstyrke') | int <
states('input_number.kontor_lux_threshold') | int }}
action: action:
- service: scene.turn_on - service: scene.turn_on
@@ -36,7 +37,8 @@
- platform: state - platform: state
entity_id: binary_sensor.kontor_motion_bevaegelse entity_id: binary_sensor.kontor_motion_bevaegelse
to: "on" to: "on"
timeout: "00:10:00" timeout:
minutes: "{{ states('input_number.kontor_timeout_day') | int }}"
continue_on_timeout: true continue_on_timeout: true
- condition: state - condition: state
entity_id: binary_sensor.kontor_motion_bevaegelse entity_id: binary_sensor.kontor_motion_bevaegelse
@@ -67,7 +69,8 @@
- platform: state - platform: state
entity_id: binary_sensor.kontor_motion_bevaegelse entity_id: binary_sensor.kontor_motion_bevaegelse
to: "on" to: "on"
timeout: "00:05:00" timeout:
minutes: "{{ states('input_number.kontor_timeout_night') | int }}"
continue_on_timeout: true continue_on_timeout: true
- condition: state - condition: state
entity_id: binary_sensor.kontor_motion_bevaegelse entity_id: binary_sensor.kontor_motion_bevaegelse
+15
View File
@@ -6,6 +6,10 @@
- condition: state - condition: state
entity_id: binary_sensor.arbejdsdag entity_id: binary_sensor.arbejdsdag
state: 'on' state: 'on'
- condition: template
value_template: >-
{{ not is_state('input_select.anne_status', 'syg') and
not is_state('input_select.claus_status', 'syg') }}
action: action:
- service: script.sunrise - service: script.sunrise
@@ -25,6 +29,10 @@
- condition: time - condition: time
after: '06:30:00' after: '06:30:00'
before: '20:00:00' before: '20:00:00'
- condition: template
value_template: >-
{{ not is_state('input_select.anne_status', 'syg') and
not is_state('input_select.claus_status', 'syg') }}
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
target: target:
@@ -47,6 +55,10 @@
- condition: time - condition: time
after: '10:00:00' after: '10:00:00'
before: '20:00:00' before: '20:00:00'
- condition: template
value_template: >-
{{ not is_state('input_select.anne_status', 'syg') and
not is_state('input_select.claus_status', 'syg') }}
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
target: target:
@@ -161,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:
+121 -140
View File
@@ -1,157 +1,138 @@
- id: stue_motion_lys - id: stue_motion_lys
alias: Stue lys via bevægelse alias: Stue lys via bevaegelse
mode: restart mode: restart
trigger: trigger:
- platform: state - platform: state
entity_id: binary_sensor.stue_bevaegelse entity_id: binary_sensor.stue_bevaegelse
to: "on" to: "on"
id: motion_on
condition: - platform: state
- condition: numeric_state entity_id: binary_sensor.stue_bevaegelse
entity_id: sensor.stue_belysningsstyrke to: "off"
below: 60 id: motion_off
- platform: state
entity_id: media_player.samsung_s95ca_55_3
to: "off"
id: tv_off
variables:
lux_limit: "{{ states('input_number.stue_lux_threshold') | int }}"
dagperiode: >
{% set t = now().strftime('%H%M') | int %}
{% if 600 <= t < 1600 %}morgen
{% elif 1600 <= t < 1900 %}eftermiddag
{% elif 1900 <= t < 2100 %}aften_lys
{% elif 2100 <= t %}aften
{% else %}nat{% endif %}
timeout_min: >
{% set t = now().strftime('%H%M') | int %}
{% if 600 <= t < 1600 %}
{{ states('input_number.stue_timeout_morgen') | int }}
{% elif 1600 <= t < 1900 %}
{{ states('input_number.stue_timeout_eftermiddag') | int }}
{% elif 1900 <= t < 2100 %}
{{ states('input_number.stue_timeout_aften') | int }}
{% elif 2100 <= t %}
{{ states('input_number.stue_timeout_aften') | int }}
{% else %}
{{ states('input_number.stue_timeout_nat') | int }}
{% endif %}
action: action:
- choose: - choose:
# Motion on: taend lys hvis lux lavt
- conditions: - conditions:
- condition: time - condition: trigger
after: "06:00:00" id: motion_on
before: "16:00:00" - condition: template
value_template: >
{{ states('sensor.stue_belysningsstyrke') | int < lux_limit }}
sequence: sequence:
- service: scene.turn_on - choose:
target: # Gæster: altid Annes favorit uanset tidspunkt
entity_id: scene.stue_bright - conditions:
- condition: state
entity_id: input_boolean.gaester
state: "on"
sequence:
- service: scene.turn_on
target:
entity_id: scene.stue_annes_favorit
- conditions:
- condition: template
value_template: "{{ dagperiode == 'morgen' }}"
sequence:
- service: scene.turn_on
target:
entity_id: scene.stue_bright
- conditions:
- condition: template
value_template: "{{ dagperiode == 'eftermiddag' }}"
sequence:
- service: scene.turn_on
target:
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:
- service: scene.turn_on
target:
entity_id: scene.stue_relax_minus_syd
# Motion off: vent timeout, sluk hvis stadig ingen bevaegelse
# Aften: springer over hvis TV er taendt (TV-off trigger haandterer det)
- conditions: - conditions:
- condition: time - condition: trigger
after: "16:00:00" id: motion_off
before: "22:00:00" - condition: template
value_template: >
{{ dagperiode not in ('aften','aften_lys') or
is_state('media_player.samsung_s95ca_55_3', 'off') }}
sequence: sequence:
- service: scene.turn_on - delay:
minutes: "{{ timeout_min }}"
- condition: state
entity_id: binary_sensor.stue_bevaegelse
state: "off"
- condition: template
value_template: >
{{ dagperiode not in ('aften','aften_lys') or
is_state('media_player.samsung_s95ca_55_3', 'off') }}
- service: light.turn_off
target: target:
entity_id: scene.stue_annes_favorit entity_id: light.livingroom
default:
- service: scene.turn_on
target:
entity_id: scene.stue_relax_minus_syd
- id: stue_motion_sluk_morgen # TV slukket om aftenen: vent 10 min, sluk hvis ingen bevaegelse
alias: Sluk stue lys efter 60 min uden bevægelse om morgenen - conditions:
mode: restart - condition: trigger
id: tv_off
trigger: - condition: template
- platform: state value_template: "{{ dagperiode in ('aften','aften_lys') }}"
entity_id: binary_sensor.stue_bevaegelse sequence:
to: "off" - delay:
minutes: "{{ timeout_min }}"
condition: - condition: state
- condition: time entity_id: binary_sensor.stue_bevaegelse
after: "06:00:00" state: "off"
before: "16:00:00" - condition: state
entity_id: media_player.samsung_s95ca_55_3
action: state: "off"
- wait_for_trigger: - service: light.turn_off
- platform: state target:
entity_id: binary_sensor.stue_bevaegelse entity_id: light.livingroom
to: "on"
timeout: "01:00:00"
continue_on_timeout: true
- condition: state
entity_id: binary_sensor.stue_bevaegelse
state: "off"
- service: light.turn_off
target:
entity_id: light.livingroom
- id: stue_motion_sluk_eftermiddag_aften
alias: Sluk stue lys efter 120 min uden bevægelse mellem 16 og 19
mode: restart
trigger:
- platform: state
entity_id: binary_sensor.stue_bevaegelse
to: "off"
condition:
- condition: time
after: "16:00:00"
before: "19:00:00"
action:
- wait_for_trigger:
- platform: state
entity_id: binary_sensor.stue_bevaegelse
to: "on"
timeout: "02:00:00"
continue_on_timeout: true
- condition: state
entity_id: binary_sensor.stue_bevaegelse
state: "off"
- service: light.turn_off
target:
entity_id: light.livingroom
- id: stue_motion_sluk_aften_tv
alias: Sluk stue lys efter 10 min uden bevægelse når TV er slukket mellem 19 og 00
mode: restart
trigger:
- platform: state
entity_id: binary_sensor.stue_bevaegelse
to: "off"
- platform: state
entity_id: media_player.samsung_s95ca_55_3
to: "off"
condition:
- condition: time
after: "19:00:00"
before: "00:00:00"
action:
- condition: state
entity_id: media_player.samsung_s95ca_55_3
state: "off"
- wait_for_trigger:
- platform: state
entity_id: binary_sensor.stue_bevaegelse
to: "on"
timeout: "00:10:00"
continue_on_timeout: true
- condition: state
entity_id: binary_sensor.stue_bevaegelse
state: "off"
- condition: state
entity_id: media_player.samsung_s95ca_55_3
state: "off"
- service: light.turn_off
target:
entity_id: light.livingroom
- id: stue_motion_sluk_nat
alias: Sluk stue lys efter 30 min uden bevægelse om natten
mode: restart
trigger:
- platform: state
entity_id: binary_sensor.stue_bevaegelse
to: "off"
condition:
- condition: time
after: "00:00:00"
before: "06:00:00"
action:
- wait_for_trigger:
- platform: state
entity_id: binary_sensor.stue_bevaegelse
to: "on"
timeout: "00:30:00"
continue_on_timeout: true
- condition: state
entity_id: binary_sensor.stue_bevaegelse
state: "off"
- service: light.turn_off
target:
entity_id: light.livingroom
+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
+13
View File
@@ -1,3 +1,15 @@
- id: mealie_generate_bilka_checklist_wednesday
alias: "Mealie indkøbsliste - onsdag morgen"
trigger:
- platform: time
at: "06:30:00"
condition:
- condition: time
weekday:
- wed
action:
- service: script.mealie_shopping_refresh
- id: mealie_update_mealplan - id: mealie_update_mealplan
alias: "Mealie opdater madplan" alias: "Mealie opdater madplan"
trigger: trigger:
@@ -7,3 +19,4 @@
minutes: "/30" minutes: "/30"
action: action:
- service: shell_command.mealie_update - 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
+87 -5
View File
@@ -1,3 +1,16 @@
- alias: 'Plæneklipper - opdater sidst klippet'
description: 'Opdater input_datetime når klipperen starter (uanset hvem der startede den)'
trigger:
- platform: state
entity_id: lawn_mower.husqvarna_automower
to: mowing
action:
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.ploeneklipper_sidst_koert
data:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
- alias: 'Plæneklipper - start arbejdsdag' - alias: 'Plæneklipper - start arbejdsdag'
description: 'Start plæneklipper kl 9 på arbejdsdage hvis det ikke regner og ingen er hjemme' description: 'Start plæneklipper kl 9 på arbejdsdage hvis det ikke regner og ingen er hjemme'
trigger: trigger:
@@ -38,11 +51,6 @@
- service: lawn_mower.start_mowing - service: lawn_mower.start_mowing
target: target:
entity_id: lawn_mower.husqvarna_automower entity_id: lawn_mower.husqvarna_automower
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.ploeneklipper_sidst_koert
data:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
- service: notify.mobile_app_claus_iphone_15pro - service: notify.mobile_app_claus_iphone_15pro
data: data:
title: "Plæneklipper" title: "Plæneklipper"
@@ -103,6 +111,9 @@
- condition: state - condition: state
entity_id: lawn_mower.husqvarna_automower entity_id: lawn_mower.husqvarna_automower
state: 'mowing' state: 'mowing'
- condition: state
entity_id: input_boolean.ploeneklipper_manuelt_startet
state: 'off'
action: action:
- service: lawn_mower.dock - service: lawn_mower.dock
target: target:
@@ -111,3 +122,74 @@
data: data:
title: "Plæneklipper" title: "Plæneklipper"
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'
description: 'Nulstil manuelt-startet flag når klipperen dokker efter kl. 20 (i dagtimerne håndteres genstarten af genstart-automation)'
trigger:
- platform: state
entity_id: lawn_mower.husqvarna_automower
to: 'docked'
condition:
- condition: time
after: '20:00:00'
action:
- service: input_boolean.turn_off
target:
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
+25 -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) }}.
@@ -153,6 +150,9 @@
- condition: state - condition: state
entity_id: vacuum.roborock_s8_pro_ultra entity_id: vacuum.roborock_s8_pro_ultra
state: "cleaning" state: "cleaning"
- condition: state
entity_id: input_boolean.roborock_manuelt_startet
state: "off"
action: action:
- service: vacuum.return_to_base - service: vacuum.return_to_base
@@ -165,6 +165,21 @@
message: "Rengøring stoppet fordi nogen er kommet hjem." message: "Rengøring stoppet fordi nogen er kommet hjem."
# 🔄 Reset manuelt-startet flag når Roborock dokker
- id: roborock_reset_manuelt_flag
alias: Roborock - Reset manuelt startet flag
trigger:
- platform: state
entity_id: vacuum.roborock_s8_pro_ultra
to: "docked"
action:
- service: input_boolean.turn_off
target:
entity_id: input_boolean.roborock_manuelt_startet
# 🧹 Syd på arbejdsdage # 🧹 Syd på arbejdsdage
- id: roborock_syd_workday_vacuum - id: roborock_syd_workday_vacuum
alias: Roborock støvsug syd på arbejdsdage alias: Roborock støvsug syd på arbejdsdage
@@ -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
+89
View File
@@ -0,0 +1,89 @@
##################################################
# Syg-status: sluk alarmer + motion-lys ved sygdom
#
# Triggeres af input_select.PERSON_status → "syg"
# Genaktiverer alarmer når status ikke længere er "syg"
# Motion-lys håndteres via conditions i lys_*.yaml
##################################################
# ---- 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 ----
- alias: "Syg - Daniel - sluk alarmer"
id: syg_daniel_sluk_alarmer
trigger:
- platform: state
entity_id: input_select.daniel_status
to: "syg"
action:
- service: homeassistant.turn_off
target:
entity_id:
- switch.sonos_alarm_377 # Daniel hverdagsalarm
- switch.sonos_alarm_1894 # Daniel afsted
- switch.sonos_alarm_2273 # Daniel man/fre
- switch.sonos_alarm_3471 # Daniel lørdag
- alias: "Syg - Daniel - genaktiver alarmer"
id: syg_daniel_genaktiver_alarmer
trigger:
- platform: state
entity_id: input_select.daniel_status
not_to: "syg"
condition:
- condition: state
entity_id: binary_sensor.arbejdsdagimorgen
state: "on"
action:
- service: homeassistant.turn_on
target:
entity_id:
- switch.sonos_alarm_377
- switch.sonos_alarm_3471
# ---- Anne / Claus (soveværelse) ----
- alias: "Syg - Soveværelse - sluk alarmer"
id: syg_sovevaerelse_sluk_alarmer
trigger:
- platform: state
entity_id: input_select.anne_status
to: "syg"
- platform: state
entity_id: input_select.claus_status
to: "syg"
action:
- service: homeassistant.turn_off
target:
entity_id:
- switch.sonos_alarm_1782 # Soft wakeup
- switch.sonos_alarm_298 # Badeværelse
- alias: "Syg - Soveværelse - genaktiver alarmer"
id: syg_sovevaerelse_genaktiver_alarmer
trigger:
- platform: state
entity_id: input_select.anne_status
not_to: "syg"
- platform: state
entity_id: input_select.claus_status
not_to: "syg"
condition:
# Kun genaktiver hvis BEGGE ikke er syge
- condition: template
value_template: >
{{ not is_state('input_select.anne_status', 'syg') and
not is_state('input_select.claus_status', 'syg') }}
- condition: state
entity_id: binary_sensor.arbejdsdagimorgen
state: "on"
action:
- service: homeassistant.turn_on
target:
entity_id:
- switch.sonos_alarm_1782
- switch.sonos_alarm_298
+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
+2 -12
View File
@@ -1,12 +1,2 @@
### Set temperature to 24 in heat mode ### Kontor-varme styres nu af script.varme_recalculate (include/scripts/varme_styring.yaml)
- alias: "Tænd varme i kontor" ### Denne fil er beholdt tom for fremtidige manuelle overrides
trigger:
platform: time
at: "19:30:00"
action:
- service: climate.set_temperature
target:
entity_id: climate.kontor
data:
temperature: 24
hvac_mode: heat
+112
View File
@@ -0,0 +1,112 @@
##################################################
# Varme: Triggers der kalder script.varme_recalculate
# Scenarierne der kræver genberegning:
# - Morgen (input_datetime.varme_morgen_tid) - nat slut → komforttemp
# - Aften (input_datetime.varme_aften_tid) - nat start → natsænkning
# - Tilstedeværelse - hjemme/væk skifter
# - Ferietilstand - ferie til/fra
# - HA genstart - genopret korrekt temp
##################################################
- alias: "Varme - Genberegn: Morgen"
id: varme_recalc_morgen
description: "Slutter natsænkning - tidspunkt styret af input_datetime.varme_morgen_tid"
trigger:
- platform: time
at: input_datetime.varme_morgen_tid
action:
- service: script.varme_recalculate
- alias: "Varme - Genberegn: Nat"
id: varme_recalc_nat
description: "Starter natsænkning - tidspunkt styret af input_datetime.varme_aften_tid"
trigger:
- platform: time
at: input_datetime.varme_aften_tid
action:
- service: script.varme_recalculate
- alias: "Varme - Genberegn: Tilstedeværelse"
id: varme_recalc_presence
description: "Justerer temperaturer når family_presence ændres (hjemme/væk)"
trigger:
- platform: state
entity_id: binary_sensor.family_presence
action:
# Kort forsinkelse så person-sensorer er stabile
- delay: "00:01:00"
- service: script.varme_recalculate
- alias: "Varme - Genberegn: Ferie"
id: varme_recalc_vacation
description: "Skifter til ferie-/frosttemperatur når vacation_mode ændres"
trigger:
- platform: state
entity_id: input_boolean.vacation_mode
action:
- service: script.varme_recalculate
- alias: "Varme - Genberegn: HA genstart"
id: varme_recalc_ha_start
description: "Genopret korrekte temperaturer efter HA genstart"
trigger:
- platform: homeassistant
event: start
action:
# Vent til integrationer er loaded
- delay: "00:01:00"
- service: script.varme_recalculate
- alias: "Varme - Genberegn: Komforttemperatur ændret"
id: varme_recalc_komfort_changed
description: "Opdater varmeanlægget straks når en komforttemperatur justeres"
trigger:
- platform: state
entity_id:
- input_number.varme_komfort_andreas
- input_number.varme_komfort_daniel
- input_number.varme_komfort_sovevaerelse
- input_number.varme_komfort_kontor
- input_number.varme_komfort_gang
- input_number.varme_komfort_forgang
- input_number.varme_komfort_lille_bad
- input_number.varme_komfort_badevarelse
- input_number.varme_komfort_stue
action:
- 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"
id: varme_ferie_forvarm
description: >
Slår vacation_mode fra 2 dage inden vacation_end så huset er
varmt ved hjemkomst. Kører dagligt ved morgen-tidspunktet.
trigger:
- platform: time
at: input_datetime.varme_morgen_tid
condition:
- condition: state
entity_id: input_boolean.vacation_mode
state: "on"
- condition: template
value_template: >
{% set end = states('input_datetime.vacation_end') %}
{% if end not in ['unknown', 'unavailable', ''] %}
{{ 0 < (as_timestamp(end) - as_timestamp(now())) < (2 * 86400) }}
{% else %}
false
{% endif %}
action:
- service: input_boolean.turn_off
target:
entity_id: input_boolean.vacation_mode
+14 -33
View File
@@ -2,14 +2,14 @@
# Vindue-automationer (Aqara vinduesensorer) # Vindue-automationer (Aqara vinduesensorer)
################################################## ##################################################
### Varme: Sluk varme når vindue åbnes, tænd igen når det lukkes ### Varme: Genberegn ved vindue-ændring (åbner og lukker)
### Rum-mapping: andreas, daniel, sovevaerelse, lille_bad ### Script slukker klimaenhed hvis vindue er åbent, tænder igen ved lukning
- alias: "Varme - sluk ved åbent vindue" - alias: "Varme - vindue åbner eller lukker"
id: varme_sluk_ved_aabent_vindue id: varme_vindue_trigger
description: "Slukker varme i rummet når vinduet åbnes og genstarter når det lukkes" description: "Kalder varme_recalculate når et vindue eller terrassedøren skifter tilstand"
mode: parallel mode: queued
max: 4 max: 10
trigger: trigger:
- platform: state - platform: state
entity_id: entity_id:
@@ -17,33 +17,14 @@
- binary_sensor.daniel_vindue - binary_sensor.daniel_vindue
- binary_sensor.sovevaerelse_vindue - binary_sensor.sovevaerelse_vindue
- binary_sensor.lille_bad_vindue - binary_sensor.lille_bad_vindue
to: "on" - binary_sensor.badevaerelse_vindue
- binary_sensor.terrassedor
to:
- "on"
- "off"
for: "00:00:05"
action: action:
- variables: - service: script.varme_recalculate
room_map:
binary_sensor.andreas_vindue: climate.andreas
binary_sensor.daniel_vindue: climate.daniel
binary_sensor.sovevaerelse_vindue: climate.sovev_prelse
binary_sensor.lille_bad_vindue: climate.lille_bad
climate_entity: "{{ room_map[trigger.entity_id] }}"
window_entity: "{{ trigger.entity_id }}"
scene_name: "vindue_varme_{{ trigger.entity_id.split('.')[1] }}"
- service: scene.create
data:
scene_id: "{{ scene_name }}"
snapshot_entities:
- "{{ climate_entity }}"
- service: climate.set_hvac_mode
target:
entity_id: "{{ climate_entity }}"
data:
hvac_mode: "off"
- wait_template: "{{ is_state(window_entity, 'off') }}"
timeout: "04:00:00"
continue_on_timeout: true
- service: scene.turn_on
target:
entity_id: "scene.{{ scene_name }}"
### Notifikation: Vindue åbner og ingen er hjemme ### Notifikation: Vindue åbner og ingen er hjemme
+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
+5 -1
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
@@ -0,0 +1,7 @@
roborock_manuelt_startet:
name: Roborock manuelt startet
icon: mdi:robot-vacuum
ploeneklipper_manuelt_startet:
name: Plæneklipper manuelt startet
icon: mdi:robot-mower
+8 -1
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"
+11
View File
@@ -0,0 +1,11 @@
varme_morgen_tid:
name: Varme - Morgen (nat slut)
has_date: false
has_time: true
icon: mdi:weather-sunny
varme_aften_tid:
name: Varme - Aften (nat start)
has_date: false
has_time: true
icon: mdi:weather-night
+1 -1
View File
@@ -4,7 +4,7 @@ badevaerelse_timeout_day:
max: 30 max: 30
step: 1 step: 1
unit_of_measurement: min unit_of_measurement: min
initial: 10 initial: 5
badevaerelse_timeout_night: badevaerelse_timeout_night:
name: Badeværelse lys timeout nat name: Badeværelse lys timeout nat
+23
View File
@@ -0,0 +1,23 @@
daniel_lux_threshold:
name: Daniel lux grænse
min: 0
max: 500
step: 5
unit_of_measurement: lux
initial: 90
daniel_timeout:
name: Daniel lys timeout
min: 1
max: 60
step: 1
unit_of_measurement: min
initial: 10
daniel_brightness:
name: Daniel lysstyrke
min: 1
max: 100
step: 1
unit_of_measurement: "%"
initial: 100
+23
View File
@@ -0,0 +1,23 @@
gang_lux_threshold:
name: Gang lux grænse
min: 0
max: 300
step: 5
unit_of_measurement: lux
initial: 70
gang_timeout_day:
name: Gang timeout dag
min: 1
max: 20
step: 1
unit_of_measurement: min
initial: 3
gang_timeout_night:
name: Gang timeout nat
min: 1
max: 10
step: 1
unit_of_measurement: min
initial: 1
+23
View File
@@ -0,0 +1,23 @@
kontor_lux_threshold:
name: Kontor lux grænse
min: 0
max: 500
step: 5
unit_of_measurement: lux
initial: 60
kontor_timeout_day:
name: Kontor timeout dag
min: 1
max: 60
step: 1
unit_of_measurement: min
initial: 5
kontor_timeout_night:
name: Kontor timeout nat
min: 1
max: 30
step: 1
unit_of_measurement: min
initial: 2
+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
+39
View File
@@ -0,0 +1,39 @@
stue_lux_threshold:
name: Stue lux grænse
min: 0
max: 500
step: 5
unit_of_measurement: lux
initial: 60
stue_timeout_morgen:
name: Stue timeout morgen (06-16)
min: 10
max: 180
step: 5
unit_of_measurement: min
initial: 30
stue_timeout_eftermiddag:
name: Stue timeout eftermiddag (16-19)
min: 10
max: 240
step: 5
unit_of_measurement: min
initial: 120
stue_timeout_aften:
name: Stue timeout aften (19-00, TV slukket)
min: 1
max: 60
step: 1
unit_of_measurement: min
initial: 5
stue_timeout_nat:
name: Stue timeout nat (00-06)
min: 5
max: 120
step: 5
unit_of_measurement: min
initial: 5
+109
View File
@@ -0,0 +1,109 @@
# Komforttemperaturer per rum (Roth gulvvarme)
varme_komfort_andreas:
name: Komfort - Andreas
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 19
icon: mdi:thermometer
varme_komfort_daniel:
name: Komfort - Daniel
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 19
icon: mdi:thermometer
varme_komfort_sovevaerelse:
name: Komfort - Soveværelse
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 20
icon: mdi:thermometer
varme_komfort_kontor:
name: Komfort - Kontor
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 20
icon: mdi:thermometer
varme_komfort_gang:
name: Komfort - Gang
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 20
icon: mdi:thermometer
varme_komfort_forgang:
name: Komfort - Forgang
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 24
icon: mdi:thermometer
varme_komfort_lille_bad:
name: Komfort - Lille bad
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 24
icon: mdi:thermometer
varme_komfort_badevarelse:
name: Komfort - Badeværelse
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 21.5
icon: mdi:thermometer
varme_komfort_stue:
name: Komfort - Stue
min: 15
max: 28
step: 0.5
unit_of_measurement: "°C"
initial: 25
icon: mdi:thermometer
# Globale sænkninger
varme_nat_saenkning:
name: Natsænkning
min: 0
max: 8
step: 0.5
unit_of_measurement: "°C"
initial: 3
icon: mdi:weather-night
varme_vaek_saenkning:
name: Sænkning - ingen hjemme
min: 0
max: 8
step: 0.5
unit_of_measurement: "°C"
initial: 3
icon: mdi:home-export-outline
varme_ferie_temp:
name: Ferietemperatur (frostbeskyttelse)
min: 10
max: 18
step: 0.5
unit_of_measurement: "°C"
initial: 11
icon: mdi:beach
-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
+51
View File
@@ -1,3 +1,54 @@
godnat_sovevaerelse:
alias: Godnat - dæmp lys i soveværelse over 15 min
mode: restart
sequence:
- service: light.turn_on
target:
entity_id:
- light.claus
- light.anne
- light.sov_dor
- light.sov_midt
- light.sov_vindue
data:
brightness_pct: 40
transition: 300
- delay: "00:05:00"
- service: light.turn_on
target:
entity_id:
- light.claus
- light.anne
- light.sov_dor
- light.sov_midt
- light.sov_vindue
data:
brightness_pct: 10
transition: 300
- delay: "00:05:00"
- service: light.turn_on
target:
entity_id:
- light.claus
- light.anne
- light.sov_dor
- light.sov_midt
- light.sov_vindue
data:
brightness_pct: 1
transition: 300
- delay: "00:05:00"
- service: light.turn_off
target:
entity_id:
- light.claus
- light.anne
- light.sov_dor
- light.sov_midt
- light.sov_vindue
data:
transition: 5
bed_standard_colors: bed_standard_colors:
sequence: sequence:
- service: light.turn_on - service: light.turn_on
+7 -47
View File
@@ -10,53 +10,13 @@ doorbell:
sequence: sequence:
- parallel: - parallel:
- sequence: - sequence:
- choose: - variables:
- conditions: lille_bad_volumen: "{{ volumennat if (now().hour >= 20 or now().hour < 6) else volumendag }}"
- condition: time lille_bad_lydfil: "{{ 'doorbell-shortened-100308.mp3' if (now().hour >= 20 or now().hour < 6) else 'doorbell.mp3' }}"
after: '20:00:00' - service: script.spil_paa_lille_bad
before: '06:00:00' data:
sequence: lydfil: "{{ lille_bad_lydfil }}"
- service: media_player.volume_set volumen: "{{ lille_bad_volumen }}"
data:
volume_level: "{{ volumennat }}"
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/doorbell-shortened-100308.mp3
media_content_type: audio/mpeg
metadata:
title: doorbell-shortened-100308.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
default:
- service: media_player.volume_set
data:
volume_level: "{{ volumendag }}"
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: media-source://media_source/local/doorbell.mp3
media_content_type: audio/mpeg
metadata:
title: doorbell.mp3
thumbnail:
media_class: music
children_media_class:
navigateIds:
- {}
- media_content_type: app
media_content_id: media-source://media_source
- sequence: - sequence:
- service: media_player.volume_set - service: media_player.volume_set
data: data:
+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:
+8
View File
@@ -0,0 +1,8 @@
mealie_shopping_refresh:
alias: Mealie shopping refresh
sequence:
- service: shell_command.mealie_shopping_merge
- service: notify.mobile_app_claus_iphone_15pro
data:
title: "Indkøbsliste opdateret i Mealie"
message: "Indkøbslisten 'Bilka ToGo' er opdateret med opskrifter fra fredag til torsdag."
-84
View File
@@ -24,12 +24,6 @@ monthly_standard_colors:
color_name: 'Gold' color_name: 'Gold'
brightness_pct: 100 brightness_pct: 100
transition: 300 transition: 300
- service: light.turn_on
data:
entity_id: light.syd
color_name: 'Gold'
brightness_pct: 80
transition: 300
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -63,12 +57,6 @@ monthly_valentine_colors:
color_name: 'Crimson' color_name: 'Crimson'
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
color_name: 'Crimson'
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -102,12 +90,6 @@ monthly_mardi_gras_colors:
color_name: 'Purple' color_name: 'Purple'
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
color_name: 'Green'
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -141,12 +123,6 @@ monthly_pi_colors:
rgb_color: [3,14,159] rgb_color: [3,14,159]
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
rgb_color: [3,14,159]
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -180,12 +156,6 @@ monthly_st_patty_colors:
color_name: 'Green' color_name: 'Green'
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
color_name: 'Green'
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -219,12 +189,6 @@ monthly_easter_colors:
color_name: 'Yellow' color_name: 'Yellow'
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
rgb_color: [255,193,204]
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -258,12 +222,6 @@ monthly_starwars_colors:
rgb_color: [204,0,0] rgb_color: [204,0,0]
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
rgb_color: [245,245,245]
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -297,12 +255,6 @@ monthly_cinco_de_mayo_colors:
rgb_color: [204,0,0] rgb_color: [204,0,0]
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
rgb_color: [245,245,245]
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -336,12 +288,6 @@ monthly_mothers_day_colors:
rgb_color: [244,187,255] rgb_color: [244,187,255]
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
rgb_color: [244,187,255]
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -375,12 +321,6 @@ monthly_fathers_day_colors:
color_name: 'Orange' color_name: 'Orange'
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
color_name: 'Blue'
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -414,12 +354,6 @@ monthly_halloween_colors:
rgb_color: [235,97,35] rgb_color: [235,97,35]
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
rgb_color: [235,97,35]
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -453,12 +387,6 @@ monthly_thanksgiving_colors:
color_name: 'Orange' color_name: 'Orange'
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
color_name: 'Orange'
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -492,12 +420,6 @@ monthly_christmas_colors:
color_name: 'Red' color_name: 'Red'
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
color_name: 'Green'
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
@@ -531,12 +453,6 @@ monthly_new_years_day_colors:
color_name: 'Blue' color_name: 'Blue'
brightness_pct: 100 brightness_pct: 100
transition: 900 transition: 900
- service: light.turn_on
data:
entity_id: light.syd
color_name: 'Yellow'
brightness_pct: 100
transition: 900
- service: light.turn_on - service: light.turn_on
data: data:
entity_id: light.spisebord entity_id: light.spisebord
+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
+59
View File
@@ -0,0 +1,59 @@
roborock_manuelt_kokken:
alias: "Roborock: Start køkken/bryggers manuelt"
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.roborock_manuelt_startet
- service: button.press
target:
entity_id: button.roborock_s8_pro_ultra_kokken_bryggers
roborock_manuelt_syd:
alias: "Roborock: Start syd manuelt"
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.roborock_manuelt_startet
- service: button.press
target:
entity_id: button.roborock_s8_pro_ultra_syd
roborock_manuelt_mop:
alias: "Roborock: Start mop manuelt"
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.roborock_manuelt_startet
- service: button.press
target:
entity_id: button.roborock_s8_pro_ultra_vac_followed_by_mop
roborock_manuelt_start:
alias: "Roborock: Start alt manuelt"
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.roborock_manuelt_startet
- service: vacuum.start
target:
entity_id: vacuum.roborock_s8_pro_ultra
ploeneklipper_manuelt_start:
alias: "Plæneklipper: Start manuelt"
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.ploeneklipper_manuelt_startet
- service: lawn_mower.start_mowing
target:
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
+31
View File
@@ -0,0 +1,31 @@
spil_paa_lille_bad:
alias: "Spil lyd på lille bad og gendan volumen bagefter"
fields:
lydfil:
description: Filnavn (fx doorbell.mp3)
example: doorbell.mp3
volumen:
description: Volumen der spilles ved (0.0-1.0)
example: 0.8
sequence:
- variables:
gammel_volumen: "{{ state_attr('media_player.lille_badevaerelse', 'volume_level') | default(0.3) }}"
- service: media_player.volume_set
data:
volume_level: "{{ volumen }}"
target:
entity_id: media_player.lille_badevaerelse
- service: media_player.play_media
target:
entity_id: media_player.lille_badevaerelse
data:
media_content_id: "media-source://media_source/local/{{ lydfil }}"
media_content_type: audio/mpeg
- delay: "00:00:02"
- wait_template: "{{ states('media_player.lille_badevaerelse') not in ['playing'] }}"
timeout: "00:01:30"
- service: media_player.volume_set
data:
volume_level: "{{ gammel_volumen }}"
target:
entity_id: media_player.lille_badevaerelse
+240
View File
@@ -0,0 +1,240 @@
##################################################
# Varme: Central genberegning af alle Roth-rum
# Kaldes af automations/varme_styring.yaml ved:
# - Morgen (06:30), Nat (22:00)
# - Tilstedeværelse ændret
# - Ferietilstand ændret
# - HA genstart
# Logik pr. rum:
# - Vindue åbent → skip (håndteres af vinduer.yaml)
# - Ferie → varme_ferie_temp (frostbeskyttelse)
# - Nat (aften_tidmorgen_tid) → komfort - varme_nat_saenkning
# - Ingen hjemme → komfort - varme_vaek_saenkning
# - Ellers → rum-komforttemperatur
##################################################
varme_recalculate:
alias: "Varme: Genberegn alle rum"
mode: restart
sequence:
- variables:
vacation: "{{ is_state('input_boolean.vacation_mode', 'on') }}"
night: >
{% set m = states('input_datetime.varme_morgen_tid') %}
{% set a = states('input_datetime.varme_aften_tid') %}
{% set t = now().strftime('%H:%M:%S') %}
{{ t >= a or t < m }}
home: "{{ is_state('binary_sensor.family_presence', 'on') }}"
nat_sænk: "{{ states('input_number.varme_nat_saenkning') | float(3) }}"
vaek_sænk: "{{ states('input_number.varme_vaek_saenkning') | float(3) }}"
ferie_temp: "{{ states('input_number.varme_ferie_temp') | float(15) }}"
# Sæt alle rum til manuel tilstand (preset: none) så Roth-tidsprogram ikke overstyrer
- service: climate.set_preset_mode
target:
entity_id:
- climate.andreas
- climate.daniel
- climate.sovev_prelse
- climate.kontor
- climate.fordelingsgang
- climate.forgang
- climate.lille_bad
data:
preset_mode: "none"
# ---- Andreas (vindue: binary_sensor.andreas_vindue) ----
- if:
- condition: template
value_template: "{{ not is_state('binary_sensor.andreas_vindue', 'on') }}"
then:
- service: climate.set_temperature
target:
entity_id: climate.andreas
data:
temperature: >
{% set k = states('input_number.varme_komfort_andreas') | 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 %}
else:
- service: climate.set_temperature
target:
entity_id: climate.andreas
data:
temperature: "{{ ferie_temp }}"
# ---- Daniel (vindue: binary_sensor.daniel_vindue) ----
- if:
- condition: template
value_template: "{{ not is_state('binary_sensor.daniel_vindue', 'on') }}"
then:
- service: climate.set_temperature
target:
entity_id: climate.daniel
data:
temperature: >
{% set k = states('input_number.varme_komfort_daniel') | 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 %}
else:
- service: climate.set_temperature
target:
entity_id: climate.daniel
data:
temperature: "{{ ferie_temp }}"
# ---- Soveværelse (vindue: binary_sensor.sovevaerelse_vindue) ----
- if:
- condition: template
value_template: "{{ not is_state('binary_sensor.sovevaerelse_vindue', 'on') }}"
then:
- service: climate.set_temperature
target:
entity_id: climate.sovev_prelse
data:
temperature: >
{% set k = states('input_number.varme_komfort_sovevaerelse') | float(18) %}
{% if vacation %} {{ ferie_temp }}
{% elif night %} {{ [k - nat_sænk, 15] | max }}
{% elif not home %} {{ [k - vaek_sænk, 15] | max }}
{% else %} {{ k }}
{% endif %}
else:
- service: climate.set_temperature
target:
entity_id: climate.sovev_prelse
data:
temperature: "{{ ferie_temp }}"
# ---- Kontor (ingen vinduesensor) ----
- service: climate.set_temperature
target:
entity_id: climate.kontor
data:
temperature: >
{% set k = states('input_number.varme_komfort_kontor') | float(21) %}
{% if vacation %} {{ ferie_temp }}
{% elif night %} {{ [k - nat_sænk, 15] | max }}
{% elif not home %} {{ [k - vaek_sænk, 15] | max }}
{% else %} {{ k }}
{% endif %}
# ---- Gang / Fordelingsgang (ingen vinduesensor) ----
- service: climate.set_temperature
target:
entity_id: climate.fordelingsgang
data:
temperature: >
{% set k = states('input_number.varme_komfort_gang') | float(18) %}
{% if vacation %} {{ ferie_temp }}
{% elif night %} {{ [k - nat_sænk, 15] | max }}
{% elif not home %} {{ [k - vaek_sænk, 15] | max }}
{% else %} {{ k }}
{% endif %}
# ---- Forgang (ingen vinduesensor) ----
- service: climate.set_temperature
target:
entity_id: climate.forgang
data:
temperature: >
{% set k = states('input_number.varme_komfort_forgang') | float(17) %}
{% if vacation %} {{ ferie_temp }}
{% elif night %} {{ [k - nat_sænk, 15] | max }}
{% elif not home %} {{ [k - vaek_sænk, 15] | max }}
{% else %} {{ k }}
{% endif %}
# ---- Lille bad (vindue: binary_sensor.lille_bad_vindue) ----
- if:
- condition: template
value_template: "{{ not is_state('binary_sensor.lille_bad_vindue', 'on') }}"
then:
- service: climate.set_temperature
target:
entity_id: climate.lille_bad
data:
temperature: >
{% set k = states('input_number.varme_komfort_lille_bad') | float(22) %}
{% if vacation %} {{ ferie_temp }}
{% elif night %} {{ [k - nat_sænk, 15] | max }}
{% elif not home %} {{ [k - vaek_sænk, 15] | max }}
{% else %} {{ k }}
{% endif %}
else:
- service: climate.set_temperature
target:
entity_id: climate.lille_bad
data:
temperature: "{{ ferie_temp }}"
# ---- Badeværelse Danfoss Ally (vindue: binary_sensor.badevaerelse_vindue) ----
- if:
- condition: template
value_template: "{{ not is_state('binary_sensor.badevaerelse_vindue', 'on') }}"
then:
- service: climate.set_temperature
target:
entity_id: climate.badevarelse
data:
hvac_mode: heat
temperature: >
{% set k = states('input_number.varme_komfort_badevarelse') | 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 %}
else:
- service: climate.set_temperature
target:
entity_id: climate.badevarelse
data:
hvac_mode: heat
temperature: "{{ ferie_temp }}"
# ---- Stue Danfoss Ally (vindue: binary_sensor.terrassedor) ----
- if:
- condition: template
value_template: "{{ not is_state('binary_sensor.terrassedor', 'on') }}"
then:
- service: climate.set_temperature
target:
entity_id: climate.stue
data:
hvac_mode: heat
temperature: >
{% set k = states('input_number.varme_komfort_stue') | float(24) %}
{% if vacation %} {{ ferie_temp }}
{% elif night %} {{ [k - nat_sænk, 15] | max }}
{% elif not home %} {{ [k - vaek_sænk, 15] | max }}
{% else %} {{ k }}
{% endif %}
else:
- service: climate.set_temperature
target:
entity_id: climate.stue
data:
hvac_mode: heat
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
@@ -1 +1,2 @@
mealie_update: "python3 /config/python_scripts/mealie_mealplan.py" mealie_update: "python3 /config/python_scripts/mealie_mealplan.py"
mealie_shopping_merge: "python3 /config/python_scripts/mealie_shopping_merge.py"
+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
+34
View File
@@ -0,0 +1,34 @@
##################################################
# Varme: Anbefalet ventilposition (sæsonbaseret)
# Bruges til manuelt at justere:
# 1. Hovedhanen i sauna (Roth-fordeling)
# 2. Hovedhanen i bryggers (fjernvarme indgang)
#
# Skala 1-5:
# 1 = næsten lukket (varmt vejr, >= 15°C)
# 5 = fuldt åben (frostgrader, <= -5°C)
# Lineær imellem: 1 + (15 - T) / 20 * 4
##################################################
- sensor:
- name: "Fjernvarme ventil anbefalet"
unique_id: fjernvarme_ventil_anbefalet
unit_of_measurement: ""
icon: mdi:valve
state: >
{% set t = state_attr('weather.norgardsvej', 'temperature') | float(10) %}
{% set raw = 1 + (15 - t) / 20 * 4 %}
{{ [[raw, 1] | max, 5] | min | round(1) }}
attributes:
anbefaling: >
{% set t = state_attr('weather.norgardsvej', 'temperature') | float(10) %}
{% set raw = 1 + (15 - t) / 20 * 4 %}
{% set pos = [[raw, 1] | max, 5] | min %}
{% if pos <= 1.5 %}Position 1 - lukket (varmt vejr)
{% elif pos <= 2.5 %}Position 2 - let åben (mildt vejr)
{% elif pos <= 3.5 %}Position 3 - halvvejs (køligt vejr)
{% elif pos <= 4.5 %}Position 4 - mest åben (koldt vejr)
{% else %}Position 5 - fuldt åben (frostgrader)
{% endif %}
udetemperatur: >
{{ state_attr('weather.norgardsvej', 'temperature') | float(10) }}
+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}")
@@ -0,0 +1,176 @@
#!/usr/bin/env python3
"""Import Valdemarsro favorites into Mealie (deduplicated by source URL).
This script imports a fixed list of favorite recipes from Valdemarsro by URL,
skipping recipes already present in Mealie.
"""
from __future__ import annotations
import json
import sys
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
BASE_URL = "http://10.0.0.142:9925"
SECRETS = Path("/Volumes/homeassistant/secrets.yaml")
FAVORITE_SLUGS = [
"hjemmelavet-pizza",
"lasagne",
"musli-opskrift",
"lakselasagne",
"mexicansk-burger-med-hjemmelavet-guacamole",
"grontsagsfad",
"humus",
"indisk_curry_med_kylling",
"pariserbof",
"skipperlabskovs",
"pizzasnegle",
"luksus-stjerneskud",
"jordbaer-og-fetasalat-med-glaserede-pecannoedder",
"spaghetti-bolognese",
"kylling-med-cornflakes",
"tarteletter-hoens-asparges",
"one-pot-pasta",
"cacio-e-pepe",
"citronpasta",
"blomkaalssalat",
"koedsovs-onepotpasta",
"kikaertegryde",
"moerbradboeffer-med-bloede-loeg",
"stegt-spidskaal",
"bagt-kylling",
"ristede-kartoffelskiver-fad",
"vikingegryde",
"spidskaalssalat-opskrift",
"kylling-med-parmesan",
"bagt-broccoli",
"pastasalat-med-pesto",
"nachos-bowl",
"barbecuesauce",
"congee-rissuppe-kylling",
"macaroni-and-cheese",
"halloween-dessert",
"feta-pasta-med-tomat",
"tortellini-i-fad",
"flyvende-jacob",
]
def read_token() -> str:
for line in SECRETS.read_text().splitlines():
if line.strip().startswith("mealie_bearer_token:"):
return line.split(":", 1)[1].strip().strip('"')
raise RuntimeError("mealie_bearer_token not found in secrets.yaml")
def api_request(path: str, token: str, method: str = "GET", payload: dict | None = None) -> dict:
headers = {"Authorization": token}
data = None
if payload is not None:
headers["Content-Type"] = "application/json"
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(f"{BASE_URL}{path}", headers=headers, data=data, method=method)
with urllib.request.urlopen(req, timeout=90) as resp:
raw = resp.read()
return json.loads(raw) if raw else {}
def fetch_existing_org_urls(token: str) -> set[str]:
urls: set[str] = set()
page = 1
per_page = 100
while True:
path = f"/api/recipes?page={page}&perPage={per_page}"
payload = api_request(path, token)
items = payload.get("items", []) or []
if not items:
break
for recipe in items:
org_url = (recipe.get("orgURL") or "").strip()
if org_url:
urls.add(org_url.rstrip("/"))
if len(items) < per_page:
break
page += 1
return urls
def import_recipe_by_url(token: str, url: str) -> tuple[bool, str]:
try:
api_request(
"/api/recipes/create/url",
token,
method="POST",
payload={"url": url, "includeTags": True},
)
return True, "imported"
except urllib.error.HTTPError as exc:
body = ""
try:
body = exc.read().decode("utf-8", errors="ignore")
except Exception:
pass
if exc.code == 422:
return False, f"422 {body[:200]}"
return False, f"HTTP {exc.code} {body[:200]}"
except Exception as exc: # noqa: BLE001
return False, str(exc)
def main() -> int:
token = read_token()
urls = [f"https://www.valdemarsro.dk/{slug}/" for slug in FAVORITE_SLUGS]
existing_urls = fetch_existing_org_urls(token)
to_import = [u for u in urls if u.rstrip("/") not in existing_urls]
skipped = [u for u in urls if u.rstrip("/") in existing_urls]
imported: list[str] = []
failed: list[tuple[str, str]] = []
for url in to_import:
ok, msg = import_recipe_by_url(token, url)
if ok:
imported.append(url)
else:
failed.append((url, msg))
print(f"TOTAL_LISTED={len(urls)}")
print(f"SKIPPED_EXISTING={len(skipped)}")
print(f"IMPORTED={len(imported)}")
print(f"FAILED={len(failed)}")
if skipped:
print("-- skipped existing --")
for url in skipped:
print(url)
if imported:
print("-- imported --")
for url in imported:
print(url)
if failed:
print("-- failed --")
for url, reason in failed:
print(url)
print(f" reason: {reason}")
# Exit non-zero only if everything failed.
if failed and not imported:
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+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": []}
+286
View File
@@ -0,0 +1,286 @@
#!/usr/bin/env python3
"""Build Mealie shopping list for the Friday-Thursday meal plan window.
Flow:
1) Compute next Friday-Thursday window.
2) Ensure/clear the 'Bilka ToGo' Mealie shopping list.
3) Add all recipes from the meal plan window to the list.
4) Fetch the resulting shopping items and write a styled HTML file for display in HA.
Output:
- /www/bilka_togo_checklist.html (iframe-renderable in HA)
- /www/bilka_togo_checklist.json (machine-readable backup)
"""
from __future__ import annotations
import json
import re
import urllib.request
from collections import defaultdict
from datetime import date, timedelta
from pathlib import Path
ROOT_CANDIDATES = [Path('/Volumes/homeassistant'), Path('/config')]
MEALIE_BASE_URL = 'http://10.0.0.142:9925'
TARGET_SHOPPING_LIST_NAME = 'Bilka ToGo'
CATEGORY_RULES = {
'frugt & grønt': ['banan', 'aeble', 'æble', 'citron', 'lime', 'tomat', 'salat', 'agurk', 'gulerod', 'kartoffel', 'log', 'løg', 'hvidlog', 'hvidløg', 'broccoli', 'spidskal', 'spidskål', 'avocado', 'peberfrugt'],
'kød & fisk': ['kylling', 'oksekød', 'hakket', 'ribeye', 'bacon', 'laks', 'fisk', 'skinke', 'pølse'],
'mejeri & æg': ['mælk', 'yoghurt', 'smør', 'ost', 'feta', 'mozzarella', 'æg', 'fløde', 'creme fraiche'],
'kolonial': ['ris', 'pasta', 'mel', 'havregryn', 'olie', 'eddike', 'krydder', 'bønne', 'linse', 'dåse', 'sukker', 'salt', 'tomatpuré', 'tomatpure', 'nori', 'soja'],
'frost': ['frost', 'frossen', 'is', 'edamame'],
'husholdning': ['toiletpapir', 'køkkenrulle', 'kokkenrulle', 'sæbe', 'sæb', 'opvasketabs', 'vaskemiddel', 'affaldspose'],
}
def detect_root() -> Path:
# Prefer repository-relative root so the script works both in HA (/config)
# and when run locally from the mounted workspace (/Volumes/homeassistant).
local_root = Path(__file__).resolve().parent.parent
if (local_root / 'secrets.yaml').exists():
return local_root
for root in ROOT_CANDIDATES:
if (root / 'secrets.yaml').exists():
return root
raise RuntimeError('Could not locate Home Assistant config root')
def read_bearer_token(secrets_path: Path) -> str:
for line in secrets_path.read_text().splitlines():
if line.strip().startswith('mealie_bearer_token:'):
return line.split(':', 1)[1].strip().strip('"')
raise RuntimeError('mealie_bearer_token not found in secrets.yaml')
def api_get(url: str, token: str) -> dict:
req = urllib.request.Request(url, headers={'Authorization': token})
with urllib.request.urlopen(req, timeout=20) as resp:
raw = resp.read()
return json.loads(raw) if raw else {}
def api_request(
base_url: str,
path: str,
token: str,
method: str = 'GET',
payload: dict | list | None = None,
timeout: int = 90,
):
data = None
headers = {'Authorization': token}
if payload is not None:
data = json.dumps(payload).encode('utf-8')
headers['Content-Type'] = 'application/json'
req = urllib.request.Request(f'{base_url}{path}', headers=headers, data=data, method=method)
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read()
if not raw:
return {}
try:
return json.loads(raw)
except json.JSONDecodeError:
return {}
def normalize_name(value: str) -> str:
value = value.lower().strip()
value = value.replace('å', 'aa').replace('æ', 'ae').replace('ø', 'oe')
value = re.sub(r'\s+', ' ', value)
value = re.sub(r'[^a-z0-9 ]', '', value)
return value
def classify_category(name: str) -> str:
key = normalize_name(name)
for category, needles in CATEGORY_RULES.items():
for needle in needles:
if needle in key:
return category
return 'andet'
def read_keep_items(_keep_path: Path) -> list[str]: # noqa: ARG001 (kept for backward compat)
return []
def friday_to_thursday_window(today: date) -> tuple[date, date]:
days_until_friday = (4 - today.weekday()) % 7
start = today + timedelta(days=days_until_friday)
end = start + timedelta(days=6)
return start, end
def ensure_shopping_list(base_url: str, token: str, name: str) -> str:
lists = api_request(base_url, '/api/households/shopping/lists?perPage=200', token).get('items', []) or []
for shopping_list in lists:
if (shopping_list.get('name') or '').strip().lower() == name.lower():
return shopping_list['id']
created = api_request(
base_url,
'/api/households/shopping/lists',
token,
method='POST',
payload={'name': name},
)
return created['id']
def reset_shopping_list(base_url: str, token: str, name: str, shopping_list_id: str) -> str:
"""Delete the list and recreate it. Returns new list id."""
api_request(base_url, f'/api/households/shopping/lists/{shopping_list_id}', token, method='DELETE')
created = api_request(
base_url,
'/api/households/shopping/lists',
token,
method='POST',
payload={'name': name},
)
return created['id']
def get_mealplan_recipe_ids(base_url: str, token: str, start_date: date, end_date: date) -> list[str]:
entries: list[dict] = []
current = start_date
while current <= end_date:
path = (
f"/api/households/mealplans?start_date={current.isoformat()}"
f"&end_date={current.isoformat()}&perPage=100"
)
day_payload = api_request(base_url, path, token)
day_items = day_payload.get('items', []) if isinstance(day_payload, dict) else []
entries.extend(day_items or [])
current = current + timedelta(days=1)
result: list[str] = []
seen: set[str] = set()
for entry in entries:
recipe_id = entry.get('recipeId')
if not recipe_id:
continue
if recipe_id in seen:
continue
seen.add(recipe_id)
result.append(recipe_id)
return result
def add_recipes_to_shopping_list(base_url: str, token: str, shopping_list_id: str, recipe_ids: list[str]) -> int:
if not recipe_ids:
return 0
payload = [{'recipeId': rid, 'recipeIncrementQuantity': 1} for rid in recipe_ids]
api_request(
base_url,
f'/api/households/shopping/lists/{shopping_list_id}/recipe',
token,
method='POST',
payload=payload,
)
return len(recipe_ids)
def fetch_mealie_items(base_url: str, token: str, shopping_list_id: str | None = None) -> list[dict]:
data = api_request(base_url, '/api/households/shopping/items?perPage=1000', token)
items = data.get('items', []) or []
if not shopping_list_id:
return items
return [item for item in items if item.get('shoppingListId') == shopping_list_id]
def extract_item_name(item: dict) -> str:
display = (item.get('display') or '').strip()
if display:
return display
food = item.get('food') or {}
return (food.get('name') or '').strip()
def build_items(mealie_items: list[dict]) -> list[dict]:
merged: dict[str, dict] = {}
for item in mealie_items:
name = extract_item_name(item)
if not name:
continue
norm = normalize_name(name)
if norm not in merged:
merged[norm] = {
'name': name,
'category': classify_category(name),
}
result = [v for v in merged.values()]
result.sort(key=lambda x: (x['category'], normalize_name(x['name'])))
return result
def write_outputs(root: Path, items: list[dict], start_date: date, end_date: date) -> None:
www = root / 'www'
www.mkdir(parents=True, exist_ok=True)
payload = {'count': len(items), 'items': items}
(www / 'bilka_togo_checklist.json').write_text(json.dumps(payload, ensure_ascii=False, indent=2))
grouped: dict[str, list[dict]] = defaultdict(list)
for item in items:
grouped[item['category']].append(item)
rows_html = ''
for category in sorted(grouped.keys()):
rows_html += f'<tr><th colspan="2" class="cat">{category.title()}</th></tr>\n'
for item in grouped[category]:
rows_html += f'<tr><td class="cb"><input type="checkbox"></td><td>{item["name"]}</td></tr>\n'
html = f"""<!DOCTYPE html>
<html lang="da">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Bilka ToGo</title>
<style>
body {{ font-family: sans-serif; padding: 8px; background: #1c1c1e; color: #e5e5ea; margin: 0; }}
h1 {{ font-size: 1.1em; margin-bottom: 2px; color: #fff; }}
p.sub {{ font-size: 0.8em; color: #8e8e93; margin: 0 0 10px; }}
table {{ width: 100%; border-collapse: collapse; }}
td, th {{ padding: 5px 6px; text-align: left; border-bottom: 1px solid #2c2c2e; }}
th.cat {{ background: #2c2c2e; color: #ffe066; font-size: 0.85em; text-transform: uppercase; letter-spacing: 0.05em; }}
td.cb {{ width: 28px; }}
input[type=checkbox] {{ width: 18px; height: 18px; accent-color: #30d158; cursor: pointer; }}
tr:hover td {{ background: #2c2c2e; }}
</style>
</head>
<body>
<h1>🛒 Bilka ToGo</h1>
<p class="sub">Plan {start_date.strftime('%d/%m')} {end_date.strftime('%d/%m')} &nbsp;·&nbsp; {len(items)} varer</p>
<table>
{rows_html}</table>
</body>
</html>
"""
(www / 'bilka_togo_checklist.html').write_text(html)
def main() -> None:
root = detect_root()
token = read_bearer_token(root / 'secrets.yaml')
start_date, end_date = friday_to_thursday_window(date.today())
shopping_list_id = ensure_shopping_list(MEALIE_BASE_URL, token, TARGET_SHOPPING_LIST_NAME)
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)
add_recipes_to_shopping_list(MEALIE_BASE_URL, token, shopping_list_id, recipe_ids)
print(
'OK: '
f'window={start_date.isoformat()}..{end_date.isoformat()} '
f'recipes_added={added_recipes} items={len(items)}'
)
if __name__ == '__main__':
main()
+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
# ######################################## # ########################################
+100
View File
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="da">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Bilka ToGo</title>
<style>
body { font-family: sans-serif; padding: 8px; background: #1c1c1e; color: #e5e5ea; margin: 0; }
h1 { font-size: 1.1em; margin-bottom: 2px; color: #fff; }
p.sub { font-size: 0.8em; color: #8e8e93; margin: 0 0 10px; }
table { width: 100%; border-collapse: collapse; }
td, th { padding: 5px 6px; text-align: left; border-bottom: 1px solid #2c2c2e; }
th.cat { background: #2c2c2e; color: #ffe066; font-size: 0.85em; text-transform: uppercase; letter-spacing: 0.05em; }
td.cb { width: 28px; }
input[type=checkbox] { width: 18px; height: 18px; accent-color: #30d158; cursor: pointer; }
tr:hover td { background: #2c2c2e; }
</style>
</head>
<body>
<h1>🛒 Bilka ToGo</h1>
<p class="sub">Plan 15/05 21/05 &nbsp;·&nbsp; 69 varer</p>
<table>
<tr><th colspan="2" class="cat">Andet</th></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 stødt spidskommen</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 æggehvider</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 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 fed hvidløg, presset</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 spsk smør, til stegning</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>100 g rejer</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>2 spsk kapers (valgfrit)</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>200 g lasagneplader</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>4 grønne asparges</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 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>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><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><th colspan="2" class="cat">Frugt & Grønt</th></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>1 citron, 1 spsk saft herfra</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>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>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>Grøntsager eller salat</td></tr>
<tr><th colspan="2" class="cat">Kolonial</th></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 spsk hvedemel</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 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 olivenolie</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 spsk olivenolie, til stegning</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>Ris eller kartofler</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><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>2 løg, finthakket</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 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>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><th colspan="2" class="cat">Mejeri & Æg</th></tr>
<tr><td class="cb"><input type="checkbox"></td><td>125 g frisk mozzarella</td></tr>
</table>
</body>
</html>
+281
View File
@@ -0,0 +1,281 @@
{
"count": 69,
"items": [
{
"name": "0,50 dl hvidvin",
"category": "andet"
},
{
"name": "0,50 tsk stødt spidskommen",
"category": "andet"
},
{
"name": "1 æg",
"category": "andet"
},
{
"name": "1 æggehvider",
"category": "andet"
},
{
"name": "1 dl cremefraiche 18 %",
"category": "andet"
},
{
"name": "1 dl mælk",
"category": "andet"
},
{
"name": "1 dl rødvin, eller grøntsagsboullion",
"category": "andet"
},
{
"name": "1 fed hvidløg, presset",
"category": "andet"
},
{
"name": "1 knivspids muskatnød, fintrevet",
"category": "andet"
},
{
"name": "1 knivspids røget paprika",
"category": "andet"
},
{
"name": "1 knivspids sød paprika",
"category": "andet"
},
{
"name": "1 spsk smør, til stegning",
"category": "andet"
},
{
"name": "1 squash, groftrevet",
"category": "andet"
},
{
"name": "1 tsk sød paprika",
"category": "andet"
},
{
"name": "1 tsk tørret timian",
"category": "andet"
},
{
"name": "100 g rejer",
"category": "andet"
},
{
"name": "100 g stenbiderrogn",
"category": "andet"
},
{
"name": "2 spsk kapers (valgfrit)",
"category": "andet"
},
{
"name": "2 spsk smør",
"category": "andet"
},
{
"name": "2 tsk tørret oregano",
"category": "andet"
},
{
"name": "200 g lasagneplader",
"category": "andet"
},
{
"name": "3 dl mælk",
"category": "andet"
},
{
"name": "4 grønne asparges",
"category": "andet"
},
{
"name": "4 gulerødder, groftrevet",
"category": "andet"
},
{
"name": "4 hvide asparges",
"category": "andet"
},
{
"name": "4 skiver brød",
"category": "andet"
},
{
"name": "5 stængler bladselleri, groftrevet",
"category": "andet"
},
{
"name": "50 g rasp",
"category": "andet"
},
{
"name": "75 g smør",
"category": "andet"
},
{
"name": "8 rødspættefilet",
"category": "andet"
},
{
"name": "2 spsk mayonnaise",
"category": "frost"
},
{
"name": "2,50 dl piskefløde",
"category": "frost"
},
{
"name": "1 citron saft og fintrevet skal",
"category": "frugt & grønt"
},
{
"name": "1 citron, 1 spsk saft herfra",
"category": "frugt & grønt"
},
{
"name": "1 citron, skåret i både",
"category": "frugt & grønt"
},
{
"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"
},
{
"name": "50 g koncentreret tomatpuré",
"category": "frugt & grønt"
},
{
"name": "70 g blandet salat",
"category": "frugt & grønt"
},
{
"name": "800 g hakkede tomater på dåse",
"category": "frugt & grønt"
},
{
"name": "Grøntsager eller salat",
"category": "frugt & grønt"
},
{
"name": "1 dl mild chilipasta",
"category": "kolonial"
},
{
"name": "1 spsk hvedemel",
"category": "kolonial"
},
{
"name": "1 spsk olivenolie, til stegning",
"category": "kolonial"
},
{
"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"
},
{
"name": "2 spsk hvedemel",
"category": "kolonial"
},
{
"name": "2 spsk olivenolie",
"category": "kolonial"
},
{
"name": "3 dl basmati ris",
"category": "kolonial"
},
{
"name": "3 spsk olivenolie, til stegning",
"category": "kolonial"
},
{
"name": "50 g saltede peanuts",
"category": "kolonial"
},
{
"name": "Ris eller kartofler",
"category": "kolonial"
},
{
"name": "salt og friskkværnet peber",
"category": "kolonial"
},
{
"name": "Salt og peber",
"category": "kolonial"
},
{
"name": "1 fed hvidløg, finthakket",
"category": "kød & fisk"
},
{
"name": "1 løg, finthakket",
"category": "kød & fisk"
},
{
"name": "2 løg, finthakket",
"category": "kød & fisk"
},
{
"name": "2 spsk frisk persille, hakket",
"category": "kød & fisk"
},
{
"name": "2 spsk persille, finthakket",
"category": "kød & fisk"
},
{
"name": "4 fed hvidløg, finthakket",
"category": "kød & fisk"
},
{
"name": "4 laksefileter med skind (ca. 150 g pr. stk)",
"category": "kød & fisk"
},
{
"name": "400 g hakket oksekød",
"category": "kød & fisk"
},
{
"name": "500 g hakket svinekød",
"category": "kød & fisk"
},
{
"name": "600 g kyllingebryst, skåret i grove tern",
"category": "kød & fisk"
},
{
"name": "75 g bacon, i skiver",
"category": "kød & fisk"
},
{
"name": "125 g frisk mozzarella",
"category": "mejeri & æg"
}
]
}
+98
View File
@@ -0,0 +1,98 @@
# Bilka ToGo - Kryds-af-liste
Gå listen igennem derhjemme først, og bestil kun de varer du mangler.
Plan-vindue: 2026-04-24 til 2026-04-30
## Andet
- [ ] 0,50 squash (Mealie)
- [ ] 0,50 tsk mediumstærk karry (Mealie)
- [ ] 0,50 tsk tørret rosmarin (Mealie)
- [ ] 1 dl cremefraiche 38% (Mealie)
- [ ] 1 dl grøntsagsbouillon (Mealie)
- [ ] 1 dl pickles (Mealie)
- [ ] 1 dl rødvin, eller grøntsagsboullion (Mealie)
- [ ] 1 dl tør hvidvin (Mealie)
- [ ] 1 fed hvidløg, presset (Mealie)
- [ ] 1 gulerødder, i tern (Mealie)
- [ ] 1 håndfuld persille (Mealie)
- [ ] 1 kg kartofler (Mealie)
- [ ] 1 knivspids muskatnød, fintrevet (Mealie)
- [ ] 1 knivspids sød paprika (Mealie)
- [ ] 1 løg, i tern (Mealie)
- [ ] 1 rødløg (Mealie)
- [ ] 1 spsk dijon sennep (Mealie)
- [ ] 1 spsk hampefrø (Mealie)
- [ ] 1 spsk honning (Mealie)
- [ ] 1 spsk majsstivelse (Mealie)
- [ ] 1 spsk smør, til stegning (Mealie)
- [ ] 1 spsk solsikkekerner (Mealie)
- [ ] 1 squash, groftrevet (Mealie)
- [ ] 1 tsk tørret timian (Mealie)
- [ ] 10 g smør, til stegning (Mealie)
- [ ] 100 g parmesan, fintrevet (Mealie)
- [ ] 12 tarteletter (Mealie)
- [ ] 2 æg (Mealie)
- [ ] 2 dl hønsebouillon (Mealie)
- [ ] 2 hønsebryst (Mealie)
- [ ] 2 laurbærblade (Mealie)
- [ ] 2 spsk grov sennep (Mealie)
- [ ] 2 spsk rosiner (Mealie)
- [ ] 2 spsk smør (Mealie)
- [ ] 2 tsk tørret oregano (Mealie)
- [ ] 200 g aspargessnitter (Mealie)
- [ ] 200 g lasagneplader (Mealie)
- [ ] 25 g smør, til stegning (Mealie)
- [ ] 3 dl grøntsagsbouillon (Mealie)
- [ ] 3 dl mælk (Mealie)
- [ ] 3 gulerødder, groftrevet (Mealie)
- [ ] 300 g torskefilet (Mealie)
- [ ] 4 dl mælk (Mealie)
- [ ] 4 fed hvidløg, fintrevet (Mealie)
- [ ] 4 gulerødder, groftrevet (Mealie)
- [ ] 40 g smør (Mealie)
- [ ] 5 stængler bladselleri, groftrevet (Mealie)
## Frost
- [ ] 1 dl piskefløde (Mealie)
- [ ] 2 spsk mayonnaise (Mealie)
- [ ] 2,50 dl piskefløde (Mealie)
## Frugt & Grønt
- [ ] 0,50 citron, saft herfra (Mealie)
- [ ] 0,50 øko citron (Mealie)
- [ ] 15 g koncentreret tomatpuré (Mealie)
- [ ] 2 æble, groftrevet (Mealie)
- [ ] 50 g koncentreret tomatpuré (Mealie)
- [ ] 75 g soltørrede tomater i olie, finthakket (Mealie)
- [ ] 800 g hakkede tomater på dåse (Mealie)
## Kolonial
- [ ] 1 håndfuld frisk basilikum (Mealie)
- [ ] 1 håndfuld frisk dild (Mealie)
- [ ] 1 spsk olivenolie (Mealie)
- [ ] 1 spsk olivenolie, til stegning (Mealie)
- [ ] 1 tsk olivenolie (Mealie)
- [ ] 2 spsk hvedemel (Mealie)
- [ ] 2 spsk olivenolie (Mealie)
- [ ] 25 g hvedemel (Mealie)
- [ ] 3 dl basmati ris, kogt efter anvisning på emballagen (Mealie)
- [ ] 30 g hvedemel (Mealie)
- [ ] 40 g hvedemel (Mealie)
- [ ] 400 g pasta (Mealie)
- [ ] flagesalt (Mealie)
- [ ] salt og friskkværnet peber (Mealie)
## Kød & Fisk
- [ ] 0,50 dl frisk estragon, finthakket (Mealie)
- [ ] 1 løg, finthakket (Mealie)
- [ ] 2 løg, finthakket (Mealie)
- [ ] 300 g laks, uden skind (Mealie)
- [ ] 4 fed hvidløg, finthakket (Mealie)
- [ ] 4 kyllingebryst (Mealie)
- [ ] 400 g hakket oksekød (Mealie)
- [ ] 600 g kyllingebryst (Mealie)
- [ ] 75 g bacon, i skiver (Mealie)
## Mejeri & Æg
- [ ] 125 g frisk mozzarella (Mealie)
- [ ] 400 g haricots verts, fra frost (Mealie)
+98
View File
@@ -0,0 +1,98 @@
# Bilka ToGo - Klar til bestilling
Kryds af hvad I allerede har i huset, og bestil resten.
Plan-vindue: 2026-04-24 til 2026-04-30
## Andet
- [ ] 0,50 squash
- [ ] 0,50 tsk mediumstærk karry
- [ ] 0,50 tsk tørret rosmarin
- [ ] 1 dl cremefraiche 38%
- [ ] 1 dl grøntsagsbouillon
- [ ] 1 dl pickles
- [ ] 1 dl rødvin, eller grøntsagsboullion
- [ ] 1 dl tør hvidvin
- [ ] 1 fed hvidløg, presset
- [ ] 1 gulerødder, i tern
- [ ] 1 håndfuld persille
- [ ] 1 kg kartofler
- [ ] 1 knivspids muskatnød, fintrevet
- [ ] 1 knivspids sød paprika
- [ ] 1 løg, i tern
- [ ] 1 rødløg
- [ ] 1 spsk dijon sennep
- [ ] 1 spsk hampefrø
- [ ] 1 spsk honning
- [ ] 1 spsk majsstivelse
- [ ] 1 spsk smør, til stegning
- [ ] 1 spsk solsikkekerner
- [ ] 1 squash, groftrevet
- [ ] 1 tsk tørret timian
- [ ] 10 g smør, til stegning
- [ ] 100 g parmesan, fintrevet
- [ ] 12 tarteletter
- [ ] 2 æg
- [ ] 2 dl hønsebouillon
- [ ] 2 hønsebryst
- [ ] 2 laurbærblade
- [ ] 2 spsk grov sennep
- [ ] 2 spsk rosiner
- [ ] 2 spsk smør
- [ ] 2 tsk tørret oregano
- [ ] 200 g aspargessnitter
- [ ] 200 g lasagneplader
- [ ] 25 g smør, til stegning
- [ ] 3 dl grøntsagsbouillon
- [ ] 3 dl mælk
- [ ] 3 gulerødder, groftrevet
- [ ] 300 g torskefilet
- [ ] 4 dl mælk
- [ ] 4 fed hvidløg, fintrevet
- [ ] 4 gulerødder, groftrevet
- [ ] 40 g smør
- [ ] 5 stængler bladselleri, groftrevet
## Frost
- [ ] 1 dl piskefløde
- [ ] 2 spsk mayonnaise
- [ ] 2,50 dl piskefløde
## Frugt & Grønt
- [ ] 0,50 citron, saft herfra
- [ ] 0,50 øko citron
- [ ] 15 g koncentreret tomatpuré
- [ ] 2 æble, groftrevet
- [ ] 50 g koncentreret tomatpuré
- [ ] 75 g soltørrede tomater i olie, finthakket
- [ ] 800 g hakkede tomater på dåse
## Kolonial
- [ ] 1 håndfuld frisk basilikum
- [ ] 1 håndfuld frisk dild
- [ ] 1 spsk olivenolie
- [ ] 1 spsk olivenolie, til stegning
- [ ] 1 tsk olivenolie
- [ ] 2 spsk hvedemel
- [ ] 2 spsk olivenolie
- [ ] 25 g hvedemel
- [ ] 3 dl basmati ris, kogt efter anvisning på emballagen
- [ ] 30 g hvedemel
- [ ] 40 g hvedemel
- [ ] 400 g pasta
- [ ] flagesalt
- [ ] salt og friskkværnet peber
## Kød & Fisk
- [ ] 0,50 dl frisk estragon, finthakket
- [ ] 1 løg, finthakket
- [ ] 2 løg, finthakket
- [ ] 300 g laks, uden skind
- [ ] 4 fed hvidløg, finthakket
- [ ] 4 kyllingebryst
- [ ] 400 g hakket oksekød
- [ ] 600 g kyllingebryst
- [ ] 75 g bacon, i skiver
## Mejeri & Æg
- [ ] 125 g frisk mozzarella
- [ ] 400 g haricots verts, fra frost
+1 -1
View File
@@ -1 +1 @@
{"count": 44, "items": [{"date": "2026-04-28", "recipe": {"name": "Hjemmelavet pizza", "slug": "hjemmelavet-pizza"}}, {"date": "2026-04-29", "recipe": {"name": "M\u00fcsli", "slug": "musli-opskrift"}}, {"date": "2026-04-30", "recipe": {"name": "Lakselasagne med spinat", "slug": "lakselasagne"}}, {"date": "2026-05-01", "recipe": {"name": "Mexicansk burger med guacamole", "slug": "mexicansk-burger-med-hjemmelavet-guacamole"}}, {"date": "2026-05-02", "recipe": {"name": "Gr\u00f8ntsagsfad", "slug": "grontsagsfad"}}, {"date": "2026-05-03", "recipe": {"name": "Hummus", "slug": "humus"}}, {"date": "2026-05-04", "recipe": {"name": "Indisk Curry med kylling", "slug": "indisk_curry_med_kylling"}}, {"date": "2026-05-05", "recipe": {"name": "Pariserbøf", "slug": "pariserbof"}}, {"date": "2026-05-06", "recipe": {"name": "Skipperlabskovs", "slug": "skipperlabskovs"}}, {"date": "2026-05-07", "recipe": {"name": "Pizzasnegle", "slug": "pizzasnegle"}}, {"date": "2026-05-08", "recipe": {"name": "Luksus stjerneskud", "slug": "luksus-stjerneskud"}}, {"date": "2026-05-09", "recipe": {"name": "Jordbærsalat med feta og pekan", "slug": "jordbaer-og-fetasalat-med-glaserede-pecannoedder"}}, {"date": "2026-05-10", "recipe": {"name": "Spaghetti Bolognese", "slug": "spaghetti-bolognese"}}, {"date": "2026-05-11", "recipe": {"name": "Kylling med cornflakes", "slug": "kylling-med-cornflakes"}}, {"date": "2026-05-12", "recipe": {"name": "Tarteletter med høns", "slug": "tarteletter-hoens-asparges"}}, {"date": "2026-05-13", "recipe": {"name": "One pot pasta", "slug": "one-pot-pasta"}}, {"date": "2026-05-14", "recipe": {"name": "Cacio e Pepe", "slug": "cacio-e-pepe"}}, {"date": "2026-05-15", "recipe": {"name": "Citronpasta", "slug": "citronpasta"}}, {"date": "2026-05-16", "recipe": {"name": "Blomk\u00e5lssalat", "slug": "blomkaalssalat"}}, {"date": "2026-05-17", "recipe": {"name": "One Pot Pasta med kødsovs", "slug": "koedsovs-onepotpasta"}}, {"date": "2026-05-18", "recipe": {"name": "Kikærtegryde", "slug": "kikaertegryde"}}, {"date": "2026-05-19", "recipe": {"name": "Mørbradbøffer", "slug": "moerbradboeffer-med-bloede-loeg"}}, {"date": "2026-05-20", "recipe": {"name": "Stegt spidskål", "slug": "stegt-spidskaal"}}, {"date": "2026-05-21", "recipe": {"name": "Bagt kylling", "slug": "bagt-kylling"}}, {"date": "2026-05-22", "recipe": {"name": "Ristede kartoffelskiver", "slug": "ristede-kartoffelskiver-fad"}}, {"date": "2026-05-23", "recipe": {"name": "Vikingegryde", "slug": "vikingegryde"}}, {"date": "2026-05-24", "recipe": {"name": "Spidskålssalat", "slug": "spidskaalssalat-opskrift"}}, {"date": "2026-05-25", "recipe": {"name": "Kylling med parmesan", "slug": "kylling-med-parmesan"}}, {"date": "2026-05-26", "recipe": {"name": "Bagt broccoli", "slug": "bagt-broccoli"}}, {"date": "2026-05-27", "recipe": {"name": "Pastasalat med pesto", "slug": "pastasalat-med-pesto"}}, {"date": "2026-05-28", "recipe": {"name": "Nachos bowl", "slug": "nachos-bowl"}}, {"date": "2026-05-29", "recipe": {"name": "Barbecuesauce", "slug": "barbecuesauce"}}, {"date": "2026-05-30", "recipe": {"name": "Congee Rissuppe", "slug": "congee-rissuppe-kylling"}}, {"date": "2026-05-31", "recipe": {"name": "Macaroni and Cheese", "slug": "macaroni-and-cheese"}}, {"date": "2026-06-01", "recipe": {"name": "Halloween dessert", "slug": "halloween-dessert"}}, {"date": "2026-06-02", "recipe": {"name": "Feta pasta", "slug": "feta-pasta-med-tomat"}}, {"date": "2026-06-03", "recipe": {"name": "Tortellini i fad", "slug": "tortellini-i-fad"}}, {"date": "2026-06-04", "recipe": {"name": "Flyvende Jacob", "slug": "flyvende-jacob"}}, {"date": "2026-04-26", "recipe": {"name": "Lasagne", "slug": "lasagne"}}, {"date": "2026-04-24", "recipe": {"name": "Fiskefrikadeller med remoulade og r\u00e5kost", "slug": "fiskefrikadeller-med-remoulade-og-rakost"}}, {"date": "2026-04-27", "recipe": {"name": "Marry Me Chicken", "slug": "marry-me-chicken"}}, {"date": "2026-04-25", "recipe": {"name": "Cheeseburger Tacos", "slug": "cheeseburger-tacos"}}, {"date": "2026-04-23", "recipe": {"name": "K\u00e5lfad med hakket oksek\u00f8d", "slug": "kalfad-med-hakket-oksekod"}}, {"date": "2026-04-22", "recipe": {"name": "Kylling med honning og sennep", "slug": "kylling-i-fad-med-honning-og-sennep"}}]} {"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"}}]}