Compare commits
105 Commits
8828281ae2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 644cb28e88 | |||
| 7cae5a8c8b | |||
| 31c1258760 | |||
| 7cb07af473 | |||
| 25ab4ceeef | |||
| ba3ba34db8 | |||
| c54f583968 | |||
| 27224a4115 | |||
| ff8324af1f | |||
| 6731257b83 | |||
| 8b533ca95a | |||
| b46d820c20 | |||
| f5f085f155 | |||
| 63b6283fdb | |||
| 9e446e0d57 | |||
| d0e3a6c3e5 | |||
| 8b2ff535de | |||
| acdc7259a7 | |||
| c297012592 | |||
| 52dc97dd3d | |||
| 532f42b310 | |||
| ad5bdf91b9 | |||
| 0cb3145257 | |||
| 54144a03b8 | |||
| af3779a573 | |||
| 3094620985 | |||
| 8057b3abf1 | |||
| 677debfc27 | |||
| 910de6ed54 | |||
| bc6799c126 | |||
| 9f9de0524a | |||
| eacd137a7c | |||
| 63288dbb4b | |||
| c394d6b974 | |||
| fa64223630 | |||
| f2ac6064b5 | |||
| bd134bafef | |||
| 4e0819d4ff | |||
| 0d112fb4d0 | |||
| 067d5c6a63 | |||
| 5a7d25fd3c | |||
| bee2028f0b | |||
| 1525cc0070 | |||
| 812199889e | |||
| 7d5b31f9ab | |||
| bf6f2feba6 | |||
| ae8b1ea6f8 | |||
| 16b68da838 | |||
| 02d63e3d2b | |||
| 7b3bf5b375 | |||
| da78422f5e | |||
| ba0c34672c | |||
| 1805b4c107 | |||
| c366831dd0 | |||
| 6310ee4055 | |||
| 0e23bceb79 | |||
| 19dcab272e | |||
| 1fdcf410ed | |||
| 83fc33f809 | |||
| 1e96c41971 | |||
| 6c3def6996 | |||
| 68db35cae3 | |||
| 4e13ccf658 | |||
| 1063166faa | |||
| 750dee2996 | |||
| 7c6c62cff4 | |||
| e0ec8391d9 | |||
| 6208f6fb49 | |||
| 9cb7550f78 | |||
| 56f21f79e1 | |||
| 6b302b7c65 | |||
| a7580b851a | |||
| 3d2cfe6437 | |||
| 40cb13662a | |||
| 9b833aef16 | |||
| 4030b5995f | |||
| cd9bc28e9e | |||
| c36e47d2ee | |||
| 94f0cd4d8a | |||
| 158beed9c5 | |||
| 9c184ed0b7 | |||
| df734d9259 | |||
| 9f1dd20f06 | |||
| 8bd20bda42 | |||
| 30fffd8651 | |||
| 5a0232eeba | |||
| 3608da8a4c | |||
| 7e38ef2e42 | |||
| 72e0e583be | |||
| 70d01bfabf | |||
| 2ae9723fa3 | |||
| 9fbc1626d7 | |||
| cea0e599f9 | |||
| b7caa8d158 | |||
| 542af023c8 | |||
| 9697dd2925 | |||
| fa79ffc0e6 | |||
| 5bbf0a5082 | |||
| 7b7dc22245 | |||
| 2c3e5bb540 | |||
| 0f3743c1cf | |||
| ea97b824e7 | |||
| ef9200f65b | |||
| 83f8908a3f | |||
| 98adadbaaa |
+1
-1
@@ -1 +1 @@
|
||||
2026.4.3
|
||||
2026.5.1
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -1,5 +1,6 @@
|
||||
# Ignore everything globally
|
||||
.DS_Store
|
||||
._*
|
||||
custom_components/
|
||||
blueprints/
|
||||
dwains-dashboard/
|
||||
@@ -37,6 +38,10 @@ configuration_minimal.yaml
|
||||
!.HA_VERSION
|
||||
!customize
|
||||
|
||||
# --- Re-ignore macOS metadata files inside whitelisted dirs ---
|
||||
include/**/.DS_Store
|
||||
include/**/._*
|
||||
|
||||
# --- Whitelist directories ---
|
||||
!www/
|
||||
!include/
|
||||
@@ -65,6 +70,7 @@ configuration_minimal.yaml
|
||||
/oldscripts.yaml
|
||||
|
||||
# --- Local media snapshots and downloads ---
|
||||
/www/snapshots/
|
||||
/www/affalddk/
|
||||
/www/community/
|
||||
/www/indkorsel_snapshot.jpg
|
||||
@@ -75,4 +81,7 @@ configuration_minimal.yaml
|
||||
|
||||
# --- Local helper scripts not used in git ---
|
||||
/python_scripts/scene_generator.py
|
||||
|
||||
# --- Temporary backup workspace ---
|
||||
/tmp_backups/
|
||||
/python_scripts/update_climate.py
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
## Installation of home-assistant on Synology
|
||||
|
||||
Follow https://www.home-assistant.io/installation/alternative/:
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ default_config:
|
||||
|
||||
homeassistant:
|
||||
name: !secret name
|
||||
external_url: "http://anneclaus.duckdns.org:8123"
|
||||
external_url: "https://ha.anneclaus.dk"
|
||||
internal_url: "http://dethlefsen:8123"
|
||||
auth_providers:
|
||||
- type: homeassistant
|
||||
@@ -27,6 +27,7 @@ http:
|
||||
trusted_proxies:
|
||||
- 127.0.0.1
|
||||
- 10.0.0.142
|
||||
- 172.17.0.0/16 # Docker bridge (NPM)
|
||||
|
||||
logger:
|
||||
default: warning
|
||||
@@ -38,6 +39,7 @@ logger:
|
||||
homeassistant.components.discovery: error
|
||||
homeassistant.components.dlna_dmr: error
|
||||
async_upnp_client: error
|
||||
automower_ble: critical
|
||||
|
||||
recorder:
|
||||
purge_keep_days: 7
|
||||
@@ -110,7 +112,6 @@ cover:
|
||||
|
||||
template: !include_dir_merge_list include/templates/
|
||||
group: !include_dir_merge_named include/groups/
|
||||
mqtt: !include include/mqtt.yaml
|
||||
sensor: !include_dir_merge_list include/sensors/
|
||||
automation: !include_dir_merge_list include/automations/
|
||||
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_boolean: !include_dir_merge_named include/input/boolean/
|
||||
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/
|
||||
panel_iframe: !include_dir_merge_named include/panels/
|
||||
script: !include_dir_merge_named include/scripts/
|
||||
|
||||
@@ -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
|
||||
@@ -4,6 +4,31 @@ icon: mdi:bed-king-outline
|
||||
type: 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
|
||||
cards:
|
||||
- type: heading
|
||||
|
||||
@@ -18,9 +18,9 @@ cards:
|
||||
[[[
|
||||
var slug = states['sensor.dagens_aftensmad_slug'].state;
|
||||
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:
|
||||
card:
|
||||
@@ -41,7 +41,7 @@ cards:
|
||||
- color: white
|
||||
- padding-top: 4px
|
||||
|
||||
# 🎵 Musik i køkken + Der er mad
|
||||
# 🎵 Musik i køkken + Vi laver mad + Der er mad
|
||||
- type: grid
|
||||
columns: 2
|
||||
square: false
|
||||
@@ -57,6 +57,13 @@ cards:
|
||||
data:
|
||||
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
|
||||
name: Der er mad!
|
||||
icon: mdi:silverware-fork-knife
|
||||
@@ -64,69 +71,626 @@ cards:
|
||||
action: call-service
|
||||
service: script.mad_announcement
|
||||
|
||||
# 📅 Ugens madplan
|
||||
- type: custom:button-card
|
||||
entity: sensor.mealie_madplan_ugen
|
||||
name: Ugens madplan
|
||||
show_icon: false
|
||||
show_name: true
|
||||
show_state: false
|
||||
styles:
|
||||
card:
|
||||
- padding: 0
|
||||
- border-radius: 12px
|
||||
name:
|
||||
- font-size: 16px
|
||||
- font-weight: bold
|
||||
- padding: 12px 12px 4px 12px
|
||||
- justify-self: start
|
||||
custom_fields:
|
||||
week: |
|
||||
[[[
|
||||
if (!entity || !entity.attributes || !entity.attributes.items)
|
||||
return '<div>Ingen madplan data</div>';
|
||||
var items = entity.attributes.items;
|
||||
var dayNames = ['Søndag','Mandag','Tirsdag','Onsdag','Torsdag','Fredag','Lørdag'];
|
||||
var today = new Date().toISOString().slice(0,10);
|
||||
var meals = {};
|
||||
for (var j = 0; j < items.length; j++) {
|
||||
var it = items[j];
|
||||
if (it.recipe && it.recipe.name) {
|
||||
meals[it.date] = { name: it.recipe.name, slug: it.recipe.slug || '' };
|
||||
}
|
||||
}
|
||||
var now = new Date();
|
||||
var dow = now.getDay();
|
||||
var monday = new Date(now);
|
||||
monday.setDate(now.getDate() - (dow === 0 ? 6 : dow - 1));
|
||||
var html = '<div style="width:100%">';
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var d = new Date(monday);
|
||||
d.setDate(monday.getDate() + i);
|
||||
var dateStr = d.toISOString().slice(0,10);
|
||||
var dayName = dayNames[d.getDay()];
|
||||
var meal = meals[dateStr];
|
||||
var isToday = dateStr === today;
|
||||
var bg = isToday ? 'var(--primary-color)' : 'transparent';
|
||||
var tc = isToday ? 'white' : 'var(--primary-text-color)';
|
||||
var dc = isToday ? 'rgba(255,255,255,0.7)' : 'var(--secondary-text-color)';
|
||||
var fw = isToday ? 'bold' : 'normal';
|
||||
var br = isToday ? '8px' : '0';
|
||||
var bb = isToday ? 'none' : '1px solid var(--divider-color)';
|
||||
var mn = meal ? meal.name : '-';
|
||||
var sl = meal ? meal.slug : '';
|
||||
var lnk = sl ? 'http://anneclaus.dk:9925/g/home/r/' + sl : '';
|
||||
var cur = sl ? 'pointer' : 'default';
|
||||
var oc = sl ? ' onclick="window.open(\x27' + lnk + '\x27,\x27_blank\x27)"' : '';
|
||||
html += '<div style="display:flex;align-items:center;padding:10px 12px;background:' + bg + ';border-radius:' + br + ';border-bottom:' + bb + ';cursor:' + cur + '"' + oc + '>';
|
||||
html += '<div style="width:70px;font-size:12px;color:' + dc + ';text-transform:uppercase;font-weight:600">' + dayName + '</div>';
|
||||
html += '<div style="flex:1;font-size:15px;color:' + tc + ';font-weight:' + fw + '">' + mn + '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
]]]
|
||||
extra_styles: |
|
||||
#week {
|
||||
width: 100%;
|
||||
}
|
||||
# 📅 Madplan: i dag + 6 dage
|
||||
- type: markdown
|
||||
content: |-
|
||||
{%- set ns = namespace(rows="") -%}
|
||||
{%- set items = state_attr('sensor.mealie_madplan_ugen', 'items') or [] -%}
|
||||
{%- set days = ['Mandag','Tirsdag','Onsdag','Torsdag','Fredag','Lørdag','Søndag'] -%}
|
||||
{%- for offset in range(7) -%}
|
||||
{%- set day = now().date() + timedelta(days=offset) -%}
|
||||
{%- set ms = items | selectattr('date', 'eq', day.isoformat()) | list -%}
|
||||
{%- set m = ms[0] if ms else none -%}
|
||||
{%- set recipe = m.recipe if m else none -%}
|
||||
{%- set name = recipe.name if recipe else '' -%}
|
||||
{%- set slug = recipe.slug if recipe else '' -%}
|
||||
{%- set label = 'I dag' if offset == 0 else days[day.weekday()] -%}
|
||||
{%- if slug -%}
|
||||
{%- set ns.rows = ns.rows + "| **" + label + "** | [" + name + "](https://mealie.anneclaus.dk/g/home/r/" + slug + ") |\n" -%}
|
||||
{%- elif name -%}
|
||||
{%- set ns.rows = ns.rows + "| **" + label + "** | " + name + " |\n" -%}
|
||||
{%- else -%}
|
||||
{%- set ns.rows = ns.rows + "| **" + label + "** | - |\n" -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
## Næste 7 dage
|
||||
|
||||
| Dag | Ret |
|
||||
| --- | --- |
|
||||
{{ ns.rows }}
|
||||
|
||||
# 🎵 Sonos Køkken
|
||||
- type: media-control
|
||||
entity: media_player.kokken
|
||||
name: Sonos Køkken
|
||||
|
||||
- type: grid
|
||||
columns: 2
|
||||
square: false
|
||||
cards:
|
||||
- type: custom:button-card
|
||||
name: Volumen ned
|
||||
icon: mdi:volume-minus
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.volume_down
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
hold_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.volume_down
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
styles:
|
||||
card:
|
||||
- height: 72px
|
||||
- font-size: 16px
|
||||
- background: var(--primary-color)
|
||||
icon:
|
||||
- color: white
|
||||
- width: 36px
|
||||
name:
|
||||
- color: white
|
||||
- font-size: 13px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Volumen op
|
||||
icon: mdi:volume-plus
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.volume_up
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
hold_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.volume_up
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
styles:
|
||||
card:
|
||||
- height: 72px
|
||||
- font-size: 16px
|
||||
- background: var(--primary-color)
|
||||
icon:
|
||||
- color: white
|
||||
- width: 36px
|
||||
name:
|
||||
- color: white
|
||||
- font-size: 13px
|
||||
|
||||
- type: grid
|
||||
columns: 3
|
||||
square: false
|
||||
cards:
|
||||
- type: custom:button-card
|
||||
name: DR P3
|
||||
icon: mdi:radio
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "0 DR P3"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Family Mix
|
||||
icon: mdi:account-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "1 Family Mix"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Danske fav.
|
||||
icon: mdi:music-note
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Danske favoritter"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Rock klassikere
|
||||
icon: mdi:music-note-outline
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Danske rock klassikere"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Anne Mix 1
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Anne Daily Mix 1"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Anne Mix 2
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Anne Daily Mix 2"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Anne Mix 3
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Anne Daily Mix 3"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Anne Mix 4
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Anne Daily Mix 4"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Anne Mix 5
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Anne Daily Mix 5"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Anne Mix 6
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Anne Daily Mix 6"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Claus Mix 1
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Claus Daily Mix 1"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Claus Mix 2
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Claus Daily Mix 2"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Claus Mix 3
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Claus Daily Mix 3"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Claus Mix 4
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Claus Daily Mix 4"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Claus Mix 5
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Claus Daily Mix 5"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Claus Mix 6
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Claus Daily Mix 6"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Andreas Mix 1
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Andreas Daily Mix 1"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Andreas Mix 2
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Andreas Daily Mix 2"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Andreas Mix 3
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Andreas Daily Mix 3"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Andreas Mix 4
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Andreas Daily Mix 4"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Andreas Mix 5
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Andreas Daily Mix 5"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Andreas Mix 6
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Andreas Daily Mix 6"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Daniel Mix 1
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Daniel Daily Mix 1"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Daniel Mix 2
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Daniel Daily Mix 2"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Daniel Mix 3
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Daniel Daily Mix 3"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Daniel Mix 4
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Daniel Daily Mix 4"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Daniel Mix 5
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Daniel Daily Mix 5"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
- type: custom:button-card
|
||||
name: Daniel Mix 6
|
||||
icon: mdi:playlist-music
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: media_player.select_source
|
||||
target:
|
||||
entity_id: media_player.kokken
|
||||
data:
|
||||
source: "Daniel Daily Mix 6"
|
||||
styles:
|
||||
card:
|
||||
- height: 52px
|
||||
- padding: 6px 8px
|
||||
icon:
|
||||
- width: 18px
|
||||
name:
|
||||
- font-size: 11px
|
||||
|
||||
|
||||
@@ -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
@@ -37,10 +37,15 @@ sections:
|
||||
name: Netatmo
|
||||
- entity: sensor.hue_motion_sensor_2_temperature_2
|
||||
name: Hue
|
||||
|
||||
- type: thermostat
|
||||
entity: climate.andreas
|
||||
name: Andreas
|
||||
- entity: climate.andreas
|
||||
attribute: current_temperature
|
||||
name: Roth aktuelt
|
||||
- entity: climate.andreas
|
||||
attribute: temperature
|
||||
name: Roth mål
|
||||
stroke_width: 1
|
||||
curve: stepline
|
||||
color: "#ff8800"
|
||||
|
||||
- type: grid
|
||||
cards:
|
||||
@@ -72,10 +77,15 @@ sections:
|
||||
name: Netatmo
|
||||
- entity: sensor.hue_motion_sensor_2_temperature
|
||||
name: Hue
|
||||
|
||||
- type: thermostat
|
||||
entity: climate.daniel
|
||||
name: Daniel
|
||||
- entity: climate.daniel
|
||||
attribute: current_temperature
|
||||
name: Roth aktuelt
|
||||
- entity: climate.daniel
|
||||
attribute: temperature
|
||||
name: Roth mål
|
||||
stroke_width: 1
|
||||
curve: stepline
|
||||
color: "#ff8800"
|
||||
|
||||
- type: grid
|
||||
cards:
|
||||
@@ -111,10 +121,15 @@ sections:
|
||||
name: Skab Claus
|
||||
- entity: sensor.temperature
|
||||
name: Skab Anne
|
||||
|
||||
- type: thermostat
|
||||
entity: climate.sovev_prelse
|
||||
name: Sovevaerelse
|
||||
- entity: climate.sovev_prelse
|
||||
attribute: current_temperature
|
||||
name: Roth aktuelt
|
||||
- entity: climate.sovev_prelse
|
||||
attribute: temperature
|
||||
name: Roth mål
|
||||
stroke_width: 1
|
||||
curve: stepline
|
||||
color: "#ff8800"
|
||||
|
||||
- type: grid
|
||||
cards:
|
||||
@@ -144,10 +159,17 @@ sections:
|
||||
series:
|
||||
- entity: sensor.kontor_motion_temperatur
|
||||
name: Hue
|
||||
|
||||
- type: thermostat
|
||||
entity: climate.kontor
|
||||
name: Kontor
|
||||
- entity: sensor.annes_havesensor_indoor_temperature
|
||||
name: Havesensor inde
|
||||
- entity: climate.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
|
||||
cards:
|
||||
@@ -177,10 +199,15 @@ sections:
|
||||
series:
|
||||
- entity: sensor.gang_sensor_temperature
|
||||
name: Hue
|
||||
|
||||
- type: thermostat
|
||||
entity: climate.fordelingsgang
|
||||
name: Gang
|
||||
- entity: climate.fordelingsgang
|
||||
attribute: current_temperature
|
||||
name: Roth aktuelt
|
||||
- entity: climate.fordelingsgang
|
||||
attribute: temperature
|
||||
name: Roth mål
|
||||
stroke_width: 1
|
||||
curve: stepline
|
||||
color: "#ff8800"
|
||||
|
||||
- type: grid
|
||||
cards:
|
||||
@@ -188,7 +215,7 @@ sections:
|
||||
graph_span: 24h
|
||||
header:
|
||||
show: true
|
||||
title: Bad
|
||||
title: Badeværelse
|
||||
show_states: true
|
||||
colorize_states: true
|
||||
now:
|
||||
@@ -210,6 +237,15 @@ sections:
|
||||
series:
|
||||
- entity: sensor.bad_motion_sensor_temperature
|
||||
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
|
||||
cards:
|
||||
@@ -239,6 +275,15 @@ sections:
|
||||
series:
|
||||
- entity: sensor.stue_motion_temperatur
|
||||
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
|
||||
cards:
|
||||
@@ -268,13 +313,201 @@ sections:
|
||||
series:
|
||||
- entity: sensor.forgang_sensor_temperature
|
||||
name: Hue
|
||||
|
||||
- type: thermostat
|
||||
entity: climate.forgang
|
||||
name: Forgang
|
||||
- entity: climate.forgang
|
||||
attribute: current_temperature
|
||||
name: Roth aktuelt
|
||||
- entity: climate.forgang
|
||||
attribute: temperature
|
||||
name: Roth mål
|
||||
stroke_width: 1
|
||||
curve: stepline
|
||||
color: "#ff8800"
|
||||
|
||||
- type: grid
|
||||
cards:
|
||||
- type: thermostat
|
||||
entity: climate.lille_bad
|
||||
name: Lille bad
|
||||
- type: custom:apexcharts-card
|
||||
graph_span: 24h
|
||||
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 (1–5)
|
||||
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)
|
||||
@@ -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%
|
||||
@@ -63,7 +63,128 @@ sections:
|
||||
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
|
||||
cards:
|
||||
- type: heading
|
||||
|
||||
@@ -3,7 +3,7 @@ path: lys
|
||||
icon: mdi:lightbulb-multiple
|
||||
type: sections
|
||||
|
||||
max_columns: 2
|
||||
max_columns: 3
|
||||
|
||||
sections:
|
||||
- type: grid
|
||||
@@ -150,7 +150,7 @@ sections:
|
||||
data:
|
||||
entity_id: script.raketloop
|
||||
- type: grid
|
||||
columns: 2
|
||||
columns: 3
|
||||
square: false
|
||||
cards:
|
||||
- type: custom:mushroom-light-card
|
||||
@@ -464,7 +464,7 @@ sections:
|
||||
- light.spejl2
|
||||
- light.badevaerelse_2
|
||||
- type: grid
|
||||
columns: 2
|
||||
columns: 3
|
||||
square: false
|
||||
cards:
|
||||
- type: custom:mushroom-light-card
|
||||
@@ -569,7 +569,7 @@ sections:
|
||||
show_color_temp_control: true
|
||||
collapsible_controls: true
|
||||
- type: grid
|
||||
columns: 2
|
||||
columns: 3
|
||||
square: false
|
||||
cards:
|
||||
- type: custom:mushroom-light-card
|
||||
@@ -647,7 +647,7 @@ sections:
|
||||
data:
|
||||
entity_id: script.have_color_scene
|
||||
- type: grid
|
||||
columns: 2
|
||||
columns: 3
|
||||
square: false
|
||||
cards:
|
||||
- type: custom:mushroom-light-card
|
||||
@@ -711,7 +711,7 @@ sections:
|
||||
show_brightness_control: true
|
||||
collapsible_controls: true
|
||||
- type: grid
|
||||
columns: 2
|
||||
columns: 3
|
||||
square: false
|
||||
cards:
|
||||
- type: custom:mushroom-light-card
|
||||
@@ -756,7 +756,7 @@ sections:
|
||||
- type: heading
|
||||
heading: Ovrige
|
||||
- type: grid
|
||||
columns: 2
|
||||
columns: 3
|
||||
square: false
|
||||
cards:
|
||||
- type: custom:mushroom-light-card
|
||||
@@ -779,3 +779,64 @@ sections:
|
||||
use_light_color: true
|
||||
show_brightness_control: 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)
|
||||
@@ -4,6 +4,7 @@ services:
|
||||
image: homeassistant/home-assistant:latest
|
||||
volumes:
|
||||
- /volume1/homeassistant:/config
|
||||
- /volume1/docker/homeassistant/backups:/backups
|
||||
devices:
|
||||
- /dev/ttyUSB0:/dev/ttyUSB0
|
||||
network_mode: host
|
||||
|
||||
@@ -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
|
||||
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:
|
||||
container_name: gitea
|
||||
image: gitea/gitea:${GITEA_IMAGE_TAG:-latest}
|
||||
@@ -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
@@ -1,30 +1,48 @@
|
||||
# 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
|
||||
**Status:** Partial - content accessible but SSL certificate warning
|
||||
|
||||
### 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
|
||||
- [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).
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
# Google Keep basis-indkøbsliste
|
||||
# En vare per linje. Linjer der starter med # ignoreres.
|
||||
# Eksempel:
|
||||
# Mælk
|
||||
# Toiletpapir
|
||||
# Bananer
|
||||
@@ -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 3–5 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
@@ -1,44 +1,35 @@
|
||||
# Ø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
|
||||
|
||||
### Zigbee Smart Plugs (med energimåling)
|
||||
|
||||
| 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
|
||||
### Zigbee Termostater – 1× Danfoss Ally TRV
|
||||
|
||||
| Antal | Rum | Beskrivelse | Status |
|
||||
|---|---|---|---|
|
||||
| 1 | Stue | Danfoss Ally TRV (Zigbee) | ⬜ Ønsket |
|
||||
| 1 | Badeværelse | Danfoss Ally TRV (Zigbee) | ⬜ Ønsket |
|
||||
| 1 | Badeværelse | Danfoss Ally TRV (Zigbee) | ✅ Monteret og online (7. maj 2026) |
|
||||
|
||||
**Bekræftet ventiltype:** Danfoss RA (snap-on clips) – Ally passer direkte med medfølgende RA-adapter.
|
||||
|
||||
@@ -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
|
||||
|
||||
### 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
|
||||
|
||||
| Enhed | Beskrivelse | Bemærkninger |
|
||||
@@ -97,6 +137,30 @@
|
||||
| Luftkvalitetssensor | VOC / PM2.5 | Udvidelse af eksisterende CO₂-måling |
|
||||
| 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 ✅
|
||||
@@ -105,4 +169,5 @@
|
||||
|
||||
| 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 |
|
||||
|
||||
@@ -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` på `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.
|
||||
@@ -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 | 184–192 | 188–192 | 184 | Stærk, tæt på koordinator |
|
||||
| stik_andreas | 184–192 | 188–192 | 184 | Stærk, tæt på koordinator |
|
||||
| stik_soveværelse | 76–192 | 76–188 | 84 | Svingende — route-afhængig |
|
||||
| stik_quooker | 144–152 | 124–148 | 144 | God |
|
||||
| stik_sonos_stue | 108–152 | 108–152 | 148 | God |
|
||||
| stik_lillebad | 112–136 | 112–136 | 144 | OK–God |
|
||||
| stik_daniel | — | 100–120 | 92 | OK |
|
||||
| stik_bryggers | 100–132 | 100–132 | 108 | OK |
|
||||
| stik_bad | 40–104 | 96–144 | 144 | Forbedret |
|
||||
| stik_kontor | 52–96 | 72–96 | 96 | Forbedret |
|
||||
| stik_fryser | 56–84 | 56–84 | 84 | Forbedret |
|
||||
| stik_indkørsel | 44–80 | 56–124 | 84 | OK efter genstart |
|
||||
| garageport sensor | 40–92 | 52–92 | 84 | Bedste måling! |
|
||||
| badevarelse | 108–136 | 108–136 | 144 | God |
|
||||
| stue | 84–116 | 84–116 | 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 40–80
|
||||
- **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`
|
||||
@@ -7,6 +7,10 @@
|
||||
- condition: state
|
||||
entity_id: binary_sensor.arbejdsdagimorgen
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ not is_state('input_select.anne_status', 'syg') and
|
||||
not is_state('input_select.claus_status', 'syg') }}
|
||||
action:
|
||||
- service: homeassistant.turn_on
|
||||
entity_id: switch.sonos_alarm_1782
|
||||
@@ -31,6 +35,8 @@
|
||||
- condition: state
|
||||
entity_id: binary_sensor.arbejdsdagimorgen
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: "{{ not is_state('input_select.andreas_status', 'syg') }}"
|
||||
action:
|
||||
- service: homeassistant.turn_on
|
||||
entity_id: switch.sonos_alarm_445
|
||||
@@ -58,6 +64,8 @@
|
||||
value_template: '{{ now().date() | string != "2022-12-24" }}'
|
||||
- condition: template
|
||||
value_template: '{{ now().date() | string != "2022-12-31" }}'
|
||||
- condition: template
|
||||
value_template: "{{ not is_state('input_select.andreas_status', 'syg') }}"
|
||||
action:
|
||||
- service: homeassistant.turn_on
|
||||
entity_id: switch.sonos_alarm_1874
|
||||
@@ -78,6 +86,8 @@
|
||||
- condition: state
|
||||
entity_id: binary_sensor.arbejdsdagimorgen
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: "{{ not is_state('input_select.daniel_status', 'syg') }}"
|
||||
action:
|
||||
- service: homeassistant.turn_on
|
||||
entity_id: switch.sonos_alarm_377
|
||||
@@ -103,6 +113,8 @@
|
||||
- condition: state
|
||||
entity_id: binary_sensor.arbejdsdagimorgen
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: "{{ not is_state('input_select.daniel_status', 'syg') }}"
|
||||
action:
|
||||
- service: homeassistant.turn_on
|
||||
entity_id: switch.sonos_alarm_1894
|
||||
@@ -128,6 +140,8 @@
|
||||
- condition: state
|
||||
entity_id: binary_sensor.arbejdsdagimorgen
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: "{{ not is_state('input_select.daniel_status', 'syg') }}"
|
||||
action:
|
||||
- service: homeassistant.turn_on
|
||||
entity_id: switch.sonos_alarm_2273
|
||||
@@ -155,6 +169,8 @@
|
||||
value_template: '{{ now().date() | string != "2022-12-24" }}'
|
||||
- condition: template
|
||||
value_template: '{{ now().date() | string != "2022-12-31" }}'
|
||||
- condition: template
|
||||
value_template: "{{ not is_state('input_select.daniel_status', 'syg') }}"
|
||||
action:
|
||||
- service: homeassistant.turn_on
|
||||
entity_id: switch.sonos_alarm_3471
|
||||
@@ -177,6 +193,10 @@
|
||||
- condition: state
|
||||
entity_id: binary_sensor.arbejdsdagimorgen
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ not is_state('input_select.anne_status', 'syg') and
|
||||
not is_state('input_select.claus_status', 'syg') }}
|
||||
action:
|
||||
- service: homeassistant.turn_on
|
||||
entity_id: switch.sonos_alarm_298
|
||||
@@ -194,27 +214,3 @@
|
||||
- service: homeassistant.turn_off
|
||||
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:
|
||||
- variables:
|
||||
lights:
|
||||
- light.indkorsel_2
|
||||
- light.extended_color_light_1
|
||||
- light.garage
|
||||
lights: >
|
||||
{% set base = ['light.indkorsel_2', 'light.garage'] %}
|
||||
{% if now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 %}
|
||||
{{ (base + ['light.extended_color_light_1']) | list }}
|
||||
{% else %}
|
||||
{{ base }}
|
||||
{% endif %}
|
||||
|
||||
lights_to_turn_on: >
|
||||
{{ lights | select('is_state','off') | list }}
|
||||
@@ -45,9 +48,17 @@
|
||||
target:
|
||||
entity_id: "{{ lights_to_turn_on }}"
|
||||
|
||||
- service: switch.turn_on
|
||||
data:
|
||||
entity_id: switch.stik_indkorsel
|
||||
|
||||
- delay: "00:10:00"
|
||||
|
||||
- service: light.turn_off
|
||||
target:
|
||||
entity_id: "{{ lights_to_turn_on }}"
|
||||
|
||||
- service: switch.turn_off
|
||||
data:
|
||||
entity_id: switch.stik_indkorsel
|
||||
|
||||
|
||||
@@ -55,53 +55,12 @@
|
||||
sequence:
|
||||
- parallel:
|
||||
- sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: time
|
||||
after: '20:00:00'
|
||||
before: '06:00:00'
|
||||
sequence:
|
||||
- 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
|
||||
- variables:
|
||||
lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
|
||||
- service: script.spil_paa_lille_bad
|
||||
data:
|
||||
lydfil: german-shephard.mp3
|
||||
volumen: "{{ lille_bad_volumen }}"
|
||||
- sequence:
|
||||
- service: media_player.volume_set
|
||||
data:
|
||||
@@ -165,53 +124,12 @@
|
||||
sequence:
|
||||
- parallel:
|
||||
- sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: time
|
||||
after: '20:00:00'
|
||||
before: '06:00:00'
|
||||
sequence:
|
||||
- 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
|
||||
- variables:
|
||||
lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
|
||||
- service: script.spil_paa_lille_bad
|
||||
data:
|
||||
lydfil: dog-barking-2-bullmastiff.mp3
|
||||
volumen: "{{ lille_bad_volumen }}"
|
||||
- sequence:
|
||||
- service: tts.speak
|
||||
target:
|
||||
@@ -233,53 +151,12 @@
|
||||
sequence:
|
||||
- parallel:
|
||||
- sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: time
|
||||
after: '20:00:00'
|
||||
before: '06:00:00'
|
||||
sequence:
|
||||
- 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
|
||||
- variables:
|
||||
lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
|
||||
- service: script.spil_paa_lille_bad
|
||||
data:
|
||||
lydfil: two-tone-chime.mp3
|
||||
volumen: "{{ lille_bad_volumen }}"
|
||||
- sequence:
|
||||
- service: tts.speak
|
||||
target:
|
||||
|
||||
@@ -44,53 +44,13 @@
|
||||
sequence:
|
||||
- parallel:
|
||||
- sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: time
|
||||
after: '20:00:00'
|
||||
before: '06:00:00'
|
||||
sequence:
|
||||
- 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/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
|
||||
- variables:
|
||||
lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
|
||||
lille_bad_lydfil: "{{ 'Halloween-doorbell.mp3' if (now().hour >= 20 or now().hour < 6) else 'doorbell.mp3' }}"
|
||||
- service: script.spil_paa_lille_bad
|
||||
data:
|
||||
lydfil: "{{ lille_bad_lydfil }}"
|
||||
volumen: "{{ lille_bad_volumen }}"
|
||||
- sequence:
|
||||
- service: tts.speak
|
||||
target:
|
||||
@@ -120,60 +80,36 @@
|
||||
- delay: 2
|
||||
- service: scene.turn_on
|
||||
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:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.payload_json.event == "SS" }}'
|
||||
sequence:
|
||||
- parallel:
|
||||
- sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: time
|
||||
after: '20:00:00'
|
||||
before: '06:00:00'
|
||||
sequence:
|
||||
- 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
|
||||
- variables:
|
||||
lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
|
||||
- service: script.spil_paa_lille_bad
|
||||
data:
|
||||
lydfil: german-shephard.mp3
|
||||
volumen: "{{ lille_bad_volumen }}"
|
||||
- sequence:
|
||||
- service: tts.speak
|
||||
target:
|
||||
@@ -195,53 +131,12 @@
|
||||
sequence:
|
||||
- parallel:
|
||||
- sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: time
|
||||
after: '20:00:00'
|
||||
before: '06:00:00'
|
||||
sequence:
|
||||
- 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
|
||||
- variables:
|
||||
lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
|
||||
- service: script.spil_paa_lille_bad
|
||||
data:
|
||||
lydfil: dog-barking-2-bullmastiff.mp3
|
||||
volumen: "{{ lille_bad_volumen }}"
|
||||
- sequence:
|
||||
- service: tts.speak
|
||||
target:
|
||||
@@ -263,53 +158,12 @@
|
||||
sequence:
|
||||
- parallel:
|
||||
- sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: time
|
||||
after: '20:00:00'
|
||||
before: '06:00:00'
|
||||
sequence:
|
||||
- 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
|
||||
- variables:
|
||||
lille_bad_volumen: "{{ 0.5 if (now().hour >= 20 or now().hour < 6) else 0.8 }}"
|
||||
- service: script.spil_paa_lille_bad
|
||||
data:
|
||||
lydfil: two-tone-chime.mp3
|
||||
volumen: "{{ lille_bad_volumen }}"
|
||||
- sequence:
|
||||
- service: tts.speak
|
||||
target:
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
data:
|
||||
group_members:
|
||||
- media_player.badevaerelse
|
||||
- media_player.lille_badevaerelse
|
||||
- media_player.sovevaerelse
|
||||
- media_player.stue
|
||||
- media_player.alrum
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
- condition: state # from sunset until sunrise
|
||||
entity_id: sun.sun
|
||||
state: 'below_horizon'
|
||||
- condition: template # Vintersæson uge 42-8
|
||||
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
|
||||
action:
|
||||
- service: light.turn_on
|
||||
data:
|
||||
@@ -19,6 +21,9 @@
|
||||
trigger:
|
||||
platform: sun
|
||||
event: sunrise
|
||||
condition:
|
||||
- condition: template # Vintersæson uge 42-8
|
||||
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
|
||||
action:
|
||||
- service: light.turn_off
|
||||
data:
|
||||
@@ -31,6 +36,8 @@
|
||||
condition:
|
||||
- condition: time
|
||||
before: '21:30:00'
|
||||
- condition: template # Vintersæson uge 42-8
|
||||
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
|
||||
action:
|
||||
- service: light.turn_on
|
||||
data:
|
||||
@@ -40,6 +47,9 @@
|
||||
trigger:
|
||||
platform: time
|
||||
at: "22:00:00"
|
||||
condition:
|
||||
- condition: template # Vintersæson uge 42-8
|
||||
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
|
||||
action:
|
||||
- service: light.turn_off
|
||||
data:
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
{{ 1000 <= t <= 2030 }}
|
||||
{% endif %}
|
||||
|
||||
# Ikke syg
|
||||
- condition: template
|
||||
value_template: "{{ not is_state('input_select.andreas_status', 'syg') }}"
|
||||
|
||||
sequence:
|
||||
|
||||
- service: light.turn_on
|
||||
|
||||
@@ -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
|
||||
alias: Badeværelse lys via bevægelse
|
||||
mode: restart
|
||||
@@ -7,6 +31,12 @@
|
||||
entity_id: binary_sensor.badevaerelse_bevaegelse
|
||||
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:
|
||||
- choose:
|
||||
# Arbejdsdag dagtid (06:00–22:00)
|
||||
@@ -49,10 +79,104 @@
|
||||
- platform: state
|
||||
entity_id: binary_sensor.badevaerelse_bevaegelse
|
||||
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:
|
||||
- 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
|
||||
target:
|
||||
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
|
||||
|
||||
@@ -1,94 +1,60 @@
|
||||
# - alias: 'Lys Daniel dag - arbejdsdag - sunrise'
|
||||
# trigger:
|
||||
# platform: time
|
||||
# at: '06:00:00'
|
||||
# condition:
|
||||
# - condition: state
|
||||
# entity_id: binary_sensor.arbejdsdag
|
||||
# state: 'on'
|
||||
# action:
|
||||
# - service: script.sunrise
|
||||
- id: daniel_motion_lys
|
||||
alias: Daniel lys via bevaegelse
|
||||
mode: restart
|
||||
|
||||
|
||||
- alias: 'Lys Daniel dag - arbejdsdag'
|
||||
trigger:
|
||||
platform: state
|
||||
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: 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
|
||||
to: "on"
|
||||
id: motion_on
|
||||
|
||||
- alias: 'Lys Daniel dag - ikke arbejdsdag'
|
||||
trigger:
|
||||
platform: state
|
||||
- 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: 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
|
||||
to: "off"
|
||||
id: motion_off
|
||||
|
||||
|
||||
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'
|
||||
trigger:
|
||||
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
|
||||
action:
|
||||
- choose:
|
||||
|
||||
# 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
|
||||
|
||||
@@ -1,171 +1,68 @@
|
||||
- alias: 'Lys i gang - arbejdsdag - dag'
|
||||
trigger:
|
||||
platform: state
|
||||
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
|
||||
- id: gang_motion_lys
|
||||
alias: Gang lys via bevægelse
|
||||
mode: restart
|
||||
|
||||
- alias: 'Lys i gang - ikke arbejdsdag - dag'
|
||||
trigger:
|
||||
platform: state
|
||||
trigger:
|
||||
- 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_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
|
||||
to: "on"
|
||||
id: motion_on
|
||||
|
||||
########## NIGHT
|
||||
- platform: state
|
||||
entity_id: binary_sensor.gang_sensor_motion
|
||||
to: "off"
|
||||
id: motion_off
|
||||
|
||||
- alias: 'Lys i gang - arbejdsdag - nat'
|
||||
trigger:
|
||||
platform: state
|
||||
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: '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
|
||||
variables:
|
||||
lux_limit: "{{ states('input_number.gang_lux_threshold') | int }}"
|
||||
is_dag: >
|
||||
{% set t = now().strftime('%H%M') | int %}
|
||||
{% if is_state('binary_sensor.arbejdsdag', 'on') %}
|
||||
{{ 630 <= t < 2130 }}
|
||||
{% else %}
|
||||
{{ 800 <= t < 2200 }}
|
||||
{% endif %}
|
||||
|
||||
- alias: 'Lys i gang - ikke arbejdsdag - nat'
|
||||
trigger:
|
||||
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
|
||||
action:
|
||||
- choose:
|
||||
|
||||
# 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,9 +8,10 @@
|
||||
to: "on"
|
||||
|
||||
condition:
|
||||
- condition: numeric_state
|
||||
entity_id: sensor.kontor_belysningsstyrke
|
||||
below: 60
|
||||
- condition: template
|
||||
value_template: >
|
||||
{{ states('sensor.kontor_belysningsstyrke') | int <
|
||||
states('input_number.kontor_lux_threshold') | int }}
|
||||
|
||||
action:
|
||||
- service: scene.turn_on
|
||||
@@ -36,7 +37,8 @@
|
||||
- platform: state
|
||||
entity_id: binary_sensor.kontor_motion_bevaegelse
|
||||
to: "on"
|
||||
timeout: "00:10:00"
|
||||
timeout:
|
||||
minutes: "{{ states('input_number.kontor_timeout_day') | int }}"
|
||||
continue_on_timeout: true
|
||||
- condition: state
|
||||
entity_id: binary_sensor.kontor_motion_bevaegelse
|
||||
@@ -67,7 +69,8 @@
|
||||
- platform: state
|
||||
entity_id: binary_sensor.kontor_motion_bevaegelse
|
||||
to: "on"
|
||||
timeout: "00:05:00"
|
||||
timeout:
|
||||
minutes: "{{ states('input_number.kontor_timeout_night') | int }}"
|
||||
continue_on_timeout: true
|
||||
- condition: state
|
||||
entity_id: binary_sensor.kontor_motion_bevaegelse
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
- condition: state
|
||||
entity_id: binary_sensor.arbejdsdag
|
||||
state: 'on'
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ not is_state('input_select.anne_status', 'syg') and
|
||||
not is_state('input_select.claus_status', 'syg') }}
|
||||
action:
|
||||
- service: script.sunrise
|
||||
|
||||
@@ -25,6 +29,10 @@
|
||||
- condition: time
|
||||
after: '06:30: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:
|
||||
- service: homeassistant.turn_on
|
||||
target:
|
||||
@@ -47,6 +55,10 @@
|
||||
- condition: time
|
||||
after: '10: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:
|
||||
- service: homeassistant.turn_on
|
||||
target:
|
||||
@@ -161,6 +173,9 @@
|
||||
- condition: time
|
||||
after: '06:30:00'
|
||||
before: '22:00:00'
|
||||
- condition: state
|
||||
entity_id: script.godnat_sovevaerelse
|
||||
state: 'off'
|
||||
action:
|
||||
- service: homeassistant.turn_off
|
||||
data:
|
||||
|
||||
+121
-140
@@ -1,157 +1,138 @@
|
||||
- id: stue_motion_lys
|
||||
alias: Stue lys via bevægelse
|
||||
alias: Stue lys via bevaegelse
|
||||
mode: restart
|
||||
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id: binary_sensor.stue_bevaegelse
|
||||
to: "on"
|
||||
id: motion_on
|
||||
|
||||
condition:
|
||||
- condition: numeric_state
|
||||
entity_id: sensor.stue_belysningsstyrke
|
||||
below: 60
|
||||
- platform: state
|
||||
entity_id: binary_sensor.stue_bevaegelse
|
||||
to: "off"
|
||||
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:
|
||||
- choose:
|
||||
|
||||
# Motion on: taend lys hvis lux lavt
|
||||
- conditions:
|
||||
- condition: time
|
||||
after: "06:00:00"
|
||||
before: "16:00:00"
|
||||
- condition: trigger
|
||||
id: motion_on
|
||||
- condition: template
|
||||
value_template: >
|
||||
{{ states('sensor.stue_belysningsstyrke') | int < lux_limit }}
|
||||
sequence:
|
||||
- service: scene.turn_on
|
||||
target:
|
||||
entity_id: scene.stue_bright
|
||||
- choose:
|
||||
# Gæster: altid Annes favorit uanset tidspunkt
|
||||
- conditions:
|
||||
- condition: state
|
||||
entity_id: input_boolean.gaester
|
||||
state: "on"
|
||||
sequence:
|
||||
- service: scene.turn_on
|
||||
target:
|
||||
entity_id: scene.stue_annes_favorit
|
||||
- conditions:
|
||||
- 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:
|
||||
- condition: time
|
||||
after: "16:00:00"
|
||||
before: "22:00:00"
|
||||
- condition: trigger
|
||||
id: motion_off
|
||||
- condition: template
|
||||
value_template: >
|
||||
{{ dagperiode not in ('aften','aften_lys') or
|
||||
is_state('media_player.samsung_s95ca_55_3', 'off') }}
|
||||
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:
|
||||
entity_id: scene.stue_annes_favorit
|
||||
default:
|
||||
- service: scene.turn_on
|
||||
target:
|
||||
entity_id: scene.stue_relax_minus_syd
|
||||
entity_id: light.livingroom
|
||||
|
||||
- id: stue_motion_sluk_morgen
|
||||
alias: Sluk stue lys efter 60 min uden bevægelse om morgenen
|
||||
mode: restart
|
||||
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id: binary_sensor.stue_bevaegelse
|
||||
to: "off"
|
||||
|
||||
condition:
|
||||
- condition: time
|
||||
after: "06:00:00"
|
||||
before: "16:00:00"
|
||||
|
||||
action:
|
||||
- wait_for_trigger:
|
||||
- platform: state
|
||||
entity_id: binary_sensor.stue_bevaegelse
|
||||
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
|
||||
# TV slukket om aftenen: vent 10 min, sluk hvis ingen bevaegelse
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: tv_off
|
||||
- condition: template
|
||||
value_template: "{{ dagperiode in ('aften','aften_lys') }}"
|
||||
sequence:
|
||||
- delay:
|
||||
minutes: "{{ timeout_min }}"
|
||||
- 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
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
- service: scene.turn_on
|
||||
data:
|
||||
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'
|
||||
trigger:
|
||||
@@ -30,6 +33,9 @@
|
||||
- service: light.turn_off
|
||||
data:
|
||||
entity_id: light.indkorsel_2
|
||||
- service: switch.turn_off
|
||||
data:
|
||||
entity_id: switch.stik_indkorsel
|
||||
|
||||
- alias: 'Tænd lys indkørsel aften'
|
||||
trigger:
|
||||
@@ -43,6 +49,9 @@
|
||||
- service: scene.turn_on
|
||||
data:
|
||||
entity_id: scene.indkorsel_bright
|
||||
- service: switch.turn_on
|
||||
data:
|
||||
entity_id: switch.stik_indkorsel
|
||||
|
||||
- alias: 'Sluk lys indkørsel aften'
|
||||
trigger:
|
||||
@@ -52,6 +61,9 @@
|
||||
- service: light.turn_off
|
||||
data:
|
||||
entity_id: light.indkorsel_2
|
||||
- service: switch.turn_off
|
||||
data:
|
||||
entity_id: switch.stik_indkorsel
|
||||
|
||||
|
||||
- alias: 'Tænd lys indkørsel ved bevægelse'
|
||||
@@ -78,6 +90,9 @@
|
||||
- service: scene.turn_on
|
||||
data:
|
||||
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'
|
||||
trigger:
|
||||
@@ -97,8 +112,11 @@
|
||||
# entity_id: sun.sun
|
||||
# state: below_horizon
|
||||
action:
|
||||
service: light.turn_off
|
||||
data:
|
||||
entity_id: light.indkorsel_2
|
||||
- service: light.turn_off
|
||||
data:
|
||||
entity_id: light.indkorsel_2
|
||||
- service: switch.turn_off
|
||||
data:
|
||||
entity_id: switch.stik_indkorsel
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
entity_id:
|
||||
- light.drivhus
|
||||
- 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'
|
||||
trigger:
|
||||
@@ -29,12 +32,15 @@
|
||||
for:
|
||||
minutes: 10
|
||||
action:
|
||||
service: homeassistant.turn_off
|
||||
data:
|
||||
entity_id:
|
||||
- light.drivhus
|
||||
- light.paradis
|
||||
- light.extended_color_light_1
|
||||
- service: homeassistant.turn_off
|
||||
data:
|
||||
entity_id:
|
||||
- light.drivhus
|
||||
- light.paradis
|
||||
- condition: template
|
||||
value_template: "{{ now().isocalendar()[1] >= 42 or now().isocalendar()[1] <= 8 }}"
|
||||
- service: homeassistant.turn_off
|
||||
entity_id: light.extended_color_light_1
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
alias: "Mealie opdater madplan"
|
||||
trigger:
|
||||
@@ -7,3 +19,4 @@
|
||||
minutes: "/30"
|
||||
action:
|
||||
- service: shell_command.mealie_update
|
||||
|
||||
|
||||
@@ -94,11 +94,14 @@
|
||||
|
||||
trigger:
|
||||
- platform: time_pattern
|
||||
minutes: "0"
|
||||
minutes: "/15"
|
||||
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ now().hour >= 19 and now().hour <= 21 }}"
|
||||
- condition: state
|
||||
entity_id: input_boolean.dishwasher_reminder_snoozed
|
||||
state: "off"
|
||||
- condition: template
|
||||
value_template: "{{ is_state('sensor.dishwasher_status_2', 'Off') }}"
|
||||
- condition: or
|
||||
@@ -108,22 +111,68 @@
|
||||
state: "off"
|
||||
- condition: template
|
||||
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:
|
||||
- variables:
|
||||
reminder_time: "{{ now().strftime('%d-%m %H:%M') }}"
|
||||
reminder_time: "{{ now().strftime('%H:%M') }}"
|
||||
remote_off: "{{ is_state('binary_sensor.dishwasher_fjernbetjening', '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: >
|
||||
{% set issues = [] %}
|
||||
{% if remote_off %}
|
||||
{% set issues = issues + ['remote control er ikke slået til'] %}
|
||||
{% endif %}
|
||||
{% if door_not_closed %}
|
||||
{% set issues = issues + ['døren er ikke lukket'] %}
|
||||
{% endif %}
|
||||
{{ issues | join(' og ') }}
|
||||
{% if remote_off %}{% set issues = issues + ['remote control er ikke slået til'] %}{% endif %}
|
||||
{% if door_not_closed %}{% set issues = issues + ['døren er ikke lukket'] %}{% endif %}
|
||||
{% if low_salt %}{% set issues = issues + ['salt er lavt (' ~ states('sensor.dishwasher_salt_level') ~ '%)'] %}{% endif %}
|
||||
{% if low_rinse %}{% set issues = issues + ['afspændingsmiddel er lavt (' ~ states('sensor.dishwasher_rinse_aid_level') ~ '%)'] %}{% endif %}
|
||||
{% if low_powerdisk %}{% set issues = issues + ['powerdisk er lav (' ~ states('sensor.dishwasher_powerdisk_level') ~ '%)'] %}{% endif %}
|
||||
{% if info_on %}{% set issues = issues + ['info-advarsel aktiv'] %}{% endif %}
|
||||
{% if svigt %}{% set issues = issues + ['maskinsvigt'] %}{% endif %}
|
||||
{{ issues | join(', ') }}
|
||||
- service: notify.mobile_app_claus_iphone_15pro
|
||||
data:
|
||||
title: "Slå fjernbetjening til på opvaskemaskinen"
|
||||
message: "[{{ reminder_time }}] Opvaskemaskinen er planlagt til natkørsel, men {{ issue_text }}."
|
||||
title: "⚠️ Opvaskemaskine - tjek inden natkørsel"
|
||||
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
|
||||
@@ -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'
|
||||
description: 'Start plæneklipper kl 9 på arbejdsdage hvis det ikke regner og ingen er hjemme'
|
||||
trigger:
|
||||
@@ -38,11 +51,6 @@
|
||||
- service: lawn_mower.start_mowing
|
||||
target:
|
||||
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
|
||||
data:
|
||||
title: "Plæneklipper"
|
||||
@@ -103,6 +111,9 @@
|
||||
- condition: state
|
||||
entity_id: lawn_mower.husqvarna_automower
|
||||
state: 'mowing'
|
||||
- condition: state
|
||||
entity_id: input_boolean.ploeneklipper_manuelt_startet
|
||||
state: 'off'
|
||||
action:
|
||||
- service: lawn_mower.dock
|
||||
target:
|
||||
@@ -111,3 +122,74 @@
|
||||
data:
|
||||
title: "Plæneklipper"
|
||||
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:
|
||||
seconds: "{{ range(30,180) | random }}"
|
||||
|
||||
- service: switch.turn_on
|
||||
data:
|
||||
entity_id: switch.stik_indkorsel
|
||||
|
||||
- delay:
|
||||
minutes: "{{ range(10,30) | random }}"
|
||||
|
||||
@@ -66,6 +70,10 @@
|
||||
- delay:
|
||||
seconds: "{{ range(20,120) | random }}"
|
||||
|
||||
- service: switch.turn_off
|
||||
data:
|
||||
entity_id: switch.stik_indkorsel
|
||||
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
@@ -146,6 +154,10 @@
|
||||
- delay:
|
||||
seconds: "{{ range(20,120) | random }}"
|
||||
|
||||
- service: switch.turn_on
|
||||
data:
|
||||
entity_id: switch.stik_indkorsel
|
||||
|
||||
- delay:
|
||||
minutes: "{{ range(15,60) | random }}"
|
||||
|
||||
@@ -158,6 +170,10 @@
|
||||
- delay:
|
||||
seconds: "{{ range(20,120) | random }}"
|
||||
|
||||
- service: switch.turn_off
|
||||
data:
|
||||
entity_id: switch.stik_indkorsel
|
||||
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
|
||||
@@ -88,13 +88,13 @@
|
||||
target:
|
||||
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:
|
||||
- conditions:
|
||||
- condition: state
|
||||
entity_id: vacuum.roborock_s8_pro_ultra
|
||||
state: "cleaning"
|
||||
- condition: template
|
||||
value_template: "{{ wait.completed }}"
|
||||
sequence:
|
||||
- service: input_number.increment
|
||||
target:
|
||||
@@ -109,17 +109,14 @@
|
||||
}} min.
|
||||
|
||||
- conditions:
|
||||
- condition: not
|
||||
conditions:
|
||||
- condition: state
|
||||
entity_id: vacuum.roborock_s8_pro_ultra
|
||||
state: "cleaning"
|
||||
- condition: template
|
||||
value_template: "{{ not wait.completed }}"
|
||||
sequence:
|
||||
- service: notify.mobile_app_claus_iphone_15pro
|
||||
data:
|
||||
title: "⚠️ Roborock start fejlede"
|
||||
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') }}.
|
||||
Status: {{ state_attr('vacuum.roborock_s8_pro_ultra', 'status') | default('ukendt', true) }}.
|
||||
Error: {{ state_attr('vacuum.roborock_s8_pro_ultra', 'error') | default('ingen', true) }}.
|
||||
@@ -153,6 +150,9 @@
|
||||
- condition: state
|
||||
entity_id: vacuum.roborock_s8_pro_ultra
|
||||
state: "cleaning"
|
||||
- condition: state
|
||||
entity_id: input_boolean.roborock_manuelt_startet
|
||||
state: "off"
|
||||
|
||||
action:
|
||||
- service: vacuum.return_to_base
|
||||
@@ -165,6 +165,21 @@
|
||||
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
|
||||
- id: roborock_syd_workday_vacuum
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
action:
|
||||
- service: media_player.unjoin
|
||||
entity_id: media_player.badevaerelse
|
||||
- service: media_player.unjoin
|
||||
entity_id: media_player.lille_badevaerelse
|
||||
- service: media_player.unjoin
|
||||
entity_id: media_player.sovevaerelse
|
||||
|
||||
|
||||
@@ -1,12 +1,2 @@
|
||||
### Set temperature to 24 in heat mode
|
||||
- alias: "Tænd varme i kontor"
|
||||
trigger:
|
||||
platform: time
|
||||
at: "19:30:00"
|
||||
action:
|
||||
- service: climate.set_temperature
|
||||
target:
|
||||
entity_id: climate.kontor
|
||||
data:
|
||||
temperature: 24
|
||||
hvac_mode: heat
|
||||
### Kontor-varme styres nu af script.varme_recalculate (include/scripts/varme_styring.yaml)
|
||||
### Denne fil er beholdt tom for fremtidige manuelle overrides
|
||||
|
||||
@@ -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
|
||||
@@ -2,14 +2,14 @@
|
||||
# Vindue-automationer (Aqara vinduesensorer)
|
||||
##################################################
|
||||
|
||||
### Varme: Sluk varme når vindue åbnes, tænd igen når det lukkes
|
||||
### Rum-mapping: andreas, daniel, sovevaerelse, lille_bad
|
||||
### Varme: Genberegn ved vindue-ændring (åbner og lukker)
|
||||
### Script slukker klimaenhed hvis vindue er åbent, tænder igen ved lukning
|
||||
|
||||
- alias: "Varme - sluk ved åbent vindue"
|
||||
id: varme_sluk_ved_aabent_vindue
|
||||
description: "Slukker varme i rummet når vinduet åbnes og genstarter når det lukkes"
|
||||
mode: parallel
|
||||
max: 4
|
||||
- alias: "Varme - vindue åbner eller lukker"
|
||||
id: varme_vindue_trigger
|
||||
description: "Kalder varme_recalculate når et vindue eller terrassedøren skifter tilstand"
|
||||
mode: queued
|
||||
max: 10
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id:
|
||||
@@ -17,33 +17,14 @@
|
||||
- binary_sensor.daniel_vindue
|
||||
- binary_sensor.sovevaerelse_vindue
|
||||
- binary_sensor.lille_bad_vindue
|
||||
to: "on"
|
||||
- binary_sensor.badevaerelse_vindue
|
||||
- binary_sensor.terrassedor
|
||||
to:
|
||||
- "on"
|
||||
- "off"
|
||||
for: "00:00:05"
|
||||
action:
|
||||
- variables:
|
||||
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 }}"
|
||||
- service: script.varme_recalculate
|
||||
|
||||
### Notifikation: Vindue åbner og ingen er hjemme
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
badevaerelse_manuel_tilstand:
|
||||
name: Badeværelse manuel tilstand
|
||||
initial: off
|
||||
@@ -0,0 +1,3 @@
|
||||
gaester:
|
||||
name: "Gæster hjemme"
|
||||
icon: mdi:account-group
|
||||
@@ -1,3 +1,7 @@
|
||||
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
|
||||
@@ -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
|
||||
@@ -1,5 +1,12 @@
|
||||
vacation_start:
|
||||
name: Vacation Start
|
||||
has_date: true
|
||||
has_time: true
|
||||
initial: "2026-07-13 10:00:00"
|
||||
|
||||
vacation_end:
|
||||
name: Vacation End
|
||||
has_date: true
|
||||
has_time: true
|
||||
|
||||
initial: "2026-07-27 12:00:00"
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -4,7 +4,7 @@ badevaerelse_timeout_day:
|
||||
max: 30
|
||||
step: 1
|
||||
unit_of_measurement: min
|
||||
initial: 10
|
||||
initial: 5
|
||||
|
||||
badevaerelse_timeout_night:
|
||||
name: Badeværelse lys timeout nat
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -4,7 +4,7 @@ shelly_bagdor_event_cnt:
|
||||
max: 99999
|
||||
step: 1
|
||||
mode: box
|
||||
initial: -1
|
||||
initial: 67
|
||||
|
||||
shelly_fordor_event_cnt:
|
||||
name: Shelly fordoer event count
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -105,7 +105,6 @@
|
||||
name: Indkørsel
|
||||
unique_id: lys_indkorsel
|
||||
entities:
|
||||
- light.indkorsel_plug
|
||||
- light.udendors_forgang
|
||||
- light.hue_ambiance_lamp_1_2
|
||||
- light.hue_ambiance_lamp_1_3
|
||||
@@ -116,7 +115,6 @@
|
||||
unique_id: lys_udenfor
|
||||
entities:
|
||||
- light.garage
|
||||
- light.indkorsel_plug
|
||||
- light.fordoer
|
||||
- light.julelys
|
||||
|
||||
|
||||
@@ -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:
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
|
||||
@@ -10,53 +10,13 @@ doorbell:
|
||||
sequence:
|
||||
- parallel:
|
||||
- sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: time
|
||||
after: '20:00:00'
|
||||
before: '06:00:00'
|
||||
sequence:
|
||||
- service: media_player.volume_set
|
||||
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
|
||||
- variables:
|
||||
lille_bad_volumen: "{{ volumennat if (now().hour >= 20 or now().hour < 6) else volumendag }}"
|
||||
lille_bad_lydfil: "{{ 'doorbell-shortened-100308.mp3' if (now().hour >= 20 or now().hour < 6) else 'doorbell.mp3' }}"
|
||||
- service: script.spil_paa_lille_bad
|
||||
data:
|
||||
lydfil: "{{ lille_bad_lydfil }}"
|
||||
volumen: "{{ lille_bad_volumen }}"
|
||||
- sequence:
|
||||
- service: media_player.volume_set
|
||||
data:
|
||||
|
||||
@@ -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:
|
||||
alias: Der er mad
|
||||
sequence:
|
||||
|
||||
@@ -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."
|
||||
@@ -24,12 +24,6 @@ monthly_standard_colors:
|
||||
color_name: 'Gold'
|
||||
brightness_pct: 100
|
||||
transition: 300
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.syd
|
||||
color_name: 'Gold'
|
||||
brightness_pct: 80
|
||||
transition: 300
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -63,12 +57,6 @@ monthly_valentine_colors:
|
||||
color_name: 'Crimson'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.syd
|
||||
color_name: 'Crimson'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -102,12 +90,6 @@ monthly_mardi_gras_colors:
|
||||
color_name: 'Purple'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.syd
|
||||
color_name: 'Green'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -141,12 +123,6 @@ monthly_pi_colors:
|
||||
rgb_color: [3,14,159]
|
||||
brightness_pct: 100
|
||||
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
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -180,12 +156,6 @@ monthly_st_patty_colors:
|
||||
color_name: 'Green'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.syd
|
||||
color_name: 'Green'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -219,12 +189,6 @@ monthly_easter_colors:
|
||||
color_name: 'Yellow'
|
||||
brightness_pct: 100
|
||||
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
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -258,12 +222,6 @@ monthly_starwars_colors:
|
||||
rgb_color: [204,0,0]
|
||||
brightness_pct: 100
|
||||
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
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -297,12 +255,6 @@ monthly_cinco_de_mayo_colors:
|
||||
rgb_color: [204,0,0]
|
||||
brightness_pct: 100
|
||||
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
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -336,12 +288,6 @@ monthly_mothers_day_colors:
|
||||
rgb_color: [244,187,255]
|
||||
brightness_pct: 100
|
||||
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
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -375,12 +321,6 @@ monthly_fathers_day_colors:
|
||||
color_name: 'Orange'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.syd
|
||||
color_name: 'Blue'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -414,12 +354,6 @@ monthly_halloween_colors:
|
||||
rgb_color: [235,97,35]
|
||||
brightness_pct: 100
|
||||
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
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -453,12 +387,6 @@ monthly_thanksgiving_colors:
|
||||
color_name: 'Orange'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.syd
|
||||
color_name: 'Orange'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -492,12 +420,6 @@ monthly_christmas_colors:
|
||||
color_name: 'Red'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.syd
|
||||
color_name: 'Green'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
@@ -531,12 +453,6 @@ monthly_new_years_day_colors:
|
||||
color_name: 'Blue'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.syd
|
||||
color_name: 'Yellow'
|
||||
brightness_pct: 100
|
||||
transition: 900
|
||||
- service: light.turn_on
|
||||
data:
|
||||
entity_id: light.spisebord
|
||||
|
||||
@@ -15,6 +15,10 @@ overvaagning:
|
||||
device_id: cf4f218aae515c84aea9f37f190dcfd5
|
||||
enabled: true
|
||||
action: camera.snapshot
|
||||
- action: homeassistant.update_entity
|
||||
data:
|
||||
entity_id: camera.indkorsel_snapshot
|
||||
enabled: true
|
||||
- delay:
|
||||
hours: 0
|
||||
minutes: 0
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_tid–morgen_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
|
||||
@@ -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
|
||||
@@ -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 +1,2 @@
|
||||
mealie_update: "python3 /config/python_scripts/mealie_mealplan.py"
|
||||
mealie_shopping_merge: "python3 /config/python_scripts/mealie_shopping_merge.py"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
varme_save_defaults: "python3 /config/python_scripts/save_varme_defaults.py"
|
||||
@@ -41,21 +41,12 @@
|
||||
- default_entity_id: switch.daniel_motionlys_toggle
|
||||
name: "Daniel motionlys"
|
||||
unique_id: daniel_motionlys_toggle
|
||||
state: >-
|
||||
{{ 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') }}
|
||||
state: "{{ is_state('automation.daniel_lys_via_bevaegelse', 'on') }}"
|
||||
turn_on:
|
||||
action: automation.turn_on
|
||||
target:
|
||||
entity_id:
|
||||
- automation.lys_daniel_dag_arbejdsdag
|
||||
- automation.lys_daniel_dag_ikke_arbejdsdag
|
||||
- automation.sluk_lys_i_daniel
|
||||
entity_id: automation.daniel_lys_via_bevaegelse
|
||||
turn_off:
|
||||
action: automation.turn_off
|
||||
target:
|
||||
entity_id:
|
||||
- automation.lys_daniel_dag_arbejdsdag
|
||||
- automation.lys_daniel_dag_ikke_arbejdsdag
|
||||
- automation.sluk_lys_i_daniel
|
||||
entity_id: automation.daniel_lys_via_bevaegelse
|
||||
@@ -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) }}
|
||||
@@ -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 }}"
|
||||
@@ -45,3 +45,9 @@ Vigtige detaljer om min opsaetning:
|
||||
- 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.
|
||||
- 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 – 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 – tryk for at opdatere</div>
|
||||
<div class="modal" id="modal">
|
||||
<span class="close" onclick="closeModal()">✕</span>
|
||||
<span class="nav prev" id="navPrev" onclick="navigate(-1)">‹</span>
|
||||
<img id="mimg" src="" alt=""/>
|
||||
<span class="nav next" id="navNext" onclick="navigate(1)">›</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,17 +20,26 @@ else:
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"Authorization": token})
|
||||
raw = json.loads(urllib.request.urlopen(req, timeout=10).read())
|
||||
items = [
|
||||
{
|
||||
"date": i["date"],
|
||||
"recipe": {
|
||||
"name": i.get("recipe", {}).get("name", ""),
|
||||
"slug": i.get("recipe", {}).get("slug", ""),
|
||||
},
|
||||
}
|
||||
for i in raw.get("items", [])
|
||||
if i.get("recipe")
|
||||
]
|
||||
items = []
|
||||
for i in raw.get("items", []):
|
||||
recipe = i.get("recipe")
|
||||
title = i.get("title") or ""
|
||||
if recipe:
|
||||
items.append({
|
||||
"date": i["date"],
|
||||
"recipe": {
|
||||
"name": recipe.get("name", ""),
|
||||
"slug": recipe.get("slug", ""),
|
||||
},
|
||||
})
|
||||
elif title:
|
||||
items.append({
|
||||
"date": i["date"],
|
||||
"recipe": {
|
||||
"name": title,
|
||||
"slug": "",
|
||||
},
|
||||
})
|
||||
data = {"count": len(items), "items": items}
|
||||
except Exception:
|
||||
data = {"count": 0, "items": []}
|
||||
|
||||
@@ -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')} · {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()
|
||||
@@ -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()
|
||||
@@ -37,6 +37,11 @@ synology_password: boss3LEY8ogh!saub
|
||||
## 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
|
||||
|
||||
# ########################################
|
||||
|
||||
@@ -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 · 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>
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
@@ -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"}}]}
|
||||
Reference in New Issue
Block a user