Ventilator: tilføj tænd/sluk + sluk-timer knapper for Andreas og Daniel

This commit is contained in:
2026-06-20 19:24:19 +02:00
parent 63d61eb111
commit dc821a5769
8 changed files with 1042 additions and 2 deletions
+913
View File
@@ -0,0 +1,913 @@
title: Overblik
path: home
icon: mdi:home
cards:
# 🌤️ Vejr + dato + sol
- type: vertical-stack
cards:
- type: weather-forecast
entity: weather.norgardsvej
show_forecast: true
- type: glance
columns: 5
show_icon: true
show_name: true
show_state: true
entities:
- entity: sensor.dato
name: Dato
icon: mdi:calendar
- entity: sensor.solopgang
name: Sol op
icon: mdi:weather-sunset-up
- entity: sensor.solnedgang
name: Sol ned
icon: mdi:weather-sunset-down
- entity: binary_sensor.arbejdsdag
name: I dag
icon: mdi:briefcase
- entity: binary_sensor.arbejdsdagimorgen
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
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
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
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
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
# 🪟 Gardiner
- type: grid
columns: 6
square: false
cards:
- type: button
name: ""
icon: mdi:blinds-open
tap_action:
action: call-service
service: scene.turn_on
target:
entity_id: scene.alle_lidt_nede
- type: button
name: ""
icon: mdi:blinds
tap_action:
action: call-service
service: scene.turn_on
target:
entity_id: scene.n22_alt_ned
- type: button
name: ""
icon: mdi:blinds-horizontal
tap_action:
action: call-service
service: scene.turn_on
target:
entity_id: scene.morgen
- type: button
name: ""
icon: mdi:window-shutter
tap_action:
action: call-service
service: scene.turn_on
target:
entity_id: scene.syd_ned_venstre_halvt_ned
- type: button
name: ""
icon: mdi:television-ambient-light
tap_action:
action: call-service
service: scene.turn_on
target:
entity_id: scene.n22_tv
- type: button
name: ""
icon: mdi:silverware-fork-knife
tap_action:
action: call-service
service: scene.turn_on
target:
entity_id: scene.n22_spisetid
# 🧹 Støvsuger
- type: horizontal-stack
cards:
- type: custom:button-card
entity: sensor.roborock_last_clean_end_compact
show_icon: false
show_name: true
show_state: true
name: Sidst kørt
tap_action:
action: none
hold_action:
action: none
styles:
card:
- padding: 8px
name:
- font-size: 11px
- color: var(--secondary-text-color)
- padding-bottom: 4px
state:
- white-space: normal
- word-break: break-word
- line-height: 1.2
- font-size: 13px
- text-align: center
- type: button
name: Køkken
icon: mdi:floor-plan
tap_action:
action: call-service
service: button.press
target:
entity_id: button.roborock_s8_pro_ultra_kokken_bryggers
- type: button
name: Syd
icon: mdi:floor-plan
tap_action:
action: call-service
service: button.press
target:
entity_id: button.roborock_s8_pro_ultra_syd
- type: button
name: Mop
icon: mdi:floor-plan
tap_action:
action: call-service
service: button.press
target:
entity_id: button.roborock_s8_pro_ultra_vac_followed_by_mop
- type: button
name: Gå til dock
icon: mdi:home-import-outline
tap_action:
action: call-service
service: vacuum.return_to_base
target:
entity_id: vacuum.roborock_s8_pro_ultra
# 🏎️ Plæneklipper
- type: horizontal-stack
cards:
- type: custom:button-card
entity: input_datetime.ploeneklipper_sidst_koert
show_icon: false
show_name: true
show_state: true
name: Sidst klippet
tap_action:
action: none
styles:
card:
- padding: 8px
name:
- font-size: 11px
- color: var(--secondary-text-color)
- padding-bottom: 4px
state:
- white-space: normal
- word-break: break-word
- line-height: 1.2
- font-size: 13px
- text-align: center
- type: button
name: Klip
icon: mdi:robot-mower
tap_action:
action: call-service
service: lawn_mower.start_mowing
target:
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
# Vanding
- type: horizontal-stack
cards:
- type: custom:button-card
entity: sensor.vanding_sidst_vandet_compact
show_icon: false
show_name: true
show_state: true
name: Sidst vandet
tap_action:
action: none
hold_action:
action: none
styles:
card:
- padding: 8px
name:
- font-size: 11px
- color: var(--secondary-text-color)
- padding-bottom: 4px
state:
- white-space: normal
- word-break: break-word
- line-height: 1.2
- font-size: 13px
- text-align: center
- type: button
name: Smart vanding
icon: mdi:sprinkler-variant
entity: script.vanding_hojbed_skiftevis
tap_action:
action: toggle
- type: custom:button-card
entity: sensor.vanding_liter_i_dag_total
show_icon: false
show_name: false
show_state: false
tap_action:
action: none
hold_action:
action: none
custom_fields:
info: |
[[[
const n = (id) => { const s = states[id]; return s ? Math.round(parseFloat(s.state) || 0) : 0; };
const total = n('sensor.vanding_liter_i_dag_total');
const rows = [
['Ærter', n('sensor.vanding_liter_hb1')],
['Kartofler', n('sensor.vanding_liter_hb2')],
['Rabarber', n('sensor.vanding_liter_hb3')],
['Drivhus', n('sensor.vanding_liter_drivhus')],
['Kapillær', n('sensor.vanding_liter_kapillaer')],
];
const list = rows.map(r => `<div style="display:flex;justify-content:space-between;gap:6px"><span>${r[0]}</span><span style="font-variant-numeric:tabular-nums">${r[1]}</span></div>`).join('');
return `<div style="display:flex;flex-direction:column;width:100%;text-align:left">
<div style="font-size:11px;color:var(--secondary-text-color)">Liter i dag</div>
<div style="font-size:20px;font-weight:bold;line-height:1.1;margin:1px 0 4px">${total} L</div>
<div style="font-size:10.5px;color:var(--secondary-text-color);line-height:1.5">${list}</div>
</div>`;
]]]
styles:
card:
- padding: 10px
grid:
- grid-template-areas: '"info"'
custom_fields:
info:
- width: 100%
- type: custom:button-card
entity: sensor.vanding_tid_i_dag_total
show_icon: false
show_name: false
show_state: false
tap_action:
action: none
hold_action:
action: none
custom_fields:
info: |
[[[
const m = (id) => { const s = states[id]; return s ? Math.round((parseFloat(s.state) || 0) * 60) : 0; };
const cap = () => { const s = states['sensor.vanding_kapillaer_tid_i_dag']; return s ? Math.round(parseFloat(s.state) || 0) : 0; };
const total = (() => { const s = states['sensor.vanding_tid_i_dag_total']; return s ? Math.round(parseFloat(s.state) || 0) : 0; })();
const rows = [
['Ærter', m('sensor.vanding_hojbed_1_i_dag')],
['Kartofler', m('sensor.vanding_hojbed_2_i_dag')],
['Rabarber', m('sensor.vanding_hojbed_3_i_dag')],
['Drivhus', m('sensor.vanding_drivhus_i_dag')],
['Kapillær', cap()],
];
const list = rows.map(r => `<div style="display:flex;justify-content:space-between;gap:6px"><span>${r[0]}</span><span style="font-variant-numeric:tabular-nums">${r[1]} min</span></div>`).join('');
return `<div style="display:flex;flex-direction:column;width:100%;text-align:left">
<div style="font-size:11px;color:var(--secondary-text-color)">Tid i dag</div>
<div style="font-size:20px;font-weight:bold;line-height:1.1;margin:1px 0 4px">${total} min</div>
<div style="font-size:10.5px;color:var(--secondary-text-color);line-height:1.5">${list}</div>
</div>`;
]]]
styles:
card:
- padding: 10px
grid:
- grid-template-areas: '"info"'
custom_fields:
info:
- width: 100%
# 💡 Lys kontrol
- type: horizontal-stack
cards:
- type: tile
entity: light.indendorslamper
name: Indenfor
icon: mdi:lightbulb-group
tap_action:
action: toggle
hold_action:
action: more-info
show_state: true
- type: tile
entity: light.udendorslamper
name: Udenfor
icon: mdi:outdoor-lamp
tap_action:
action: toggle
hold_action:
action: more-info
show_state: true
- type: tile
entity: binary_sensor.garageport
name: Garage
features_position: bottom
vertical: false
tap_action:
action: call-service
service: cover.toggle
target:
entity_id: cover.anne
show_state: true
# 🎵 Sonos
- type: grid
columns: 3
square: false
cards:
- type: button
name: Stop alt
icon: mdi:stop-circle-outline
tap_action:
action: call-service
service: media_player.media_stop
target:
entity_id:
- media_player.stue
- media_player.andreas
- media_player.daniel
- media_player.kokken
- media_player.alrum
- media_player.badevaerelse
- media_player.sovevaerelse
- media_player.lille_badevaerelse
- type: button
name: P3 i bad
icon: mdi:radio
tap_action:
action: call-service
service: media_player.select_source
target:
entity_id: media_player.badevaerelse
data:
source: "0 DR P3"
- type: button
name: P3 i alrum
icon: mdi:radio
tap_action:
action: call-service
service: media_player.select_source
target:
entity_id: media_player.alrum
data:
source: "0 DR P3"
- type: button
name: Daily Mix bad
icon: mdi:music-circle-outline
tap_action:
action: call-service
service: media_player.select_source
target:
entity_id: media_player.badevaerelse
data:
source: "Andreas Daily Mix 1"
- type: button
name: Daily Mix køkken
icon: mdi:music-circle-outline
tap_action:
action: call-service
service: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "Andreas Daily Mix 1"
- type: button
name: Family Mix køkken
icon: mdi:music-note-outline
tap_action:
action: call-service
service: media_player.select_source
target:
entity_id: media_player.kokken
data:
source: "1 Family Mix"
# 🔊 Receiver presets
- type: grid
columns: 4
square: false
cards:
- type: button
name: Sonos
icon: mdi:speaker-multiple
tap_action:
action: call-service
service: script.receiver_sonos_mch_stereo
- type: button
name: TV Dolby
icon: mdi:television-speaker
tap_action:
action: call-service
service: script.receiver_tv_dolby_digital
- type: button
name: TV Stereo
icon: mdi:surround-sound
tap_action:
action: call-service
service: script.receiver_tv_mch_stereo
- type: horizontal-stack
cards:
- type: vertical-stack
cards:
- type: button
name: ""
icon: mdi:power
tap_action:
action: call-service
service: media_player.turn_off
target:
entity_id: media_player.denon_avr_x2300w
- type: button
name: ""
icon: mdi:volume-plus
tap_action:
action: call-service
service: script.receiver_volume_up_5
- type: button
name: ""
icon: mdi:volume-minus
tap_action:
action: call-service
service: script.receiver_volume_down_5
# 🍽️ Hjemmeknapper
- type: grid
columns: 2
square: false
cards:
- type: button
name: Der er mad
icon: mdi:silverware-fork-knife
tap_action:
action: call-service
service: script.mad_announcement
- type: button
name: TV hygge
icon: mdi:television-ambient-light
tap_action:
action: call-service
service: script.tv_hygge_announcement
# 🗑️ Affald
- type: glance
columns: 3
show_icon: true
show_name: false
show_state: true
entities:
- entity: sensor.affalddk_norgardsvej_22_restaffald
icon: mdi:trash-can-outline
- entity: sensor.affalddk_norgardsvej_22_papir_plast
icon: mdi:recycle
- entity: sensor.affalddk_norgardsvej_22_haveaffald
icon: mdi:leaf
# ⚡ El-priser
- type: vertical-stack
cards:
- type: custom:apexcharts-card
graph_span: 24h
span:
start: hour
stacked: false
header:
show: true
title: El-priser næste 24 timer
show_states: true
colorize_states: true
now:
show: true
label: Nu
all_series_config:
stroke_width: 0
apex_config:
chart:
height: 260
grid:
strokeDashArray: 2
xaxis:
type: datetime
labels:
datetimeFormatter:
hour: HH:mm
yaxis:
decimalsInFloat: 2
tickAmount: 5
plotOptions:
bar:
columnWidth: 82%
borderRadius: 3
series:
- entity: sensor.energi_data_service
name: Pris
type: column
float_precision: 2
unit: ' kr/kWh'
show:
in_header: raw
in_chart: true
data_generator: |
const startOfHour = new Date();
startOfHour.setMinutes(0, 0, 0);
const endTime = startOfHour.getTime() + (24 * 60 * 60 * 1000);
const rawToday = entity.attributes.raw_today || [];
const rawTomorrow = entity.attributes.tomorrow_valid ? (entity.attributes.raw_tomorrow || []) : [];
const forecast = entity.attributes.forecast || [];
const allKnown = [...rawToday, ...rawTomorrow];
const data = [];
const seen = new Set();
const pushPoint = (item) => {
const timestamp = new Date(item.hour).getTime();
if (Number.isNaN(timestamp) || timestamp < startOfHour.getTime() || timestamp >= endTime || seen.has(timestamp)) {
return;
}
const price = Number(item.price);
if (Number.isNaN(price)) {
return;
}
seen.add(timestamp);
data.push({ x: timestamp, y: price });
};
allKnown.forEach(pushPoint);
if (data.length < 24) {
forecast.forEach(pushPoint);
}
data.sort((left, right) => left.x - right.x);
const trimmed = data.slice(0, 24);
if (!trimmed.length) {
return [];
}
const prices = trimmed.map((item) => item.y);
const minPrice = Math.min(...prices);
const maxPrice = Math.max(...prices);
const mix = (start, end, ratio) => Math.round(start + ((end - start) * ratio));
const toHex = (value) => value.toString(16).padStart(2, '0');
const rgbToHex = (red, green, blue) => `#${toHex(red)}${toHex(green)}${toHex(blue)}`;
const colorByValue = (value) => {
if (maxPrice === minPrice) {
return '#16a34a';
}
const normalized = (value - minPrice) / (maxPrice - minPrice);
if (normalized <= 0.5) {
const ratio = normalized / 0.5;
return rgbToHex(
mix(22, 250, ratio),
mix(163, 204, ratio),
mix(74, 21, ratio)
);
}
const ratio = (normalized - 0.5) / 0.5;
return rgbToHex(
mix(250, 220, ratio),
mix(204, 38, ratio),
mix(21, 38, ratio)
);
};
return trimmed.map((item) => ({
x: item.x,
y: item.y,
fillColor: colorByValue(item.y)
}));
# 🏠 Hus kontrol
- type: entities
title: Modes
entities:
- 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:
- condition: numeric_state
entity: sensor.antal_vedligeholdskort
above: 0
card:
type: tile
entity: sensor.antal_vedligeholdskort
name: Vedligehold
icon: mdi:wrench-cog
+44
View File
@@ -255,6 +255,50 @@ sections:
- word-break: break-word
- line-height: 1.15
- type: grid
cards:
- type: heading
heading: Ventilator
- type: custom:button-card
entity: light.indkorsel_plug
name: Ventilator
icon: mdi:fan
show_state: true
tap_action:
action: toggle
hold_action:
action: more-info
styles:
card:
- padding: 8px 6px
- height: 72px
icon:
- width: 26px
name:
- font-size: 12px
- type: entities
entities:
- entity: input_number.andreas_ventilator_timer
name: Sluk-timer (min)
- type: custom:button-card
icon: mdi:fan-off
name: |
[[[ return 'Sluk om ' + Math.round(states['input_number.andreas_ventilator_timer'].state) + ' min'; ]]]
tap_action:
action: perform-action
perform_action: script.andreas_ventilator_sluk_timer
styles:
card:
- padding: 8px 6px
- height: 56px
icon:
- width: 22px
name:
- font-size: 12px
- type: grid
cards:
- type: heading
+44
View File
@@ -255,6 +255,50 @@ sections:
- word-break: break-word
- line-height: 1.15
- type: grid
cards:
- type: heading
heading: Ventilator
- type: custom:button-card
entity: switch.stik_daniel
name: Ventilator
icon: mdi:fan
show_state: true
tap_action:
action: toggle
hold_action:
action: more-info
styles:
card:
- padding: 8px 6px
- height: 72px
icon:
- width: 26px
name:
- font-size: 12px
- type: entities
entities:
- entity: input_number.daniel_ventilator_timer
name: Sluk-timer (min)
- type: custom:button-card
icon: mdi:fan-off
name: |
[[[ return 'Sluk om ' + Math.round(states['input_number.daniel_ventilator_timer'].state) + ' min'; ]]]
tap_action:
action: perform-action
perform_action: script.daniel_ventilator_sluk_timer
styles:
card:
- padding: 8px 6px
- height: 56px
icon:
- width: 22px
name:
- font-size: 12px
- type: grid
cards:
- type: heading
@@ -5,5 +5,5 @@ panel: true
cards:
- type: iframe
url: /local/snapshots/indkorsel_loader.html?v=20260619185107
url: /local/snapshots/indkorsel_loader.html?v=20260620192035
aspect_ratio: 100%