Hi everyone,
Based on this integration, I created a Lovelace UI. It’s my first attempt, so it’s not perfect — but maybe it helps someone as a starting point.
Feel free to improve it or build on it!
Cheers,
Timon
type: custom:mod-card
style: |
ha-card {
border-radius: 14px;
box-shadow: 0 4px 24px 0 #0004;
padding: 10px;
border: none;
background: #1D1D1D;
}
card:
type: vertical-stack
cards:
- type: custom:button-card
entity: switch.kwl_on_off_switch
icon: mdi:fan
name: Belüftungsanlage
label: Air Conditioner
show_icon: true
tap_action:
action: toggle
state:
- value: "on"
spin: true
styles:
card:
- background: none
icon:
- animation: rotating 5s linear infinite
- value: "off"
icon: mdi:fan-off
styles:
card:
- background: none
custom_fields:
kwlstate: |
[[[
const mode = states['select.select_state_of_the_kwl']?.state || 'Unknown';
const iconMap = {
AtHome: {icon: "mdi:home-variant", color:"#2d9cfa", label:"Home"},
Away: {icon: "mdi:account-arrow-right",color:"#607d8b", label:"Away"},
Intensive: {icon: "mdi:flash", color:"#fea726", label:"Boost"},
Individual: {icon: "mdi:autorenew", color:"#7c4dff", label:"Auto"},
};
const meta = iconMap[mode] || {icon:"mdi:help-circle", color:"#757575", label:mode};
return `
<span style="
display:inline-flex;align-items:center;
gap:9px;background:${meta.color};color:white;
border-radius:14px;
padding:3px 16px 3px 12px;
font-size:15px;font-weight:600;box-shadow:0 1px 4px 0 rgba(0,0,0,0.10);letter-spacing:0.5px;">
<ha-icon icon="${meta.icon}" style="width:19px;height:19px;margin-right:2px;color:white;opacity:0.87"></ha-icon>
${meta.label}
</span>
`;
]]]
fanspeed: |
[[[
const percent = parseInt(states['sensor.kwl_220_d_l_current_fan_speed']?.state || 0, 10);
const is_on = states['switch.kwl_on_off_switch']?.state === "on";
const fan_icon = is_on ? "mdi:fan" : "mdi:fan-off";
const size = 80;
const stroke = 4;
const radius = (size - stroke) / 2;
const circumference = 2 * Math.PI * radius;
const offset = circumference * (1 - percent / 100);
const innerPadding = 10;
return `
<style>
@keyframes spin-fan {
0% { transform: rotate(0deg);}
100% { transform: rotate(360deg);}
}
</style>
<div style="position:relative;width:${size}px;height:${size}px;display:flex;align-items:center;justify-content:center;">
<svg width="${size}" height="${size}" style="position:absolute;top:0;left:0;">
<circle
cx="${size/2}" cy="${size/2}" r="${radius}"
stroke="#122E63"
stroke-opacity="0.25"
stroke-width="${stroke}"
fill="none"
/>
<circle
cx="${size/2}" cy="${size/2}" r="${radius}"
stroke="#2D9CFA"
stroke-width="${stroke}"
fill="none"
stroke-dasharray="${circumference}"
stroke-dashoffset="${offset}"
stroke-linecap="round"
transform="rotate(-90 ${size/2} ${size/2})"
/>
</svg>
<div style="position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:1;padding:${innerPadding}px;">
<ha-icon icon="${fan_icon}" style="color:#2D9CFA;width:22px;height:22px;${is_on ? 'animation: spin-fan 4s linear infinite;' : ''};margin-bottom:2px;"></ha-icon>
<span style="font-size:20px;font-weight:600;color:white;margin-top:2px;">${percent}%</span>
</div>
</div>
`;
]]]
icon: ""
exhaust: |
[[[
return '<div style="color: #FF6F61;">Exhaust</div>' + (states['sensor.kwl_220_d_l_exhaust_temperature']?.state || 'N/A') + '°C';
]]]
supply: |
[[[
return '<div style="color: #6BB9FF;">Supply</div>' + (states['sensor.kwl_220_d_l_supply_temperature']?.state || 'N/A') + '°C';
]]]
styles:
card:
- padding: 0px 24px 12px 24px
- background: none
grid:
- grid-template-areas: |
". name kwlstate"
"supply fanspeed exhaust"
- grid-template-columns: 1fr 1fr 1fr
- grid-template-rows: min-content min-content min-content min-content min-content
name:
- grid-area: name
- justify-self: start
- font-size: 16px
- font-weight: 500
- color: rgba(255, 255, 255, 0.85)
custom_fields:
kwlstate:
- grid-area: kwlstate
- justify-self: end
- align-self: start
- margin-top: 0px
- margin-right: 0px
exhaust:
- grid-area: exhaust
- font-size: 20px
- font-weight: 300
- color: rgba(255, 255, 255, 0.85)
- justify-self: end
- margin-top: 20px
supply:
- grid-area: supply
- font-size: 20px
- font-weight: 300
- color: rgba(255, 255, 255, 0.85)
- justify-self: start
- margin-top: 20px
fanspeed:
- grid-area: fanspeed
- justify-self: center
- align-self: center
- margin-top: 8px
- margin-bottom: 0px
img_cell:
- position: absolute
- left: 0
- top: 0px
- width: 32px
- height: 32px
- justify-self: baseline
- background-color: rgba(55,55,55,1)
- border-radius: 50%
- padding: 4px
icon:
- grid-area: icon
- width: 64px
- color: white
- type: horizontal-stack
cards:
- type: custom:button-card
entity: select.select_state_of_the_kwl
name: Home
icon: mdi:home-variant
tap_action:
action: call-service
service: select.select_option
data:
entity_id: select.select_state_of_the_kwl
option: AtHome
styles:
card:
- border-radius: 12px
- background-color: |
[[[
return entity.state === "AtHome" ? "#2d9cfa" : "rgba(255,255,255,0.06)";
]]]
- color: |
[[[
return entity.state === "AtHome" ? "#fff" : "#7b8ca7";
]]]
- font-weight: 600px
- box-shadow: |
[[[
return entity.state === "AtHome" ? "0 2px 8px 0 #2d9cfa30" : "none";
]]]
- min-width: 56px
- min-height: 46px
- transition: 0.22s
- cursor: pointer
icon:
- color: |
[[[
return entity.state === "AtHome" ? "#fff" : "#4b5a70";
]]]
- width: 26px
- height: 26px
- type: custom:button-card
entity: select.select_state_of_the_kwl
name: Away
icon: mdi:account-arrow-right
tap_action:
action: call-service
service: select.select_option
data:
entity_id: select.select_state_of_the_kwl
option: Away
styles:
card:
- border-radius: 12px
- background-color: |
[[[
return entity.state === "Away" ? "#607d8b" : "rgba(255,255,255,0.06)";
]]]
- color: |
[[[
return entity.state === "Away" ? "#fff" : "#7b8ca7";
]]]
- font-weight: 600px
- box-shadow: |
[[[
return entity.state === "Away" ? "0 2px 8px 0 #607d8b30" : "none";
]]]
- min-width: 56px
- min-height: 46px
- transition: 0.22s
- cursor: pointer
icon:
- color: |
[[[
return entity.state === "Away" ? "#fff" : "#4b5a70";
]]]
- width: 26px
- height: 26px
- type: custom:button-card
entity: select.select_state_of_the_kwl
name: Boost
icon: mdi:flash
tap_action:
action: call-service
service: select.select_option
data:
entity_id: select.select_state_of_the_kwl
option: Intensive
styles:
card:
- border-radius: 12px
- background-color: |
[[[
return entity.state === "Intensive" ? "#fea726" : "rgba(255,255,255,0.06)";
]]]
- color: |
[[[
return entity.state === "Intensive" ? "#fff" : "#7b8ca7";
]]]
- font-weight: 600px
- box-shadow: |
[[[
return entity.state === "Intensive" ? "0 2px 8px 0 #fea72630" : "none";
]]]
- min-width: 56px
- min-height: 46px
- transition: 0.22s
- cursor: pointer
icon:
- color: |
[[[
return entity.state === "Intensive" ? "#fff" : "#4b5a70";
]]]
- width: 26px
- height: 26px
- type: custom:button-card
entity: select.select_state_of_the_kwl
name: Auto
icon: mdi:autorenew
tap_action:
action: call-service
service: select.select_option
data:
entity_id: select.select_state_of_the_kwl
option: Individual
styles:
card:
- border-radius: 12px
- background-color: |
[[[
return entity.state === "Individual" ? "#7c4dff" : "rgba(255,255,255,0.06)";
]]]
- color: |
[[[
return entity.state === "Individual" ? "#fff" : "#7b8ca7";
]]]
- font-weight: 600px
- box-shadow: |
[[[
return entity.state === "Individual" ? "0 2px 8px 0 #7c4dff30" : "none";
]]]
- min-width: 56px
- min-height: 46px
- transition: 0.22s
- cursor: pointer
icon:
- color: |
[[[
return entity.state === "Individual" ? "#fff" : "#4b5a70";
]]]
- width: 26px
- height: 26px