diff --git a/hosts/server/nixcloud/home-assistant/automations/bedroom_ac_adaptive_fan_speed.yaml b/hosts/server/nixcloud/home-assistant/automations/bedroom_ac_adaptive_fan_speed.yaml
new file mode 100644
index 000000000..246513cd1
--- /dev/null
+++ b/hosts/server/nixcloud/home-assistant/automations/bedroom_ac_adaptive_fan_speed.yaml
@@ -0,0 +1,133 @@
+- id: bedroom_ac_adaptive_fan_speed
+ alias: Bedroom AC - Adaptive Fan Speed
+ description: >
+ Automatically adjusts bedroom AC fan speed based on how quickly the room's
+ temperature distance to target is shrinking.
+ mode: single
+
+ trigger:
+ - platform: time_pattern
+ minutes: "/5"
+ - platform: state
+ entity_id: sensor.bedroom_ac_temperature
+ - platform: state
+ entity_id: climate.bedroom_ac
+ attribute: current_temperature
+
+ condition:
+ # Only run while the AC is actively heating or cooling.
+ - condition: template
+ value_template: "{{ states('climate.bedroom_ac') in ['cool', 'heat'] }}"
+
+ # Cooldown: if this automation changed fan speed in the last 5 minutes, stop.
+ - condition: template
+ value_template: >
+ {% set ts = as_timestamp(states('input_datetime.bedroom_ac_fan_last_adjusted')) %}
+ {{ ts is none or (as_timestamp(now()) - ts) >= 300 }}
+
+ action:
+ - variables:
+ current_temp: "{{ state_attr('climate.bedroom_ac', 'current_temperature') | float(0) }}"
+ target_temp: "{{ states('input_number.target_temperature') | float(0) }}"
+ current_distance: "{{ (current_temp - target_temp) | abs }}"
+
+ elapsed_minutes: >
+ {% set ts = as_timestamp(states('input_datetime.bedroom_ac_fan_last_adjusted')) %}
+ {% if ts is none %}
+ 15
+ {% else %}
+ {{ ((as_timestamp(now()) - ts) / 60) | float(0) }}
+ {% endif %}
+
+ # Use the shorter window: time since last fan adjustment or 15 minutes.
+ # Implemented as 5/10/15 minute buckets (automation cadence is 5 minutes).
+ window_minutes: >
+ {% set e = elapsed_minutes | float(15) %}
+ {% if e < 10 %}
+ 5
+ {% elif e < 15 %}
+ 10
+ {% else %}
+ 15
+ {% endif %}
+
+ selected_change_sensor: >
+ {% if (window_minutes | int) == 5 %}
+ sensor.bedroom_ac_distance_change_5m
+ {% elif (window_minutes | int) == 10 %}
+ sensor.bedroom_ac_distance_change_10m
+ {% else %}
+ sensor.bedroom_ac_distance_change_15m
+ {% endif %}
+
+ # Statistics change sensor is newest - oldest.
+ # If distance to target is reducing, this value is negative.
+ distance_change: "{{ states(selected_change_sensor) | float(0) }}"
+ distance_reduced_by: "{{ (0 - distance_change) | float(0) }}"
+
+ fan_modes:
+ - quiet
+ - low
+ - medium_low
+ - medium
+ - medium_high
+ - high
+
+ current_fan_mode: "{{ state_attr('climate.bedroom_ac', 'fan_mode') | default('medium', true) }}"
+ current_index: >
+ {% if current_fan_mode in fan_modes %}
+ {{ fan_modes.index(current_fan_mode) }}
+ {% else %}
+ {{ fan_modes.index('medium') }}
+ {% endif %}
+
+ increase_fan_mode: >
+ {% if (current_index | int) < (fan_modes | count - 1) %}
+ {{ fan_modes[current_index | int + 1] }}
+ {% else %}
+ {{ current_fan_mode }}
+ {% endif %}
+
+ decrease_fan_mode: >
+ {% if (current_index | int) > 0 %}
+ {{ fan_modes[current_index | int - 1] }}
+ {% else %}
+ {{ current_fan_mode }}
+ {% endif %}
+
+ - choose:
+ # If we're reducing distance fast (>0.4) and we're already close (<2.0), reduce fan speed.
+ - conditions:
+ - condition: template
+ value_template: "{{ distance_reduced_by > 0.4 and current_distance < 2 }}"
+ - condition: template
+ value_template: "{{ decrease_fan_mode != current_fan_mode }}"
+ sequence:
+ - service: climate.set_fan_mode
+ target:
+ entity_id: climate.bedroom_ac
+ data:
+ fan_mode: "{{ decrease_fan_mode }}"
+ - service: input_datetime.set_datetime
+ target:
+ entity_id: input_datetime.bedroom_ac_fan_last_adjusted
+ data:
+ datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
+
+ # If distance has not reduced by at least 0.2, increase fan speed.
+ - conditions:
+ - condition: template
+ value_template: "{{ distance_reduced_by < 0.2 }}"
+ - condition: template
+ value_template: "{{ increase_fan_mode != current_fan_mode }}"
+ sequence:
+ - service: climate.set_fan_mode
+ target:
+ entity_id: climate.bedroom_ac
+ data:
+ fan_mode: "{{ increase_fan_mode }}"
+ - service: input_datetime.set_datetime
+ target:
+ entity_id: input_datetime.bedroom_ac_fan_last_adjusted
+ data:
+ datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
diff --git a/hosts/server/nixcloud/home-assistant/dashboard.nix b/hosts/server/nixcloud/home-assistant/dashboard.nix
index 814c57420..9320584a4 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard.nix
@@ -150,7 +150,7 @@
];
lovelace = {
- mode = "yaml";
+ resource_mode = "yaml";
resources = [
{
url = "/local/nixos-lovelace-modules/bubble-pop-up-fix.js";
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/components/admin-panel.nix b/hosts/server/nixcloud/home-assistant/dashboard/components/admin-panel.nix
index a7830035b..a64fd0469 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard/components/admin-panel.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard/components/admin-panel.nix
@@ -1,3 +1,8 @@
+{ lib }:
+let
+ dashLib = import ../lib.nix { inherit lib; };
+ inherit (dashLib) entities ids;
+in
{
type = "vertical-stack";
cards = [
@@ -13,11 +18,11 @@
badges = [
{
type = "entity";
- entity = "switch.adguard_home_protection";
+ entity = entities.adguardProtection;
}
{
type = "entity";
- entity = "sensor.adguard_home_average_processing_speed";
+ entity = entities.adguardSpeed;
}
];
}
@@ -30,10 +35,10 @@
conditions = [ ];
card = {
type = "custom:button-card";
- entity = "sensor.uptimekuma_uptime_racci_dev";
+ entity = entities.sensors.uptimekuma;
icon = "mdi:devices";
name = "Monitored";
- label = "[[[return states[\"sensor.uptimekuma_uptime_racci_dev\"].attributes.monitored]]]";
+ label = "[[[return states[\"${entities.sensors.uptimekuma}\"].attributes.monitored]]]";
template = "nav_button_state_small";
variables = {
navigation_path = "server#monitored";
@@ -49,10 +54,10 @@
conditions = [ ];
card = {
type = "custom:button-card";
- entity = "sensor.uptimekuma_uptime_racci_dev";
+ entity = entities.sensors.uptimekuma;
icon = "mdi:sort-clock-descending-outline";
name = "Uptime Kuma";
- label = "[[[return states[\"sensor.uptimekuma_uptime_racci_dev\"].attributes.monitors]]]";
+ label = "[[[return states[\"${entities.sensors.uptimekuma}\"].attributes.monitors]]]";
template = "nav_button_small";
variables = {
navigation_path = "#uptime";
@@ -71,7 +76,7 @@
{
condition = "user";
users = [
- "3eea636aa3de4c7f9c662ad29c6e92e0"
+ ids.james
"c82f30a396fb42a9a10514fd63d5aac7"
];
}
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards.nix b/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards.nix
new file mode 100644
index 000000000..92b9a2b8a
--- /dev/null
+++ b/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards.nix
@@ -0,0 +1,1682 @@
+{ lib, ... }:
+with (import ../lib.nix { inherit lib; });
+{
+ setup = {
+ state = [
+ {
+ value = "unavailable";
+ icon = "mdi:alert-circle-outline";
+ styles = mkStyles {
+ state = {
+ text-decoration = "line-through";
+ };
+ label = {
+ text-decoration = "line-through";
+ };
+ };
+ }
+ ];
+ styles = mkStyles {
+ name = {
+ font-family = null;
+ };
+ label = {
+ font-family = null;
+ };
+ state = {
+ font-family = null;
+ };
+ };
+ };
+
+ nav_button_small = {
+ template = "setup";
+ variables = {
+ navigation_path = "home";
+ icon_on = entity.icon;
+ icon_off = entity.icon;
+ state_on = "on";
+ state_off = "off";
+ background_color_on = "var(--red)";
+ background_color_off = "var(--green)";
+ color_on = "var(--black)";
+ color_off = "var(--black)";
+ };
+ type = "custom:button-card";
+ inherit (entity) icon;
+ show_label = true;
+ tap_action = {
+ action = "navigate";
+ navigation_path = "[[[ return variables.navigation_path ]]]";
+ haptic = "success";
+ };
+ hold_action.action = "more-info";
+ state = [
+ (style.iconState "on")
+ (style.iconState "off")
+ ];
+ custom_fields = {
+ icon2 = {
+ card = {
+ type = "custom:button-card";
+ icon = "mdi:arrow-right-bold";
+ styles = mkStyles {
+ card = {
+ background-color = "var(--contrast1)";
+ width = "27px";
+ height = "27px";
+ };
+ icon = {
+ width = "15px";
+ color = "var(--contrast20)";
+ };
+ };
+ };
+ };
+ };
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''" l l icon2 " " i n n "'';
+ grid-template-columns = "14px 1fr 1fr";
+ grid-template-rows = "1fr min-content";
+ };
+ icon = {
+ width = "14px";
+ margin-bottom = "5px";
+ color = colourOnOff "black" "contrast20";
+ };
+ img_cell = {
+ justify-content = "flex-start";
+ };
+ name = {
+ justify-self = "start";
+ font-size = "11px";
+ margin-bottom = "2px";
+ margin-left = "3px";
+ color = colourOnOff "black" "contrast20";
+ opacity = 0.7;
+ };
+ card = {
+ height = "75px";
+ background-color = colourOnOff "yellow" "contrast2";
+ box-shadow = "none";
+ border-radius = "24px";
+ padding = "12px 0 12px 14px";
+ z-index = 1;
+ };
+ label = {
+ justify-self = "start";
+ font-size = "20px";
+ margin-top = "11px";
+ font-weight = 500;
+ color = colourOnOff "black" "contrast20";
+ };
+ custom_fields = {
+ icon2 = {
+ margin-top = "-20px";
+ justify-self = "end";
+ align-self = "center";
+ width = "24px";
+ padding-right = "10px";
+ color = colourOnOff "black" "contrast20";
+ };
+ };
+ };
+ };
+
+ nav_button_state_small = {
+ template = "setup";
+ variables = {
+ navigation_path = "home";
+ icon_on = entity.icon;
+ icon_off = entity.icon;
+ state_on = "on";
+ state_off = "off";
+ background_color_on = "var(--red)";
+ background_color_off = "var(--green)";
+ color_on = "var(--black)";
+ color_off = "var(--black)";
+ };
+ type = "custom:button-card";
+ inherit (entity) icon;
+ show_label = false;
+ show_state = true;
+
+ tap_action = {
+ action = "navigate";
+ navigation_path = "[[[ return variables.navigation_path ]]]";
+ haptic = "success";
+ };
+ hold_action.action = "more-info";
+
+ state = [
+ (style.iconState "on")
+ (style.iconState "off")
+ ];
+ custom_fields = {
+ icon2 = {
+ card = {
+ type = "custom:button-card";
+ icon = "mdi:arrow-right-bold";
+ styles = mkStyles {
+ card = {
+ background-color = "var(--contrast1)";
+ width = "27px";
+ height = "27px";
+ };
+ icon = {
+ width = "15px";
+ color = "var(--contrast20)";
+ };
+ };
+ };
+ };
+ };
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''" s s icon2 " " i n n "'';
+ grid-template-columns = "14px 1fr 1fr";
+ grid-template-rows = "1fr min-content";
+ };
+ icon = {
+ width = "14px";
+ margin-bottom = "5px";
+ color = colourOnOff "black" "contrast20";
+ };
+ img_cell = {
+ justify-content = "flex-start";
+ };
+ name = {
+ justify-self = "start";
+ font-size = "11px";
+ margin-bottom = "2px";
+ margin-left = "3px";
+ color = colourOnOff "black" "contrast20";
+ opacity = 0.7;
+ };
+ card = {
+ height = "75px";
+ background-color = colourOnOff "yellow" "contrast2";
+ box-shadow = "none";
+ border-radius = "24px";
+ padding = "12px 0 12px 14px";
+ z-index = 1;
+ };
+ state = {
+ justify-self = "start";
+ font-size = "20px";
+ margin-top = "11px";
+ font-weight = 500;
+ color = colourOnOff "black" "contrast20";
+ };
+ custom_fields = {
+ icon2 = {
+ margin-top = "-20px";
+ justify-self = "end";
+ align-self = "center";
+ width = "24px";
+ padding-right = "10px";
+ color = colourOnOff "black" "contrast20";
+ };
+ };
+ };
+ };
+
+ custom_card_alarm_bottom = {
+ template = "setup";
+ entity = "sensor.wake_time_1";
+ show_label = false;
+ name = "[[[ return states[\"sensor.time\"].state ]]]";
+ show_state = false;
+ show_entity_picture = false;
+ icon = "mdi:alarm";
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"i n snooze stop"'';
+ grid-template-columns = "min-content min-content 1fr 1fr";
+ grid-template-rows = "min-content";
+ column-gap = 15;
+ };
+ icon = {
+ width = "60px";
+ color = "var(--red)";
+ animation = "alarm 0.8s ease infinite";
+ };
+ card = {
+ padding = "15px 15px 15px 15px";
+ height = "180px";
+ width = "100vw";
+ overflow = "hidden";
+ position = jsMatch {
+ value = ''states["${entities.inputBooleans.debugRounded}"].state'';
+ cases = [
+ {
+ match = "on";
+ ret = "'static'";
+ }
+ ];
+ default = "'fixed'";
+ };
+ margin = 0;
+ bottom = 0;
+ left = 0;
+ z-index = 2;
+ border-radius = "20px 20px 0px 0px";
+ box-shadow = "rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px;";
+ };
+ img_cell = {
+ background = "none";
+ border-radius = "10px";
+ width = "50px";
+ height = "50px";
+ justify-self = "start";
+ align-self = "start";
+ left = "10px";
+ };
+ name = {
+ font-size = "28px";
+ font-weight = 700;
+ justify-self = "start";
+ align-self = "center";
+ padding-left = "10px";
+ };
+ custom_fields = {
+ snooze = {
+ justify-self = "end";
+ };
+ stop = {
+ justify-self = "end";
+ };
+ };
+ };
+ extra_styles = ''
+ @keyframes alarm {
+ 0%, 80%, 100% { transform: translateY(0); }
+ 10% { transform: translateY(-2px) rotate(-8deg); }
+ 20% { transform: translateY(-2px) rotate(9deg); }
+ 30% { transform: translateY(-2px) rotate(-5deg); }
+ 40% { transform: translateY(-2px) rotate(4deg); }
+ 50% { transform: translateY(0); }
+ 60% { transform: translateY(-1.2px) }
+ }
+ '';
+ custom_fields = {
+ snooze = {
+ card = {
+ type = "custom:button-card";
+ icon = "mdi:alarm-snooze";
+ entity = entities.inputBooleans.alarmSnoozed;
+ name = "Snooze";
+ show_name = true;
+ show_icon = true;
+ tap_action = {
+ action = "toggle";
+ };
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = "\"i n\"";
+ grid-template-columns = "min-content min-content";
+ };
+ card = {
+ padding = "4px";
+ background = "var(--orange)";
+ border-radius = "24px";
+ width = "90px";
+ };
+ img_cell = {
+ justify-self = "start";
+ width = "25px";
+ height = "25px";
+ background = "var(--contrast1)";
+ border-radius = "100%";
+ };
+ name = {
+ font-size = "12px";
+ color = "var(--black)";
+ font-weight = 500;
+ justify-self = "start";
+ align-self = "center";
+ padding-left = "5px";
+ };
+ icon = {
+ width = "14px";
+ color = "var(--orange)";
+ align-self = "center";
+ };
+ };
+ };
+ };
+ stop = {
+ card = {
+ type = "custom:button-card";
+ icon = "mdi:alarm-off";
+ entity = entities.inputBooleans.soundAlarmRunning;
+ name = "STOP";
+ show_name = true;
+ show_icon = true;
+ tap_action.action = "toggle";
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"i n"'';
+ grid-template-columns = "min-content min-content";
+ };
+ card = {
+ padding = "4px";
+ background = "var(--red)";
+ border-radius = "24px";
+ width = "90px";
+ margin-right = "5px";
+ };
+ img_cell = {
+ justify-self = "start";
+ width = "25px";
+ height = "25px";
+ background = "var(--contrast1)";
+ border-radius = "100%";
+ };
+ name = {
+ font-size = "12px";
+ color = "var(--black)";
+ font-weight = 500;
+ justify-self = "start";
+ align-self = "center";
+ padding-left = "5px";
+ };
+ icon = {
+ width = "14px";
+ color = "var(--red)";
+ align-self = "center";
+ };
+ };
+ };
+ };
+ };
+ };
+
+ custom_card_mediaplayer_bottom = {
+ template = "setup";
+ show_label = true;
+ name = ''
+ [[[
+ if (states[ entity.entity_id ].attributes.active_child == "media_player.playstation_5") {
+ return states["sensor.ps5_343_activity"].attributes.players
+ } else if (states[ entity.entity_id ].attributes.active_child == "media_player.steam_jlnbln") {
+ return 'jlnbln'
+ } else if (states[ entity.entity_id ].attributes.active_child == "media_player.plex_bedroom") {
+ return states[ entity.entity_id ].attributes.media_series_title
+ } else {
+ if (states[ entity.entity_id ].attributes.media_artist != null ) {
+ return states[ entity.entity_id ].attributes.media_artist
+ } else {
+ return "Streaming"
+ }
+ }
+ ]]]
+ '';
+ label = "[[[ return states[ entity.entity_id ].attributes.media_title ]]]";
+ show_state = false;
+ show_entity_picture = true;
+ entity_picture = ''
+ [[[
+ if (states[ entity.entity_id ].attributes.active_child == "media_player.playstation_5") {
+ return states["sensor.ps5_343_activity"].attributes.title_image
+ } else if (states[ entity.entity_id ].attributes.active_child == "media_player.apple_tv_4k_2") {
+ if (states[ entity.entity_id ].attributes.media_artist == "Rocket League Esports") {
+ return '/local/images/rlesports.jpg';
+ } else {
+ return states["media_player.apple_tv_4k_2"].attributes.entity_picture
+ }
+ } else if (states[ entity.entity_id ].attributes.active_child == "media_player.steam_jlnbln") {
+ return states["sensor.steam_76561197981585794"].attributes.game_image_main
+ } else if (states[ entity.entity_id ].attributes.active_child == "media_player.plex_bedroom") {
+ return states["media_player.plex_bedroom"].attributes.entity_picture
+ } else if (states[ entity.entity_id ].attributes.active_child == "media_player.music") {
+ if (states["media_player.music"].attributes.active_child == "media_player.bedroom_nest_music_assistant") {
+ return states["media_player.bedroom_speakers"].attributes.entity_picture
+ } else {
+ return states[ entity.entity_id ].attributes.entity_picture
+ }
+ } else if (states[ entity.entity_id ].attributes.active_child == "media_player.audiobook") {
+ if (states["input_select.audiobook"].state == "Harry Potter") {
+ if (states["input_number.hp_book"].state == 1.0) {
+ return "/local/images/HP_1.jpg";
+ } else if (states["input_number.hp_book"].state == 2.0) {
+ return "/local/images/HP_2.jpg";
+ } else if (states["input_number.hp_book"].state == 3.0) {
+ return "/local/images/HP_3.jpg";
+ } else if (states["input_number.hp_book"].state == 4.0) {
+ return "/local/images/HP_4.jpg";
+ } else if (states["input_number.hp_book"].state == 5.0) {
+ return "/local/images/HP_5.jpg";
+ } else if (states["input_number.hp_book"].state == 6.0) {
+ return "/local/images/HP_6.jpg";
+ } else if (states["input_number.hp_book"].state == 7.0) {
+ return "/local/images/HP_7.jpg";
+ }
+ }
+ } else {
+ return states[ entity.entity_id ].attributes.entity_picture
+ }
+ ]]]
+ '';
+ icon = ''
+ [[[
+ const child = states[entity.entity_id].attributes.active_child;
+ if (child === "media_player.playstation_5") return 'mdi:sony-playstation';
+ const app = states[entity.entity_id].attributes.app_name;
+ if (app === "YouTube") return 'mdi:youtube-tv';
+ if (app === "Netflix") return 'mdi:netflix';
+ if (app === "Plex") return 'mdi:plex';
+ return 'mdi:television-guide';
+ ]]]
+ '';
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"i play_state button" "i l button" "i n button" "bar bar bar"'';
+ grid-template-columns = "min-content";
+ grid-template-rows = "min-content min-content min-content";
+ column-gap = 15;
+ };
+ icon = {
+ width = 60;
+ height = 60;
+ color = "var(--red)";
+ };
+ card = {
+ padding = "15px 15px 15px 15px";
+ height = 180;
+ width = "100vw";
+ overflow = "hidden";
+ position = jsMatch {
+ value = ''states["${entities.inputBooleans.debugRounded}"].state'';
+ cases = [
+ {
+ match = "on";
+ ret = "'static'";
+ }
+ ];
+ default = "'fixed'";
+ };
+ margin = 0;
+ bottom = 0;
+ left = 0;
+ z-index = 2;
+ border-radius = "20px 20px 0px 0px";
+ box-shadow = "rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px";
+ };
+ img_cell = {
+ background = "none";
+ border-radius = 10;
+ width = 60;
+ height = 60;
+ justify-self = "start";
+ align-self = "start";
+ left = 20;
+ };
+ entity_picture = {
+ object-fit = "cover";
+ };
+ state = {
+ font-size = 10;
+ justify-self = "start";
+ align-self = "start";
+ color = "var(--contrast10)";
+ padding-right = 25;
+ };
+ name = {
+ font-size = 11;
+ justify-self = "start";
+ align-self = "start";
+ padding-right = 20;
+ animation = "my-animation 15s linear infinite";
+ };
+ label = {
+ justify-self = "start";
+ align-self = "end";
+ font-weight = 700;
+ padding-right = 20;
+ font-size = 13;
+ margin-top = "-5px";
+ };
+ custom_fields = {
+ button = {
+ justify-self = "end";
+ align-self = "center";
+ padding-right = 20;
+ padding-top = 7;
+ };
+ play_state = {
+ font-size = 10;
+ justify-self = "start";
+ align-self = "start";
+ color = "var(--contrast14)";
+ padding-right = 21;
+ padding-top = 10;
+ };
+ progress = {
+ background-color = "var(--contrast10)";
+ position = "absolute";
+ top = "unset";
+ bottom = 90;
+ left = 40;
+ height = 2;
+ width = "80%";
+ };
+ bar = {
+ background-color = "var(--green)";
+ position = "absolute";
+ bottom = 90;
+ left = 40;
+ top = "unset";
+ height = 2;
+ z-index = 1;
+ transition = "1s ease-out";
+ };
+ };
+ };
+ custom_fields = {
+ button.card = {
+ type = "custom:button-card";
+ icon = "mdi:play";
+ entity = "[[[ return entity.entity_id ]]]";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "media_player.media_play_pause";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ };
+ styles = mkStyles {
+ card = {
+ overflow = "visible";
+ background = "var(--contrast6)";
+ border-radius = 10;
+ };
+ icon = {
+ width = 17;
+ color = "var(--contrast16)";
+ };
+ img_cell = {
+ padding = 10;
+ width = 20;
+ };
+ };
+ state = [
+ {
+ value = "playing";
+ icon = jsMatch {
+ value = "states[entity.entity_id].attributes.active_child";
+ cases = [
+ {
+ match = "media_player.playstation_5";
+ ret = "'mdi:sony-playstation'";
+ }
+ {
+ match = "media_player.steam_jlnbln";
+ ret = "'mdi:steam'";
+ }
+ ];
+ default = "'mdi:pause'";
+ };
+ }
+ ];
+ };
+ play_state = jsMatch {
+ value = "states[entity.entity_id].attributes.active_child";
+ cases = [
+ {
+ match = "media_player.spotify_james";
+ ret = ''states[entity.entity_id].state + " - " + states[entity.entity_id].attributes.source'';
+ }
+ {
+ match = "media_player.playstation_5";
+ ret = ''states[entity.entity_id].state + " - Playstation 5"'';
+ }
+ {
+ match = "media_player.audiobook";
+ ret = ''states[entity.entity_id].state + " - Alarm Clock"'';
+ }
+ {
+ match = "media_player.plex_bedroom";
+ ret = ''states[entity.entity_id].state + " - Bedroom TV"'';
+ }
+ {
+ match = "media_player.steam_jlnbln";
+ ret = ''states[entity.entity_id].state + " - Steam"'';
+ }
+ {
+ match = "media_player.apple_tv_4k_2";
+ ret = ''states[entity.entity_id].state + " - " + states[entity.entity_id].attributes.app_name'';
+ }
+ ];
+ default = ''states[entity.entity_id].state + " - " + states["${entities.sensors.musicRoom}"].state'';
+ };
+ bar = ''
+ [[[
+ if (entity.attributes.media_position !== undefined) {
+ setTimeout(() => {
+ let elt = this.shadowRoot,
+ card = elt.getElementById('card'),
+ container = elt.getElementById('container'),
+ bar = elt.getElementById('bar');
+ if (elt && card && container && bar) {
+ card.insertBefore(bar, container);
+ function update() {
+ let mediaPositionUpdatedAt = entity.attributes.media_position_updated_at,
+ mediaPosition = entity.attributes.media_position,
+ mediaDuration = entity.attributes.media_duration,
+ mediaContentType = entity.attributes.media_content_type;
+ let percentage = entity.state === 'paused'
+ ? (mediaPosition / mediaDuration * 80)
+ : entity.state === 'playing'
+ ? (((Date.now() / 1000) - (new Date(mediaPositionUpdatedAt).getTime() / 1000) + mediaPosition) / mediaDuration * 80)
+ : 0;
+ bar.style.width = percentage.toFixed(1) + '%';
+ requestAnimationFrame(update);
+ }
+ requestAnimationFrame(update);
+ }
+ }, 0);
+ return ' ';}
+ ]]]
+ '';
+ };
+ };
+
+ custom_card_mediaplayer_music = {
+ template = "setup";
+ show_label = true;
+ show_name = true;
+ show_entity_picture = true;
+ entity_picture = ''
+ [[[
+ if (entity.attributes.entity_picture == undefined) {
+ return '/local/images/abstract-gif-colors.gif';
+ } else {
+ return entity.attributes.entity_picture;
+ }
+ ]]]
+ '';
+ name = "[[[ return entity.attributes.media_title || 'No media' ]]]";
+ label = "[[[ return entity.attributes.media_artist || '' ]]]";
+ tap_action.action = "more-info";
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"i play_state icon1" "i l l" "i n n" "i buttons buttons" "bar bar bar"'';
+ grid-template-columns = "min-content 1fr min-content";
+ grid-template-rows = "min-content min-content min-content min-content min-content";
+ column-gap = "10px";
+ };
+ card = {
+ border-radius = "24px";
+ background-color = "var(--contrast2)";
+ box-shadow = "none";
+ padding = "15px";
+ overflow = "visible";
+ };
+ entity_picture = {
+ border-radius = "12px";
+ width = "80px";
+ height = "80px";
+ object-fit = "cover";
+ };
+ img_cell = {
+ width = "80px";
+ height = "80px";
+ };
+ name = {
+ justify-self = "start";
+ font-size = "14px";
+ font-weight = 700;
+ color = "var(--contrast20)";
+ white-space = "nowrap";
+ overflow = "hidden";
+ text-overflow = "ellipsis";
+ max-width = "100%";
+ };
+ label = {
+ justify-self = "start";
+ font-size = "12px";
+ font-weight = 500;
+ color = "var(--contrast14)";
+ };
+ custom_fields = {
+ play_state = {
+ justify-self = "start";
+ font-size = "10px";
+ font-weight = 500;
+ color = "var(--contrast10)";
+ padding-top = "5px";
+ };
+ icon1 = {
+ justify-self = "end";
+ color = "var(--contrast14)";
+ };
+ buttons = {
+ justify-self = "start";
+ margin-top = "5px";
+ };
+ progress = {
+ background-color = "var(--contrast6)";
+ position = "absolute";
+ bottom = "15px";
+ left = "15px";
+ height = "3px";
+ width = "calc(100% - 30px)";
+ border-radius = "2px";
+ };
+ bar = {
+ background-color = "var(--green)";
+ position = "absolute";
+ bottom = "15px";
+ left = "15px";
+ height = "3px";
+ z-index = 1;
+ border-radius = "2px";
+ transition = "1s ease-out";
+ };
+ };
+ };
+ custom_fields = {
+ play_state = jsMatch {
+ value = "entity.state";
+ cases = [
+ {
+ match = "playing";
+ ret = "'Playing'";
+ }
+ {
+ match = "paused";
+ ret = "'Paused'";
+ }
+ ];
+ default = "entity.state";
+ };
+ icon1 = "[[[ return variables.icon_1 || '' ]]]";
+ progress = " ";
+ bar = ''
+ [[[
+ if (entity.attributes.media_position !== undefined) {
+ setTimeout(() => {
+ let elt = this.shadowRoot,
+ card = elt.getElementById('card'),
+ container = elt.getElementById('container'),
+ bar = elt.getElementById('bar');
+ if (elt && card && container && bar) {
+ card.insertBefore(bar, container);
+ function update() {
+ let mediaPositionUpdatedAt = entity.attributes.media_position_updated_at,
+ mediaPosition = entity.attributes.media_position,
+ mediaDuration = entity.attributes.media_duration;
+ let percentage = entity.state === 'paused'
+ ? (mediaPosition / mediaDuration * 93)
+ : entity.state === 'playing'
+ ? (((Date.now() / 1000) - (new Date(mediaPositionUpdatedAt).getTime() / 1000) + mediaPosition) / mediaDuration * 93)
+ : 0;
+ bar.style.width = percentage.toFixed(1) + '%';
+ requestAnimationFrame(update);
+ }
+ requestAnimationFrame(update);
+ }
+ }, 0);
+ return ' ';
+ }
+ ]]]
+ '';
+ buttons.card = {
+ type = "custom:button-card";
+ styles = mkStyles {
+ card = {
+ background = "none";
+ box-shadow = "none";
+ padding = "0";
+ };
+ grid = {
+ grid-template-areas = ''"prev play_pause next"'';
+ grid-template-columns = "1fr 1fr 1fr";
+ column-gap = "8px";
+ };
+ };
+ custom_fields = {
+ prev.card = {
+ type = "custom:button-card";
+ icon = "mdi:skip-previous";
+ entity = "[[[ return entity.entity_id ]]]";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "media_player.media_previous_track";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ };
+ styles = mkStyles {
+ card = {
+ background = "var(--contrast4)";
+ border-radius = "10px";
+ padding = "8px";
+ };
+ icon = {
+ width = "16px";
+ color = "var(--contrast16)";
+ };
+ };
+ };
+ play_pause.card = {
+ type = "custom:button-card";
+ icon = jsMatch {
+ value = "entity.state";
+ cases = [
+ {
+ match = "playing";
+ ret = "'mdi:pause'";
+ }
+ ];
+ default = "'mdi:play'";
+ };
+ entity = "[[[ return entity.entity_id ]]]";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "media_player.media_play_pause";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ };
+ styles = mkStyles {
+ card = {
+ background = "var(--green)";
+ border-radius = "10px";
+ padding = "8px";
+ };
+ icon = {
+ width = "18px";
+ color = "var(--black)";
+ };
+ };
+ };
+ next.card = {
+ type = "custom:button-card";
+ icon = "mdi:skip-next";
+ entity = "[[[ return entity.entity_id ]]]";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "media_player.media_next_track";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ };
+ styles = mkStyles {
+ card = {
+ background = "var(--contrast4)";
+ border-radius = "10px";
+ padding = "8px";
+ };
+ icon = {
+ width = "16px";
+ color = "var(--contrast16)";
+ };
+ };
+ };
+ };
+ };
+ };
+ variables = {
+ icon_1 = "";
+ };
+ };
+
+ custom_card_mediaplayer_tv = {
+ template = "setup";
+ show_label = true;
+ show_name = true;
+ show_state = true;
+ show_entity_picture = true;
+ entity_picture = ''
+ [[[
+ if (entity.attributes.entity_picture == undefined) {
+ return '/local/images/tv-placeholder.png';
+ } else {
+ return entity.attributes.entity_picture;
+ }
+ ]]]
+ '';
+ label = ''
+ [[[
+ if (entity.attributes.app_name) return entity.attributes.app_name;
+ else return entity.state;
+ ]]]
+ '';
+ name = ''
+ [[[
+ if (entity.attributes.active_child == "media_player.playstation_5") {
+ return states["sensor.ps5_343_activity"].attributes.players || "Playstation 5";
+ } else if (entity.attributes.active_child == "media_player.steam_jlnbln") {
+ return "Steam";
+ } else if (entity.attributes.media_artist) {
+ return entity.attributes.media_artist;
+ } else if (entity.attributes.media_title) {
+ return entity.attributes.media_title;
+ } else {
+ return entity.attributes.friendly_name || "TV";
+ }
+ ]]]
+ '';
+ state_display = ''
+ [[[
+ if (entity.attributes.media_title) return entity.attributes.media_title;
+ else return "";
+ ]]]
+ '';
+ tap_action.action = "more-info";
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"app_icon l i" "s s s" "n n n"'';
+ grid-template-columns = "min-content 1fr min-content";
+ grid-template-rows = "min-content min-content min-content";
+ column-gap = "10px";
+ };
+ card = {
+ border-radius = "24px";
+ background-color = "var(--contrast2)";
+ box-shadow = "none";
+ padding = "15px";
+ overflow = "hidden";
+ };
+ entity_picture = {
+ border-radius = "8px";
+ width = "40px";
+ height = "40px";
+ object-fit = "cover";
+ };
+ img_cell = {
+ width = "40px";
+ height = "40px";
+ display = "none";
+ };
+ icon = {
+ width = "24px";
+ color = "var(--contrast14)";
+ };
+ label = {
+ justify-self = "start";
+ align-self = "center";
+ font-size = "12px";
+ font-weight = 600;
+ color = "var(--contrast14)";
+ };
+ state = {
+ justify-self = "start";
+ font-size = "14px";
+ font-weight = 500;
+ color = "var(--contrast16)";
+ margin-top = "5px";
+ };
+ name = {
+ justify-self = "start";
+ font-size = "16px";
+ font-weight = 700;
+ color = "var(--contrast20)";
+ margin-top = "2px";
+ white-space = "nowrap";
+ overflow = "hidden";
+ text-overflow = "ellipsis";
+ };
+ custom_fields = {
+ app_icon = {
+ width = "40px";
+ height = "40px";
+ border-radius = "8px";
+ overflow = "hidden";
+ };
+ background_cover = {
+ position = "absolute";
+ top = "0";
+ left = "0";
+ width = "100%";
+ height = "100%";
+ opacity = "0.15";
+ z-index = "0";
+ background-size = "cover";
+ background-position = "center";
+ };
+ };
+ };
+ custom_fields = {
+ app_icon = ''
+ [[[
+ if (entity.attributes.active_child == "media_player.playstation_5") {
+ return '';
+ } else if (entity.attributes.active_child == "media_player.steam_jlnbln") {
+ return '';
+ } else if (entity.attributes.active_child == "media_player.plex_bedroom") {
+ return '';
+ } else if (entity.attributes.app_name == "YouTube") {
+ return '';
+ } else if (entity.attributes.app_name == "Netflix") {
+ return '';
+ } else {
+ return '';
+ }
+ ]]]
+ '';
+ background_cover = ''
+ [[[
+ let bgImage = "";
+ if (entity.attributes.active_child == "media_player.playstation_5") {
+ bgImage = states["sensor.ps5_343_activity"].attributes.title_image || "";
+ } else if (entity.attributes.active_child == "media_player.steam_jlnbln") {
+ bgImage = states["sensor.steam_76561197981585794"].attributes.game_image_main || "";
+ } else if (entity.attributes.entity_picture) {
+ bgImage = entity.attributes.entity_picture;
+ }
+ if (bgImage) {
+ return '
';
+ }
+ return "";
+ ]]]
+ '';
+ };
+ };
+
+ custom_card_user = {
+ template = "setup";
+ show_name = true;
+ show_label = true;
+ show_icon = false;
+ show_entity_picture = false;
+ name = ''
+ [[[
+ var hour = new Date().getHours();
+ var greeting = "";
+ if (hour >= 5 && hour < 12) greeting = "Good morning";
+ else if (hour >= 12 && hour < 17) greeting = "Good afternoon";
+ else if (hour >= 17 && hour < 21) greeting = "Good evening";
+ else greeting = "Good night";
+ var name = entity.attributes.friendly_name || "User";
+ return greeting + ", " + name + "!";
+ ]]]
+ '';
+ label = jsMatch {
+ value = "entity.state";
+ cases = [
+ {
+ match = "home";
+ ret = "'Welcome home'";
+ }
+ ];
+ default = "'You are away'";
+ };
+ tap_action.action = "more-info";
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"pic n badge" "pic l badge" "one two three"'';
+ grid-template-columns = "min-content 1fr min-content";
+ grid-template-rows = "min-content min-content min-content";
+ column-gap = "10px";
+ row-gap = "5px";
+ };
+ card = {
+ border-radius = "24px";
+ background-color = "var(--contrast2)";
+ box-shadow = "none";
+ padding = "15px";
+ };
+ name = {
+ justify-self = "start";
+ align-self = "end";
+ font-size = "18px";
+ font-weight = 700;
+ color = "var(--contrast20)";
+ };
+ label = {
+ justify-self = "start";
+ align-self = "start";
+ font-size = "12px";
+ font-weight = 500;
+ color = "var(--contrast14)";
+ };
+ custom_fields = {
+ pic = {
+ width = "50px";
+ height = "50px";
+ border-radius = "50%";
+ overflow = "hidden";
+ };
+ badge = {
+ justify-self = "end";
+ align-self = "center";
+ };
+ one = {
+ justify-self = "start";
+ margin-top = "10px";
+ };
+ two = {
+ justify-self = "center";
+ margin-top = "10px";
+ };
+ three = {
+ justify-self = "end";
+ margin-top = "10px";
+ };
+ };
+ };
+ custom_fields = {
+ pic = ''
+ [[[
+ var pic = entity.attributes.entity_picture || "";
+ if (pic) {
+ return '
';
+ }
+ return '';
+ ]]]
+ '';
+ badge = jsMatch {
+ value = "entity.state";
+ cases = [
+ {
+ match = "home";
+ ret = ''""'';
+ }
+ ];
+ default = ''""'';
+ };
+ one = ''
+ [[[
+ var james = states["person.james"];
+ if (james) {
+ var pic = james.attributes.entity_picture || "";
+ var home = james.state == "home";
+ var opacity = home ? "1" : "0.4";
+ if (pic) {
+ return '
';
+ }
+ }
+ return "";
+ ]]]
+ '';
+ two = ''
+ [[[
+ var savannah = states["person.savannah"];
+ if (savannah) {
+ var pic = savannah.attributes.entity_picture || "";
+ var home = savannah.state == "home";
+ var opacity = home ? "1" : "0.4";
+ if (pic) {
+ return '
';
+ }
+ }
+ return "";
+ ]]]
+ '';
+ three = ''
+ [[[
+ var poppy = states["person.poppy"];
+ if (poppy) {
+ var pic = poppy.attributes.entity_picture || "";
+ var home = poppy.state == "home";
+ var opacity = home ? "1" : "0.4";
+ if (pic) {
+ return '
';
+ }
+ }
+ return "";
+ ]]]
+ '';
+ };
+ };
+
+ custom_card_climate = {
+ template = "setup";
+ variables = {
+ name = "Climate";
+ sensor = "";
+ open = "Open";
+ radius = "24px";
+ };
+ show_name = true;
+ show_label = true;
+ show_icon = true;
+ show_state = false;
+ icon = "mdi:thermometer";
+ name = "[[[ return variables.name ]]]";
+ label = ''
+ [[[
+ if (entity.attributes.current_temperature !== undefined) {
+ return entity.attributes.current_temperature + "°C";
+ }
+ return entity.state;
+ ]]]
+ '';
+ tap_action.action = "more-info";
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"i n window" "i l slider" "i humidity slider"'';
+ grid-template-columns = "min-content 1fr min-content";
+ grid-template-rows = "min-content min-content min-content";
+ column-gap = "10px";
+ };
+ card = {
+ border-radius = "[[[ return variables.radius ]]]";
+ background-color = "var(--contrast2)";
+ box-shadow = "none";
+ padding = "15px";
+ };
+ icon = {
+ width = "40px";
+ height = "40px";
+ color = "var(--orange)";
+ };
+ name = {
+ justify-self = "start";
+ align-self = "end";
+ font-size = "14px";
+ font-weight = 600;
+ color = "var(--contrast20)";
+ };
+ label = {
+ justify-self = "start";
+ align-self = "center";
+ font-size = "24px";
+ font-weight = 700;
+ color = "var(--contrast20)";
+ };
+ custom_fields = {
+ window = {
+ justify-self = "end";
+ align-self = "start";
+ };
+ humidity = {
+ justify-self = "start";
+ align-self = "start";
+ font-size = "12px";
+ color = "var(--contrast14)";
+ };
+ slider = {
+ justify-self = "end";
+ align-self = "center";
+ };
+ };
+ };
+ custom_fields = {
+ window = ''
+ [[[
+ if (variables.sensor && states[variables.sensor]) {
+ if (states[variables.sensor].state == "on") {
+ return '';
+ }
+ }
+ return "";
+ ]]]
+ '';
+ humidity = ''
+ [[[
+ if (entity.attributes.current_humidity !== undefined) {
+ return ' ' + entity.attributes.current_humidity + '%';
+ }
+ return "";
+ ]]]
+ '';
+ slider.card = {
+ type = "custom:button-card";
+ styles = mkStyles {
+ card = {
+ background = "none";
+ box-shadow = "none";
+ padding = "0";
+ };
+ grid = {
+ grid-template-areas = ''"minus temp plus"'';
+ grid-template-columns = "1fr 1fr 1fr";
+ column-gap = "5px";
+ };
+ };
+ custom_fields = {
+ minus.card = {
+ type = "custom:button-card";
+ icon = "mdi:minus";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "climate.set_temperature";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ data.temperature = "[[[ return entity.attributes.temperature - 0.5 ]]]";
+ };
+ styles = mkStyles {
+ card = {
+ background = "var(--contrast4)";
+ border-radius = "10px";
+ padding = "8px";
+ };
+ icon = {
+ width = "16px";
+ color = "var(--contrast16)";
+ };
+ };
+ };
+ temp = ''
+ [[[
+ if (entity.attributes.temperature !== undefined) {
+ return entity.attributes.temperature + "°";
+ }
+ return "";
+ ]]]
+ '';
+ plus.card = {
+ type = "custom:button-card";
+ icon = "mdi:plus";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "climate.set_temperature";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ data.temperature = "[[[ return entity.attributes.temperature + 0.5 ]]]";
+ };
+ styles = mkStyles {
+ card = {
+ background = "var(--contrast4)";
+ border-radius = "10px";
+ padding = "8px";
+ };
+ icon = {
+ width = "16px";
+ color = "var(--contrast16)";
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+
+ custom_card_sensor_big = {
+ template = "setup";
+ show_name = true;
+ show_label = false;
+ show_icon = true;
+ show_state = true;
+ tap_action.action = "more-info";
+ state_display = ''
+ [[[
+ var unit = entity.attributes.unit_of_measurement || "";
+ return entity.state + ' ' + unit + '';
+ ]]]
+ '';
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"i" "n" "s"'';
+ grid-template-columns = "1fr";
+ grid-template-rows = "1fr min-content min-content";
+ };
+ card = {
+ border-radius = "24px";
+ background-color = "var(--contrast2)";
+ box-shadow = "none";
+ padding = "15px";
+ height = "120px";
+ };
+ icon = {
+ width = "32px";
+ color = "var(--contrast14)";
+ justify-self = "start";
+ };
+ name = {
+ justify-self = "start";
+ font-size = "12px";
+ font-weight = 500;
+ color = "var(--contrast14)";
+ margin-top = "auto";
+ };
+ state = {
+ justify-self = "start";
+ font-size = "28px";
+ font-weight = 700;
+ color = "var(--contrast20)";
+ };
+ };
+ };
+
+ custom_card_sensor_medium = {
+ template = "setup";
+ show_name = true;
+ show_label = false;
+ show_icon = true;
+ show_state = true;
+ tap_action.action = "more-info";
+ state_display = ''
+ [[[
+ var unit = entity.attributes.unit_of_measurement || "";
+ return entity.state + ' ' + unit + '';
+ ]]]
+ '';
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"i n" "s s"'';
+ grid-template-columns = "min-content 1fr";
+ grid-template-rows = "min-content min-content";
+ column-gap = "8px";
+ };
+ card = {
+ border-radius = "24px";
+ background-color = "var(--contrast2)";
+ box-shadow = "none";
+ padding = "12px";
+ };
+ icon = {
+ width = "24px";
+ color = "var(--contrast14)";
+ };
+ name = {
+ justify-self = "start";
+ align-self = "center";
+ font-size = "12px";
+ font-weight = 500;
+ color = "var(--contrast14)";
+ };
+ state = {
+ justify-self = "start";
+ font-size = "20px";
+ font-weight = 700;
+ color = "var(--contrast20)";
+ margin-top = "5px";
+ };
+ };
+ };
+
+ custom_card_sensor_small = {
+ template = "setup";
+ show_name = true;
+ show_label = false;
+ show_icon = true;
+ show_state = true;
+ tap_action.action = "more-info";
+ state_display = ''
+ [[[
+ var unit = entity.attributes.unit_of_measurement || "";
+ return entity.state + " " + unit;
+ ]]]
+ '';
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"i n s"'';
+ grid-template-columns = "min-content 1fr min-content";
+ grid-template-rows = "min-content";
+ column-gap = "8px";
+ };
+ card = {
+ border-radius = "16px";
+ background-color = "var(--contrast2)";
+ box-shadow = "none";
+ padding = "10px 12px";
+ };
+ icon = {
+ width = "20px";
+ color = "var(--contrast14)";
+ };
+ name = {
+ justify-self = "start";
+ align-self = "center";
+ font-size = "12px";
+ font-weight = 500;
+ color = "var(--contrast14)";
+ };
+ state = {
+ justify-self = "end";
+ align-self = "center";
+ font-size = "14px";
+ font-weight = 600;
+ color = "var(--contrast20)";
+ };
+ };
+ };
+
+ custom_card_timer_bottom = {
+ template = "setup";
+ type = "custom:button-card";
+ entity = ''
+ [[[
+ if (states["sensor.kitchen_timer"].state == "on") {
+ return 'sensor.kitchen_timer'
+ } else if (states["sensor.living_room_timer"].state == "on") {
+ return 'sensor.living_room_timer'
+ } else if (states["sensor.office_timer"].state == "on") {
+ return 'sensor.office_timer'
+ } else if (states["sensor.bed_room_timer"].state == "on") {
+ return 'sensor.bed_room_timer'
+ } else if (states["sensor.bathroom_timer"].state == "on") {
+ return 'sensor.bathroom_timer'
+ } else {
+ return 'sensor.kitchen_timer'
+ }
+ ]]]
+ '';
+ show_name = true;
+ show_icon = false;
+ show_label = false;
+ show_state = false;
+ tap_action.action = "more-info";
+ hold_action.action = "more-info";
+ custom_fields = {
+ bar = ''
+ [[[
+ var color = "var(--green)";
+ var state = 100 - states[ entity.entity_id ].attributes.remaining_perc;
+ if (state < 10) color = "var(--red)";
+ else if (state < 50) color = "var(--yellow)";
+ else if (state < 90) color = "var(--orange)";
+ return `
+ `
+ ]]]
+ '';
+ rem = {
+ card = {
+ type = "custom:button-card";
+ entity = "[[[ return entity.entity_id ]]]";
+ name = template.remainingTime;
+ show_icon = false;
+ styles = mkStyles {
+ card = {
+ width = "min";
+ background = "none";
+ overflow = "visible";
+ };
+ name = {
+ font-size = 14;
+ margin-top = 6;
+ font-weight = 600;
+ color = "var(--contrast20)";
+ };
+ };
+ };
+ };
+ icon1 = ''[[[ return ''; ]]]'';
+ };
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = ''"rem icon1" "n icon2" "bar bar"'';
+ grid-template-rows = "min-content min-content min-content";
+ grid-template-columns = "60% 40%";
+ };
+ card = {
+ padding = "15px 35px 15px 35px";
+ background = "var(--contrast2)";
+ height = template.stateIfElse {
+ entity = mediaPlayers.special.music_and_tv;
+ states = [
+ "playing"
+ "paused"
+ ];
+ ifTrue = "280px";
+ ifFalse = "180px";
+ };
+ width = "100vw";
+ overflow = "hidden";
+ position = template.stateOnIfElse {
+ entity = entities.inputBooleans.debugRounded;
+ valueOn = "static";
+ valueOff = "fixed";
+ };
+ margin = 0;
+ bottom = 0;
+ left = 0;
+ z-index = 1;
+ border-radius = "20px 20px 0px 0px";
+ box-shadow = "rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px";
+ };
+ img_cell = {
+ position = "absolute";
+ top = "20%";
+ left = "40%";
+ overflow = "visible";
+ };
+ label = {
+ text-align = "left";
+ font-size = 18;
+ font-weight = 500;
+ justify-self = "start";
+ align-self = "start";
+ overflow = "visible";
+ color = "var(--contrast20)";
+ };
+ name = {
+ text-align = "left";
+ font-size = 12;
+ justify-self = "start";
+ align-self = "start";
+ overflow = "visible";
+ color = "var(--contrast20)";
+ };
+ custom_fields = {
+ bar = {
+ justify-self = "start";
+ align-self = "start";
+ margin-top = 10;
+ width = "100%";
+ height = template.stateOnIfElse {
+ entity = "entity.state";
+ valueOn = "12px";
+ valueOff = "0px";
+ };
+ background = "var(--contrast1)";
+ border-radius = 24;
+ };
+ rem = {
+ justify-self = "start";
+ font-size = 14;
+ font-weight = 600;
+ align-self = "end";
+ height = template.stateOnIfElse {
+ entity = "entity.state";
+ valueOn = "27px";
+ valueOff = "0px";
+ };
+ };
+ icon1 = {
+ justify-self = "end";
+ align-self = "start";
+ width = 24;
+ color = "var(--contrast20)";
+ };
+ };
+
+ hold_action.action = "more-info";
+ };
+ };
+}
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards/default.nix b/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards/default.nix
new file mode 100644
index 000000000..634739f38
--- /dev/null
+++ b/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards/default.nix
@@ -0,0 +1,6 @@
+{
+ lib,
+}:
+{
+ media-player = import ./media-player.nix { inherit lib; };
+}
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards/media-player.nix b/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards/media-player.nix
new file mode 100644
index 000000000..ddfe10581
--- /dev/null
+++ b/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards/media-player.nix
@@ -0,0 +1,351 @@
+{ lib, ... }:
+with (import ../../lib.nix { inherit lib; });
+{
+ template = "setup";
+ variables = {
+ room = "Select Room";
+ icon_1 = '''';
+ accent_color = "var(--green)";
+ };
+ show_entity_picture = true;
+ show_label = true;
+ entity_picture = ''
+ [[[
+ if (entity.attributes.media_title == undefined || entity.attributes.entity_picture == undefined) {
+ return "/local/images/abstract-gif-colors.gif";
+ } else {
+ return states[entity.entity_id].attributes.entity_picture;
+ }
+ ]]]
+ '';
+ name = "[[[ return entity.attributes.media_title ]]]";
+ label = "[[[ return entity.attributes.media_artist ]]]";
+ tap_action.action = "none";
+ styles = {
+ grid = {
+ grid-template-areas = ''"icon1 select_room play_state icon2" "i i i i" "n n n n" "l l l l" "album album album album" "buttons buttons buttons buttons"'';
+ grid-template-columns = "min-content 1fr 1fr min-content";
+ # grid-template-rows = "min-content min-content min-content min-content min-content min-content";
+ };
+ card = {
+ background = "none";
+ box-shadow = "none";
+ border-radius = "0px";
+ margin-top = "-10px";
+ margin-bottom = "-10px";
+ "--mdc-ripple-color" = "black";
+ "--mdc-ripple-press-opacity" = 0;
+ };
+ entity_picture = {
+ border-radius = "16px";
+ margin-bottom = "35px";
+ margin-top = "20px";
+ object-fit = "cover";
+ width = "200px";
+ height = "200px";
+ };
+ name = {
+ justify-self = "center";
+ font-weight = 600;
+ font-size = "15px";
+ padding-top = "10px";
+ width = "90%";
+ };
+ label = {
+ justify-self = "center";
+ align-self = "start";
+ color = "var(--contrast16)";
+ font-weight = 500;
+ font-size = "12px";
+ };
+ custom_fields = {
+ icon1 = {
+ justify-self = "start";
+ width = "24px";
+ color = "var(--contrast20)";
+ };
+ icon2 = {
+ justify-self = "end";
+ width = "24px";
+ color = "var(--contrast20)";
+ };
+ select_room = {
+ font-size = "10px";
+ justify-self = "start";
+ align-self = "center";
+ color = "var(--contrast16)";
+ padding-left = "10px";
+ };
+ play_state = {
+ font-size = "10px";
+ justify-self = "end";
+ align-self = "center";
+ color = "var(--contrast16)";
+ padding-right = "10px";
+ };
+ album = {
+ justify-self = "center";
+ align-self = "start";
+ color = "var(--contrast16)";
+ font-weight = 400;
+ font-size = "10px";
+ margin-bottom = "5px";
+ };
+ progress = {
+ background-color = "var(--contrast4)";
+ position = "absolute";
+ top = "unset";
+ bottom = "185px";
+ left = "20%";
+ height = "2px";
+ width = "60%";
+ border-radius = "4px";
+ };
+ bar = {
+ background-color = "[[[ return variables.accent_color ]]]";
+ position = "absolute";
+ top = "unset";
+ bottom = "185px";
+ left = "20%";
+ height = "2px";
+ z-index = 1;
+ border-radius = "4px";
+ transition = "1s ease-out";
+ };
+ };
+ };
+ customFields = {
+ icon1 = "[[[ return variables.icon_1 ]]]";
+ icon2 = ''[[[ return '' ]]]'';
+ select_room = "[[[ return variables.room ]]]";
+ play_state = jsMatch {
+ value = "states[entity.entity_id].attributes.active_child";
+ cases = [
+ {
+ match = "media_player.spotify_james";
+ ret = ''states[entity.entity_id].state + " - " + states[entity.entity_id].attributes.source'';
+ }
+ {
+ match = "media_player.apple_tv";
+ ret = ''states[entity.entity_id].state + " - " + states[entity.entity_id].attributes.app_name'';
+ }
+ ];
+ default = ''states[entity.entity_id].state + " - " + states[entity.entity_id].attributes.friendly_name'';
+ };
+ album = mkButtonCard {
+ showLabel = true;
+ label = "[[[ return states[entity.entity_id].attributes.media_album_name ]]]";
+ styles = {
+ card = {
+ background = "none";
+ boxShadow = "none";
+ borderRadius = 0;
+ padding = 0;
+ };
+ label = {
+ justifySelf = "center";
+ alignSelf = "start";
+ color = "var(--contrast16)";
+ fontWeight = 400;
+ fontSize = "10px";
+ marginBottom = "5px";
+ };
+ };
+ };
+ progress = " ";
+ bar = ''
+ [[[
+ if (entity.attributes.media_position !== undefined) {
+ setTimeout(() => {
+ let elt = this.shadowRoot,
+ card = elt.getElementById('card'),
+ container = elt.getElementById('container'),
+ bar = elt.getElementById('bar');
+ if (elt && card && container && bar) {
+ card.insertBefore(bar, container);
+ function update() {
+ let mediaPositionUpdatedAt = entity.attributes.media_position_updated_at,
+ mediaPosition = entity.attributes.media_position,
+ mediaDuration = entity.attributes.media_duration;
+ let percentage = entity.state === 'paused'
+ ? (mediaPosition / mediaDuration * 60)
+ : entity.state === 'playing'
+ ? (((Date.now() / 1000) - (new Date(mediaPositionUpdatedAt).getTime() / 1000) + mediaPosition) / mediaDuration * 60)
+ : 0;
+ bar.style.width = percentage.toFixed(1) + '%';
+ requestAnimationFrame(update);
+ }
+ requestAnimationFrame(update);
+ }
+ }, 0);
+ return ' ';
+ }
+ ]]]
+ '';
+ buttons.card = {
+ type = "custom:button-card";
+ styles = {
+ card = {
+ background = "none";
+ box-shadow = "none";
+ padding = "0";
+ };
+ grid = {
+ grid-template-areas = ''"loop prev play_pause next shuffle"'';
+ grid-template-columns = "1fr 1fr 1fr 1fr 1fr";
+ column-gap = "10px";
+ };
+ };
+ custom_fields = {
+ loop.card = {
+ type = "custom:button-card";
+ icon = "mdi:repeat";
+ entity = "[[[ return entity.entity_id ]]]";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "media_player.repeat_set";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ data.repeat = jsMatch {
+ value = "entity.attributes.repeat";
+ cases = [
+ {
+ match = "off";
+ ret = "'all'";
+ }
+ {
+ match = "all";
+ ret = "'one'";
+ }
+ ];
+ default = "'off'";
+ };
+ };
+ styles = {
+ card = {
+ background = "var(--contrast4)";
+ border-radius = "12px";
+ padding = "10px";
+ };
+ icon = {
+ width = "20px";
+ color = jsMatch {
+ value = "entity.attributes.repeat";
+ cases = [
+ {
+ match = "off";
+ ret = "'var(--contrast10)'";
+ }
+ ];
+ default = "'var(--green)'";
+ };
+ };
+ };
+ };
+ prev.card = {
+ type = "custom:button-card";
+ icon = "mdi:skip-previous";
+ entity = "[[[ return entity.entity_id ]]]";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "media_player.media_previous_track";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ };
+ styles = {
+ card = {
+ background = "var(--contrast4)";
+ border-radius = "12px";
+ padding = "10px";
+ };
+ icon = {
+ width = "20px";
+ color = "var(--contrast16)";
+ };
+ };
+ };
+ play_pause.card = {
+ type = "custom:button-card";
+ icon = jsMatch {
+ value = "entity.state";
+ cases = [
+ {
+ match = "playing";
+ ret = "'mdi:pause'";
+ }
+ ];
+ default = "'mdi:play'";
+ };
+ entity = "[[[ return entity.entity_id ]]]";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "media_player.media_play_pause";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ };
+ styles = {
+ card = {
+ background = "var(--green)";
+ border-radius = "12px";
+ padding = "10px";
+ };
+ icon = {
+ width = "24px";
+ color = "var(--black)";
+ };
+ };
+ };
+ next.card = {
+ type = "custom:button-card";
+ icon = "mdi:skip-next";
+ entity = "[[[ return entity.entity_id ]]]";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "media_player.media_next_track";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ };
+ styles = {
+ card = {
+ background = "var(--contrast4)";
+ border-radius = "12px";
+ padding = "10px";
+ };
+ icon = {
+ width = "20px";
+ color = "var(--contrast16)";
+ };
+ };
+ };
+ shuffle.card = {
+ type = "custom:button-card";
+ icon = "mdi:shuffle";
+ entity = "[[[ return entity.entity_id ]]]";
+ show_name = false;
+ tap_action = {
+ action = "call-service";
+ service = "media_player.shuffle_set";
+ target.entity_id = "[[[ return entity.entity_id ]]]";
+ data.shuffle = "[[[ return !entity.attributes.shuffle ]]]";
+ };
+ styles = {
+ card = {
+ background = "var(--contrast4)";
+ border-radius = "12px";
+ padding = "10px";
+ };
+ icon = {
+ width = "20px";
+ color = ''
+ [[[
+ if (entity.attributes.shuffle) return "var(--green)";
+ else return "var(--contrast10)";
+ ]]]
+ '';
+ };
+ };
+ };
+ };
+ };
+ };
+}
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards/media_player.nix b/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards/media_player.nix
deleted file mode 100644
index d56e25421..000000000
--- a/hosts/server/nixcloud/home-assistant/dashboard/components/button-cards/media_player.nix
+++ /dev/null
@@ -1,47 +0,0 @@
-{ lib, ... }:
-with (import ../lib.nix { inherit lib; });
-{
- type = "custom:button-card";
- template = "setup";
- variables = {
- room = entity.name;
- icon_1 = "";
- accent_color = "var(--green)";
- };
- show_entity_picture = true;
- show_name = true;
- show_state = true;
- name = "[[[ return (variables.room && variables.room.length > 0) ? variables.room : (entity.attributes.friendly_name || 'Player') ]]]";
- state = [
- {
- value = "playing";
- icon = "mdi:play-circle-outline";
- }
- {
- value = "paused";
- icon = "mdi:pause-circle-outline";
- }
- ];
- tap_action.action = "more-info";
- styles = {
- card = {
- border-radius = "12px";
- background-color = "var(--contrast2)";
- box-shadow = "none";
- padding = "12px";
- };
- icon.color = "var(--black)";
- name = {
- justify-self = "start";
- font-size = 14;
- font-weight = 600;
- color = "var(--contrast14)";
- };
- state = {
- justify-self = "start";
- font-size = 12;
- font-weight = 500;
- color = "var(--contrast20)";
- };
- };
-}
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/templates/decluttering.nix b/hosts/server/nixcloud/home-assistant/dashboard/components/decluttering.nix
similarity index 100%
rename from hosts/server/nixcloud/home-assistant/dashboard/templates/decluttering.nix
rename to hosts/server/nixcloud/home-assistant/dashboard/components/decluttering.nix
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/components/navbar.nix b/hosts/server/nixcloud/home-assistant/dashboard/components/navbar.nix
index 4cbab4b6a..4c2d26aa4 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard/components/navbar.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard/components/navbar.nix
@@ -1,6 +1,11 @@
{
+ lib,
basePath ? "/lovelace",
}:
+let
+ dashLib = import ../lib.nix { inherit lib; };
+ inherit (dashLib) entities mediaPlayers;
+in
{
type = "custom:navbar-card";
@@ -69,13 +74,14 @@
url = "${basePath}/music";
label = "Music";
badge = {
- show = "[[[ return states['media_player.music'].state === 'playing' ]]]";
+ show = "[[[ return states['${mediaPlayers.special.music}'].state === 'playing' ]]]";
color = "var(--green-color)";
};
hidden = ''
[[[
- if (states['media_player.music'].state == "playing") return false;
- else return false;
+ if (states['${mediaPlayers.special.music}'].state == "playing") return true;
+ else if (states['remote.apple_tv'].state == "on") return false;
+ else return true;
]]]
'';
hold_action.action = "open-popup";
@@ -97,12 +103,12 @@
url = "${basePath}/home#remote";
label = "TV";
badge = {
- show = "[[[ return states['media_player.apple_tv'].state === 'playing' ]]]";
+ show = "[[[ return states['${mediaPlayers.special.apple_tv}'].state === 'playing' ]]]";
color = "var(--green-color)";
};
hidden = ''
[[[
- if (states['media_player.music'].state == "playing") return true;
+ if (states['${mediaPlayers.special.music}'].state == "playing") return true;
else if (states['remote.apple_tv'].state == "on") return false;
else return true;
]]]
@@ -136,7 +142,7 @@
selected = "[[[ return location.hash == '#james'; ]]]";
hidden = ''[[[ return user.name != "James" ]]]'';
badge = {
- show = "[[[ return states['input_boolean.debug_rounded'].state === 'on' ]]]";
+ show = "[[[ return states['${entities.inputBooleans.debugRounded}'].state === 'on' ]]]";
color = "var(--red-color)";
};
}
@@ -153,9 +159,9 @@
badge = {
show = ''
[[[
- if (states['binary_sensor.monitored_entities'].state == "on") return true;
- else if (states['binary_sensor.home_assistant_update'].state == "on") return true;
- else if (states['binary_sensor.battery_health_attention'].state == "on") return true;
+ if (states['${entities.binarySensors.monitoredEntities}'].state == "on") return true;
+ else if (states['${entities.binarySensors.homeAssistantUpdate}'].state == "on") return true;
+ else if (states['${entities.binarySensors.batteryHealthAttention}'].state == "on") return true;
else return false;
]]]
'';
@@ -172,8 +178,8 @@
color = "var(--red-color)";
show = ''
[[[
- if (states['binary_sensor.monitored_entities'].state == "on") return true;
- else if (states['binary_sensor.battery_health_attention'].state == "on") return true;
+ if (states['${entities.binarySensors.monitoredEntities}'].state == "on") return true;
+ else if (states['${entities.binarySensors.batteryHealthAttention}'].state == "on") return true;
else return false;
]]]
'';
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/components/templates/popup.nix b/hosts/server/nixcloud/home-assistant/dashboard/components/templates/popup.nix
new file mode 100644
index 000000000..b57058f9d
--- /dev/null
+++ b/hosts/server/nixcloud/home-assistant/dashboard/components/templates/popup.nix
@@ -0,0 +1,15 @@
+{ lib, ... }:
+with (import ../../lib.nix { inherit lib; });
+{
+ popup =
+ {
+ location,
+ }:
+ {
+ type = "custom:bubble-card";
+ card_type = "pop-up";
+ hash = "#${location}";
+ button_type = "name";
+
+ };
+}
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/default.nix b/hosts/server/nixcloud/home-assistant/dashboard/default.nix
index bf51de753..69962ff60 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard/default.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard/default.nix
@@ -1,9 +1,9 @@
{ lib, ... }:
{
services.home-assistant.lovelaceConfig = {
- kiosk_mode = import ./kiosk-mode.nix;
- decluttering_templates = import ./templates/decluttering.nix { inherit lib; };
- button_card_templates = import ./templates/button-cards.nix { inherit lib; };
+ kiosk_mode = import ./kiosk-mode.nix { inherit lib; };
+ decluttering_templates = import ./components/decluttering.nix { inherit lib; };
+ button_card_templates = import ./components/button-cards { inherit lib; };
views = [
(import ./views/home.nix { inherit lib; })
(import ./views/music.nix { inherit lib; })
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/kiosk-mode.nix b/hosts/server/nixcloud/home-assistant/dashboard/kiosk-mode.nix
index c6d6868e7..74db23ece 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard/kiosk-mode.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard/kiosk-mode.nix
@@ -1,3 +1,8 @@
+{ lib }:
+let
+ dashLib = import ./lib.nix { inherit lib; };
+ inherit (dashLib) entities;
+in
{
non_admin_settings = {
hide_header = true;
@@ -5,11 +10,11 @@
};
entity_settings = [
{
- entity."input_boolean.debug_rounded" = "on";
+ entity.${entities.inputBooleans.debugRounded} = "on";
hide_header = false;
}
{
- entity."input_boolean.debug_rounded" = "off";
+ entity.${entities.inputBooleans.debugRounded} = "off";
hide_header = true;
}
];
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/lib.nix b/hosts/server/nixcloud/home-assistant/dashboard/lib.nix
index d01da6710..3804101e8 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard/lib.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard/lib.nix
@@ -1,10 +1,290 @@
{ lib }:
+let
+ inherit (lib) nameValuePair mapAttrs';
+ inherit (lib.mine.strings) toSnakeCase;
+in
rec {
+ # User IDs for Home Assistant user conditions
ids = {
james = "3eea636aa3de4c7f9c662ad29c6e92e0";
savannah = "82def695e9504f63b1eb09150073737d";
};
+ # User display names for UI labels
+ users = {
+ james = "James";
+ savannah = "Savannah";
+ };
+
+ # Centralized entity definitions to avoid duplication
+ entities = {
+ # Sensors
+ sensors = {
+ uptimekuma = "sensor.uptimekuma_uptime_racci_dev";
+ musicRoom = "sensor.music_room";
+ musicAssistantPlaying = "sensor.music_assistant_playing_devices";
+ timeOfDay = "sensor.time_of_day";
+ time = "sensor.time";
+ windowOpenCount = "sensor.window_open_count";
+ lightsOnCount = "sensor.lights_on_count";
+ choresOnCount = "sensor.chores_on_count";
+ batteryHealthAttention = "sensor.battery_health_attention";
+ batteryLevelAttention = "sensor.battery_level_attention";
+ monitoredEntities = "sensor.monitored_entities";
+ powerConsumption = "sensor.power_consumption";
+ homeAssistantUpdate = "sensor.home_assistant_update";
+ # Timers
+ kitchenTimer = "sensor.kitchen_timer";
+ livingRoomTimer = "sensor.living_room_timer";
+ bedroomTimer = "sensor.bed_room_timer";
+ bathroomTimer = "sensor.bathroom_timer";
+ officeTimer = "sensor.office_timer";
+ # 3D Printer
+ printerState = "sensor.k1c_276e_current_print_state";
+ printerProgress = "sensor.k1c_276e_progress";
+ # Gaming
+ ps5Activity = "sensor.ps5_343_activity";
+ steamActivity = "sensor.steam_76561197981585794";
+ # Alarm
+ wakeTime = "sensor.wake_time_1";
+ isAlarmOn = "sensor.is_alarm_on";
+ };
+
+ # Binary sensors
+ binarySensors = {
+ alexaTimerActive = "binary_sensor.alexa_timer_active";
+ batteryHealthAttention = "binary_sensor.battery_health_attention";
+ monitoredEntities = "binary_sensor.monitored_entities";
+ isAlarmOn = "binary_sensor.is_alarm_on";
+ homeAssistantUpdate = "binary_sensor.home_assistant_update";
+ };
+
+ # Input booleans
+ inputBooleans = {
+ debugRounded = "input_boolean.debug_rounded";
+ vacationMode = "input_boolean.vacation_mode";
+ soundAlarmRunning = "input_boolean.sound_alarm_is_running";
+ alarmSnoozed = "input_boolean.alarm_snoozed";
+ notificationsJames = "input_boolean.notifications_james_phone";
+ notificationsSavannah = "input_boolean.notifications_savannah_phone";
+ musicPlayerIdleHelper = "input_boolean.music_player_idle_helper";
+ };
+
+ # Persons
+ persons = {
+ james = "person.james";
+ savannah = "person.savannah";
+ };
+
+ # Other
+ alarm = "alarm_control_panel.security_system";
+ adguardProtection = "switch.adguard_home_protection";
+ adguardSpeed = "sensor.adguard_home_average_processing_speed";
+ };
+
+ # Centralized media player entity definitions
+ mediaPlayers = {
+ # Room-based speakers
+ rooms = {
+ living_room = "media_player.living_room";
+ kitchen = "media_player.kitchen";
+ bed_room = "media_player.bed_room";
+ bedroom_speakers = "media_player.bedroom_speakers";
+ bedroom_nest = "media_player.bedroom_nest_music_assistant";
+ bathroom = "media_player.bathroom";
+ everywhere = "media_player.everywhere";
+ spare_room = "media_player.spare_room";
+ };
+
+ # Devices that also act as media players
+ special = {
+ apple_tv = "media_player.apple_tv";
+ music = "media_player.music";
+ music_and_tv = "media_player.music_and_tv";
+ steam = "media_player.steam_jlnbln";
+ };
+ };
+
+ jsColours = {
+ yellow = "var(--yellow)";
+ orange = "var(--orange)";
+ black = "var(--black)";
+ blue = "var(--blue)";
+ contrast2 = "var(--contrast2)";
+ contrast20 = "var(--contrast20)";
+ };
+
+ # Helper to create a music room button card for the desktop view
+ mkMusicRoomButton =
+ {
+ room, # entity suffix (e.g., "bed_room")
+ icon,
+ script, # script to call on double tap
+ }:
+ {
+ type = "custom:button-card";
+ entity = "media_player.${room}";
+ inherit icon;
+ show_label = false;
+ show_state = true;
+ show_entity_picture = true;
+ styles = {
+ card = [
+ { border-radius = "0px"; }
+ { box-shadow = "none"; }
+ { padding-right = "5px"; }
+ { background = "none"; }
+ ];
+ grid = [
+ { grid-template-areas = ''" 'i n' 'i s' "''; }
+ { grid-template-columns = "min-content"; }
+ { column-gap = "10px"; }
+ ];
+ entity_picture = [ { border-radius = "100%"; } ];
+ icon = [ { width = 20; } ];
+ img_cell = [ { width = 20; } ];
+ name = [
+ { justify-self = "start"; }
+ { font-size = 10; }
+ { font-weight = 500; }
+ { color = "var(--contrast14)"; }
+ ];
+ state = [
+ { justify-self = "start"; }
+ { font-size = 15; }
+ { font-weight = 700; }
+ ];
+ };
+ tap_action.action = "more-info";
+ double_tap_action = {
+ action = "perform-action";
+ perform_action = script;
+ };
+ };
+
+ # Helper to create a mobile music room transfer button
+ mkMusicRoomTransferButton =
+ {
+ room, # entity suffix
+ icon,
+ color,
+ targetEntity, # entity to transfer to
+ script, # script for double tap
+ }:
+ {
+ type = "custom:button-card";
+ entity = "media_player.${room}";
+ inherit icon;
+ show_name = false;
+ aspect_ratio = "1/1";
+ state = [
+ {
+ value = "playing";
+ icon = "mdi:play-circle-outline";
+ }
+ {
+ value = "paused";
+ icon = "mdi:pause-circle-outline";
+ }
+ ];
+ tap_action = {
+ action = "call-service";
+ service = "music_assistant.transfer_queue";
+ data.source_player = jsMatch {
+ value = "states['sensor.music_room'].state";
+ cases = [
+ {
+ match = "Living Room";
+ ret = "'${mediaPlayers.rooms.living_room}'";
+ }
+ {
+ match = "Kitchen";
+ ret = "'${mediaPlayers.rooms.kitchen}'";
+ }
+ {
+ match = "Bathroom";
+ ret = "'${mediaPlayers.rooms.bathroom}'";
+ }
+ {
+ match = "Bedroom";
+ ret = "'${mediaPlayers.rooms.bedroom_speakers}'";
+ }
+ {
+ match = "Spare Room";
+ ret = "'${mediaPlayers.rooms.spare_room}'";
+ }
+ {
+ match = "Everywhere";
+ ret = "'${mediaPlayers.rooms.everywhere}'";
+ }
+ ];
+ default = "''";
+ };
+ target.entity_id = targetEntity;
+ haptic = "success";
+ };
+ double_tap_action = {
+ action = "call-service";
+ service = script;
+ haptic = "success";
+ };
+ styles.card = [
+ { border-radius = "12px"; }
+ { background-color = "var(--${color})"; }
+ ];
+ styles.icon = [ { color = "var(--black)"; } ];
+ };
+
+ # Helper to create a conditional swipe card for a music room
+ mkMusicSwipeCard =
+ {
+ room, # display name (e.g., "Living Room")
+ entity, # full entity id
+ conditionEntity ? entity, # entity to check for playing/paused state
+ }:
+ {
+ type = "conditional";
+ conditions = [
+ {
+ condition = "numeric_state";
+ entity = "sensor.music_assistant_playing_devices";
+ above = 1;
+ }
+ {
+ condition = "state";
+ entity = mediaPlayers.rooms.everywhere;
+ state_not = "playing";
+ }
+ {
+ condition = "state";
+ entity = entities.sensors.musicRoom;
+ state_not = room;
+ }
+ {
+ condition = "or";
+ conditions = [
+ {
+ condition = "state";
+ entity = conditionEntity;
+ state = "paused";
+ }
+ {
+ condition = "state";
+ entity = conditionEntity;
+ state = "playing";
+ }
+ ];
+ }
+ ];
+ card = {
+ type = "custom:button-card";
+ inherit entity;
+ template = "media_player";
+ variables.room = room;
+ variables.icon_1 = '''';
+ };
+ };
+
entity = {
id = "[[[ return entity.entity_id ]]]";
icon = "[[[ return entity.attributes.icon ]]]";
@@ -66,11 +346,11 @@ rec {
];
timerActive = condState {
- entity = "binary_sensor.alexa_timer_active";
+ entity = entities.binarySensors.alexaTimerActive;
state = "on";
};
alarmActive = condState {
- entity = "input_boolean.sound_alarm_is_running";
+ entity = entities.inputBooleans.soundAlarmRunning;
state = "on";
};
};
@@ -83,6 +363,18 @@ rec {
]]]
'';
+ getState = entity: ''
+ [[[
+ return states[ "${entity}" ].state;
+ ]]]
+ '';
+
+ returnIcon = icon: ''
+ [[[
+ return '';
+ ]]]
+ '';
+
stateOnIfElse =
{
entity ? "entity.state",
@@ -128,23 +420,91 @@ rec {
value = "[[[ return variables.state_${state} ]]]";
icon = "[[[ return variables.icon_${state} ]]]";
styles = {
- card.background-color = "[[[ return variables.background_color_${state} ]]]";
- name.color = "[[[ return variables.color_${state} ]]]";
- label.color = "[[[ return variables.color_${state} ]]]";
- icon.color = "[[[ return variables.color_${state} ]]]";
+ card = [ { background-color = "[[[ return variables.background_color_${state} ]]]"; } ];
+ name = [ { color = "[[[ return variables.color_${state} ]]]"; } ];
+ label = [ { color = "[[[ return variables.color_${state} ]]]"; } ];
+ icon = [ { color = "[[[ return variables.color_${state} ]]]"; } ];
};
};
};
compact = attrs: lib.filterAttrs (_: v: v != null) attrs;
+ parseConvertCompact =
+ attrs: attrs |> compact |> mapAttrs' (name: value: nameValuePair (toSnakeCase name) value);
+
+ # Convert a style attrset to an array of single-key objects for button-card
+ # { a = 1; b = 2; } -> [ { a = 1; } { b = 2; } ]
+ styleArray = attrs: lib.mapAttrsToList (n: v: { ${n} = v; }) attrs;
+
+ # Convert a full styles block where each key (card, icon, etc.) needs to be an array
+ # Recursively handles nested custom_fields
+ mkStyles =
+ styles:
+ lib.mapAttrs (
+ name: value:
+ if name == "custom_fields" then
+ # custom_fields is nested - each field's styles should also be arrays
+ lib.mapAttrs (_: styleArray) value
+ else if !lib.isAttrs value then
+ # Not an attrset, leave as-is
+ value
+ else
+ styleArray value
+ ) styles;
+
colourOnOff = on: off: ''
[[[
- if (entity.state == "on") return "var(--${on})";
+ if (entity && entity.state == "on") return "var(--${on})";
else return "var(--${off})";
]]]
'';
+ # Generate a JavaScript template with match/switch-like semantics
+ # Usage:
+ # jsMatch {
+ # value = "entity.state"; # or "states['sensor.foo'].state"
+ # cases = [
+ # { match = "on"; ret = "'green'"; }
+ # { match = "off"; ret = "'red'"; }
+ # { match = [ "unavailable" "unknown" ]; ret = "'gray'"; } # multiple values
+ # ];
+ # default = "'black'"; # optional, defaults to "null"
+ # }
+ # Produces:
+ # [[[
+ # const v = entity.state;
+ # if (v === "on") return 'green';
+ # if (v === "off") return 'red';
+ # if (v === "unavailable" || v === "unknown") return 'gray';
+ # return 'black';
+ # ]]]
+ jsMatch =
+ {
+ value,
+ cases,
+ default ? "null",
+ }:
+ let
+ mkCondition =
+ m:
+ if builtins.isList m then
+ builtins.concatStringsSep " || " (map (w: ''v === "${w}"'') m)
+ else
+ ''v === "${m}"'';
+
+ mkCase = { match, ret, ... }: "if (${mkCondition match}) return ${ret};";
+
+ caseLines = map mkCase cases;
+ in
+ ''
+ [[[
+ const v = ${value};
+ ${builtins.concatStringsSep "\n " caseLines}
+ return ${default};
+ ]]]
+ '';
+
condScreen = media_query: {
condition = "screen";
inherit media_query;
@@ -156,7 +516,7 @@ rec {
state ? null,
state_not ? null,
}:
- compact {
+ parseConvertCompact {
condition = "state";
inherit entity state state_not;
};
@@ -167,7 +527,7 @@ rec {
above ? null,
below ? null,
}:
- compact {
+ parseConvertCompact {
condition = "numeric_state";
inherit entity above below;
};
@@ -225,7 +585,7 @@ rec {
type ? "sections",
max_columns ? null,
}:
- compact {
+ parseConvertCompact {
inherit
title
header
@@ -253,7 +613,7 @@ rec {
show_entity_picture ? null,
name ? null,
}:
- compact {
+ lib.warn "Convert to mkEntity, shit stain" (parseConvertCompact {
type = "entity";
inherit
entity
@@ -267,6 +627,41 @@ rec {
show_entity_picture
name
;
+ });
+
+ mkEntity =
+ {
+ entity,
+ name ? null,
+ icon ? null,
+ color ? null,
+ iconColor ? null,
+ visibility ? null,
+ contentInfo ? null,
+ stateContent ? null,
+ showName ? null,
+ showState ? null,
+ showIcon ? null,
+ showEntityPicture ? null,
+ tapAction ? null,
+ }:
+ parseConvertCompact {
+ type = "entity";
+ inherit
+ entity
+ name
+ icon
+ color
+ iconColor
+ visibility
+ contentInfo
+ stateContent
+ showName
+ showState
+ showIcon
+ showEntityPicture
+ tapAction
+ ;
};
mkMushroomTemplateBadge =
@@ -281,7 +676,7 @@ rec {
tap_action ? null,
visibility ? null,
}:
- compact {
+ parseConvertCompact {
type = "custom:mushroom-template-badge";
inherit
content
@@ -296,17 +691,38 @@ rec {
;
};
+ mkMushroomChipsCard =
+ {
+ chips,
+ alignment ? "center",
+ }:
+ parseConvertCompact {
+ type = "custom:mushroom-chips-card";
+ inherit chips alignment;
+ };
+
+ mkAction =
+ {
+ action,
+ haptic ? null,
+ }:
+ parseConvertCompact {
+ inherit action haptic;
+ };
+
mkGridSection =
{
cards,
+ columns ? null,
grid_options ? null,
visibility ? null,
column_span ? null,
}:
- compact {
+ parseConvertCompact {
type = "grid";
inherit
cards
+ columns
grid_options
visibility
column_span
@@ -356,21 +772,131 @@ rec {
;
};
+ # Convert attribute names from camelCase to snake_case for card definitions
+ # Does not recurse into nested attrsets
+ convertNames =
+ attrsList:
+ attrsList |> map (_attrs: mapAttrs' (name: value: nameValuePair (toSnakeCase name) value));
+
+ mkBubbleCard =
+ {
+ name ? null,
+ entity ? null,
+ icon ? null,
+ hash ? null,
+
+ cardType,
+ subButton ? [ ],
+ showState ? null,
+ showName ? null,
+ showIcon ? null,
+ scrollingEffect ? null,
+
+ tapAction ? null,
+ doubleTapAction ? null,
+ holdAction ? null,
+ buttonAction ? null,
+
+ openAction ? null,
+ closeAction ? null,
+
+ buttonType ? "name",
+ cardLayout ? "large",
+ hideBackdrop ? null,
+ styles ? null,
+ }:
+ parseConvertCompact {
+ type = "custom:bubble-card";
+ inherit
+ name
+ entity
+ icon
+ hash
+ cardType
+ subButton
+ showState
+ showName
+ showIcon
+ scrollingEffect
+ tapAction
+ doubleTapAction
+ holdAction
+ buttonAction
+ openAction
+ closeAction
+ buttonType
+ cardLayout
+ hideBackdrop
+ styles
+ ;
+ };
+
+ mkButtonCard =
+ {
+ entity ? null,
+ name ? null,
+ label ? null,
+ icon ? null,
+ template ? null,
+
+ showName ? null,
+ showIcon ? null,
+ showLabel ? null,
+ showState ? null,
+
+ tapAction ? null,
+ holdAction ? null,
+
+ variables ? { },
+ styles ? { },
+ customFields ? { },
+ }:
+ parseConvertCompact {
+ type = "custom:button-card";
+ inherit
+ entity
+ name
+ label
+ icon
+ template
+ showName
+ showIcon
+ showLabel
+ showState
+ tapAction
+ holdAction
+ styles
+ customFields
+ ;
+ variables = parseConvertCompact variables;
+ };
+
+ mkSwipeCard =
+ {
+ cardWidth ? null,
+ parameters ? null,
+ cards ? [ ],
+ }:
+ parseConvertCompact {
+ type = "custom:swipe-card";
+ inherit cardWidth parameters cards;
+ };
+
mkDeclutteringCard =
{
template,
variables ? null,
}:
- compact {
+ parseConvertCompact {
type = "custom:decluttering-card";
inherit template variables;
};
standardBadges = [
- (mkBadgeEntity {
+ (mkEntity {
entity = "input_boolean.vacation_mode";
color = "orange";
- tap_action.action = "toggle";
+ tapAction.action = "toggle";
visibility = [
(condState {
entity = "input_boolean.vacation_mode";
@@ -379,10 +905,10 @@ rec {
];
})
- (mkBadgeEntity {
+ (mkEntity {
entity = "input_boolean.notifications_james_phone";
color = "primary";
- tap_action.action = "toggle";
+ tapAction.action = "toggle";
visibility = [
(condState {
entity = "input_boolean.notifications_james_phone";
@@ -392,12 +918,12 @@ rec {
];
})
- (mkBadgeEntity {
+ (mkEntity {
entity = "input_boolean.notifications_savannah_phone";
color = "primary";
icon = "mdi:cellphone-message-off";
- show_entity_picture = false;
- tap_action.action = "toggle";
+ showEntityPicture = false;
+ tapAction.action = "toggle";
visibility = [
(condState {
entity = "input_boolean.notifications_savannah_phone";
@@ -407,13 +933,13 @@ rec {
];
})
- (mkBadgeEntity {
+ (mkEntity {
entity = "sensor.chores_on_count";
color = "yellow";
- show_state = true;
- tap_action = {
+ showState = true;
+ tapAction = {
action = "navigate";
- navigation_path = "home#chores";
+ navigationPath = "home#chores";
};
visibility = [
(condNumericState {
@@ -423,9 +949,9 @@ rec {
];
})
- (mkBadgeEntity {
+ (mkEntity {
entity = "sensor.k1c_276e_progress";
- show_state = true;
+ showState = true;
icon = "mdi:printer-3d";
color = "orange";
visibility = [
@@ -435,9 +961,9 @@ rec {
})
(condUser [ "3eea636aa3de4c7f9c662ad29c6e92e0" ])
];
- tap_action = {
+ tapAction = {
action = "navigate";
- navigation_path = "server#3d-printer";
+ navigationPath = "server#3d-printer";
};
})
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/templates/button-cards.nix b/hosts/server/nixcloud/home-assistant/dashboard/templates/button-cards.nix
deleted file mode 100644
index 6c4f69ade..000000000
--- a/hosts/server/nixcloud/home-assistant/dashboard/templates/button-cards.nix
+++ /dev/null
@@ -1,812 +0,0 @@
-{ lib, ... }:
-with (import ../lib.nix { inherit lib; });
-{
- imports = [
- ../components/button-cards/media_player.nix
- ];
-
- setup = {
- state = [
- {
- value = "unavailable";
- icon = "mdi:alert-circle-outline";
- styles = {
- state.text-decoration = "line-through";
- label.text-decoration = "line-through";
- };
- }
- ];
- styles = {
- name.font-family = null;
- label.font-family = null;
- state.font-family = null;
- };
- };
-
- nav_button_small = {
- template = "setup";
- variables = {
- navigation_path = "home";
- icon_on = entity.icon;
- icon_off = entity.icon;
- state_on = "on";
- state_off = "off";
- background_color_on = "var(--red)";
- background_color_off = "var(--green)";
- color_on = "var(--black)";
- color_off = "var(--black)";
- };
- type = "custom:button-card";
- inherit (entity) icon;
- show_label = true;
- tap_action = {
- action = "navigate";
- navigation_path = "[[[ return variables.navigation_path ]]]";
- haptic = "success";
- };
- hold_action.action = "more-info";
- state = [
- (style.iconState "on")
- (style.iconState "off")
- ];
- custom_fields = {
- icon2 = {
- card = {
- type = "custom:button-card";
- icon = "mdi:arrow-right-bold";
- styles = {
- card = {
- background-color = "var(--contrast1)";
- width = "27px";
- height = "27px";
- };
- icon = {
- width = "15px";
- color = "var(--contrast20)";
- };
- };
- };
- };
- };
- styles = {
- grid = {
- grid-template-areas = ''" l l icon2 " " i n n "'';
- grid-template-columns = "14px 1fr 1fr";
- grid-template-rows = "1fr min-content";
- };
- icon = {
- width = "14px";
- margin-bottom = "5px";
- color = colourOnOff "black" "contrast20";
- };
- img_cell.justify-content = "flex-start";
- name = {
- justify-self = "start";
- font-size = "11px";
- margin-bottom = "2px";
- margin-left = "3px";
- color = colourOnOff "black" "contrast20";
- opacity = 0.7;
- };
- card = {
- height = "75px";
- background-color = colourOnOff "yellow" "contrast2";
- box-shadow = "none";
- border-radius = "24px";
- padding = "12px 0 12px 14px";
- z-index = 1;
- };
- label = {
- justify-self = "start";
- font-size = "20px";
- margin-top = "11px";
- font-weight = 500;
- color = colourOnOff "black" "contrast20";
- };
- custom_fields = {
- icon2 = {
- margin-top = "-20px";
- justify-self = "end";
- align-self = "center";
- width = "24px";
- padding-right = "10px";
- color = colourOnOff "black" "contrast20";
- };
- };
- };
- };
-
- nav_button_state_small = {
- template = "setup";
- variables = {
- navigation_path = "home";
- icon_on = entity.icon;
- icon_off = entity.icon;
- state_on = "on";
- state_off = "off";
- background_color_on = "var(--red)";
- background_color_off = "var(--green)";
- color_on = "var(--black)";
- color_off = "var(--black)";
- };
- type = "custom:button-card";
- inherit (entity) icon;
- show_label = false;
- show_state = true;
-
- tap_action = {
- action = "navigate";
- navigation_path = "[[[ return variables.navigation_path ]]]";
- haptic = "success";
- };
- hold_action.action = "more-info";
-
- state = [
- (style.iconState "on")
- (style.iconState "off")
- ];
- custom_fields = {
- icon2 = {
- card = {
- type = "custom:button-card";
- icon = "mdi:arrow-right-bold";
- styles = {
- card = {
- background-color = "var(--contrast1)";
- width = "27px";
- height = "27px";
- };
- icon = {
- width = "15px";
- color = "var(--contrast20)";
- };
- };
- };
- };
- };
- styles = {
- grid = {
- grid-template-areas = ''" s s icon2 " " i n n "'';
- grid-template-columns = "14px 1fr 1fr";
- grid-template-rows = "1fr min-content";
- };
- icon = {
- width = "14px";
- margin-bottom = "5px";
- color = colourOnOff "black" "contrast20";
- };
- img_cell.justify-content = "flex-start";
- name = {
- justify-self = "start";
- font-size = "11px";
- margin-bottom = "2px";
- margin-left = "3px";
- color = colourOnOff "black" "contrast20";
- opacity = 0.7;
- };
- card = {
- height = "75px";
- background-color = colourOnOff "yellow" "contrast2";
- box-shadow = "none";
- border-radius = "24px";
- padding = "12px 0 12px 14px";
- z-index = 1;
- };
- state = {
- justify-self = "start";
- font-size = "20px";
- margin-top = "11px";
- font-weight = 500;
- color = colourOnOff "black" "contrast20";
- };
- custom_fields = {
- icon2 = {
- margin-top = "-20px";
- justify-self = "end";
- align-self = "center";
- width = "24px";
- padding-right = "10px";
- color = colourOnOff "black" "contrast20";
- };
- };
- };
- };
-
- custom_card_alarm_bottom = {
- template = "setup";
- entity = "sensor.wake_time_1";
- show_label = false;
- name = "[[[ return states[\"sensor.time\"].state ]]]";
- show_state = false;
- show_entity_picture = false;
- icon = "mdi:alarm";
- styles = {
- grid = {
- grid-template-areas = ''"i n snooze stop"'';
- grid-template-columns = "min-content min-content 1fr 1fr";
- grid-template-rows = "min-content";
- column-gap = 15;
- };
- icon = {
- width = "60px";
- color = "var(--red)";
- animation = "alarm 0.8s ease infinite";
- };
- card = {
- padding = "15px 15px 15px 15px";
- height = "180px";
- width = "100vw";
- overflow = "hidden";
- position = ''
- [[[
- if (states["input_boolean.debug_rounded"].state == "on") {
- return 'static'
- } else {
- return 'fixed'
- }
- ]]]
- '';
- margin = 0;
- bottom = 0;
- left = 0;
- z-index = 2;
- border-radius = "20px 20px 0px 0px";
- box-shadow = "rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px;";
- };
- img_cell = {
- background = "none";
- border-radius = "10px";
- width = "50px";
- height = "50px";
- justify-self = "start";
- align-self = "start";
- left = "10px";
- };
- name = {
- font-size = "28px";
- font-weight = 700;
- justify-self = "start";
- align-self = "center";
- padding-left = "10px";
- };
- custom_fields = {
- snooze.justify-self = "end";
- stop.justify-self = "end";
- };
- };
- extra_styles = ''
- @keyframes alarm {
- 0%, 80%, 100% { transform: translateY(0); }
- 10% { transform: translateY(-2px) rotate(-8deg); }
- 20% { transform: translateY(-2px) rotate(9deg); }
- 30% { transform: translateY(-2px) rotate(-5deg); }
- 40% { transform: translateY(-2px) rotate(4deg); }
- 50% { transform: translateY(0); }
- 60% { transform: translateY(-1.2px) }
- }
- '';
- custom_fields = {
- snooze = {
- card = {
- type = "custom:button-card";
- icon = "mdi:alarm-snooze";
- entity = "input_boolean.alarm_snoozed";
- name = "Snooze";
- show_name = true;
- show_icon = true;
- tap_action = {
- action = "toggle";
- };
- styles = {
- grid = {
- grid-template-areas = "\"i n\"";
- grid-template-columns = "min-content min-content";
- };
- card = {
- padding = "4px";
- background = "var(--orange)";
- border-radius = "24px";
- width = "90px";
- };
- img_cell = {
- justify-self = "start";
- width = "25px";
- height = "25px";
- background = "var(--contrast1)";
- border-radius = "100%";
- };
- name = {
- font-size = "12px";
- color = "var(--black)";
- font-weight = 500;
- justify-self = "start";
- align-self = "center";
- padding-left = "5px";
- };
- icon = {
- width = "14px";
- color = "var(--orange)";
- align-self = "center";
- };
- };
- };
- };
- stop = {
- card = {
- type = "custom:button-card";
- icon = "mdi:alarm-off";
- entity = "input_boolean.sound_alarm_is_running";
- name = "STOP";
- show_name = true;
- show_icon = true;
- tap_action.action = "toggle";
- styles = {
- grid = {
- grid-template-areas = ''"i n"'';
- grid-template-columns = "min-content min-content";
- };
- card = {
- padding = "4px";
- background = "var(--red)";
- border-radius = "24px";
- width = "90px";
- margin-right = "5px";
- };
- img_cell = {
- justify-self = "start";
- width = "25px";
- height = "25px";
- background = "var(--contrast1)";
- border-radius = "100%";
- };
- name = {
- font-size = "12px";
- color = "var(--black)";
- font-weight = 500;
- justify-self = "start";
- align-self = "center";
- padding-left = "5px";
- };
- icon = {
- width = "14px";
- color = "var(--red)";
- align-self = "center";
- };
- };
- };
- };
- };
- };
-
- custom_card_mediaplayer_bottom = {
- template = "setup";
- show_label = true;
- name = ''
- [[[
- if (states[ entity.entity_id ].attributes.active_child == "media_player.playstation_5") {
- return states["sensor.ps5_343_activity"].attributes.players
- } else if (states[ entity.entity_id ].attributes.active_child == "media_player.steam_jlnbln") {
- return 'jlnbln'
- } else if (states[ entity.entity_id ].attributes.active_child == "media_player.plex_bedroom") {
- return states[ entity.entity_id ].attributes.media_series_title
- } else {
- if (states[ entity.entity_id ].attributes.media_artist != null ) {
- return states[ entity.entity_id ].attributes.media_artist
- } else {
- return "Streaming"
- }
- }
- ]]]
- '';
- label = "[[[ return states[ entity.entity_id ].attributes.media_title ]]]";
- show_state = false;
- show_entity_picture = true;
- entity_picture = ''
- [[[
- if (states[ entity.entity_id ].attributes.active_child == "media_player.playstation_5") {
- return states["sensor.ps5_343_activity"].attributes.title_image
- } else if (states[ entity.entity_id ].attributes.active_child == "media_player.apple_tv_4k_2") {
- if (states[ entity.entity_id ].attributes.media_artist == "Rocket League Esports") {
- return '/local/images/rlesports.jpg';
- } else {
- return states["media_player.apple_tv_4k_2"].attributes.entity_picture
- }
- } else if (states[ entity.entity_id ].attributes.active_child == "media_player.steam_jlnbln") {
- return states["sensor.steam_76561197981585794"].attributes.game_image_main
- } else if (states[ entity.entity_id ].attributes.active_child == "media_player.plex_bedroom") {
- return states["media_player.plex_bedroom"].attributes.entity_picture
- } else if (states[ entity.entity_id ].attributes.active_child == "media_player.music") {
- if (states["media_player.music"].attributes.active_child == "media_player.bedroom_nest_music_assistant") {
- return states["media_player.bedroom_speakers"].attributes.entity_picture
- } else {
- return states[ entity.entity_id ].attributes.entity_picture
- }
- } else if (states[ entity.entity_id ].attributes.active_child == "media_player.audiobook") {
- if (states["input_select.audiobook"].state == "Harry Potter") {
- if (states["input_number.hp_book"].state == 1.0) {
- return "/local/images/HP_1.jpg";
- } else if (states["input_number.hp_book"].state == 2.0) {
- return "/local/images/HP_2.jpg";
- } else if (states["input_number.hp_book"].state == 3.0) {
- return "/local/images/HP_3.jpg";
- } else if (states["input_number.hp_book"].state == 4.0) {
- return "/local/images/HP_4.jpg";
- } else if (states["input_number.hp_book"].state == 5.0) {
- return "/local/images/HP_5.jpg";
- } else if (states["input_number.hp_book"].state == 6.0) {
- return "/local/images/HP_6.jpg";
- } else if (states["input_number.hp_book"].state == 7.0) {
- return "/local/images/HP_7.jpg";
- }
- }
- } else {
- return states[ entity.entity_id ].attributes.entity_picture
- }
- ]]]
- '';
- icon = ''
- [[[
- if (states[ entity.entity_id ].attributes.active_child == "media_player.playstation_5") {
- return 'mdi:sony-playstation'
- } else if (states[ entity.entity_id ].attributes.app_name == "YouTube") {
- return 'mdi:youtube-tv'
- } else if (states[ entity.entity_id ].attributes.app_name == "Netflix") {
- return 'mdi:netflix'
- } else if (states[ entity.entity_id ].attributes.app_name == "Plex") {
- return 'mdi:plex'
- } else {
- return 'mdi:television-guide'
- }
- ]]]
- '';
- styles = {
- grid = {
- grid-template-areas = ''"i play_state button" "i l button" "i n button" "bar bar bar"'';
- grid-template-columns = "min-content";
- grid-template-rows = "min-content min-content min-content";
- column-gap = 15;
- };
- icon = {
- width = 60;
- height = 60;
- color = "var(--red)";
- };
- card = {
- padding = "15px 15px 15px 15px";
- height = 180;
- width = "100vw";
- overflow = "hidden";
- position = ''
- [[[
- if (states["input_boolean.debug_rounded"].state == "on") {
- return 'static'
- } else {
- return 'fixed'
- }
- ]]]
- '';
- margin = 0;
- bottom = 0;
- left = 0;
- z-index = 2;
- border-radius = "20px 20px 0px 0px";
- box-shadow = "rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px";
- };
- img_cell = {
- background = "none";
- border-radius = 10;
- width = 60;
- height = 60;
- justify-self = "start";
- align-self = "start";
- left = 20;
- };
- entity_picture.object-fit = "cover";
- state = {
- font-size = 10;
- justify-self = "start";
- align-self = "start";
- color = "var(--contrast10)";
- padding-right = 25;
- };
- name = {
- font-size = 11;
- justify-self = "start";
- align-self = "start";
- padding-right = 20;
- animation = "my-animation 15s linear infinite";
- };
- label = {
- justify-self = "start";
- align-self = "end";
- font-weight = 700;
- padding-right = 20;
- font-size = 13;
- margin-top = "-5px";
- };
- custom_fields = {
- button = {
- justify-self = "end";
- align-self = "center";
- padding-right = 20;
- padding-top = 7;
- };
- play_state = {
- font-size = 10;
- justify-self = "start";
- align-self = "start";
- color = "var(--contrast14)";
- padding-right = 21;
- padding-top = 10;
- };
- progress = {
- background-color = "var(--contrast10)";
- position = "absolute";
- top = "unset";
- bottom = 90;
- left = 40;
- height = 2;
- width = "80%";
- };
- bar = {
- background-color = "var(--green)";
- position = "absolute";
- bottom = 90;
- left = 40;
- top = "unset";
- height = 2;
- z-index = 1;
- transition = "1s ease-out";
- };
- };
- };
- custom_fields = {
- button.card = {
- type = "custom:button-card";
- icon = "mdi:play";
- entity = "[[[ return entity.entity_id ]]]";
- show_name = false;
- tap_action = {
- action = "call-service";
- service = "media_player.media_play_pause";
- target.entity_id = "[[[ return entity.entity_id ]]]";
- };
- styles = {
- card = {
- overflow = "visible";
- background = "var(--contrast6)";
- "border-radius" = 10;
- };
- icon = {
- width = 17;
- color = "var(--contrast16)";
- };
- img_cell = {
- padding = 10;
- width = 20;
- };
- };
- state = [
- {
- value = "playing";
- icon = ''
- [[[
- if (states[entity.entity_id].attributes.active_child == "media_player.playstation_5") {
- return 'mdi:sony-playstation'
- } else if (states[entity.entity_id].attributes.active_child == "media_player.steam_jlnbln") {
- return 'mdi:steam'
- } else {
- return 'mdi:pause'
- }
- ]]]
- '';
- }
- ];
- };
- play_state = ''
- [[[
- if (states[entity.entity_id].attributes.active_child == "media_player.spotify_james") {
- return states[entity.entity_id].state + " - " + states[entity.entity_id].attributes.source
- } else if (states[entity.entity_id].attributes.active_child == "media_player.playstation_5") {
- return states[entity.entity_id].state + " - Playstation 5"
- } else if (states[entity.entity_id].attributes.active_child == "media_player.audiobook") {
- return states[entity.entity_id].state + " - Alarm Clock"
- } else if (states[entity.entity_id].attributes.active_child == "media_player.plex_bedroom") {
- return states[entity.entity_id].state + " - Bedroom TV"
- } else if (states[entity.entity_id].attributes.active_child == "media_player.steam_jlnbln") {
- return states[entity.entity_id].state + " - Steam"
- } else if (states[entity.entity_id].attributes.active_child == "media_player.apple_tv_4k_2") {
- return states[entity.entity_id].state + " - " + states[entity.entity_id].attributes.app_name
- } else {
- return states[entity.entity_id].state + " - " + states["sensor.music_room"].state
- }
- ]]]
- '';
- bar = ''
- [[[
- if (entity.attributes.media_position !== undefined) {
- setTimeout(() => {
- let elt = this.shadowRoot,
- card = elt.getElementById('card'),
- container = elt.getElementById('container'),
- bar = elt.getElementById('bar');
- if (elt && card && container && bar) {
- card.insertBefore(bar, container);
- function update() {
- let mediaPositionUpdatedAt = entity.attributes.media_position_updated_at,
- mediaPosition = entity.attributes.media_position,
- mediaDuration = entity.attributes.media_duration,
- mediaContentType = entity.attributes.media_content_type;
- let percentage = entity.state === 'paused'
- ? (mediaPosition / mediaDuration * 80)
- : entity.state === 'playing'
- ? (((Date.now() / 1000) - (new Date(mediaPositionUpdatedAt).getTime() / 1000) + mediaPosition) / mediaDuration * 80)
- : 0;
- bar.style.width = percentage.toFixed(1) + '%';
- requestAnimationFrame(update);
- }
- requestAnimationFrame(update);
- }
- }, 0);
- return ' ';}
- ]]]
- '';
- };
- };
-
- custom_card_timer_bottom = {
- template = "setup";
- type = "custom:button-card";
- entity = ''
- [[[
- if (states["sensor.kitchen_timer"].state == "on") {
- return 'sensor.kitchen_timer'
- } else if (states["sensor.living_room_timer"].state == "on") {
- return 'sensor.living_room_timer'
- } else if (states["sensor.office_timer"].state == "on") {
- return 'sensor.office_timer'
- } else if (states["sensor.bed_room_timer"].state == "on") {
- return 'sensor.bed_room_timer'
- } else if (states["sensor.bathroom_timer"].state == "on") {
- return 'sensor.bathroom_timer'
- } else {
- return 'sensor.kitchen_timer'
- }
- ]]]
- '';
- show_name = true;
- show_icon = false;
- show_label = false;
- show_state = false;
- tap_action.action = "more-info";
- hold_action.action = "more-info";
- custom_fields = {
- bar = ''
- [[[
- var color = "var(--green)";
- var state = 100 - states[ entity.entity_id ].attributes.remaining_perc;
- if (state < 10) color = "var(--red)";
- else if (state < 50) color = "var(--yellow)";
- else if (state < 90) color = "var(--orange)";
- return `
- `
- ]]]
- '';
- rem = {
- card = {
- type = "custom:button-card";
- entity = entity.id;
- name = template.remainingTime;
- show_icon = false;
- styles = {
- card = {
- width = "min";
- background = "none";
- overflow = "visible";
- };
- name = {
- font-size = 14;
- margin-top = 6;
- font-weight = 600;
- color = "var(--contrast20)";
- };
- };
- };
- };
- icon1 = ''[[[ return ''; ]]]'';
- };
- styles = {
- grid = {
- grid-template-areas = ''"rem icon1" "n icon2" "bar bar"'';
- grid-template-rows = "min-content min-content min-content";
- grid-template-columns = "60% 40%";
- };
- card = {
- padding = "15px 35px 15px 35px";
- background = "var(--contrast2)";
- height = template.stateIfElse {
- entity = "media_player.music_and_tv";
- states = [
- "playing"
- "paused"
- ];
- ifTrue = "280px";
- ifFalse = "180px";
- };
- width = "100vw";
- overflow = "hidden";
- position = template.stateOnIfElse {
- entity = "input_boolean.debug_rounded";
- valueOn = "static";
- valueOff = "fixed";
- };
- margin = 0;
- bottom = 0;
- left = 0;
- z-index = 1;
- border-radius = "20px 20px 0px 0px";
- box-shadow = "rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px";
- };
- img_cell = {
- position = "absolute";
- top = "20%";
- left = "40%";
- overflow = "visible";
- };
- label = {
- text-align = "left";
- font-size = 18;
- font-weight = 500;
- justify-self = "start";
- align-self = "start";
- overflow = "visible";
- color = "var(--contrast20)";
- };
- name = {
- text-align = "left";
- font-size = 12;
- justify-self = "start";
- align-self = "start";
- overflow = "visible";
- color = "var(--contrast20)";
- };
- custom_fields = {
- bar = {
- justify-self = "start";
- align-self = "start";
- margin-top = 10;
- width = "100%";
- height = template.stateOnIfElse {
- entity = "entity.state";
- valueOn = "12px";
- valueOff = "0px";
- };
- background = "var(--contrast1)";
- border-radius = 24;
- };
- rem = {
- justify-self = "start";
- font-size = 14;
- font-weight = 600;
- align-self = "end";
- height = template.stateOnIfElse {
- entity = "entity.state";
- valueOn = "27px";
- valueOff = "0px";
- };
- };
- icon1 = {
- justify-self = "end";
- align-self = "start";
- width = 24;
- color = "var(--contrast20)";
- };
- };
-
- hold_action.action = "more-info";
- };
- };
-}
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/views/energy.nix b/hosts/server/nixcloud/home-assistant/dashboard/views/energy.nix
index e7e2cbcc7..3b2cfd575 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard/views/energy.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard/views/energy.nix
@@ -6,7 +6,7 @@ let
'';
};
- navbar = import ../components/navbar.nix { };
+ navbar = import ../components/navbar.nix { inherit lib; };
in
with (import ../lib.nix { inherit lib; });
mkView {
@@ -51,8 +51,11 @@ mkView {
icon = "mdi:flash";
})
{
- type = "markdown";
- content = "Add power graphs (e.g., history-graph/statistics-graph) for your power sensors here.";
+ type = "history-graph";
+ hours_to_show = 24;
+ entities = [
+ { entity = "sensor.power_consumption"; }
+ ];
}
];
})
@@ -64,12 +67,10 @@ mkView {
icon = "mdi:gauge";
})
{
- type = "markdown";
- content = "Add your energy-related sensors (e.g., daily energy, grid import/export) to an Entities card here.";
+ type = "energy-date-selection";
}
{
- type = "entities";
- entities = [ ];
+ type = "energy-solar-consumed-gauge";
}
];
})
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/views/home.nix b/hosts/server/nixcloud/home-assistant/dashboard/views/home.nix
index 3bda15c4b..4818f7fc6 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard/views/home.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard/views/home.nix
@@ -1,8 +1,8 @@
{ lib }:
let
viewHeader = import ../components/view-header.nix { };
- navbar = import ../components/navbar.nix { };
- adminPanel = import ../components/admin-panel.nix;
+ navbar = import ../components/navbar.nix { inherit lib; };
+ adminPanel = import ../components/admin-panel.nix { inherit lib; };
in
with (import ../lib.nix { inherit lib; });
mkView {
@@ -24,5 +24,414 @@ mkView {
rows = "auto";
};
})
+
+ # (mkGridSection)
+
+ # Popups
+ (mkGridSection {
+ cards = [
+ (mkVerticalStack {
+ cards = [
+ (mkBubbleCard {
+ name = "Bedroom";
+ icon = "mdi:bed-king-outline";
+ cardType = "pop-up";
+ hash = "#bedroom";
+ })
+
+ (mkMushroomChipsCard {
+ chips = [
+ (mkEntity {
+ entity = "binary_sensor.bedroom_presence_sensor";
+ contentInfo = "last-changed";
+ icon = "mdi:motion-sensor";
+ iconColor = "yellow";
+ })
+ (mkEntity {
+ entity = "binary_sensor.bedroom_window_sensor";
+ icon = "mdi:window-closed-variant";
+ iconColor = "red";
+ })
+ (mkEntity {
+ entity = "binary_sensor.bedroom_door_sensor";
+ icon = "mdi:door-closed";
+ iconColor = "blue";
+ })
+ ];
+ })
+
+ (mkHeading {
+ heading = "Climate";
+ heading_style = "title";
+ icon = "mdi:thermostat";
+ badges = [
+ (mkEntity {
+ entity = "climate.bedroom_ac";
+ stateContent = "current_temperature";
+ color = "red";
+ showIcon = true;
+ showState = true;
+ tapAction = mkAction { action = "toggle"; };
+ })
+ (mkEntity {
+ entity = "climate.bedroom_ac";
+ stateContent = "current_humidity";
+ color = "blue";
+ showIcon = true;
+ showState = true;
+ tapAction = mkAction { action = "toggle"; };
+ })
+ ];
+ })
+ (mkBubbleCard {
+ cardType = "climate";
+ entity = "climate.bedroom_ac";
+ showState = true;
+ subButton = [
+ {
+ name = "HVAC modes menu";
+ select_attribute = "hvac_modes";
+ state_background = false;
+ show_arrow = false;
+ }
+ ];
+ })
+
+ (mkHeading {
+ heading = "Devices";
+ heading_style = "title";
+ icon = "mdi:devices";
+ badges = [
+ (mkEntity {
+ entity = "input_boolean.bedroom_occupancy";
+ icon = "mdi:light-switch";
+ color = "red";
+ showState = false;
+ showIcon = true;
+ tapAction = mkAction { action = "toggle"; };
+ })
+ (mkEntity {
+ entity = "input_boolean.bedroom_night_mode";
+ showState = false;
+ showIcon = true;
+ color = "blue";
+ tapAction = mkAction { action = "toggle"; };
+ })
+ ];
+ })
+
+ (mkGridSection {
+ columns = 3;
+ cards = [
+ (mkButtonCard {
+ entity = "binary_sensor.is_alarm_on";
+ name = "Alarm";
+ label = template.getState "sensor.wake_time_1";
+ template = "nav_button_small";
+ variables = {
+ navigationPath = "home#alarm";
+ iconOn = "mdi:alarm";
+ iconOff = "mdi:alarm-off";
+ backgroundColorOn = jsColours.yellow;
+ backgroundColorOff = jsColours.contrast2;
+ colorOn = jsColours.black;
+ colorOff = jsColours.contrast20;
+ };
+ })
+ (mkConditional {
+ conditions = [
+ (condState {
+ entity = "alarm_control_panel.security_system";
+ state = "armed_night";
+ })
+ ];
+ card = mkButtonCard {
+ entity = "input_boolean.waking_up";
+ name = "Waking Up";
+ label = template.getState "input_boolean.waking_up";
+ showLabel = true;
+ tapAction = mkAction {
+ action = "toggle";
+ haptic = "success";
+ };
+ template = "button_template";
+ variables = {
+ icon2 = "mdi:weather-sunset-up";
+ backgroundColorOff = jsColours.contrast2;
+ backgroundColorOn = jsColours.orange;
+ colorOn = jsColours.black;
+ colorOff = jsColours.contrast20;
+ };
+ };
+ })
+ (mkConditional {
+ conditions = [
+ (condState {
+ entity = "alarm_control_panel.security_system";
+ state = "armed_night";
+ })
+ ];
+ card = mkButtonCard {
+ entity = "input_boolean.going_to_bed";
+ name = "Waking Up";
+ label = template.getState "input_boolean.going_to_bed";
+ showLabel = true;
+ tapAction = mkAction {
+ action = "toggle";
+ haptic = "success";
+ };
+ template = "button_template";
+ variables = {
+ icon2 = "mdi:bed";
+ backgroundColorOff = jsColours.contrast2;
+ backgroundColorOn = jsColours.blue;
+ colorOn = jsColours.black;
+ colorOff = jsColours.contrast20;
+ };
+ };
+ })
+ ];
+ })
+
+ (mkGridSection {
+ columns = 1;
+ cards = [
+ (mkHeading {
+ heading = "Lights";
+ heading_style = "subtitle";
+ icon = "mdi:lightbulb-group";
+ badges = [
+ (mkEntity {
+ entity = "light.bedroom_lights";
+ showIcon = true;
+ showState = false;
+ color = "yellow";
+ tapAction = mkAction { action = "toggle"; };
+ })
+ (mkEntity {
+ entity = "switch.adaptive_lighting_bedroom";
+ showIcon = true;
+ showState = false;
+ color = "orange";
+ tapAction = mkAction { action = "toggle"; };
+ })
+ (mkEntity {
+ entity = "switch.adaptive_lighting_adapt_color_bedroom";
+ showIcon = true;
+ showState = false;
+ color = "pink";
+ tapAction = mkAction { action = "toggle"; };
+ })
+ (mkEntity {
+ entity = "switch.adaptive_lighting_adapt_brightness_bedroom";
+ showIcon = true;
+ showState = false;
+ color = "yellow";
+ tapAction = mkAction { action = "toggle"; };
+ })
+ (mkEntity {
+ entity = "switch.adaptive_lighting_sleep_mode_bedroom";
+ showIcon = true;
+ showState = false;
+ color = "blue";
+ tapAction = mkAction { action = "toggle"; };
+ })
+ ];
+ })
+
+ (mkGridSection {
+ columns = 1;
+ cards = [
+ (mkButtonCard {
+ name = "Top Light";
+ entity = "lighting.bedroom_light";
+ template = "light_rgb";
+ })
+ ];
+ })
+ ];
+ })
+
+ (mkHeading {
+ heading = "Media";
+ heading_style = "title";
+ icon = "mdi:music";
+ })
+ (mkSwipeCard {
+ cardWidth = "calc(100% - 48px)";
+ parameters = {
+ centeredSlides = true;
+ slidesPerView = "auto";
+ spaceBetween = 16;
+ initialSlide = 0;
+ };
+ cards = [
+ (mkConditional {
+ conditions = [
+ (condState {
+ entity = "media_player.bedroom_speakers";
+ state_not = "standby";
+ })
+ (condState {
+ entity = "media_player.bedroom_speakers";
+ state_not = "off";
+ })
+ ];
+ card = mkButtonCard {
+ entity = "media_player.bedroom_speakers";
+ template = "custom_card_mediaplayer_music";
+ };
+ })
+
+ (mkConditional {
+ conditions = [
+ (condState {
+ entity = "sensor.bedroom_next_timer";
+ state_not = "unknown";
+ })
+ (condState {
+ entity = "sensor.bedroom_next_timer";
+ state_not = "unavailable";
+ })
+ ];
+ card = mkButtonCard {
+ entity = "sensor.bedroom_next_timer";
+ name = "Bedroom Timer";
+ icon = "mdi:timer-outline";
+ showName = true;
+ showIcon = true;
+ showLabel = false;
+ showState = false;
+ tapAction = mkAction {
+ action = "more-info";
+ };
+ holdAction = mkAction {
+ action = "navigate";
+ };
+ customFields = {
+ bar = ''
+ [[[
+ var color = "var(--green)";
+ var state = 100 - states['sensor.bed_room_timer'].attributes.remaining_perc;
+ if (state < 10) color = "var(--red)";
+ else if (state < 50) color = "var(--yellow)";
+ else if (state < 90) color = "var(--orange)";
+ return `
+ `
+ ]]]
+ '';
+ rem.card = mkConditional {
+ conditions = [
+ (condState {
+ entity = "sensor.bedroom_next_timer";
+ state_not = "unknown";
+ })
+ ];
+ card = mkButtonCard {
+ entity = "sensor.bedroom_next_timer";
+ name = template.remainingTime;
+ showIcon = false;
+ styles = mkStyles {
+ card = {
+ width = "min";
+ background = "none";
+ overflow = "visible";
+ };
+ name = {
+ font-size = "14px";
+ margin-top = "6px";
+ font-weight = 600;
+ color = jsColours.contrast20;
+ };
+ };
+ };
+ };
+ icon1 = template.returnIcon "mdi:bed-king-outline";
+ };
+ styles = mkStyles {
+ grid = {
+ grid-template-areas = "\"rem icon1\" \"n icon2\" \"bar bar\"";
+ grid-template-rows = "24px 1fr 24px min-content min-content min-content";
+ grid-template-columns = "60% 40%";
+ };
+ card = {
+ height = "100%";
+ padding = "1rem";
+ background = jsColours.contrast2;
+ };
+ img_cell = {
+ position = "absolute";
+ top = "20%";
+ left = "40%";
+ overflow = "visible";
+ };
+ icon = {
+ position = "absolute";
+ width = "20em";
+ opacity = "20%";
+ color = jsColours.contrast20;
+ transform = "rotate(-20deg)";
+ };
+ label = {
+ text-align = "left";
+ font-size = "18px";
+ font-weight = 500;
+ justify-self = "start";
+ align-self = "end";
+ overflow = "visible";
+ color = jsColours.contrast20;
+ };
+ name = {
+ text-align = "left";
+ font-size = "12px";
+ justify-self = "start";
+ align-self = "center";
+ overflow = "visible";
+ color = jsColours.contrast20;
+ };
+ custom_fields = {
+ bar = {
+ justify-self = "start";
+ width = "100%";
+ height = template.stateIfElse {
+ entity = "entity.state";
+ states = [ "unknown" ];
+ ifTrue = "0px";
+ ifFalse = "12px";
+ };
+ };
+ rem = {
+ justify-self = "start";
+ font-size = "14px";
+ font-weight = 600;
+ align-self = "end";
+ height = template.stateIfElse {
+ entity = "entity.state";
+ states = [ "unknown" ];
+ ifTrue = "0px";
+ ifFalse = "27px";
+ };
+ };
+ };
+ icon1 = {
+ justify-self = "end";
+ width = "24px";
+ color = jsColours.contrast20;
+ };
+ };
+ };
+ })
+ ];
+ })
+ ];
+ })
+ ];
+ grid_options = {
+ column_span = 3;
+ };
+ })
];
}
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/views/music.nix b/hosts/server/nixcloud/home-assistant/dashboard/views/music.nix
index 90944be5c..308d87a5e 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard/views/music.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard/views/music.nix
@@ -1,9 +1,10 @@
{ lib, ... }:
let
viewHeader = import ../components/view-header.nix { };
- navbar = import ../components/navbar.nix { };
+ navbar = import ../components/navbar.nix { inherit lib; };
+ dashLib = import ../lib.nix { inherit lib; };
in
-with (import ../lib.nix { inherit lib; });
+with dashLib;
mkView {
title = "Music";
header = viewHeader;
@@ -47,285 +48,40 @@ mkView {
};
};
custom_fields = {
- item1.card = {
- type = "custom:button-card";
- entity = "media_player.bed_room";
+ item1.card = mkMusicRoomButton {
+ room = "bed_room";
icon = "mdi:bed-king-outline";
- show_label = false;
- show_state = true;
- show_entity_picture = true;
- styles = {
- card = [
- { border-radius = "0px"; }
- { box-shadow = "none"; }
- { padding-right = "5px"; }
- { background = "none"; }
- ];
- grid = [
- { grid-template-areas = ''" 'i n' 'i s' "''; }
- { grid-template-columns = "min-content"; }
- { column-gap = "10px"; }
- ];
- entity_picture = [ { border-radius = "100%"; } ];
- icon = [ { width = 20; } ];
- img_cell = [ { width = 20; } ];
- name = [
- { justify-self = "start"; }
- { font-size = 10; }
- { font-weight = 500; }
- { color = "var(--contrast14)"; }
- ];
- state = [
- { justify-self = "start"; }
- { font-size = 15; }
- { font-weight = 700; }
- ];
- };
- tap_action.action = "more-info";
- double_tap_action = {
- action = "perform-action";
- perform_action = "script.play_on_repeat_on_alarm_clock";
- };
+ script = "script.play_on_repeat_on_alarm_clock";
};
- item2.card = {
- type = "custom:button-card";
- entity = "media_player.office";
+ item2.card = mkMusicRoomButton {
+ room = "office";
icon = "mdi:monitor";
- show_label = false;
- show_state = true;
- show_entity_picture = true;
- styles = {
- card = [
- { border-radius = "0px"; }
- { box-shadow = "none"; }
- { padding-right = "5px"; }
- { background = "none"; }
- ];
- grid = [
- { grid-template-areas = ''" 'i n' 'i s' "''; }
- { grid-template-columns = "min-content"; }
- { column-gap = "10px"; }
- ];
- entity_picture = [ { border-radius = "100%"; } ];
- icon = [ { width = 20; } ];
- img_cell = [ { width = 20; } ];
- name = [
- { justify-self = "start"; }
- { font-size = 10; }
- { font-weight = 500; }
- { color = "var(--contrast14)"; }
- ];
- state = [
- { justify-self = "start"; }
- { font-size = 15; }
- { font-weight = 700; }
- ];
- };
- tap_action.action = "more-info";
- double_tap_action = {
- action = "perform-action";
- perform_action = "script.play_on_repeat_on_office_nest";
- };
+ script = "script.play_on_repeat_on_office_nest";
};
- item3.card = {
- type = "custom:button-card";
- entity = "media_player.kitchen";
+ item3.card = mkMusicRoomButton {
+ room = "kitchen";
icon = "mdi:silverware-variant";
- show_label = false;
- show_state = true;
- show_entity_picture = true;
- styles = {
- card = [
- { border-radius = "0px"; }
- { box-shadow = "none"; }
- { padding-right = "5px"; }
- { background = "none"; }
- ];
- grid = [
- { grid-template-areas = ''" 'i n' 'i s' "''; }
- { grid-template-columns = "min-content"; }
- { column-gap = "10px"; }
- ];
- entity_picture = [ { border-radius = "100%"; } ];
- icon = [ { width = 20; } ];
- img_cell = [ { width = 20; } ];
- name = [
- { justify-self = "start"; }
- { font-size = 10; }
- { font-weight = 500; }
- { color = "var(--contrast14)"; }
- ];
- state = [
- { justify-self = "start"; }
- { font-size = 15; }
- { font-weight = 700; }
- ];
- };
- tap_action.action = "more-info";
- double_tap_action = {
- action = "perform-action";
- perform_action = "script.play_on_repeat_on_kitchen_nest";
- };
+ script = "script.play_on_repeat_on_kitchen_nest";
};
- item4.card = {
- type = "custom:button-card";
- entity = "media_player.bathroom";
+ item4.card = mkMusicRoomButton {
+ room = "bathroom";
icon = "mdi:paper-roll-outline";
- show_label = false;
- show_state = true;
- show_entity_picture = true;
- styles = {
- card = [
- { border-radius = "0px"; }
- { box-shadow = "none"; }
- { padding-right = "5px"; }
- { background = "none"; }
- ];
- grid = [
- { grid-template-areas = ''" 'i n' 'i s' "''; }
- { grid-template-columns = "min-content"; }
- { column-gap = "10px"; }
- ];
- entity_picture = [ { border-radius = "100%"; } ];
- icon = [ { width = 20; } ];
- img_cell = [ { width = 20; } ];
- name = [
- { justify-self = "start"; }
- { font-size = 10; }
- { font-weight = 500; }
- { color = "var(--contrast14)"; }
- ];
- state = [
- { justify-self = "start"; }
- { font-size = 15; }
- { font-weight = 700; }
- ];
- };
- tap_action.action = "more-info";
- double_tap_action = {
- action = "perform-action";
- perform_action = "script.play_on_repeat_on_bathroom_nest";
- };
+ script = "script.play_on_repeat_on_bathroom_nest";
};
- item5.card = {
- type = "custom:button-card";
- entity = "media_player.spare_room";
+ item5.card = mkMusicRoomButton {
+ room = "spare_room";
icon = "mdi:desk";
- show_label = false;
- show_state = true;
- show_entity_picture = true;
- styles = {
- card = [
- { border-radius = "0px"; }
- { box-shadow = "none"; }
- { padding-right = "5px"; }
- { background = "none"; }
- ];
- grid = [
- { grid-template-areas = ''" 'i n' 'i s' "''; }
- { grid-template-columns = "min-content"; }
- { column-gap = "10px"; }
- ];
- entity_picture = [ { border-radius = "100%"; } ];
- icon = [ { width = 20; } ];
- img_cell = [ { width = 20; } ];
- name = [
- { justify-self = "start"; }
- { font-size = 10; }
- { font-weight = 500; }
- { color = "var(--contrast14)"; }
- ];
- state = [
- { justify-self = "start"; }
- { font-size = 15; }
- { font-weight = 700; }
- ];
- };
- tap_action.action = "more-info";
- double_tap_action = {
- action = "perform-action";
- perform_action = "script.play_on_repeat_on_guest_room_nest";
- };
+ script = "script.play_on_repeat_on_guest_room_nest";
};
- item6.card = {
- type = "custom:button-card";
- entity = "media_player.everywhere";
+ item6.card = mkMusicRoomButton {
+ room = "everywhere";
icon = "mdi:home-outline";
- show_label = false;
- show_state = true;
- show_entity_picture = true;
- styles = {
- card = [
- { border-radius = "0px"; }
- { box-shadow = "none"; }
- { padding-right = "5px"; }
- { background = "none"; }
- ];
- grid = [
- { grid-template-areas = ''" 'i n' 'i s' "''; }
- { grid-template-columns = "min-content"; }
- { column-gap = "10px"; }
- ];
- entity_picture = [ { border-radius = "100%"; } ];
- icon = [ { width = 20; } ];
- img_cell = [ { width = 20; } ];
- name = [
- { justify-self = "start"; }
- { font-size = 10; }
- { font-weight = 500; }
- { color = "var(--contrast14)"; }
- ];
- state = [
- { justify-self = "start"; }
- { font-size = 15; }
- { font-weight = 700; }
- ];
- };
- tap_action.action = "more-info";
- double_tap_action = {
- action = "perform-action";
- perform_action = "script.play_on_repeat_on_nest_party";
- };
+ script = "script.play_on_repeat_on_nest_party";
};
- item7.card = {
- type = "custom:button-card";
- entity = "media_player.living_room";
+ item7.card = mkMusicRoomButton {
+ room = "living_room";
icon = "mdi:sofa-outline";
- show_label = false;
- show_state = true;
- show_entity_picture = true;
- styles = {
- card = [
- { border-radius = "0px"; }
- { box-shadow = "none"; }
- { padding-right = "5px"; }
- { background = "none"; }
- ];
- grid = [
- { grid-template-areas = ''" 'i n' 'i s' "''; }
- { grid-template-columns = "min-content"; }
- { column-gap = "10px"; }
- ];
- entity_picture = [ { border-radius = "100%"; } ];
- icon = [ { width = 20; } ];
- img_cell = [ { width = 20; } ];
- name = [
- { justify-self = "start"; }
- { font-size = 10; }
- { font-weight = 500; }
- { color = "var(--contrast14)"; }
- ];
- state = [
- { justify-self = "start"; }
- { font-size = 15; }
- { font-weight = 700; }
- ];
- };
- tap_action.action = "more-info";
- double_tap_action = {
- action = "perform-action";
- perform_action = "script.play_on_repeat_on_living_room_nest";
- };
+ script = "script.play_on_repeat_on_living_room_nest";
};
};
}
@@ -359,12 +115,12 @@ mkView {
}
{
condition = "state";
- entity = "media_player.music";
+ entity = mediaPlayers.special.music;
state_not = "off";
}
{
condition = "state";
- entity = "media_player.music";
+ entity = mediaPlayers.special.music;
state_not = "standby";
}
condition.mobileOnly
@@ -374,273 +130,51 @@ mkView {
cards = [
{
square = true;
- columns = 7;
+ columns = 6;
type = "grid";
cards = [
- {
- type = "custom:button-card";
- entity = "media_player.everywhere";
+ (mkMusicRoomTransferButton {
+ room = "everywhere";
icon = "mdi:home-outline";
- show_name = false;
- aspect_ratio = "1/1";
- state = [
- {
- value = "playing";
- icon = "mdi:play-circle-outline";
- }
- {
- value = "paused";
- icon = "mdi:pause-circle-outline";
- }
- ];
- tap_action = {
- action = "call-service";
- service = "music_assistant.transfer_queue";
- data.source_player = ''
- [[[
- if (states['sensor.music_room'].state == "Living Room") return "media_player.living_room";
- else if (states['sensor.music_room'].state == "Kitchen") return "media_player.kitchen";
- else if (states['sensor.music_room'].state == "Bathroom") return "media_player.bathroom_2";
- else if (states['sensor.music_room'].state == "Bedroom") return "media_player.bedroom_speaker";
- else if (states['sensor.music_room'].state == "Spare Room") return "media_player.spare_room";
- else if (states['sensor.music_room'].state == "Everywhere") return "media_player.everywhere";
- else return "";
- ]]]
- '';
- target.entity_id = "media_player.everywhere";
- haptic = "success";
- };
- double_tap_action = {
- action = "call-service";
- service = "script.play_on_repeat_on_nest_party";
- haptic = "success";
- };
- styles.card = [
- { border-radius = "12px"; }
- { background-color = "var(--teal)"; }
- ];
- styles.icon = [ { color = "var(--black)"; } ];
- }
- {
- type = "custom:button-card";
- entity = "media_player.living_room";
+ color = "teal";
+ targetEntity = mediaPlayers.rooms.everywhere;
+ script = "script.play_on_repeat_on_nest_party";
+ })
+ (mkMusicRoomTransferButton {
+ room = "living_room";
icon = "mdi:sofa-outline";
- show_name = false;
- aspect_ratio = "1/1";
- state = [
- {
- value = "playing";
- icon = "mdi:play-circle-outline";
- }
- {
- value = "paused";
- icon = "mdi:pause-circle-outline";
- }
- ];
- tap_action = {
- action = "call-service";
- service = "music_assistant.transfer_queue";
- data.source_player = ''
- [[[
- if (states['sensor.music_room'].state == "Living Room") return "media_player.living_room";
- else if (states['sensor.music_room'].state == "Kitchen") return "media_player.kitchen";
- else if (states['sensor.music_room'].state == "Bathroom") return "media_player.bathroom_2";
- else if (states['sensor.music_room'].state == "Bedroom") return "media_player.bedroom_speaker";
- else if (states['sensor.music_room'].state == "Spare Room") return "media_player.spare_room";
- else if (states['sensor.music_room'].state == "Everywhere") return "media_player.everywhere";
- else return "";
- ]]]
- '';
- target.entity_id = "media_player.living_room_nest_2";
- haptic = "success";
- };
- double_tap_action = {
- action = "call-service";
- service = "script.play_on_repeat_on_living_room_nest";
- haptic = "success";
- };
- styles.card = [
- { border-radius = "12px"; }
- { background-color = "var(--green)"; }
- ];
- styles.icon = [ { color = "var(--black)"; } ];
- }
- {
- type = "custom:button-card";
- entity = "media_player.bed_room";
+ color = "green";
+ targetEntity = mediaPlayers.rooms.living_room;
+ script = "script.play_on_repeat_on_living_room_nest";
+ })
+ (mkMusicRoomTransferButton {
+ room = "bed_room";
icon = "mdi:bed";
- show_name = false;
- aspect_ratio = "1/1";
- state = [
- {
- value = "playing";
- icon = "mdi:play-circle-outline";
- }
- {
- value = "paused";
- icon = "mdi:pause-circle-outline";
- }
- ];
- tap_action = {
- action = "call-service";
- service = "music_assistant.transfer_queue";
- data.source_player = ''
- [[[
- if (states['sensor.music_room'].state == "Living Room") return "media_player.living_room";
- else if (states['sensor.music_room'].state == "Kitchen") return "media_player.kitchen";
- else if (states['sensor.music_room'].state == "Bathroom") return "media_player.bathroom_2";
- else if (states['sensor.music_room'].state == "Bedroom") return "media_player.bedroom_speaker";
- else if (states['sensor.music_room'].state == "Spare Room") return "media_player.spare_room";
- else if (states['sensor.music_room'].state == "Everywhere") return "media_player.everywhere";
- else return "";
- ]]]
- '';
- target.entity_id = "media_player.bedroom_speakers";
- haptic = "success";
- };
- double_tap_action = {
- action = "call-service";
- service = "script.play_on_repeat_on_alarm_clock";
- haptic = "success";
- };
- styles.card = [
- { border-radius = "12px"; }
- { background-color = "var(--blue)"; }
- ];
- styles.icon = [ { color = "var(--black)"; } ];
- }
- {
- type = "custom:button-card";
- entity = "media_player.kitchen";
+ color = "blue";
+ targetEntity = mediaPlayers.rooms.bedroom_speakers;
+ script = "script.play_on_repeat_on_alarm_clock";
+ })
+ (mkMusicRoomTransferButton {
+ room = "kitchen";
icon = "mdi:silverware-variant";
- show_name = false;
- aspect_ratio = "1/1";
- state = [
- {
- value = "playing";
- icon = "mdi:play-circle-outline";
- }
- {
- value = "paused";
- icon = "mdi:pause-circle-outline";
- }
- ];
- tap_action = {
- action = "call-service";
- service = "music_assistant.transfer_queue";
- data.source_player = ''
- [[[
- if (states['sensor.music_room'].state == "Living Room") return "media_player.living_room";
- else if (states['sensor.music_room'].state == "Kitchen") return "media_player.kitchen";
- else if (states['sensor.music_room'].state == "Bathroom") return "media_player.bathroom_2";
- else if (states['sensor.music_room'].state == "Bedroom") return "media_player.bedroom_speaker";
- else if (states['sensor.music_room'].state == "Spare Room") return "media_player.spare_room";
- else if (states['sensor.music_room'].state == "Everywhere") return "media_player.everywhere";
- else return "";
- ]]]
- '';
- target.entity_id = "media_player.kitchen";
- haptic = "success";
- };
- double_tap_action = {
- action = "call-service";
- service = "script.play_on_repeat_on_kitchen_nest";
- haptic = "success";
- };
- styles.card = [
- { border-radius = "12px"; }
- { background-color = "var(--yellow)"; }
- ];
- styles.icon = [ { color = "var(--black)"; } ];
- }
- {
- type = "custom:button-card";
- entity = "media_player.bathroom";
+ color = "yellow";
+ targetEntity = mediaPlayers.rooms.kitchen;
+ script = "script.play_on_repeat_on_kitchen_nest";
+ })
+ (mkMusicRoomTransferButton {
+ room = "bathroom";
icon = "mdi:paper-roll-outline";
- show_name = false;
- aspect_ratio = "1/1";
- state = [
- {
- value = "playing";
- icon = "mdi:play-circle-outline";
- }
- {
- value = "paused";
- icon = "mdi:pause-circle-outline";
- }
- ];
- tap_action = {
- action = "call-service";
- service = "music_assistant.transfer_queue";
- data.source_player = ''
- [[[
- if (states['sensor.music_room'].state == "Living Room") return "media_player.living_room";
- else if (states['sensor.music_room'].state == "Kitchen") return "media_player.kitchen";
- else if (states['sensor.music_room'].state == "Bathroom") return "media_player.bathroom_2";
- else if (states['sensor.music_room'].state == "Bedroom") return "media_player.bedroom_speaker";
- else if (states['sensor.music_room'].state == "Spare Room") return "media_player.spare_room";
- else if (states['sensor.music_room'].state == "Everywhere") return "media_player.everywhere";
- else return "";
- ]]]
- '';
- target.entity_id = "media_player.bathroom";
- haptic = "success";
- };
- double_tap_action = {
- action = "call-service";
- service = "script.play_on_repeat_on_bathroom_nest";
- haptic = "success";
- };
- styles.card = [
- { border-radius = "12px"; }
- { background-color = "var(--purple)"; }
- ];
- styles.icon = [ { color = "var(--black)"; } ];
- }
- {
- type = "custom:button-card";
- entity = "media_player.spare_room";
+ color = "purple";
+ targetEntity = mediaPlayers.rooms.bathroom;
+ script = "script.play_on_repeat_on_bathroom_nest";
+ })
+ (mkMusicRoomTransferButton {
+ room = "spare_room";
icon = "mdi:desk";
- show_name = false;
- aspect_ratio = "1/1";
- state = [
- {
- value = "playing";
- icon = "mdi:play-circle-outline";
- }
- {
- value = "paused";
- icon = "mdi:pause-circle-outline";
- }
- ];
- tap_action = {
- action = "call-service";
- service = "music_assistant.transfer_queue";
- data.source_player = ''
- [[[
- if (states['sensor.music_room'].state == "Living Room") return "media_player.living_room";
- else if (states['sensor.music_room'].state == "Kitchen") return "media_player.kitchen";
- else if (states['sensor.music_room'].state == "Bathroom") return "media_player.bathroom_2";
- else if (states['sensor.music_room'].state == "Bedroom") return "media_player.bedroom_speaker";
- else if (states['sensor.music_room'].state == "Spare Room") return "media_player.spare_room";
- else if (states['sensor.music_room'].state == "Everywhere") return "media_player.everywhere";
- else return "";
- ]]]
- '';
- target.entity_id = "media_player.spare_room";
- haptic = "success";
- };
- double_tap_action = {
- action = "call-service";
- service = "script.play_on_repeat_on_guest_room_nest";
- haptic = "success";
- };
- styles.card = [
- { border-radius = "12px"; }
- { background-color = "var(--orange)"; }
- ];
- styles.icon = [ { color = "var(--black)"; } ];
- }
+ color = "orange";
+ targetEntity = mediaPlayers.rooms.spare_room;
+ script = "script.play_on_repeat_on_guest_room_nest";
+ })
];
}
{
@@ -655,219 +189,30 @@ mkView {
cards = [
{
type = "custom:button-card";
- entity = "media_player.music";
+ entity = mediaPlayers.special.music;
template = "media_player";
}
- {
- type = "conditional";
- conditions = [
- {
- condition = "numeric_state";
- entity = "sensor.music_assistant_playing_devices";
- above = 1;
- }
- {
- condition = "state";
- entity = "media_player.everywhere";
- state_not = "playing";
- }
- {
- condition = "state";
- entity = "sensor.music_room";
- state_not = "Living Room";
- }
- {
- condition = "or";
- conditions = [
- {
- condition = "state";
- entity = "media_player.living_room";
- state = "paused";
- }
- {
- condition = "state";
- entity = "media_player.living_room";
- state = "playing";
- }
- ];
- }
- ];
- card = {
- type = "custom:button-card";
- entity = "media_player.living_room";
- template = "media_player";
- variables.room = "Living Room";
- variables.icon_1 = "";
- };
- }
- {
- type = "conditional";
- conditions = [
- {
- condition = "numeric_state";
- entity = "sensor.music_assistant_playing_devices";
- above = 1;
- }
- {
- condition = "state";
- entity = "media_player.everywhere";
- state_not = "playing";
- }
- {
- condition = "state";
- entity = "sensor.music_room";
- state_not = "Kitchen";
- }
- {
- condition = "or";
- conditions = [
- {
- condition = "state";
- entity = "media_player.kitchen";
- state = "paused";
- }
- {
- condition = "state";
- entity = "media_player.kitchen";
- state = "playing";
- }
- ];
- }
- ];
- card = {
- type = "custom:button-card";
- entity = "media_player.kitchen";
- template = "media_player";
- variables.room = "Kitchen";
- variables.icon_1 = "";
- };
- }
- {
- type = "conditional";
- conditions = [
- {
- condition = "numeric_state";
- entity = "sensor.music_assistant_playing_devices";
- above = 1;
- }
- {
- condition = "state";
- entity = "media_player.everywhere";
- state_not = "playing";
- }
- {
- condition = "state";
- entity = "sensor.music_room";
- state_not = "Bathroom";
- }
- {
- condition = "or";
- conditions = [
- {
- condition = "state";
- entity = "media_player.bathroom";
- state = "paused";
- }
- {
- condition = "state";
- entity = "media_player.bathroom";
- state = "playing";
- }
- ];
- }
- ];
- card = {
- type = "custom:button-card";
- entity = "media_player.bathroom";
- template = "media_player";
- variables.room = "Bathroom";
- variables.icon_1 = "";
- };
- }
- {
- type = "conditional";
- conditions = [
- {
- condition = "numeric_state";
- entity = "sensor.music_assistant_playing_devices";
- above = 1;
- }
- {
- condition = "state";
- entity = "media_player.everywhere";
- state_not = "playing";
- }
- {
- condition = "state";
- entity = "sensor.music_room";
- state_not = "Bedroom";
- }
- {
- condition = "or";
- conditions = [
- {
- condition = "state";
- entity = "media_player.bedroom_nest_music_assistant";
- state = "paused";
- }
- {
- condition = "state";
- entity = "media_player.bedroom_nest_music_assistant";
- state = "playing";
- }
- ];
- }
- ];
- card = {
- type = "custom:button-card";
- entity = "media_player.bedroom_speakers";
- template = "media_player";
- variables.room = "Bedroom";
- variables.icon_1 = "";
- };
- }
- {
- type = "conditional";
- conditions = [
- {
- condition = "numeric_state";
- entity = "sensor.music_assistant_playing_devices";
- above = 1;
- }
- {
- condition = "state";
- entity = "media_player.everywhere";
- state_not = "playing";
- }
- {
- condition = "state";
- entity = "sensor.music_room";
- state_not = "Spare Room";
- }
- {
- condition = "or";
- conditions = [
- {
- condition = "state";
- entity = "media_player.spare_room";
- state = "paused";
- }
- {
- condition = "state";
- entity = "media_player.spare_room";
- state = "playing";
- }
- ];
- }
- ];
- card = {
- type = "custom:button-card";
- entity = "media_player.spare_room";
- template = "media_player";
- variables.room = "Spare Room";
- variables.icon_1 = "";
- };
- }
+ (mkMusicSwipeCard {
+ room = "Living Room";
+ entity = mediaPlayers.rooms.living_room;
+ })
+ (mkMusicSwipeCard {
+ room = "Kitchen";
+ entity = mediaPlayers.rooms.kitchen;
+ })
+ (mkMusicSwipeCard {
+ room = "Bathroom";
+ entity = mediaPlayers.rooms.bathroom;
+ })
+ (mkMusicSwipeCard {
+ room = "Bedroom";
+ entity = mediaPlayers.rooms.bedroom_speakers;
+ conditionEntity = mediaPlayers.rooms.bedroom_nest;
+ })
+ (mkMusicSwipeCard {
+ room = "Spare Room";
+ entity = mediaPlayers.rooms.spare_room;
+ })
];
}
];
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/views/security.nix b/hosts/server/nixcloud/home-assistant/dashboard/views/security.nix
index b195e4986..35546833c 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard/views/security.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard/views/security.nix
@@ -7,7 +7,7 @@ let
'';
};
- navbar = import ../components/navbar.nix { };
+ navbar = import ../components/navbar.nix { inherit lib; };
in
mkView {
title = "Security";
diff --git a/hosts/server/nixcloud/home-assistant/dashboard/views/server.nix b/hosts/server/nixcloud/home-assistant/dashboard/views/server.nix
index bdabab6c6..a5d204c9e 100644
--- a/hosts/server/nixcloud/home-assistant/dashboard/views/server.nix
+++ b/hosts/server/nixcloud/home-assistant/dashboard/views/server.nix
@@ -1,7 +1,7 @@
{ lib, ... }:
with (import ../lib.nix { inherit lib; });
let
- navbar = import ../components/navbar.nix { };
+ navbar = import ../components/navbar.nix { inherit lib; };
viewHeader = import ../components/view-header.nix {
content = ''
# Server
diff --git a/hosts/server/nixcloud/home-assistant/default.nix b/hosts/server/nixcloud/home-assistant/default.nix
index cd16936c5..5c9e85e7d 100644
--- a/hosts/server/nixcloud/home-assistant/default.nix
+++ b/hosts/server/nixcloud/home-assistant/default.nix
@@ -43,7 +43,7 @@
pkgs.pyarlo
spotifyaio
- aiobotocore # For S3 Backup to Minio
+ # aiobotocore # For S3 Backup to Minio
# Required for home-generative-agent integration
# Source: https://github.com/goruck/home-generative-agent/blob/main/custom_components/home_generative_agent/manifest.json
@@ -57,7 +57,7 @@
langchain-core
langchain-ollama
langchain-openai
- langchain-google-genai
+ # langchain-google-genai
];
};
customComponents = with pkgs.home-assistant-custom-components; [
diff --git a/hosts/server/nixcloud/home-assistant/music.nix b/hosts/server/nixcloud/home-assistant/music.nix
index 5f3642419..81eed6f00 100644
--- a/hosts/server/nixcloud/home-assistant/music.nix
+++ b/hosts/server/nixcloud/home-assistant/music.nix
@@ -10,10 +10,7 @@ _: {
"chromecast"
"hass"
"hass_players"
- "jellyfin"
- "spotify"
- "spotify_connect"
- "ytmusic"
+ "opensubsonic"
];
};
};
diff --git a/hosts/server/nixcloud/home-assistant/static.nix b/hosts/server/nixcloud/home-assistant/static.nix
new file mode 100644
index 000000000..4699a1e56
--- /dev/null
+++ b/hosts/server/nixcloud/home-assistant/static.nix
@@ -0,0 +1,24 @@
+# Helper for defining static elements for the HassIO configuration.
+# Entities, Users, Rooms etc.
+{
+ entities = {
+
+ };
+
+ # Each user needs a profile created in Home Assistant first.
+ users = {
+ james = {
+ displayName = "James";
+ id = "3eea636aa3de4c7f9c662ad29c6e92e0";
+ };
+
+ savannah = {
+ displayName = "Savannah";
+ id = "82def695e9504f63b1eb09150073737d";
+ };
+ };
+
+ rooms = {
+
+ };
+}