V5.1 SIMPLE AIR COMFORT CARD
- Increased
floating circleto 20px size - Improved centering of flashing red, moved into card-mod css, from custom button card.
- Flashing starts when
floating circlebreaches top ofouter ring limits26.5 - High Temp Alert when
floating circlebreaches top ofouter ring limits26.5 - Default center temperature 22
Horizontal Stack
Two Vertical Stacks within Horizontal Stack
- Fully refactored code
- One card code for all screen sizes
- No NodeRED Dependency
- Minimal card-mod used (improves loading times)
- Previous card-mod code moved into button card native
- All border and background transparent
- Default white icons
- Error handling for unknown and unavailable sensors (text and colour)
- Improved scaling for (Floating Circle)
- Macro Driven
- Most of the configuration of the card completed from the macro
- Calculates Dewpoint
- Calculates Feels like Temperature (Australian Apparent Temperature)
- Calculates Temperature Colour (Card Background)
- Calculates Dewpoint Colour (Outer Ring Limits)
- Calculates Temp and Humidity Alert Colour (Inner Circle Comfort Zone)
- Calculates Temperature and Humidity Position and Blinking Warning (Floating Circle)
- Configurable setpoints in the macros, to allow customisation.
- Sensors that get inserted into the card
- Dewpoint
- Feels like Temperature (Australian Apparent Temperature)
- Temperature Colour (Card Background)
- Dewpoint Colour (Outer Ring Limits)
- Temp and Humidity Alert Colour (Inner Circle Comfort Zone)
- Temperature and Humidity Position and Blinking Warning (Floating Circle)
Background Overlay
- Using Visual Studio Code Editor or other, create a new folder called
simple-air-comfort-cardinconfig/www/community - Right Click on you newly created folder
/config/www/community/simple-air-comfort-cardand select New File - Name the new file
sac_background_overlay.svg - Click on the file and paste the following:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150" />
MACRO CODE
- Using Visual Studio Code Editor or other, create a new folder called
custom_templatesin yourconfig, if it is not already there. - Right Click on you newly created folder
/config/custom_templatesand select New File - Name the new file
sac_macros.jinja
- Click on the file and paste the following: MACRO CODE
Macro Code
{# Dewpoint Temperature #}
{% macro calculate_dew_point(entity_temperature, entity_humidity) %}
{% set T, RH = states(entity_temperature), states(entity_humidity) %}
{% if (T == 'unknown') or (RH == 'unknown') %}
unknown
{% elif (T == 'unavailable') or (RH == 'unavailable') %}
unavailable
{% else %}
{% set T = T | float %}
{% set RH = RH | float %}
{# Arden Buck Method constants #}
{% set a = 6.1121 | float %}
{% set b = 18.678 | float %}
{% set c = 257.14 | float %}
{% set d = 234.5 | float %}
{% set e = 2.71828 | float %}
{# Calculate gamma #}
{% set gamma = log((RH/100)*e**((b-T/d)*(T/(c+T)))) | float %}
{# Calculate dew point #}
{% set dew_point = ((c * gamma) / (b - gamma)) | round(1) %}
{{ dew_point }}
{% endif %}
{% endmacro %}
{# Australian Apparent Temperature Feels Like #}
{% macro calculate_apparent_temperature(entity_temperature, entity_humidity, entity_wind_speed=None, Q=0) %}
{% set T, RH = states(entity_temperature), states(entity_humidity) %}
{% if (T == 'unknown') or (RH == 'unknown') %}
unknown
{% elif (T == 'unavailable') or (RH == 'unavailable') %}
unavailable
{% else %}
{% set T = T | float %}
{% set RH = RH | float %}
{% if entity_wind_speed %}
{% set WS = states(entity_wind_speed) %}
{% if (WS == 'unknown') or (WS == 'unavailable') %}
{% set WS = 0 %}
{% else %}
{% set WS = (WS | float) / 3.6 %} {# Convert km/h to m/s #}
{% endif %}
{% else %}
{% set WS = 0 %}
{% endif %}
{# Calculate water vapor pressure (e) #}
{% set e = (RH / 100) * 6.105 * (2.71828 ** ((17.27 * T) / (237.7 + T))) %}
{# Calculate apparent temperature (AT) #}
{% set AT = T + 0.348 * e - 0.70 * WS + 0.70 * (Q / (WS + 10)) - 4.25 %}
{{ AT | round(1) }}
{% endif %}
{% endmacro %}
{# Dewpoint Comfort Text #}
{% macro calculate_text_dewpoint_comfort(sensor_dewpoint) %}
{# Retrieve the current state of the dew point sensor #}
{% set dewpoint = states(sensor_dewpoint) %}
{# Check for 'unknown' or 'unavailable' states first #}
{% if dewpoint == 'unknown' %}
{% set comfort_level = 'Unknown' %}
{% elif dewpoint == 'unavailable' %}
{% set comfort_level = 'Unavailable' %}
{% else %}
{# Convert the dew point value to float and categorize it #}
{% set dewpoint = dewpoint | float(0) %}
{% if dewpoint < 5 %}
{% set comfort_level = 'Very Dry' %}
{% elif dewpoint >= 5 and dewpoint <= 10 %}
{% set comfort_level = 'Dry' %}
{% elif dewpoint >= 10.1 and dewpoint <= 12.79 %}
{% set comfort_level = 'Pleasant' %}
{% elif dewpoint >= 12.8 and dewpoint <= 15.49 %}
{% set comfort_level = 'Comfortable' %}
{% elif dewpoint >= 15.5 and dewpoint <= 18.39 %}
{% set comfort_level = 'Sticky Humid' %}
{% elif dewpoint >= 18.4 and dewpoint <= 21.19 %}
{% set comfort_level = 'Muggy' %}
{% elif dewpoint >= 21.2 and dewpoint <= 23.9 %}
{% set comfort_level = 'Sweltering' %}
{% else %}
{% set comfort_level = 'Stifling' %}
{% endif %}
{% endif %}
{# Return the comfort level text #}
{{ comfort_level }}
{% endmacro %}
{# Temperature Comfort Text #}
{% macro calculate_text_temperature_comfort(sensor_temperature) %}
{# Retrieve the current state of the temperature sensor #}
{% set temperature = states(sensor_temperature) %}
{# Check for 'unknown' or 'unavailable' states first #}
{% if temperature == 'unknown' %}
{% set comfort_level = 'N/A' %}
{% elif temperature == 'unavailable' %}
{% set comfort_level = 'N/A' %}
{% else %}
{# Convert the temperature value to float and categorize it #}
{% set temperature = temperature | float(0) %}
{% if temperature < 3 %}
{% set comfort_level = 'FROSTY' %}
{% elif temperature >= 3.1 and temperature <= 4.9 %}
{% set comfort_level = 'COLD' %}
{% elif temperature >= 5 and temperature <= 8.9 %}
{% set comfort_level = 'CHILLY' %}
{% elif temperature >= 9 and temperature <= 13.9 %}
{% set comfort_level = 'COOL' %}
{% elif temperature >= 14 and temperature <= 18.9 %}
{% set comfort_level = 'MILD' %}
{% elif temperature >= 19 and temperature <= 23.9 %}
{% set comfort_level = 'PERFECT' %}
{% elif temperature >= 24 and temperature <= 27.9 %}
{% set comfort_level = 'WARM' %}
{% elif temperature >= 28 and temperature <= 34.9 %}
{% set comfort_level = 'HOT' %}
{% else %}
{% set comfort_level = 'BOILING' %}
{% endif %}
{% endif %}
{# Return the comfort level text #}
{{ comfort_level }}
{% endmacro %}
{# Humidity Comfort Text #}
{% macro calculate_text_humidity_comfort(entity_humidity) %}
{# Get the current humidity value from the specified entity_humidity #}
{% set humidity = states(entity_humidity) %}
{# Check for 'unknown' or 'unavailable' states first #}
{% if humidity == 'unknown' %}
N/A
{% elif humidity == 'unavailable' %}
N/A
{% else %}
{# Convert the humidity value to float and categorize it #}
{% set humidity = humidity | float(0) %}
{# Check if the humidity is less than 40% #}
{% if humidity < 40 %}
DRY
{# Check if the humidity is between 40% and 60% #}
{% elif humidity >= 40 and humidity <= 60 %}
COMFY
{# If the humidity is greater than 60% #}
{% else %}
HUMID
{% endif %}
{% endif %}
{% endmacro %}
{# SAC Card Picture Elements Card Background #}
{% macro calculate_temperature_colour_card_background(entity) %}
{% if is_state(entity, 'Unknown') or is_state(entity, 'Unavailable') or is_state(entity, 'N/A') %}
radial-gradient(circle, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), dimgray) !important;
{% elif is_state(entity, 'FROSTY') %}
radial-gradient(circle, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), mediumblue) !important;
{% elif is_state(entity, 'COLD') %}
radial-gradient(circle, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), dodgerblue) !important;
{% elif is_state(entity, 'CHILLY') %}
--ha-card-background: radial-gradient(circle, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), deepskyblue) !important;
{% elif is_state(entity, 'COOL') %}
radial-gradient(circle, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), mediumaquamarine) !important;
{% elif is_state(entity, 'MILD') %}
radial-gradient(circle, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), seagreen) !important;
{% elif is_state(entity, 'PERFECT') %}
radial-gradient(circle, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), limegreen) !important;
{% elif is_state(entity, 'WARM') %}
radial-gradient(circle, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), gold) !important;
{% elif is_state(entity, 'HOT') %}
radial-gradient(circle, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), orange) !important;
{% elif is_state(entity, 'BOILING') %}
radial-gradient(circle, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15), crimson) !important;
{% endif %}
{% endmacro %}
{# SAC Card Element Outer Ring Limits #}
{% macro calculate_dewpoint_colour_outer_ring_limits(entity) %}
{# Set a default value for dewpoint_color #}
{% set dewpoint_color = 'radial-gradient(circle, transparent, 55%, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15)) !important;' %}
{% if is_state(entity, 'Unknown') or is_state(entity, 'Unavailable') or is_state(entity, 'N/A') or is_state(entity, 'unavailable') or is_state(entity, 'unknown') %}
{% set dewpoint_color = 'radial-gradient(circle, dimgray, 55%, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15)) !important;' %}
{% elif is_state(entity, 'Very Dry') %}
{% set dewpoint_color = 'radial-gradient(circle, deepskyblue, 55%, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15)) !important;' %}
{% elif is_state(entity, 'Dry') %}
{% set dewpoint_color = 'radial-gradient(circle, mediumaquamarine, 55%, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15)) !important;' %}
{% elif is_state(entity, 'Pleasant') %}
{% set dewpoint_color = 'radial-gradient(circle, limegreen, 55%, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15)) !important;' %}
{% elif is_state(entity, 'Comfortable') %}
{% set dewpoint_color = 'radial-gradient(circle, yellowgreen, 55%, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15)) !important;' %}
{% elif is_state(entity, 'Sticky Humid') %}
{% set dewpoint_color = 'radial-gradient(circle, yellow, 55%, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15)) !important;' %}
{% elif is_state(entity, 'Muggy') %}
{% set dewpoint_color = 'radial-gradient(circle, gold, 55%, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15)) !important;' %}
{% elif is_state(entity, 'Sweltering') %}
{% set dewpoint_color = 'radial-gradient(circle, orange, 55%, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15)) !important;' %}
{% elif is_state(entity, 'Stifling') %}
{% set dewpoint_color = 'radial-gradient(circle, crimson, 55%, rgba(100, 100, 100, 0.15), rgba(100, 100, 100, 0.15)) !important;' %}
{% endif %}
{# Apply gradient #}
{{ dewpoint_color }}
{% endmacro %}
{# SAC Card Element Inner Circle Comfort Zone #}
{% macro inner_circle_comfort_zone_alert_colour(humidity_entity, temperature_entity) %}
{% set humidity_state = states(humidity_entity) %}
{% set temperature_state = states(temperature_entity) %}
{# Alert Levels #}
{% set temp_boiling_alert = 34.9 %}
{% set temp_hot_alert = 26.5 %}
{% set temp_warm_alert = 24.0 %}
{% set temp_mild_alert = 19.0 %}
{% set temp_cool_alert = 14.0 %}
{% set temp_chilly_alert = 9.0 %}
{% set temp_cold_alert = 5.0 %}
{% set temp_frosty_alert = 3.0 %}
{% set humidity_min_alert = 40 %}
{% set humidity_max_alert = 60 %}
{# Default colors for unknown/unavailable states #}
{% set default_color = 'dimgray' %}
{# Determine humidity colour #}
{% if humidity_state == 'unknown' or humidity_state == 'unavailable' %}
{% set humidity_color = default_color %}
{% else %}
{% set humidity_float = humidity_state | float(0) %}
{% if humidity_float > humidity_max_alert %}
{% set humidity_color = 'hotpink' %}
{% elif humidity_float >= humidity_min_alert and humidity_float <= humidity_max_alert %}
{% set humidity_color = 'black' %}
{% elif humidity_float < humidity_min_alert %}
{% set humidity_color = 'hotpink' %}
{% endif %}
{% endif %}
{# Determine temperature colour #}
{% if temperature_state == 'unknown' or temperature_state == 'unavailable' %}
{% set temperature_color = default_color %}
{% else %}
{% set temperature_float = temperature_state | float(0) %}
{% if temperature_float > temp_boiling_alert %}
{% set temperature_color = 'rgba(255, 69, 0, 0.8)' %}
{% elif temperature_float > temp_hot_alert and temperature_float <= temp_boiling_alert %}
{% set temperature_color = 'rgba(255, 69, 0, 0.8)' %}
{% elif temperature_float > temp_warm_alert and temperature_float <= temp_hot_alert %}
{% set temperature_color = 'dimgray' %}
{% elif temperature_float > temp_mild_alert and temperature_float <= temp_warm_alert %}
{% set temperature_color = 'dimgray' %}
{% elif temperature_float > temp_cool_alert and temperature_float <= temp_mild_alert %}
{% set temperature_color = 'rgba(0, 102, 255, 0.8)' %}
{% elif temperature_float > temp_chilly_alert and temperature_float <= temp_cool_alert %}
{% set temperature_color = 'rgba(0, 102, 255, 0.8)' %}
{% elif temperature_float > temp_cold_alert and temperature_float <= temp_chilly_alert %}
{% set temperature_color = 'rgba(0, 102, 255, 0.8)' %}
{% elif temperature_float > temp_frosty_alert and temperature_float <= temp_cold_alert %}
{% set temperature_color = 'rgba(0, 102, 255, 0.8)' %}
{% elif temperature_float < temp_frosty_alert %}
{% set temperature_color = 'rgba(0, 102, 255, 0.8)' %}
{% endif %}
{% endif %}
{# Apply gradient with spaces #}
radial-gradient(circle, {{ humidity_color }} 0%, black, {{ temperature_color }} 70%) !important;
{% endmacro %}
{# SAC Card Element Floating Circle #}
{% macro card_mod_floating_circle_movement_and_color(humidity_entity, temperature_entity) %}
{% set humidity_state = states(humidity_entity) %}
{% set temperature_state = states(temperature_entity) %}
{% set humidity_float = humidity_state | float(50) if humidity_state not in ['unknown', 'unavailable'] else 50 %}
{% set temperature_float = temperature_state | float(22) if temperature_state not in ['unknown', 'unavailable'] else 22 %}
{# Alert Levels and scaling factors #}
{% set temp_min = 15 %}
{% set temp_max = 35 %}
{% set temp_min_warning = 18 %}
{% set temp_max_warning = 26.4 %}
{% set humidity_min = 40 %}
{% set humidity_max = 60 %}
{# Scaling Map temp to 0% (bottom) → 100% (top) of the card #}
{% set temp_scaled = (((temperature_float - temp_min) * (100 - 0) / (temp_max - temp_min)) + 0) | round(1) %}
{% set temp_scaled = [0, temp_scaled, 100] | sort | join(' ') | regex_replace('^.*? (.*?) .*$', '\\1') %}
{# Check if the Floating Circle is outside Outer Ring Limits Temp or Inner Circle Comfort Zone Humidity #}
{% set outside_limits = (humidity_float < humidity_min) or (humidity_float > humidity_max) or (temperature_float < temp_min_warning) or (temperature_float > temp_max_warning) or (humidity_float == 50) or (temperature_float == 22.0) %}
{# Positioning the floating circle #}
left: {{ humidity_float + 0.5 }}%;
bottom: {{ temp_scaled }}%;
transition: bottom 0.8s ease-in-out, left 0.8s ease-in-out;
transform: translate(-50%, 50%);
{# Ensures the icon itself does NOT blink #}
ha-icon {
color: white !important;
}
{# Default state: Fully transparent #}
ha-card {
border-radius: 50%;
background: transparent !important;
position: relative;
aspect-ratio: 1 / 1;
width: 100%;
height: auto;
}
{# When outside limits, apply blinking red gradient #}
{% if outside_limits %}
ha-card::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
aspect-ratio: 1 / 1;
width: 100%;
height: 100%;
background: radial-gradient(circle,
rgba(255,0,0,0.8) 20%,
rgba(255,0,0,0.3) 50%,
rgba(255,0,0,0.1) 70%,
rgba(255,0,0,0) 100%
) !important;
animation: blink 1s infinite alternate;
}
{% endif %}
{# Define blinking animation #}
@keyframes blink {
0% { opacity: 1; }
100% { opacity: 0.3; }
}
{% endmacro %}
SENSOR CODE
- In your
configuration.yamlfile paste the followingtemplate:sensors:into your configuration to make a single card. - Ensure you enter your Temperature and Humidity Sensors. Wind Speed Optional for Australian Apparent Temperature
- Any sensors that start with
sensor.sacare calculated in the Macro. - Rename i.e.
living_roomto suit your needs
SENSOR CODE
template:
- sensor:
# Template Australian Apparent Temperature - Wind Speed Optional (km/h) - Utilising custom_templates/macros.jinja
- name: "sac_feels_like_temperature_living_room"
unique_id: 4dc577f3-7d28-4083-a010-ac3825fd6fd1
unit_of_measurement: "°C"
icon: mdi:thermometer
state: >
{% from 'sac_macros.jinja' import calculate_apparent_temperature %}
{{ calculate_apparent_temperature('sensor.insert_temperature', 'sensor.insert_humidity','sensor.insert_wind_speed') }}
# Template Dew Point Sensors - Utilising custom_templates/macros.jinja
- name: "dewpoint_living_room"
unique_id: c5e921de-47d2-4338-9158-8097911df43e
unit_of_measurement: "°C"
icon: mdi:thermometer-water
state: >
{% from 'sac_macros.jinja' import calculate_dew_point %}
{{ calculate_dew_point('sensor.insert_temperature', 'sensor.insert_humidity') }}
# Template Simple Air Comfort Card - Dewpoint Colour - Outer Ring Limits - Living Room
- name: "sac_dewpoint_colour_living_room"
unique_id: c6639b18-ac02-441f-badd-815c6c810254
state: >-
{% from 'sac_macros.jinja' import calculate_dewpoint_colour_outer_ring_limits %}
{{ calculate_dewpoint_colour_outer_ring_limits('sensor.sac_text_dewpoint_comfort_living_room') }}
# Template Simple Air Comfort Card - Alert Colour - Inner Circle - Living Room
- name: "sac_inner_circle_alert_colour_living_room"
unique_id: a3c9d6d1-e51d-450b-a61c-1f7d9cc7baeb
state: >-
{% from 'sac_macros.jinja' import inner_circle_comfort_zone_alert_colour %}
{{ inner_circle_comfort_zone_alert_colour('sensor.air_conditioning_humidity', 'air_conditioning_temperature') }}
# Template Simple Air Comfort Card - Temperature Colour - Picture Elements Card Background - Living Room
- name: "sac_temperature_colour_living_room"
unique_id: dc6c834b-f775-4b36-a2f3-d43356632cc6
state: >-
{% from 'sac_macros.jinja' import calculate_temperature_colour_card_background %}
{{ calculate_temperature_colour_card_background('sensor.sac_text_temperature_comfort_living_room') }}
# Template Simple Air Comfort Card - Text Dewpoint Comfort - Living Room
- name: "sac_text_dewpoint_comfort_living_room"
unique_id: e8b76541-7fe7-407e-b53d-f012a3d6152b
state: >-
{% from 'sac_macros.jinja' import calculate_text_dewpoint_comfort %}
{{ calculate_text_dewpoint_comfort('sensor.dewpoint_living_room') }}
# Template Simple Air Comfort Card - Text Temperature Comfort - Living Room
- name: "sac_text_temperature_comfort_living_room"
unique_id: 5e2b7728-1630-4d3f-b511-04d48bf76cd4
state: >-
{% from 'sac_macros.jinja' import calculate_text_temperature_comfort %}
{{ calculate_text_temperature_comfort('sensor.air_conditioning_temperature') }}
# Template Simple Air Comfort Card - Text Humidity Comfort - Living Room
- name: "sac_text_humidity_comfort_living_room"
unique_id: f299c759-e50c-4f9f-a775-b99734dab132
state: >-
{% from 'sac_macros.jinja' import calculate_text_humidity_comfort %}
{{ calculate_text_humidity_comfort('sensor.air_conditioning_humidity') }}
Thanks @LiQuid_cOOled for the input and @vajdum, may be of interest to you.




