Compare commits
60 Commits
6c3def6996
...
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 |
+1
-1
@@ -1 +1 @@
|
||||
2026.4.3
|
||||
2026.5.1
|
||||
@@ -38,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/
|
||||
@@ -66,6 +70,7 @@ configuration_minimal.yaml
|
||||
/oldscripts.yaml
|
||||
|
||||
# --- Local media snapshots and downloads ---
|
||||
/www/snapshots/
|
||||
/www/affalddk/
|
||||
/www/community/
|
||||
/www/indkorsel_snapshot.jpg
|
||||
|
||||
@@ -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/
|
||||
|
||||
+34
-346
@@ -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: lawn_mower.dock
|
||||
target:
|
||||
entity_id: lawn_mower.husqvarna_automower
|
||||
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
|
||||
|
||||
@@ -16,7 +16,7 @@ sections:
|
||||
action: call-service
|
||||
service: light.turn_off
|
||||
service_data:
|
||||
entity_id: light.bedroom
|
||||
entity_id: light.sovevaerelse
|
||||
styles:
|
||||
card:
|
||||
- height: 64px
|
||||
|
||||
@@ -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
|
||||
@@ -79,7 +86,7 @@ cards:
|
||||
{%- 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 + "](http://anneclaus.dk:9925/g/home/r/" + slug + ") |\n" -%}
|
||||
{%- set ns.rows = ns.rows + "| **" + label + "** | [" + name + "](https://mealie.anneclaus.dk/g/home/r/" + slug + ") |\n" -%}
|
||||
{%- elif name -%}
|
||||
{%- set ns.rows = ns.rows + "| **" + label + "** | " + name + " |\n" -%}
|
||||
{%- else -%}
|
||||
@@ -92,21 +99,598 @@ cards:
|
||||
| --- | --- |
|
||||
{{ ns.rows }}
|
||||
|
||||
# 🛒 Bilka ToGo - opdater og vis kryds-af liste
|
||||
- type: vertical-stack
|
||||
# 🎵 Sonos Køkken
|
||||
- type: media-control
|
||||
entity: media_player.kokken
|
||||
name: Sonos Køkken
|
||||
|
||||
- type: grid
|
||||
columns: 2
|
||||
square: false
|
||||
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
|
||||
- type: custom:button-card
|
||||
name: Volumen ned
|
||||
icon: mdi:volume-minus
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: script.mealie_shopping_refresh
|
||||
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
|
||||
|
||||
- type: iframe
|
||||
url: /local/bilka_togo_checklist.html
|
||||
aspect_ratio: 100%
|
||||
|
||||
@@ -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
|
||||
@@ -159,6 +159,8 @@ sections:
|
||||
series:
|
||||
- entity: sensor.kontor_motion_temperatur
|
||||
name: Hue
|
||||
- entity: sensor.annes_havesensor_indoor_temperature
|
||||
name: Havesensor inde
|
||||
- entity: climate.kontor
|
||||
attribute: current_temperature
|
||||
name: Roth aktuelt
|
||||
@@ -357,6 +359,84 @@ sections:
|
||||
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:
|
||||
@@ -372,6 +452,9 @@ sections:
|
||||
- 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
|
||||
@@ -381,8 +464,6 @@ sections:
|
||||
- entity: input_number.varme_nat_saenkning
|
||||
- entity: input_number.varme_vaek_saenkning
|
||||
- entity: input_number.varme_ferie_temp
|
||||
- entity: input_datetime.vacation_end
|
||||
name: Hjemkomst (ferie slutter)
|
||||
|
||||
- type: button
|
||||
name: Genberegn varme nu
|
||||
@@ -391,12 +472,19 @@ sections:
|
||||
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_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
|
||||
@@ -414,9 +502,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
+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,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*
|
||||
+73
-10
@@ -1,6 +1,25 @@
|
||||
# Ønskeliste – Nyt udstyr til Home Assistant
|
||||
|
||||
*Sidst opdateret: april 2026*
|
||||
*Sidst opdateret: maj 2026*
|
||||
|
||||
---
|
||||
|
||||
## Netværk / UniFi
|
||||
|
||||
### IoT VLAN-segmentering (trin 4 fra UniFi check 10. maj 2026)
|
||||
|
||||
**Formål:** Isolere IoT-enheder fra primært hjemmenetværk af sikkerhedshensyn.
|
||||
|
||||
**Plan:**
|
||||
- Nyt netværk: `IoT` med VLAN 20, subnet `192.168.20.0/24`
|
||||
- Genaktivér eksisterende `sonoff`-SSID og tilknyt til IoT-netværk
|
||||
- Firewall-regler:
|
||||
- IoT → LAN: **BLOKERET**
|
||||
- Home Assistant → IoT: **TILLADT** (nødvendigt for styring)
|
||||
- IoT → Internet: **TILLADT**
|
||||
- Enheder der skal flyttes til IoT-netværk: Sonoff, Zaptec, Tesla-lader, Wavin, øvrige IoT
|
||||
|
||||
**Kræver:** Manuel re-tilslutning af WiFi-enheder til nyt SSID. Kablede enheder tildeles VLAN via switch-port.
|
||||
|
||||
---
|
||||
|
||||
@@ -10,7 +29,7 @@
|
||||
|
||||
| Antal | Rum | Beskrivelse | Status |
|
||||
|---|---|---|---|
|
||||
| 1 | Badeværelse | Danfoss Ally TRV (Zigbee) | 🔧 To be fixed (kan ikke skrues ordentligt fast) |
|
||||
| 1 | Badeværelse | Danfoss Ally TRV (Zigbee) | ✅ Monteret og online (7. maj 2026) |
|
||||
|
||||
**Bekræftet ventiltype:** Danfoss RA (snap-on clips) – Ally passer direkte med medfølgende RA-adapter.
|
||||
|
||||
@@ -46,22 +65,42 @@
|
||||
|
||||
---
|
||||
|
||||
### Gulvvarme: Wavin bryggers + køkken – Sonoff ZBMINI-L2 + temp-sensorer
|
||||
|
||||
| Antal | Enhed | Beskrivelse | Status |
|
||||
|---|---|---|---|
|
||||
| 2 | Sonoff ZBMINI-L2 | Zigbee relæ, erstatter Wavin RF-modtager | ⬜ Ønsket |
|
||||
| 2 | SONOFF SNZB-02D | Zigbee temperatur/fugt sensor | ⬜ Ønsket |
|
||||
|
||||
**Baggrund:** Bryggers og køkken har i dag en dumb Wavin RF-modtager (JT6/3003-boksen) med to relækanaler (X = bryggers, Y = køkken) der styres af simple trådløse Wavin-termostater. Ingen smart protokol.
|
||||
|
||||
**Plan:** ZBMINI-L2 sættes i serie med fasen ud til aktuatoren (kanalernes brune ledning ud) inde i Wavin-boksen. Temp-sensor per rum. HA `generic_thermostat` samler dem til climate-entiteter der integreres med `script.varme_recalculate` som de øvrige rum.
|
||||
|
||||
**Se:** `dokumenter/wavin_sonoff_installation.md` for komplet installationsguide.
|
||||
|
||||
**Pris:** ~260 kr (ZBMINI) + ~200 kr (temp-sensorer) = **~460 kr total**
|
||||
|
||||
---
|
||||
|
||||
## Normal prioritet
|
||||
|
||||
### Vindues-/dørsensorer – Aqara
|
||||
|
||||
| Antal | Placering | Beskrivelse | Status |
|
||||
|---|---|---|---|
|
||||
| 2 | Køkken | Aqara kontaktsensor (vindue) | ⬜ Ønsket |
|
||||
| 1 | Bryggers | Aqara kontaktsensor (vindue) | ⬜ Ønsket |
|
||||
| 2 | Forgang | Aqara kontaktsensor (vindue/dør) | ⬜ Ønsket |
|
||||
| 1 | Stue | Aqara kontaktsensor (vindue) | ⬜ Ønsket |
|
||||
| 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:** 6 stk. Aqara Door & Window Sensor (Zigbee)
|
||||
**Total:** 15 stk. Aqara Door & Window Sensor (Zigbee) — 6 eksisterende + 9 nye
|
||||
|
||||
**Formål:** Automatisk stop af gulvvarme i rum med åbent vindue — samme logik som eksisterende sensorer i Andreas, Daniel, Soveværelse og Lille bad. Kobles direkte ind i `script.varme_recalculate` med if-betingelse per rum.
|
||||
|
||||
**Bemærk:** Forgang-sensorer dækker sandsynligvis yderdøren + en vinduesramme — afklar placering inden køb.
|
||||
**Bemærk:** Afklar præcis placering (vinduesramme vs. dørfals) inden køb, særligt forgang og fordør/bagdør.
|
||||
|
||||
---
|
||||
|
||||
@@ -69,11 +108,11 @@
|
||||
|
||||
| Antal | Beskrivelse | Status |
|
||||
|---|---|---|
|
||||
| 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -98,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 ✅
|
||||
|
||||
@@ -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`
|
||||
@@ -214,31 +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'
|
||||
- condition: template
|
||||
value_template: >-
|
||||
{{ not is_state('input_select.anne_status', 'syg') and
|
||||
not is_state('input_select.claus_status', 'syg') }}
|
||||
action:
|
||||
- service: homeassistant.turn_on
|
||||
entity_id: switch.sonos_alarm_1899
|
||||
|
||||
|
||||
- alias: 'Turn off alarms Badeværelse Afsted'
|
||||
trigger:
|
||||
platform: time
|
||||
at: '20:06:20'
|
||||
condition:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.arbejdsdagimorgen
|
||||
state: 'off'
|
||||
action:
|
||||
- service: homeassistant.turn_off
|
||||
entity_id: switch.sonos_alarm_1899
|
||||
|
||||
@@ -29,10 +29,13 @@
|
||||
|
||||
action:
|
||||
- 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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -62,11 +92,91 @@
|
||||
action:
|
||||
- delay:
|
||||
minutes: >
|
||||
{{ states('input_number.badevaerelse_timeout_day') | int if is_dag
|
||||
else states('input_number.badevaerelse_timeout_night') | int }}
|
||||
{% 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
|
||||
|
||||
@@ -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,20 @@
|
||||
- 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:
|
||||
@@ -76,7 +102,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 +112,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 +123,7 @@
|
||||
- condition: trigger
|
||||
id: tv_off
|
||||
- condition: template
|
||||
value_template: "{{ dagperiode == 'aften' }}"
|
||||
value_template: "{{ dagperiode in ('aften','aften_lys') }}"
|
||||
sequence:
|
||||
- delay:
|
||||
minutes: "{{ timeout_min }}"
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -124,12 +124,72 @@
|
||||
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'
|
||||
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) }}.
|
||||
|
||||
@@ -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
|
||||
@@ -7,7 +7,8 @@
|
||||
##################################################
|
||||
|
||||
# ---- 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 ----
|
||||
|
||||
@@ -60,7 +61,6 @@
|
||||
entity_id:
|
||||
- switch.sonos_alarm_1782 # Soft wakeup
|
||||
- switch.sonos_alarm_298 # Badeværelse
|
||||
- switch.sonos_alarm_1899 # Badeværelse afsted
|
||||
|
||||
- alias: "Syg - Soveværelse - genaktiver alarmer"
|
||||
id: syg_sovevaerelse_genaktiver_alarmer
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -75,6 +75,17 @@
|
||||
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: >
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
@@ -50,7 +50,7 @@ varme_komfort_forgang:
|
||||
max: 28
|
||||
step: 0.5
|
||||
unit_of_measurement: "°C"
|
||||
initial: 22
|
||||
initial: 24
|
||||
icon: mdi:thermometer
|
||||
|
||||
varme_komfort_lille_bad:
|
||||
@@ -59,7 +59,7 @@ varme_komfort_lille_bad:
|
||||
max: 28
|
||||
step: 0.5
|
||||
unit_of_measurement: "°C"
|
||||
initial: 22
|
||||
initial: 24
|
||||
icon: mdi:thermometer
|
||||
|
||||
varme_komfort_badevarelse:
|
||||
@@ -68,7 +68,7 @@ varme_komfort_badevarelse:
|
||||
max: 28
|
||||
step: 0.5
|
||||
unit_of_measurement: "°C"
|
||||
initial: 20
|
||||
initial: 21.5
|
||||
icon: mdi:thermometer
|
||||
|
||||
varme_komfort_stue:
|
||||
@@ -77,7 +77,7 @@ varme_komfort_stue:
|
||||
max: 28
|
||||
step: 0.5
|
||||
unit_of_measurement: "°C"
|
||||
initial: 24
|
||||
initial: 25
|
||||
icon: mdi:thermometer
|
||||
|
||||
# Globale sænkninger
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -4,28 +4,48 @@ godnat_sovevaerelse:
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: light.sovevaerelse
|
||||
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.sovevaerelse
|
||||
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.sovevaerelse
|
||||
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.sovevaerelse
|
||||
entity_id:
|
||||
- light.claus
|
||||
- light.anne
|
||||
- light.sov_dor
|
||||
- light.sov_midt
|
||||
- light.sov_vindue
|
||||
data:
|
||||
transition: 5
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -47,3 +47,13 @@ ploeneklipper_manuelt_start:
|
||||
- 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
|
||||
|
||||
@@ -223,3 +223,18 @@ varme_recalculate:
|
||||
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"
|
||||
@@ -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,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}")
|
||||
@@ -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": []}
|
||||
|
||||
@@ -273,11 +273,7 @@ def main() -> None:
|
||||
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)
|
||||
added_recipes = add_recipes_to_shopping_list(MEALIE_BASE_URL, token, shopping_list_id, recipe_ids)
|
||||
|
||||
mealie_items = fetch_mealie_items(MEALIE_BASE_URL, token, shopping_list_id=shopping_list_id)
|
||||
items = build_items(mealie_items)
|
||||
write_outputs(root, items, start_date, end_date)
|
||||
add_recipes_to_shopping_list(MEALIE_BASE_URL, token, shopping_list_id, recipe_ids)
|
||||
|
||||
print(
|
||||
'OK: '
|
||||
|
||||
@@ -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
|
||||
|
||||
# ########################################
|
||||
|
||||
@@ -18,96 +18,83 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>🛒 Bilka ToGo</h1>
|
||||
<p class="sub">Plan 24/04 – 30/04 · 82 varer</p>
|
||||
<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 squash</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>0,50 tsk mediumstærk karry</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>0,50 tsk tørret rosmarin</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 dl cremefraiche 38%</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 dl grøntsagsbouillon</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 dl pickles</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>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 dl tør hvidvin</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 fed hvidløg, presset</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 gulerødder, i tern</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 håndfuld persille</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 kg kartofler</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 knivspids muskatnød, fintrevet</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 knivspids 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 løg, i tern</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 rødløg</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk dijon sennep</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk hampefrø</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk honning</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk majsstivelse</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk smør, til stegning</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk solsikkekerner</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 squash, groftrevet</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 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>10 g smør, til stegning</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>100 g parmesan, fintrevet</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>12 tarteletter</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 æg</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 dl hønsebouillon</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 hønsebryst</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 laurbærblade</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk grov sennep</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk rosiner</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>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 aspargessnitter</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>200 g lasagneplader</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>25 g smør, til stegning</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>3 dl grøntsagsbouillon</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>3 dl mælk</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>3 gulerødder, groftrevet</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>300 g torskefilet</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>4 dl mælk</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>4 fed hvidløg, fintrevet</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>4 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>40 g smør</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>4 hvide asparges</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>4 skiver brød</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>5 stængler bladselleri, groftrevet</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>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>1 dl piskefløde</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk mayonnaise</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2,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>0,50 citron, saft herfra</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>0,50 øko citron</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>15 g koncentreret tomatpuré</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 æble, groftrevet</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>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>75 g soltørrede tomater i olie, finthakket</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>70 g blandet salat</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>800 g hakkede tomater på dåse</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>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 håndfuld frisk basilikum</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 håndfuld frisk dild</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk olivenolie</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 dl mild chilipasta</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 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 tsk olivenolie</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 spsk smør eller olie til stegning</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 håndfulde frisk dild, til pynt</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk finvalset havregryn</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk hvedemel</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk olivenolie</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>25 g hvedemel</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>3 dl basmati ris, kogt efter anvisning på emballagen</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>30 g hvedemel</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>40 g hvedemel</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>400 g pasta</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>flagesalt</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>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>0,50 dl frisk estragon, finthakket</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 fed hvidløg, finthakket</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>1 løg, finthakket</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 løg, finthakket</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>300 g laks, uden skind</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk frisk persille, hakket</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>2 spsk persille, finthakket</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>4 fed hvidløg, finthakket</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>4 kyllingebryst</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>4 laksefileter med skind (ca. 150 g pr. stk)</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>400 g hakket oksekød</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>600 g kyllingebryst</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>500 g hakket svinekød</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>600 g kyllingebryst, skåret i grove tern</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>75 g bacon, i skiver</td></tr>
|
||||
<tr><th colspan="2" class="cat">Mejeri & Æg</th></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>125 g frisk mozzarella</td></tr>
|
||||
<tr><td class="cb"><input type="checkbox"></td><td>400 g haricots verts, fra frost</td></tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+84
-136
@@ -1,96 +1,60 @@
|
||||
{
|
||||
"count": 82,
|
||||
"count": 69,
|
||||
"items": [
|
||||
{
|
||||
"name": "0,50 squash",
|
||||
"name": "0,50 dl hvidvin",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "0,50 tsk mediumstærk karry",
|
||||
"name": "0,50 tsk stødt spidskommen",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "0,50 tsk tørret rosmarin",
|
||||
"name": "1 æg",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 dl cremefraiche 38%",
|
||||
"name": "1 æggehvider",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 dl grøntsagsbouillon",
|
||||
"name": "1 dl cremefraiche 18 %",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 dl pickles",
|
||||
"name": "1 dl mælk",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 dl rødvin, eller grøntsagsboullion",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 dl tør hvidvin",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 fed hvidløg, presset",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 gulerødder, i tern",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 håndfuld persille",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 kg kartofler",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 knivspids muskatnød, fintrevet",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 knivspids røget paprika",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 knivspids sød paprika",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 løg, i tern",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 rødløg",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 spsk dijon sennep",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 spsk hampefrø",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 spsk honning",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 spsk majsstivelse",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 spsk smør, til stegning",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 spsk solsikkekerner",
|
||||
"name": "1 squash, groftrevet",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 squash, groftrevet",
|
||||
"name": "1 tsk sød paprika",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
@@ -98,39 +62,15 @@
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "10 g smør, til stegning",
|
||||
"name": "100 g rejer",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "100 g parmesan, fintrevet",
|
||||
"name": "100 g stenbiderrogn",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "12 tarteletter",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "2 æg",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "2 dl hønsebouillon",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "2 hønsebryst",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "2 laurbærblade",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "2 spsk grov sennep",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "2 spsk rosiner",
|
||||
"name": "2 spsk kapers (valgfrit)",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
@@ -141,40 +81,16 @@
|
||||
"name": "2 tsk tørret oregano",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "200 g aspargessnitter",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "200 g lasagneplader",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "25 g smør, til stegning",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "3 dl grøntsagsbouillon",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "3 dl mælk",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "3 gulerødder, groftrevet",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "300 g torskefilet",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "4 dl mælk",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "4 fed hvidløg, fintrevet",
|
||||
"name": "4 grønne asparges",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
@@ -182,7 +98,11 @@
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "40 g smør",
|
||||
"name": "4 hvide asparges",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "4 skiver brød",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
@@ -190,8 +110,16 @@
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "1 dl piskefløde",
|
||||
"category": "frost"
|
||||
"name": "50 g rasp",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "75 g smør",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "8 rødspættefilet",
|
||||
"category": "andet"
|
||||
},
|
||||
{
|
||||
"name": "2 spsk mayonnaise",
|
||||
@@ -202,19 +130,31 @@
|
||||
"category": "frost"
|
||||
},
|
||||
{
|
||||
"name": "0,50 citron, saft herfra",
|
||||
"name": "1 citron – saft og fintrevet skal",
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
"name": "0,50 øko citron",
|
||||
"name": "1 citron, 1 spsk saft herfra",
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
"name": "15 g koncentreret tomatpuré",
|
||||
"name": "1 citron, skåret i både",
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
"name": "2 æble, groftrevet",
|
||||
"name": "12 skiver agurk",
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
"name": "2 banan, skåret i skiver på ca. 1 cm",
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
"name": "2 spsk koncentreret tomatpuré",
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
"name": "4 håndfulde grøn salat, til anretning",
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
@@ -222,7 +162,7 @@
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
"name": "75 g soltørrede tomater i olie, finthakket",
|
||||
"name": "70 g blandet salat",
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
@@ -230,15 +170,15 @@
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
"name": "1 håndfuld frisk basilikum",
|
||||
"name": "Grøntsager eller salat",
|
||||
"category": "frugt & grønt"
|
||||
},
|
||||
{
|
||||
"name": "1 dl mild chilipasta",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "1 håndfuld frisk dild",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "1 spsk olivenolie",
|
||||
"name": "1 spsk hvedemel",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
@@ -246,7 +186,15 @@
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "1 tsk olivenolie",
|
||||
"name": "1 spsk smør eller olie til stegning",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "2 håndfulde frisk dild, til pynt",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "2 spsk finvalset havregryn",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
@@ -258,27 +206,19 @@
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "25 g hvedemel",
|
||||
"name": "3 dl basmati ris",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "3 dl basmati ris, kogt efter anvisning på emballagen",
|
||||
"name": "3 spsk olivenolie, til stegning",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "30 g hvedemel",
|
||||
"name": "50 g saltede peanuts",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "40 g hvedemel",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "400 g pasta",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "flagesalt",
|
||||
"name": "Ris eller kartofler",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
@@ -286,7 +226,11 @@
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "0,50 dl frisk estragon, finthakket",
|
||||
"name": "Salt og peber",
|
||||
"category": "kolonial"
|
||||
},
|
||||
{
|
||||
"name": "1 fed hvidløg, finthakket",
|
||||
"category": "kød & fisk"
|
||||
},
|
||||
{
|
||||
@@ -298,7 +242,11 @@
|
||||
"category": "kød & fisk"
|
||||
},
|
||||
{
|
||||
"name": "300 g laks, uden skind",
|
||||
"name": "2 spsk frisk persille, hakket",
|
||||
"category": "kød & fisk"
|
||||
},
|
||||
{
|
||||
"name": "2 spsk persille, finthakket",
|
||||
"category": "kød & fisk"
|
||||
},
|
||||
{
|
||||
@@ -306,7 +254,7 @@
|
||||
"category": "kød & fisk"
|
||||
},
|
||||
{
|
||||
"name": "4 kyllingebryst",
|
||||
"name": "4 laksefileter med skind (ca. 150 g pr. stk)",
|
||||
"category": "kød & fisk"
|
||||
},
|
||||
{
|
||||
@@ -314,7 +262,11 @@
|
||||
"category": "kød & fisk"
|
||||
},
|
||||
{
|
||||
"name": "600 g kyllingebryst",
|
||||
"name": "500 g hakket svinekød",
|
||||
"category": "kød & fisk"
|
||||
},
|
||||
{
|
||||
"name": "600 g kyllingebryst, skåret i grove tern",
|
||||
"category": "kød & fisk"
|
||||
},
|
||||
{
|
||||
@@ -324,10 +276,6 @@
|
||||
{
|
||||
"name": "125 g frisk mozzarella",
|
||||
"category": "mejeri & æg"
|
||||
},
|
||||
{
|
||||
"name": "400 g haricots verts, fra frost",
|
||||
"category": "mejeri & æg"
|
||||
}
|
||||
]
|
||||
}
|
||||
+10
-14
@@ -13,15 +13,16 @@ Plan-vindue: 2026-04-24 til 2026-04-30
|
||||
- [ ] 1 dl rødvin, eller grøntsagsboullion (Mealie)
|
||||
- [ ] 1 dl tør hvidvin (Mealie)
|
||||
- [ ] 1 fed hvidløg, presset (Mealie)
|
||||
- [ ] 1 gulerødder, Groftrevet (Mealie)
|
||||
- [ ] 1 gulerødder, i tern (Mealie)
|
||||
- [ ] 1 håndfuld persille (Mealie)
|
||||
- [ ] 1 kg kartofler (Mealie)
|
||||
- [ ] 1 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 ingefær, fintrevet (Mealie)
|
||||
- [ ] 1 spsk majsstivelse (Mealie)
|
||||
- [ ] 1 spsk smør, til stegning (Mealie)
|
||||
- [ ] 1 spsk solsikkekerner (Mealie)
|
||||
@@ -29,27 +30,30 @@ Plan-vindue: 2026-04-24 til 2026-04-30
|
||||
- [ ] 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)
|
||||
- [ ] 30 forårsrulleplader (Mealie)
|
||||
- [ ] 300 g torskefilet (Mealie)
|
||||
- [ ] 35 g glasnudler (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)
|
||||
- [ ] vand til pensling (Mealie)
|
||||
|
||||
## Frost
|
||||
- [ ] 1 dl piskefløde (Mealie)
|
||||
- [ ] 1 spsk fishsauce (Mealie)
|
||||
- [ ] 2 spsk mayonnaise (Mealie)
|
||||
- [ ] 2,50 dl piskefløde (Mealie)
|
||||
|
||||
@@ -65,13 +69,9 @@ Plan-vindue: 2026-04-24 til 2026-04-30
|
||||
## Kolonial
|
||||
- [ ] 1 håndfuld frisk basilikum (Mealie)
|
||||
- [ ] 1 håndfuld frisk dild (Mealie)
|
||||
- [ ] 1 liter fritureolie (Mealie)
|
||||
- [ ] 1 spsk olivenolie (Mealie)
|
||||
- [ ] 1 spsk olivenolie, til stegning (Mealie)
|
||||
- [ ] 1 spsk soja (Mealie)
|
||||
- [ ] 1 tsk olivenolie (Mealie)
|
||||
- [ ] 1 tsk sesamolie, eller anden olie til stegning (Mealie)
|
||||
- [ ] 1 tsk sukker (Mealie)
|
||||
- [ ] 2 spsk hvedemel (Mealie)
|
||||
- [ ] 2 spsk olivenolie (Mealie)
|
||||
- [ ] 25 g hvedemel (Mealie)
|
||||
@@ -85,15 +85,11 @@ Plan-vindue: 2026-04-24 til 2026-04-30
|
||||
## Kød & Fisk
|
||||
- [ ] 0,50 dl frisk estragon, finthakket (Mealie)
|
||||
- [ ] 1 løg, finthakket (Mealie)
|
||||
- [ ] 1 tsk rød chili, finthakket (Mealie)
|
||||
- [ ] 2 løg, finthakket (Mealie)
|
||||
- [ ] 250 g champignon, finthakket (Mealie)
|
||||
- [ ] 3 fed hvidløg, finthakket (Mealie)
|
||||
- [ ] 300 g laks, uden skind (Mealie)
|
||||
- [ ] 4 fed hvidløg, finthakket (Mealie)
|
||||
- [ ] 4 kyllingebryst (Mealie)
|
||||
- [ ] 400 g hakket oksekød (Mealie)
|
||||
- [ ] 400 g hakket svinekød (Mealie)
|
||||
- [ ] 600 g kyllingebryst (Mealie)
|
||||
- [ ] 75 g bacon, i skiver (Mealie)
|
||||
|
||||
|
||||
@@ -13,15 +13,16 @@ Plan-vindue: 2026-04-24 til 2026-04-30
|
||||
- [ ] 1 dl rødvin, eller grøntsagsboullion
|
||||
- [ ] 1 dl tør hvidvin
|
||||
- [ ] 1 fed hvidløg, presset
|
||||
- [ ] 1 gulerødder, Groftrevet
|
||||
- [ ] 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 ingefær, fintrevet
|
||||
- [ ] 1 spsk majsstivelse
|
||||
- [ ] 1 spsk smør, til stegning
|
||||
- [ ] 1 spsk solsikkekerner
|
||||
@@ -29,27 +30,30 @@ Plan-vindue: 2026-04-24 til 2026-04-30
|
||||
- [ ] 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
|
||||
- [ ] 30 forårsrulleplader
|
||||
- [ ] 300 g torskefilet
|
||||
- [ ] 35 g glasnudler
|
||||
- [ ] 4 dl mælk
|
||||
- [ ] 4 fed hvidløg, fintrevet
|
||||
- [ ] 4 gulerødder, groftrevet
|
||||
- [ ] 40 g smør
|
||||
- [ ] 5 stængler bladselleri, groftrevet
|
||||
- [ ] vand til pensling
|
||||
|
||||
## Frost
|
||||
- [ ] 1 dl piskefløde
|
||||
- [ ] 1 spsk fishsauce
|
||||
- [ ] 2 spsk mayonnaise
|
||||
- [ ] 2,50 dl piskefløde
|
||||
|
||||
@@ -65,13 +69,9 @@ Plan-vindue: 2026-04-24 til 2026-04-30
|
||||
## Kolonial
|
||||
- [ ] 1 håndfuld frisk basilikum
|
||||
- [ ] 1 håndfuld frisk dild
|
||||
- [ ] 1 liter fritureolie
|
||||
- [ ] 1 spsk olivenolie
|
||||
- [ ] 1 spsk olivenolie, til stegning
|
||||
- [ ] 1 spsk soja
|
||||
- [ ] 1 tsk olivenolie
|
||||
- [ ] 1 tsk sesamolie, eller anden olie til stegning
|
||||
- [ ] 1 tsk sukker
|
||||
- [ ] 2 spsk hvedemel
|
||||
- [ ] 2 spsk olivenolie
|
||||
- [ ] 25 g hvedemel
|
||||
@@ -85,15 +85,11 @@ Plan-vindue: 2026-04-24 til 2026-04-30
|
||||
## Kød & Fisk
|
||||
- [ ] 0,50 dl frisk estragon, finthakket
|
||||
- [ ] 1 løg, finthakket
|
||||
- [ ] 1 tsk rød chili, finthakket
|
||||
- [ ] 2 løg, finthakket
|
||||
- [ ] 250 g champignon, finthakket
|
||||
- [ ] 3 fed hvidløg, finthakket
|
||||
- [ ] 300 g laks, uden skind
|
||||
- [ ] 4 fed hvidløg, finthakket
|
||||
- [ ] 4 kyllingebryst
|
||||
- [ ] 400 g hakket oksekød
|
||||
- [ ] 400 g hakket svinekød
|
||||
- [ ] 600 g kyllingebryst
|
||||
- [ ] 75 g bacon, i skiver
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"count": 6, "items": [{"date": "2026-04-28", "recipe": {"name": "Lasagne", "slug": "lasagne"}}, {"date": "2026-04-27", "recipe": {"name": "Kylling i cremet sennepssauce", "slug": "kylling-i-cremet-sennepssauce"}}, {"date": "2026-04-26", "recipe": {"name": "Lasagne", "slug": "lasagne"}}, {"date": "2026-04-24", "recipe": {"name": "Marry Me Chicken", "slug": "marry-me-chicken"}}, {"date": "2026-04-22", "recipe": {"name": "Kylling i cremet sennepssauce", "slug": "kylling-i-cremet-sennepssauce"}}, {"date": "2026-04-23", "recipe": {"name": "K\u00e5lfad med hakket oksek\u00f8d", "slug": "kalfad-med-hakket-oksekod"}}]}
|
||||
{"count": 7, "items": [{"date": "2026-05-23", "recipe": {"name": "Ingen hjemme", "slug": ""}}, {"date": "2026-05-24", "recipe": {"name": "Kylling i cremet sennepssauce", "slug": "kylling-i-cremet-sennepssauce"}}, {"date": "2026-05-22", "recipe": {"name": "M\u00f8rbradb\u00f8ffer med bl\u00f8de l\u00f8g og fl\u00f8desauce", "slug": "morbradboffer-med-blode-log-og-flodesauce"}}, {"date": "2026-05-21", "recipe": {"name": "Frikadeller", "slug": "frikadeller"}}, {"date": "2026-05-20", "recipe": {"name": "Rester fra mandag (Lasagne)", "slug": ""}}, {"date": "2026-05-19", "recipe": {"name": "Rester fra s\u00f8ndag (Flyvende Jacob)", "slug": ""}}, {"date": "2026-05-18", "recipe": {"name": "Lasagne", "slug": "lasagne"}}]}
|
||||
Reference in New Issue
Block a user