diff --git a/.HA_VERSION b/.HA_VERSION index 881a774..92a9c85 100755 --- a/.HA_VERSION +++ b/.HA_VERSION @@ -1 +1 @@ -2026.4.4 \ No newline at end of file +2026.5.1 \ No newline at end of file diff --git a/configuration.yaml b/configuration.yaml index 119075a..b403795 100755 --- a/configuration.yaml +++ b/configuration.yaml @@ -38,6 +38,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 +111,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 +120,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/ diff --git a/dashboards/views/01_home.yaml b/dashboards/views/01_home.yaml index 62b1403..61d64bf 100644 --- a/dashboards/views/01_home.yaml +++ b/dashboards/views/01_home.yaml @@ -33,275 +33,19 @@ cards: name: I morgen icon: mdi:briefcase-outline - # 👨👩👧👦 Familien – tryk for at toggle syg/rask - - type: grid - columns: 4 - square: false - cards: - - - type: custom:button-card - entity: person.daniel_schusler_dethlefsen + # 👨👩👧👦 Familien + - type: glance + entities: + - entity: person.daniel_schusler_dethlefsen name: Daniel - show_name: true - show_state: false - show_label: true - show_icon: false - show_entity_picture: true - label: > - [[[ - const s = entity.state; - const sick = states['input_select.daniel_status'] && - states['input_select.daniel_status'].state === 'syg'; - const loc = s === 'home' ? 'Hjemme' : s === 'not_home' ? 'Ude' : s; - return sick ? loc + ' · Syg' : loc; - ]]] - styles: - card: - - padding: 8px 4px - - border: > - [[[ - return states['input_select.daniel_status'] && - states['input_select.daniel_status'].state === 'syg' - ? '2px solid rgba(220,50,50,0.8)' : '2px solid transparent'; - ]]] - - border-radius: 12px - entity_picture: - - width: 60px - - height: 60px - - border-radius: 50% - - object-fit: cover - - filter: > - [[[ - return states['input_select.daniel_status'] && - states['input_select.daniel_status'].state === 'syg' - ? 'grayscale(100%)' : 'none'; - ]]] - name: - - font-size: 12px - - font-weight: 600 - - padding-top: 6px - - color: > - [[[ - return states['input_select.daniel_status'] && - states['input_select.daniel_status'].state === 'syg' - ? 'rgb(220,50,50)' : 'var(--primary-text-color)'; - ]]] - label: - - font-size: 10px - - color: var(--secondary-text-color) - - padding-bottom: 2px - tap_action: - action: call-service - service: input_select.select_option - service_data: - entity_id: input_select.daniel_status - option: > - [[[ - return states['input_select.daniel_status'] && - states['input_select.daniel_status'].state === 'syg' - ? 'hjemme' : 'syg'; - ]]] - hold_action: - action: more-info - entity: person.daniel_schusler_dethlefsen - - - type: custom:button-card - entity: person.claus_dethlefsen + - entity: person.claus_dethlefsen name: Claus - show_name: true - show_state: false - show_label: true - show_icon: false - show_entity_picture: true - label: > - [[[ - const s = entity.state; - const sick = states['input_select.claus_status'] && - states['input_select.claus_status'].state === 'syg'; - const loc = s === 'home' ? 'Hjemme' : s === 'not_home' ? 'Ude' : s; - return sick ? loc + ' · Syg' : loc; - ]]] - styles: - card: - - padding: 8px 4px - - border: > - [[[ - return states['input_select.claus_status'] && - states['input_select.claus_status'].state === 'syg' - ? '2px solid rgba(220,50,50,0.8)' : '2px solid transparent'; - ]]] - - border-radius: 12px - entity_picture: - - width: 60px - - height: 60px - - border-radius: 50% - - object-fit: cover - - filter: > - [[[ - return states['input_select.claus_status'] && - states['input_select.claus_status'].state === 'syg' - ? 'grayscale(100%)' : 'none'; - ]]] - name: - - font-size: 12px - - font-weight: 600 - - padding-top: 6px - - color: > - [[[ - return states['input_select.claus_status'] && - states['input_select.claus_status'].state === 'syg' - ? 'rgb(220,50,50)' : 'var(--primary-text-color)'; - ]]] - label: - - font-size: 10px - - color: var(--secondary-text-color) - - padding-bottom: 2px - tap_action: - action: call-service - service: input_select.select_option - service_data: - entity_id: input_select.claus_status - option: > - [[[ - return states['input_select.claus_status'] && - states['input_select.claus_status'].state === 'syg' - ? 'hjemme' : 'syg'; - ]]] - hold_action: - action: more-info - entity: person.claus_dethlefsen - - - type: custom:button-card - entity: person.anne_schusler_dethlefsen + - entity: person.anne_schusler_dethlefsen name: Anne - show_name: true - show_state: false - show_label: true - show_icon: false - show_entity_picture: true - label: > - [[[ - const s = entity.state; - const sick = states['input_select.anne_status'] && - states['input_select.anne_status'].state === 'syg'; - const loc = s === 'home' ? 'Hjemme' : s === 'not_home' ? 'Ude' : s; - return sick ? loc + ' · Syg' : loc; - ]]] - styles: - card: - - padding: 8px 4px - - border: > - [[[ - return states['input_select.anne_status'] && - states['input_select.anne_status'].state === 'syg' - ? '2px solid rgba(220,50,50,0.8)' : '2px solid transparent'; - ]]] - - border-radius: 12px - entity_picture: - - width: 60px - - height: 60px - - border-radius: 50% - - object-fit: cover - - filter: > - [[[ - return states['input_select.anne_status'] && - states['input_select.anne_status'].state === 'syg' - ? 'grayscale(100%)' : 'none'; - ]]] - name: - - font-size: 12px - - font-weight: 600 - - padding-top: 6px - - color: > - [[[ - return states['input_select.anne_status'] && - states['input_select.anne_status'].state === 'syg' - ? 'rgb(220,50,50)' : 'var(--primary-text-color)'; - ]]] - label: - - font-size: 10px - - color: var(--secondary-text-color) - - padding-bottom: 2px - tap_action: - action: call-service - service: input_select.select_option - service_data: - entity_id: input_select.anne_status - option: > - [[[ - return states['input_select.anne_status'] && - states['input_select.anne_status'].state === 'syg' - ? 'hjemme' : 'syg'; - ]]] - hold_action: - action: more-info - entity: person.anne_schusler_dethlefsen - - - type: custom:button-card - entity: person.andreas_schusler_dethlefsen + - entity: person.andreas_schusler_dethlefsen name: Andreas - show_name: true - show_state: false - show_label: true - show_icon: false - show_entity_picture: true - label: > - [[[ - const s = entity.state; - const sick = states['input_select.andreas_status'] && - states['input_select.andreas_status'].state === 'syg'; - const loc = s === 'home' ? 'Hjemme' : s === 'not_home' ? 'Ude' : s; - return sick ? loc + ' · Syg' : loc; - ]]] - styles: - card: - - padding: 8px 4px - - border: > - [[[ - return states['input_select.andreas_status'] && - states['input_select.andreas_status'].state === 'syg' - ? '2px solid rgba(220,50,50,0.8)' : '2px solid transparent'; - ]]] - - border-radius: 12px - entity_picture: - - width: 60px - - height: 60px - - border-radius: 50% - - object-fit: cover - - filter: > - [[[ - return states['input_select.andreas_status'] && - states['input_select.andreas_status'].state === 'syg' - ? 'grayscale(100%)' : 'none'; - ]]] - name: - - font-size: 12px - - font-weight: 600 - - padding-top: 6px - - color: > - [[[ - return states['input_select.andreas_status'] && - states['input_select.andreas_status'].state === 'syg' - ? 'rgb(220,50,50)' : 'var(--primary-text-color)'; - ]]] - label: - - font-size: 10px - - color: var(--secondary-text-color) - - padding-bottom: 2px - tap_action: - action: call-service - service: input_select.select_option - service_data: - entity_id: input_select.andreas_status - option: > - [[[ - return states['input_select.andreas_status'] && - states['input_select.andreas_status'].state === 'syg' - ? 'hjemme' : 'syg'; - ]]] - hold_action: - action: more-info - entity: person.andreas_schusler_dethlefsen + - entity: binary_sensor.family_presence + name: Familie # 🪟 Gardiner - type: grid @@ -389,71 +133,36 @@ cards: icon: mdi:floor-plan tap_action: action: call-service - service: script.turn_on + service: button.press target: - entity_id: script.roborock_manuelt_kokken + entity_id: button.roborock_s8_pro_ultra_kokken_bryggers - type: button name: Syd icon: mdi:floor-plan tap_action: action: call-service - service: script.turn_on + service: button.press target: - entity_id: script.roborock_manuelt_syd + entity_id: button.roborock_s8_pro_ultra_syd - type: button name: Mop icon: mdi:floor-plan tap_action: action: call-service - service: script.turn_on + service: button.press target: - entity_id: script.roborock_manuelt_mop + entity_id: button.roborock_s8_pro_ultra_vac_followed_by_mop - - type: custom:button-card - entity: vacuum.roborock_s8_pro_ultra - name: Start - icon: mdi:robot-vacuum + - type: button + name: Gå til dock + icon: mdi:home-import-outline tap_action: action: call-service - service: script.turn_on + service: vacuum.return_to_base target: - entity_id: script.roborock_manuelt_start - state: - - value: cleaning - name: Dock - styles: - card: - - background-color: rgba(255, 200, 0, 0.25) - - border: 1px solid rgba(255, 200, 0, 0.8) - tap_action: - action: call-service - service: vacuum.return_to_base - target: - entity_id: vacuum.roborock_s8_pro_ultra - - value: returning - name: Dock - styles: - card: - - background-color: rgba(255, 200, 0, 0.25) - - border: 1px solid rgba(255, 200, 0, 0.8) - tap_action: - action: call-service - service: vacuum.return_to_base - target: - entity_id: vacuum.roborock_s8_pro_ultra - - value: paused - name: Dock - styles: - card: - - background-color: rgba(255, 200, 0, 0.25) - - border: 1px solid rgba(255, 200, 0, 0.8) - tap_action: - action: call-service - service: vacuum.return_to_base - target: - entity_id: vacuum.roborock_s8_pro_ultra + entity_id: vacuum.roborock_s8_pro_ultra # 🏎️ Plæneklipper - type: horizontal-stack @@ -462,25 +171,8 @@ cards: entity: input_datetime.ploeneklipper_sidst_koert show_icon: false show_name: true - show_state: false - show_label: true + show_state: true name: Sidst klippet - label: > - [[[ - const s = entity.state; - if (!s || s === 'unknown') return 'Ukendt'; - const d = new Date(s.replace(' ', 'T')); - if (isNaN(d)) return s; - const now = new Date(); - const isToday = d.toDateString() === now.toDateString(); - const yesterday = new Date(now); - yesterday.setDate(now.getDate() - 1); - const isYesterday = d.toDateString() === yesterday.toDateString(); - const t = d.toLocaleTimeString('da-DK', {hour: '2-digit', minute: '2-digit'}); - if (isToday) return 'I dag ' + t; - if (isYesterday) return 'I g\u00e5r ' + t; - return d.toLocaleDateString('da-DK', {day: 'numeric', month: 'short'}) + ' ' + t; - ]]] tap_action: action: none styles: @@ -490,34 +182,30 @@ cards: - font-size: 11px - color: var(--secondary-text-color) - padding-bottom: 4px - label: + state: - white-space: normal - word-break: break-word - line-height: 1.2 - font-size: 13px - text-align: center - - type: custom:button-card - entity: lawn_mower.husqvarna_automower + - type: button name: Klip icon: mdi:robot-mower tap_action: action: call-service - service: script.turn_on + service: lawn_mower.start_mowing target: - entity_id: script.ploeneklipper_manuelt_start - state: - - value: mowing - name: Stop - styles: - card: - - background-color: rgba(255, 200, 0, 0.25) - - border: 1px solid rgba(255, 200, 0, 0.8) - tap_action: - action: call-service - service: script.turn_on - target: - entity_id: script.ploeneklipper_manuelt_stop + entity_id: lawn_mower.husqvarna_automower + + - type: button + name: Stop + icon: mdi:home-import-outline + tap_action: + action: call-service + service: lawn_mower.dock + target: + entity_id: lawn_mower.husqvarna_automower # 💡 Lys kontrol - type: horizontal-stack @@ -542,60 +230,17 @@ cards: action: more-info show_state: true - - type: custom:button-card + - type: tile entity: binary_sensor.garageport name: Garage - show_name: true - show_state: false - show_label: true - label: > - [[[ - const isOpen = entity.state === 'on'; - const lastChanged = new Date(entity.last_changed); - const secsAgo = (Date.now() - lastChanged) / 1000; - const inMotion = secsAgo < 30; - if (inMotion) return isOpen ? 'Åbner...' : 'Lukker...'; - return isOpen ? 'Åben' : 'Lukket'; - ]]] - icon: > - [[[ - return entity.state === 'on' - ? 'mdi:garage-open-variant' - : 'mdi:garage-variant'; - ]]] - extra_styles: | - @keyframes garage-pulse { - 0% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.55; transform: scale(1.04); } - 100% { opacity: 1; transform: scale(1); } - } - styles: - card: - - animation: > - [[[ - const secsAgo = (Date.now() - new Date(entity.last_changed)) / 1000; - return secsAgo < 30 ? 'garage-pulse 0.8s ease-in-out infinite' : 'none'; - ]]] - icon: - - color: > - [[[ - const isOpen = entity.state === 'on'; - const secsAgo = (Date.now() - new Date(entity.last_changed)) / 1000; - if (secsAgo < 30) return 'dodgerblue'; - return isOpen ? 'orange' : 'var(--primary-text-color)'; - ]]] - label: - - font-size: 11px - - color: > - [[[ - const secsAgo = (Date.now() - new Date(entity.last_changed)) / 1000; - return secsAgo < 30 ? 'dodgerblue' : 'var(--secondary-text-color)'; - ]]] + features_position: bottom + vertical: false tap_action: action: call-service service: cover.toggle target: entity_id: cover.anne + show_state: true # 🎵 Sonos - type: grid @@ -741,18 +386,6 @@ cards: action: call-service service: script.tv_hygge_announcement - # 🏖️ Ferie - - type: entities - title: Ferie - icon: mdi:beach - entities: - - entity: input_datetime.vacation_start - name: Afrejse - - entity: input_datetime.vacation_end - name: Hjemkomst - - entity: input_boolean.vacation_mode - name: Ferietilstand aktiv - # 🗑️ Affald - type: glance columns: 3 @@ -897,6 +530,10 @@ cards: - entity: input_boolean.guests_mode name: Vi har gæster icon: mdi:account-group + - entity: input_boolean.vacation_mode + name: 🌴 Vacation Mode + - entity: input_datetime.vacation_end + name: Slutter - type: conditional conditions: diff --git a/dashboards/views/04c_madplan.yaml b/dashboards/views/04c_madplan.yaml index 47b0f9a..d7a219d 100644 --- a/dashboards/views/04c_madplan.yaml +++ b/dashboards/views/04c_madplan.yaml @@ -99,21 +99,3 @@ cards: | --- | --- | {{ ns.rows }} - # 🛒 Bilka ToGo - opdater og vis kryds-af liste - - type: vertical-stack - cards: - - type: markdown - content: | - ## Bilka ToGo - kryds-af - Tryk på knappen for at hente ingredienser fra ugeplanen (fredag–torsdag). - - - type: button - name: Opdater Bilka ToGo-liste nu - icon: mdi:cart-check - tap_action: - action: call-service - service: script.mealie_shopping_refresh - - - type: iframe - url: /local/bilka_togo_checklist.html - aspect_ratio: 100% diff --git a/dashboards/views/05b_vanding.yaml b/dashboards/views/05b_vanding.yaml new file mode 100644 index 0000000..41f5fd8 --- /dev/null +++ b/dashboards/views/05b_vanding.yaml @@ -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: weather.norgardsvej + 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 diff --git a/dashboards/views/06_varme.yaml b/dashboards/views/06_varme.yaml index 0901680..d61642c 100644 --- a/dashboards/views/06_varme.yaml +++ b/dashboards/views/06_varme.yaml @@ -393,8 +393,8 @@ sections: - type: grid cards: - type: gauge - entity: sensor.fjernvarme_ventil_anbefalet - name: Anbefalet ventilposition (1–5) + entity: sensor.fjernvarme_ventil_3_ugers_gennemsnit + name: Anbefalet ventilposition – 3 ugers snit (1–5) min: 1 max: 5 needle: true @@ -412,9 +412,11 @@ sections: - type: markdown content: |- - **{{ state_attr('sensor.fjernvarme_ventil_anbefalet', 'anbefaling') }}** + **Anbefalet stilling (3 ugers snit): {{ states('sensor.fjernvarme_ventil_3_ugers_gennemsnit') | float(0) | round(1) }}** - Udetemperatur: {{ state_attr('sensor.fjernvarme_ventil_anbefalet', 'udetemperatur') }}°C + Øjeblikkelig (vejrbaseret): {{ states('sensor.fjernvarme_ventil_anbefalet') }} – {{ state_attr('sensor.fjernvarme_ventil_anbefalet', 'anbefaling') }} + + Udetemperatur nu: {{ state_attr('sensor.fjernvarme_ventil_anbefalet', 'udetemperatur') }}°C Gælder for begge manuelle hoveddrejehaner: - Roth-fordeler (sauna) diff --git a/dashboards/views/06b_security.yaml b/dashboards/views/06b_security.yaml new file mode 100644 index 0000000..534b920 --- /dev/null +++ b/dashboards/views/06b_security.yaml @@ -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) diff --git a/dashboards/views/06c_indkorsel_snapshots.yaml b/dashboards/views/06c_indkorsel_snapshots.yaml new file mode 100644 index 0000000..9a935ef --- /dev/null +++ b/dashboards/views/06c_indkorsel_snapshots.yaml @@ -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 + aspect_ratio: 100% diff --git a/dashboards/views/05b_lys.yaml b/dashboards/views/12b_lys.yaml similarity index 100% rename from dashboards/views/05b_lys.yaml rename to dashboards/views/12b_lys.yaml diff --git a/dokumenter/oensker.md b/dokumenter/oensker.md index 2b397c4..07c7644 100644 --- a/dokumenter/oensker.md +++ b/dokumenter/oensker.md @@ -108,11 +108,11 @@ | Antal | Beskrivelse | Status | |---|---|---| -| 1 stk | Crucial 4GB DDR4-2666 SODIMM — CT4G4SFS8266 | ⬜ Ønsket | +| 1 stk | Crucial 16GB DDR4-2666 SODIMM — CT16G4SFD8266 | ✅ Installeret 13. maj 2026 | **Baggrund:** OOM-kill (exit 137) 23. april 2026 — Synology løb tør for RAM og dræbte mosquitto, gitea, gitea-db og DokuWiki på samme tid. HA selv overlevede men crashede urent. -**Model:** DS920+ har 4GB loddet + 1 ledig SODIMM-slot. Crucial CT4G4SFS8266 giver 4+4=8GB total. Pris ca. 150–200 kr. hos Komplett/Dustin. +**Resultat:** DS920+ kører nu med 4GB (loddet) + 16GB SODIMM = **20GB total RAM**. Bekræftet i DSM Info Center. Sundhedscheck 13. maj 2026: 3,4GB brugt af 19GB tilgængeligt, swap-brug 0B — ingen memory-pressure. Alle 8 Docker containere kører healthy. --- diff --git a/dokumenter/wavin_sonoff_installation.md b/dokumenter/wavin_sonoff_installation.md new file mode 100644 index 0000000..20cc7ca --- /dev/null +++ b/dokumenter/wavin_sonoff_installation.md @@ -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. diff --git a/include/automations/lys_badevaerelse.yaml b/include/automations/lys_badevaerelse.yaml index b456ffe..df303e9 100644 --- a/include/automations/lys_badevaerelse.yaml +++ b/include/automations/lys_badevaerelse.yaml @@ -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 diff --git a/include/automations/lys_stue.yaml b/include/automations/lys_stue.yaml index 5bd4648..1a33a8a 100644 --- a/include/automations/lys_stue.yaml +++ b/include/automations/lys_stue.yaml @@ -24,7 +24,8 @@ {% set t = now().strftime('%H%M') | int %} {% if 600 <= t < 1600 %}morgen {% elif 1600 <= t < 1900 %}eftermiddag - {% elif 1900 <= t %}aften + {% elif 1900 <= t < 2100 %}aften_lys + {% elif 2100 <= t %}aften {% else %}nat{% endif %} timeout_min: > {% set t = now().strftime('%H%M') | int %} @@ -32,7 +33,9 @@ {{ states('input_number.stue_timeout_morgen') | int }} {% elif 1600 <= t < 1900 %} {{ states('input_number.stue_timeout_eftermiddag') | int }} - {% elif 1900 <= t %} + {% elif 1900 <= t < 2100 %} + {{ states('input_number.stue_timeout_aften') | int }} + {% elif 2100 <= t %} {{ states('input_number.stue_timeout_aften') | int }} {% else %} {{ states('input_number.stue_timeout_nat') | int }} @@ -50,6 +53,15 @@ {{ states('sensor.stue_belysningsstyrke') | int < lux_limit }} sequence: - 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' }}" @@ -64,6 +76,13 @@ - 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 default: - service: scene.turn_on target: @@ -76,7 +95,7 @@ id: motion_off - condition: template value_template: > - {{ dagperiode != 'aften' or + {{ dagperiode not in ('aften','aften_lys') or is_state('media_player.samsung_s95ca_55_3', 'off') }} sequence: - delay: @@ -86,7 +105,7 @@ state: "off" - condition: template value_template: > - {{ dagperiode != 'aften' or + {{ dagperiode not in ('aften','aften_lys') or is_state('media_player.samsung_s95ca_55_3', 'off') }} - service: light.turn_off target: @@ -97,7 +116,7 @@ - condition: trigger id: tv_off - condition: template - value_template: "{{ dagperiode == 'aften' }}" + value_template: "{{ dagperiode in ('aften','aften_lys') }}" sequence: - delay: minutes: "{{ timeout_min }}" diff --git a/include/automations/mealie.yaml b/include/automations/mealie.yaml index bb7b9bf..7530dad 100644 --- a/include/automations/mealie.yaml +++ b/include/automations/mealie.yaml @@ -1,13 +1,3 @@ -- id: mealie_update_mealplan - alias: "Mealie opdater madplan" - trigger: - - platform: homeassistant - event: start - - platform: time_pattern - minutes: "/30" - action: - - service: shell_command.mealie_update - - id: mealie_generate_bilka_checklist_wednesday alias: "Mealie indkøbsliste - onsdag morgen" trigger: @@ -19,3 +9,14 @@ - wed action: - service: script.mealie_shopping_refresh + +- id: mealie_update_mealplan + alias: "Mealie opdater madplan" + trigger: + - platform: homeassistant + event: start + - platform: time_pattern + minutes: "/30" + action: + - service: shell_command.mealie_update + diff --git a/include/automations/roborock.yaml b/include/automations/roborock.yaml index 92e34fb..6de833f 100644 --- a/include/automations/roborock.yaml +++ b/include/automations/roborock.yaml @@ -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) }}. diff --git a/include/automations/snapshots_indkorsel.yaml b/include/automations/snapshots_indkorsel.yaml new file mode 100644 index 0000000..4f10070 --- /dev/null +++ b/include/automations/snapshots_indkorsel.yaml @@ -0,0 +1,26 @@ +- 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 diff --git a/include/automations/vinduer.yaml b/include/automations/vinduer.yaml index cc955af..ed47645 100644 --- a/include/automations/vinduer.yaml +++ b/include/automations/vinduer.yaml @@ -9,7 +9,7 @@ id: varme_vindue_trigger description: "Kalder varme_recalculate når et vindue eller terrassedøren skifter tilstand" mode: queued - max: 3 + max: 10 trigger: - platform: state entity_id: diff --git a/include/input/boolean/guests.yaml b/include/input/boolean/guests.yaml new file mode 100644 index 0000000..a480c30 --- /dev/null +++ b/include/input/boolean/guests.yaml @@ -0,0 +1,3 @@ +gaester: + name: "Gæster hjemme" + icon: mdi:account-group diff --git a/include/input/number/shelly_buttons.yaml b/include/input/number/shelly_buttons.yaml index 4f624bf..9b9280d 100644 --- a/include/input/number/shelly_buttons.yaml +++ b/include/input/number/shelly_buttons.yaml @@ -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 diff --git a/include/input/number/varme.yaml b/include/input/number/varme.yaml index cb81652..470ff2d 100644 --- a/include/input/number/varme.yaml +++ b/include/input/number/varme.yaml @@ -23,7 +23,7 @@ varme_komfort_sovevaerelse: max: 28 step: 0.5 unit_of_measurement: "°C" - initial: 18 + initial: 20 icon: mdi:thermometer varme_komfort_kontor: @@ -32,7 +32,7 @@ varme_komfort_kontor: max: 28 step: 0.5 unit_of_measurement: "°C" - initial: 19 + initial: 20 icon: mdi:thermometer varme_komfort_gang: @@ -41,7 +41,7 @@ varme_komfort_gang: max: 28 step: 0.5 unit_of_measurement: "°C" - initial: 19 + initial: 20 icon: mdi:thermometer varme_komfort_forgang: @@ -68,7 +68,7 @@ varme_komfort_badevarelse: max: 28 step: 0.5 unit_of_measurement: "°C" - initial: 20 + initial: 24.5 icon: mdi:thermometer varme_komfort_stue: diff --git a/include/scripts/mealie_shopping.yaml b/include/scripts/mealie_shopping.yaml index 0a26b6e..b294250 100644 --- a/include/scripts/mealie_shopping.yaml +++ b/include/scripts/mealie_shopping.yaml @@ -4,5 +4,5 @@ mealie_shopping_refresh: - service: shell_command.mealie_shopping_merge - service: notify.mobile_app_claus_iphone_15pro data: - title: "Bilka ToGo liste opdateret" - message: "Mealie-indkøb (fredag til torsdag) er flettet med Keep-basislisten." + title: "Indkøbsliste opdateret i Mealie" + message: "Indkøbslisten 'Bilka ToGo' er opdateret med opskrifter fra fredag til torsdag." diff --git a/include/scripts/overvaagning.yaml b/include/scripts/overvaagning.yaml index f49e32c..7005776 100644 --- a/include/scripts/overvaagning.yaml +++ b/include/scripts/overvaagning.yaml @@ -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 diff --git a/include/sensors/varme_ventil.yaml b/include/sensors/varme_ventil.yaml new file mode 100644 index 0000000..4149d7f --- /dev/null +++ b/include/sensors/varme_ventil.yaml @@ -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 diff --git a/include/shell_commands/indkorsel.yaml b/include/shell_commands/indkorsel.yaml new file mode 100644 index 0000000..47dd83a --- /dev/null +++ b/include/shell_commands/indkorsel.yaml @@ -0,0 +1 @@ +indkorsel_generate_gallery: "python3 /config/python_scripts/generate_indkorsel_gallery.py" diff --git a/python_scripts/generate_indkorsel_gallery.py b/python_scripts/generate_indkorsel_gallery.py new file mode 100644 index 0000000..ac6b047 --- /dev/null +++ b/python_scripts/generate_indkorsel_gallery.py @@ -0,0 +1,155 @@ +#!/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 = 100 + + +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""" +
Plan 08/05 – 14/05 · 78 varer
+Plan 15/05 – 21/05 · 69 varer
| Andet | |
|---|---|
| 0,50 tsk chiliflager | |
| 0,50 tsk røget paprika | |
| 0,50 dl hvidvin | |
| 0,50 tsk stødt spidskommen | |
| 1 æg | |
| 1 æggehvider | |
| 1 dl cremefraiche 18 % | |
| 1 dl hvidvin | |
| 1 dl mælk | |
| 1 dl rødvin, eller grøntsagsboullion | |
| 1 fed hvidløg, presset | |
| 1 iceberg | |
| 1 rødløg, i tynde ringe | |
| 1 spsk lage fra de syltede cornichoner | |
| 1 spsk sennep, - gerne sød | |
| 1 spsk sesamfrø | |
| 1 tsk garam masala | |
| 1 tsk hvide peberkorn (knuste) | |
| 1 tsk ketchup | |
| 1 tsk majsstivelse | |
| 1 knivspids muskatnød, fintrevet | |
| 1 knivspids røget paprika | |
| 1 knivspids sød paprika | |
| 1 spsk smør, til stegning | |
| 1 squash, groftrevet | |
| 1 tsk sød paprika | |
| 100 g cheddar | |
| 1,2 kg bagekartofler | |
| 1,2 liter vand | |
| 1/2 tsk chiliflager | |
| 1/2 tsk paprika | |
| 1,50 stødt spidskommen | |
| 1,50 tsk sød paprika | |
| 2 dl grøntsagsbouillon | |
| 2 fed hvidlag (flaekket) | |
| 2 fed hvidlag presset (til marinade) | |
| 2 skalottelag (finthakkede) | |
| 2 spsk smaor | |
| 2 spsk tikka masala paste | |
| 2 spsk toervin (hvidvin) | |
| 2 store lag (finthakkede) | |
| 20 g cornichoner, meget finthakkede | |
| 200 g squash, groftrevet | |
| 3 aeggeblommer | |
| 3 fed hvidlag (finthakkede) | |
| 3 fed hvidløg, presset | |
| 3 spsk smaor | |
| 300 g gulerødder, i små tern | |
| 4 fed hvidlag presset (til sauce) | |
| 4 tortillas pandekager, små | |
| 400 g spidskål, fintsnittet | |
| 400g spaghetti eller tagliatelle | |
| 600g jomfruhummerhaler (optaot, pillede) | |
| 1 tsk tørret timian | |
| 100 g rejer | |
| 100 g stenbiderrogn | |
| 2 spsk kapers (valgfrit) | |
| 2 spsk smør | |
| 2 tsk tørret oregano | |
| 200 g lasagneplader | |
| 3 dl mælk | |
| 4 grønne asparges | |
| 4 gulerødder, groftrevet | |
| 4 hvide asparges | |
| 4 skiver brød | |
| 5 stængler bladselleri, groftrevet | |
| 50 g rasp | |
| 75 g smør | |
| 8 rødspættefilet | |
| Frost | |
| 1 dl piskefløde | |
| 2 dl piskefloede | |
| 200g smaor (til bearnaise) | |
| 50 g mayonnaise | |
| 2 spsk mayonnaise | |
| 2,50 dl piskefløde | |
| Frugt & Grønt | |
| 0,50 agurk, i skiver | |
| 1 citron (saft og skal) | |
| 1 spsk tomatpure | |
| 2 daaser hakkede tomater (a 400g) | |
| 200g cherrytomater (halverede) | |
| 4 tomater, i tern | |
| 1 citron – saft og fintrevet skal | |
| 1 citron, 1 spsk saft herfra | |
| 1 citron, skåret i både | |
| 12 skiver agurk | |
| 2 banan, skåret i skiver på ca. 1 cm | |
| 2 spsk koncentreret tomatpuré | |
| 4 håndfulde grøn salat, til anretning | |
| 50 g koncentreret tomatpuré | |
| 70 g blandet salat | |
| 800 g hakkede tomater på dåse | |
| Grøntsager eller salat | |
| Kolonial | |
| 1 1/2 tsk salt (til ris) | |
| 1 spsk olivenolie | |
| 1 tsk frisk ingefaer revet (til marinade) | |
| 1 tsk salt | |
| 1/2 bundt frisk estragon | |
| 2 spsk hvidvinseddike | |
| 2 spsk smaor (til ris) | |
| 2 spsk tandoorikrydderi | |
| 2 tsk frisk ingefaer revet (til sauce) | |
| 3 spsk olivenolie | |
| 4 kviste frisk timian | |
| 600g basmatiris | |
| Frisk koriander til servering | |
| Salt | |
| 1 dl mild chilipasta | |
| 1 spsk hvedemel | |
| 1 spsk olivenolie, til stegning | |
| 1 spsk smør eller olie til stegning | |
| 2 håndfulde frisk dild, til pynt | |
| 2 spsk finvalset havregryn | |
| 2 spsk hvedemel | |
| 2 spsk olivenolie | |
| 3 dl basmati ris | |
| 3 spsk olivenolie, til stegning | |
| 50 g saltede peanuts | |
| Ris eller kartofler | |
| salt og friskkværnet peber | |
| Salt og hvid peber | |
| Salt og peber | |
| Kød & Fisk | |
| 1 dl persille, finthakket | |
| 1 fed hvidløg, finthakket | |
| 1 løg, finthakket | |
| 1 skalottelag (finthakket) | |
| 1/2 bundt frisk persille (hakket) | |
| 1,2 kg kyllingebryst (i mundrette stykker) | |
| 4 ribeye steaks a ca. 250g | |
| 2 løg, finthakket | |
| 2 spsk frisk persille, hakket | |
| 2 spsk persille, finthakket | |
| 4 fed hvidløg, finthakket | |
| 4 laksefileter med skind (ca. 150 g pr. stk) | |
| 400 g hakket oksekød | |
| 50 g cornichoner, finthakket | |
| 500 g hakket svinekød | |
| 600 g kyllingebryst, skåret i grove tern | |
| 75 g bacon, i skiver | |
| Mejeri & Æg | |
| 2 dl yoghurt naturel | |
| 125 g frisk mozzarella | |