Tag: smart home

  • Generating a Beautiful Annual Solar PV Chart in Home Assistant

    This beautiful apexcharts chart gives me a clean annual view of solar PV performance month on month.

    chart showing annual solar pv generation month to month

    I show two things at once:

    1. Total kWh per month as orange columns.
    2. Average daily PV for that month as a dashed line.

    That pairing makes it easy to compare seasonal variation and spot how consistent generation is within each month.


    1) The time window

    I want a rolling 12-month view, aligned to the current year start, updating every 30 minutes.

    graph_span: 12month
    update_interval: 30m
    span:
      start: year
      offset: "-0d"

    2) Monthly totals as columns

    I read SolarEdge lifetime energy, convert Wh to kWh, then sum by month. I also enable statistics so ApexCharts aligns changes to month boundaries for accurate month on month totals.

    - entity: sensor.solaredge_lifetime_energy
      name: PV Last 12 Months
      color: var(--energy-solar-color)
      type: column
      float_precision: 0
      yaxis_id: first
      transform: return x / 1000;
      show:
        datalabels: true
      group_by:
        func: sum
        duration: 1month
        fill: zero
      statistics:
        type: change
        period: month
        align: end

    Why:

    • transform converts Wh to kWh.
    • group_by: sum gives a single monthly bar.
    • statistics: period: month ensures the change is aligned to the end of each month.

    3) Average daily PV as a dashed line

    I reuse the same entity and conversion, but average across the month, with statistics aligned by day. This produces a smooth “average day in this month” figure.

    - entity: sensor.solaredge_lifetime_energy
      name: PV Last 12 Months
      type: line
      stroke_dash: 3
      float_precision: 0
      color: "#3399FF"
      yaxis_id: second
      transform: return x / 1000;
      show:
        datalabels: true
      group_by:
        func: avg
        duration: 1month
        fill: zero
      statistics:
        type: change
        period: day
        align: end
      extend_to: false

    Why:

    • group_by: avg over a month produces the per-month daily average.
    • statistics: period: day uses daily changes to derive a meaningful daily rate.
    • stroke_dash: 3 makes the line read as a secondary metric.

    4) Dual y-axes for tidy scales

    Totals and averages sit on different ranges, so I bind columns to the left axis and the line to the right axis. Both are hidden to keep the design clean.

    yaxis:
      - id: first
        decimals: 2
        show: false
        min: 0
      - id: second
        opposite: true
        decimals: 0
        min: 0
        max: 80
        show: false

    5) Bar styling and stroke

    I keep columns compact with a subtle corner radius, and give lines a clear white stroke outline for contrast on dark themes.

    apex_config:
      plotOptions:
        bar:
          columnWidth: 28
          borderRadius: 1
      stroke:
        width: 2
        colors:
          - "#FFFFFF"

    6) Data labels with a custom formatter

    The line’s labels always show. Column labels only show for useful values, which keeps winter months uncluttered.

    apex_config:
      dataLabels:
        offsetY: -10
        style:
          fontSize: 10
        formatter: |
          EVAL: function(value, {seriesIndex, dataPointIndex, w }) {
            const roundedValue = Number(value).toFixed(0);
            if (seriesIndex === 1) {
              return roundedValue;
            }
            return roundedValue < 100 ? '' : roundedValue;
          }

    Why:

    • When seriesIndex === 1 (the dashed line), always show the rounded value.
    • For the column series, hide values below 100 kWh to avoid visual noise.

    7) Minimal legend and tooltip

    This is a glanceable tile, so I remove the legend and tooltips.

    apex_config:
      legend:
        show: false
      tooltip:
        enabled: false

    8) Month labels

    Three-letter month labels save space and remain readable.

    apex_config:
      xaxis:
        labels:
          hideOverlappingLabels: false
          rotate: 90
          show: true
          style:
            fontSize: 9
          format: MMM

    Full YAML

    type: custom:apexcharts-card
    graph_span: 12month
    update_interval: 30m
    span:
      start: year
      offset: "-0d"
    header:
      show: true
      title: Total Solar PV 2025 (kWh)
    series:
      - entity: sensor.solaredge_lifetime_energy
        name: PV Last 12 Months
        color: var(--energy-solar-color)
        type: column
        float_precision: 0
        yaxis_id: first
        transform: return x / 1000;
        show:
          datalabels: true
        group_by:
          func: sum
          duration: 1month
          fill: zero
        statistics:
          type: change
          period: month
          align: end
      - entity: sensor.solaredge_lifetime_energy
        name: PV Last 12 Months
        type: line
        stroke_dash: 3
        float_precision: 0
        color: "#3399FF"
        yaxis_id: second
        transform: return x / 1000;
        show:
          datalabels: true
        group_by:
          func: avg
          duration: 1month
          fill: zero
        statistics:
          type: change
          period: day
          align: end
        extend_to: false
    yaxis:
      - id: first
        decimals: 2
        show: false
        min: 0
      - id: second
        opposite: true
        decimals: 0
        min: 0
        max: 80
        show: false
    apex_config:
      chart:
        height: 300px
      legend:
        show: false
      plotOptions:
        bar:
          columnWidth: 28
          borderRadius: 1
      stroke:
        width: 2
        colors:
          - "#FFFFFF"
      tooltip:
        enabled: false
      dataLabels:
        offsetY: -10
        style:
          fontSize: 10
        formatter: |
          EVAL: function(value, {seriesIndex, dataPointIndex, w }) {
            const roundedValue = Number(value).toFixed(0);
            if (seriesIndex === 1) {
              return roundedValue;
            }
            return roundedValue < 100 ? '' : roundedValue;      
          }      
      xaxis:
        labels:
          hideOverlappingLabels: false
          rotate: 90
          show: true
          style:
            fontSize: 9
          format: MMM
  • Measuring Forecast PV Vs Actual PV in Realtime on Home Assistant

    One of the most powerful aspects of Home Assistant is being able to stitch together data from multiple sources to create meaningful insights. In this post, I’ll walk through how I created a sensor that compares the predicted solar generation against the actual solar energy generated by my system, giving me a live percentage difference throughout the day.

    Screenshot 2025-09-01 at 17.36.56

    This is particularly useful to understand how accurate your solar predictions are, and whether your solar generation is outperforming (or underperforming) expectations as the day progresses.

    Why this sensor?

    I use Predbat to manage my solar battery charging and discharging logic. One of its inputs is a daily solar PV forecast, which it sources from Solcast (if configured).

    While my GivEnergy inverter provides solar generation figures, I’ve found that the SolarEdge integration (via my SolarEdge export meter) provides more accurate daily totals. You can install a SolarEdge integration through Home Assistant if you have this setup. So for this comparison, I use:

    • Predbat’s detailedForecast attribute from sensor.predbat_pv_today for the expected PV generation in 30 minute slots
    • SolarEdge’s sensor.solaredge_energy_today as the actual generated PV value

    How it works

    The custom template sensor does the following:

    1. Iterates over each 30-minute slot in the Predbat detailedForecast.
    2. Sums up all past PV forecasts (based on current time) to create a "so-far" forecast.
    3. Compares that to the actual solar generated today from SolarEdge.
    4. Calculates the percentage difference, showing how far above or below the forecast we are right now.

    This is a dynamic sensor, updating as time passes and more of the forecast becomes "in the past."

    What it tells me

    Solar PV Prediction chart

    The resulting percentage gives me a clear sense of solar performance in real time. For example:

    • If the value is positive, I’m generating more than expected.
    • If it’s negative, I’m falling behind the forecast.

    This can help explain battery decisions, why Predbat may or may not be dispatching energy, and offer insight into Solcast’s accuracy over time. I tend to find, due to the nature of the solarEdge updates, that the PV predicted is always higher than what I have achieved, but slowly throughout the day it catches up. Sometimes I get lucky and outperform the PV prediction!

    Predbat’s DetailedForecast attribute

    Within the PV prediction entity for today, Predbat provides a DetailedForecast attribute, which is a 30 minute breakdown. My sensor is based on pv_estimate, but you can choose whichever you want, just update the YAML accordingly.

    DetailedForecast
        - period_start: 2025-09-04T15:30:00+0000
            pv_estimate: 1.96
            pv_estimate10: 0.6
            pv_estimate90: 2.44
            pv_estimateCL: 1.66
        - period_start: 2025-09-04T16:00:00+0000
            pv_estimate: 1.76
            pv_estimate10: 0.48
            pv_estimate90: 2.08
            pv_estimateCL: 2.01

    The Template Sensor YAML

    Below is the full YAML snippet you can use in your configuration.yaml or template sensor configuration file:

    sensor:
      - name: "PV Actual Vs Forecast"
        unique_id: pv_actual_vs_forecast
        state_class: "measurement"
        unit_of_measurement: "%" 
        state: >
          {% set total = namespace(value=0) %}
          {% for item in state_attr('sensor.predbat_pv_today', 'detailedForecast') %}
              {% if item.pv_estimate is number %}
                  {% if strptime(item.period_start, "%Y-%m-%dT%H:%M:%S%z") <= now() %}
                      {% set total.value = total.value + (item.pv_estimate / 2) %}
                  {% endif %}
              {% endif %}
          {% endfor %}
          {% set currentSolar = states('sensor.solaredge_energy_today') | float %}
          {% set absolute_difference = currentSolar - total.value %}
          {% set percentage_difference = (absolute_difference / total.value) * 100 %}
          {{ percentage_difference }}
        icon: mdi:solar-panel

    Note: The division by 2 (item.pv_estimate / 2) accounts for each forecast slot being half an hour, and we want pv_estimate to be the kWh value.

  • Capturing Min / Max Weather values in a Home Assistant Entity

    Tracking daily weather extremes in Home Assistant can be a really useful addition to your dashboard, especially if you’re keen to understand trends over time or highlight key information visually.

    Screenshot 2025-09-01 at 11.29.31

    In my case, I wanted a lightweight, efficient way to display the maximum temperature, minimum temperature, and maximum wind speed recorded during the day. Not only does this create a simple historical snapshot, but it also enables me to power Mushroom Template Cards with rich attribute data, without needing to rely on external integrations or long-term statistics.

    The Template Sensor

    Here’s the YAML I used to create a template sensor that records today’s key weather stats. This sensor uses a combination of state and time-based triggers to reset at midnight and accumulate data throughout the day.

    - trigger:
        - platform: state
          entity_id: 
            - sensor.tempest_temperature
            - sensor.tempest_wind_speed
        - platform: time
          at: "00:00:00"
      sensor:
        - name: 'Today Weather Statistics'
          unique_id: today_weather_statistics
          state: "OK"
          attributes:
            max_temp: >
              {% set t_new = states('sensor.tempest_temperature') | float(-99) %}
              {% set prev = this.attributes.max_temp | float(-99) %}
              {{ [t_new, prev] | max if trigger.platform != 'time' else t_new }}
            min_temp: >
              {% set t_new = states('sensor.tempest_temperature') | float(99) %}
              {% set prev = this.attributes.min_temp | float(99) %}
              {{ [t_new, prev] | min if trigger.platform != 'time' else t_new }}
            max_wind: >
              {% set w_new = states('sensor.tempest_wind_speed') | float(-1) %}
              {% set prev = this.attributes.max_wind | float(-1) %}
              {{ [w_new, prev] | max if trigger.platform != 'time' else w_new }}

    How it works

    • State triggers respond to changes in sensor.tempest_temperature and sensor.tempest_wind_speed. Every time they update, the template checks if the new value exceeds (or undercuts) the previous attribute value and stores the result.
    • Midnight reset is achieved via a time-based trigger at 00:00:00. At this point, the values reset to the current readings (effectively seeding the day).
    • The state is set to "OK" as a placeholder—it’s not used for charting or display, but helps avoid null states.

    Home Assistant issues with adding new attributes

    I found (randomly it seems not predictably) that some of the new attributes would not be created on their first load, once I restarted Home Assistant. It was very frustrating to debug, the logic is to set a variable from a entity state, or otherwise set a float – but something is failing. If you too are finding that the attribute isnt appearing, instead – establish the attribute more simply with a default value. Restart HA and confirm the attribute now shows up. E.g.

            max_wind: >
              {{ float(0) }}

    This creates the new max_wind attribute with a default value of 0.0. Now update your YAML per the above with the logic to capture the maximum wind speed, this time it should update without issue.

    Using This Sensor in Mushroom Cards

    Now that the max and min values are stored in the attributes, you can easily access them in mushroom-template-card components. Here’s an example that shows the max temperature for today:

    - type: custom:mushroom-template-card
      primary: "Max Temp"
      secondary: >
        {{ state_attr('sensor.today_weather_statistics', 'max_temp') | round(1) }}°C
      icon: mdi:thermometer-high

    Or for minimum temperature:

    - type: custom:mushroom-template-card
      primary: "Min Temp"
      secondary: >
        {{ state_attr('sensor.today_weather_statistics', 'min_temp') | round(1) }}°C
      icon: mdi:thermometer-low

    And similarly for wind:

    - type: custom:mushroom-template-card
      primary: "Max Wind"
      secondary: >
        {{ state_attr('sensor.today_weather_statistics', 'max_wind') | round(1) }} mph
      icon: mdi:weather-windy

    Why I Like This Approach

    • It’s simple and fast. No need to use mulitple sensors or long-term statistics.
    • It works entirely in real-time, using native templating.
    • It powers lightweight dashboard cards with just the information I want to see.
    • It’s resettable and predictable – every day starts clean at midnight.
  • Building a Min-Max-Avg Temperature Chart in Home Assistant

    This guide walks you through how I created a clean, monthly temperature overview card in Home Assistant, showing maximum, minimum, and average temperatures across the year – with a live annotation for the current reading.

    Chart showing monthly temperatures for my location

    The chart provides a fantastic visual snapshot of temperature trends throughout the year and uses the excellent apexcharts-card combined with config-template-card for dynamic annotations.


    Why I Built This

    I wanted a quick way to compare month-to-month temperatures for 2025 – not just the highs and lows, but also how the average temperatures trend over time. And with the real-time temperature annotated on the chart, it’s easy to see how the current conditions stack up against the year so far.


    What You’ll Need

    Before you start, make sure you have:

    • Home Assistant running with HACS
    • Installed the following custom cards via HACS:
    • A temperature sensor that reports regularly (e.g., sensor.tempest_temperature in my case)

    How Monthly Max, Min, and Average Temperatures Work

    The magic behind this chart lies in the use of Home Assistant’s built-in long-term statistics capability, which powers the statistics: feature of apexcharts-card.

    Each series uses the following:

    • type: max and type: min with period: month to extract monthly maximum and minimum temperatures respectively
    • type: mean along with group_by.func: avg to generate a monthly average line (in white)

    This means you’re not visualising raw sensor data points, but summarised monthly metrics – ideal for year-on-year comparisons or spotting unusual weather patterns.

    You can control how values are aggregated using align: start or align: end, which defines where in the month each value is anchored on the timeline.


    How the Live Annotation Works

    This chart includes a live annotation showing the current temperature as a small dot with a label.

    This is done using the annotations: section of ApexCharts, made dynamic by wrapping the entire card in a config-template-card.

    Here’s what happens:

    • We define datenow using JavaScript to get the timestamp for the first day of the current month
    • We read the current value from the sensor (sensor.tempest_temperature) using states[...]
    • We format that value to one decimal place and append the unit (°C)
    • These variables (${datenow}, ${temp}, ${tempString}) are injected into the chart’s annotations dynamically

    Without config-template-card, you couldn’t do this interpolation – it’s what allows us to reference JavaScript and Home Assistant state directly within the card definition.


    Customising the X-Axis Labels

    A small detail, but one that makes a big visual difference: the X-axis month labels are rotated and styled for clarity.

    xaxis:
      labels:
        rotate: 90
        style:
          fontSize: 9
        format: MMM

    This does three things:

    1. Rotates the labels so they don’t overlap
    2. Uses short month format (e.g. Jan, Feb, Mar) for a cleaner look
    3. Applies smaller font size to avoid clutter

    This is particularly useful when plotting an entire year, where 12 data points can otherwise get cramped.


    YAML Configuration

    Below is the full YAML you can drop into your dashboard. I used sensor.tempest_temperature as the data source, but you can replace it with any temperature sensor you have.

    type: custom:config-template-card
    variables:
      datenow: new Date(new Date().getFullYear(), new Date().getMonth(), 1).getTime()
      temp: parseFloat(states["sensor.tempest_temperature"].state)
      tempString: >-
        parseFloat(states["sensor.tempest_temperature"].state).toFixed(1) +
        states["sensor.tempest_temperature"].attributes.unit_of_measurement
    entities:
      - sensor.tempest_temperature
    card:
      type: custom:apexcharts-card
      update_interval: 30m
      graph_span: 11month
      span:
        start: year
      header:
        show: true
        title: Max|Min Temps 2025 (°C)
      now:
        show: false
      series:
        - entity: sensor.tempest_temperature
          name: Max Temp
          type: area
          color: "#e63946"
          opacity: 0.3
          float_precision: 1
          show:
            datalabels: true
          statistics:
            type: max
            period: month
            align: start
          extend_to: false
    
        - entity: sensor.tempest_temperature
          name: Min Temp
          type: area
          color: "#457b9d"
          opacity: 0.3
          float_precision: 1
          show:
            datalabels: true
          statistics:
            type: min
            period: month
            align: start
          extend_to: false
    
        - entity: sensor.tempest_temperature
          name: Avg Temp
          type: line
          color: "#ffffff"
          stroke_dash: 3
          float_precision: 1
          opacity: 0.8
          show:
            datalabels: false
          group_by:
            func: avg
            duration: 1month
            fill: zero
          statistics:
            type: mean
            period: month
            align: end
          extend_to: false
    
      apex_config:
        chart:
          height: 200px
        legend:
          show: false
        tooltip:
          enabled: false
        stroke:
          width: 2
        dataLabels:
          offsetY: -5
          style:
            fontSize: 9px
          formatter: |
            EVAL:function(val, opts) {
              return val.toFixed(1) + "°C";
            }
        xaxis:
          labels:
            hideOverlappingLabels: false
            rotate: 90
            show: true
            style:
              fontSize: 9
            format: MMM
        yaxis:
          show: false
        annotations:
          points:
            - x: ${datenow}
              y: ${temp}
              marker:
                size: 2
                shape: circle
                fillColor: "#FFFFFF"
                strokeColor: "#FFFFFF"
              label:
                offsetX: 26
                offsetY: 11
                text: ${tempString}
                style:
                  fontSize: 9
                  color: "#000000"

    Final Thoughts

    This kind of chart makes your Home Assistant dashboard much more informative. With a little templating and the power of ApexCharts, you can surface meaningful trends without needing to dive into graphs or statistics manually.

    The addition of dynamic annotations, paired with Home Assistant’s native statistics engine, creates a beautiful and functional visual that updates automatically.

  • Using WeatherFlow Tempest to Warm My Tesla for the School Run

    As a parent juggling the school run every morning, there’s one thing I really don’t enjoy – scraping ice off the car while trying to wrangle two teenagers and get out the door on time. So I’ve automated the process of pre-heating and defrosting my Tesla using my WeatherFlow Tempest weather station and Home Assistant.

    Tesla being heated

    WeatherFlow Tempest is a smart weather station that gives me highly accurate outdoor temperature data from my own garden. More accurate, in fact, than the Tesla’s own temperature sensors when the car is asleep.

    I use the Tessie integration in Home Assistant to communicate with my car. It allows me to send commands like “turn on climate control” even when the Tesla is sleeping, without the delay or failure that sometimes occurs with the native Tesla integration.


    Why Use Tempest Instead of Tesla’s Own Temperature Reading?

    Tesla goes into a deep sleep mode overnight to conserve energy. That’s great for battery life, but not so great if you want reliable temperature data in the early morning. The external temperature reported by the car can be outdated or missing altogether until the car is awake and responsive.

    Instead, I rely on my WeatherFlow Tempest’s temperature sensor, which continues to report accurate live data overnight.


    The Automation Setup

    Every weekday morning at exactly 8.00am, my Home Assistant instance checks the current outside temperature reported by the Tempest. If it’s below 3°C, the automation triggers Tessie to enable climate control in the car for 15 minutes – enough to warm the cabin and defrost the windows. I also make sure I only set this on school days.

    I also receive a notification letting me know that the defrost has been enabled.

    Here’s the automation in full:

    alias: Warm Car Automation
    description: At 08.00 every school day check outside temp and defrost if below 3C
    mode: single
    triggers:
      - at: "08:00:00"
        platform: time
    conditions:
      - condition: time
        weekday:
          - mon
          - tue
          - wed
          - thu
          - fri
      - condition: numeric_state
        entity_id: sensor.tempest_temperature
        below: 3
    actions:
      - service: climate.turn_on
        target:
          entity_id: climate.none_climate
      - service: notify.notify
        data:
          message: "Car Defrost Enabled for 15 mins"
      - service: notify.persistent_notification
        data:
          title: "Car Defrost Enabled"
          message: "Car Defrost Enabled for 15 mins"

    You can take this further by:

    • Tracking how often the automation is triggered using a counter or helper.
    • Adding a secondary check for battery level to avoid defrosting if your range is low.
    • Turning the climate off again after a set duration (or allowing Tesla’s own timeout to do it for you).

    This small quality-of-life improvement saves me time and hassle every winter morning, and it’s yet another example of how Home Assistant can bring together multiple smart devices into a seamless routine.

  • Accessing UK Rainfall Data in Home Assistant with the Environment Agency’s Beta API

    If you’re looking to add real-time UK rainfall data to your Home Assistant setup, the Environment Agency (EA) offers a beta API that exposes official rain gauge readings from across England. This post explains how to access that data, find your nearest measurement station, and integrate it into Home Assistant using a REST sensor.

    dashboard image from home assistant showing a rain sensor

    What Is the EA Rainfall API?

    The EA’s Flood Monitoring API includes rainfall measurements from hundreds of monitoring stations across the UK. You can query specific stations to retrieve rainfall data from the past 24 hours.

    Note:
    This API is officially in beta, which means it’s not guaranteed to be available 24/7. The service occasionally times out or becomes unresponsive, so any sensors you create may intermittently return "unknown" or fail to update.


    Step 1: Find Your Nearest Rainfall Station

    To begin, you’ll need the station ID of the rainfall monitor closest to your location.

    1. Visit the EA’s monitoring station search page:
      https://check-for-flooding.service.gov.uk/river-and-sea-levels

    2. Use the search bar for your location / home / postcode.

    3. In the results page, choose the Rainfall tab.

    4. Click on a nearby station marker to bring up its detail page. It will look like this:
      https://check-for-flooding.service.gov.uk/rainfall-station/E1234
      The ID at the end (E1234 in this example) is what you’ll use in your sensor configuration.

    5. You can also test live readings using this format:
      https://environment.data.gov.uk/flood-monitoring/id/stations/E1234/readings?parameter=rainfall&today


    Step 2: Add the REST Sensor in Home Assistant

    Once you’ve chosen a suitable station ID, add the following to your Home Assistant configuration.yaml under sensor: (or a new YAML file if using rest: integration directly):

    - platform: rest
      name: Rainfall Today (EA)
      unique_id: rainfall_today_ea
      resource: https://environment.data.gov.uk/flood-monitoring/id/stations/YOUR_STATION_ID/readings?parameter=rainfall&today
      method: GET
      value_template: >
        {% set readings = value_json["items"] | default([]) %}
        {% if readings %}
          {% set total = readings
              | map(attribute='value')
              | map('float')
              | sum %}
          {{ '%.2f' | format(total) }}
        {% else %}
          0.00
        {% endif %}
      unit_of_measurement: mm
      device_class: precipitation
      state_class: total
      scan_interval: 900

    Important: Replace YOUR_STATION_ID with your chosen ID (e.g., E1234).

    Important: Restart Home Assistant to enable the new sensor.


    Step 3: Display on a Dashboard

    Now that the sensor is added, you can display the total rainfall for today using any standard card in Lovelace, using the entity sensor.rainfall_today_ea. You could also combine it with daily or weekly trend charts for deeper insight.


    Troubleshooting Tips

    • If the sensor value shows as "unknown" or doesn’t update, it’s likely due to temporary API downtime.
    • Double-check that the station you’ve chosen reports rainfall and not only water level or flow.
    • You can test the API manually in your browser to confirm it’s returning valid JSON.

    Using the EA’s beta rainfall API is a great way to get highly localised, official rainfall data into your smart home dashboard. While it’s not perfect due to its beta status, it adds valuable environmental awareness to any Home Assistant setup.

    If you’ve found a reliable station near you, this integration can help you:

    • Track rainfall for automation purposes
    • Cross-check rainfall against your garden’s needs
    • Build weather prediction models using historical patterns

    I am also using it to compare to the rainfall totals I get through my Weatherflow Tempest device. It uses haptics to measure rainfall which is not as exact as I hoped. Comparing its results with the official "close by" EA measurement station helped me understand how accurate it really was… in short – its close enough!

  • A Small but Mighty Wind Card in Home Assistant

    As part of refining my Home Assistant dashboards, I wanted a lightweight, dynamic card to show current wind conditions from my Weatherflow Tempest.

    Screenshot 2025-09-01 at 12.36.38

    There are plenty of ways to visualise weather data, but most are either too large, too basic, or just not very readable. I wanted something compact, clean, and glanceable. A quick read, no fluff, and no wasted screen space.

    This is where the Mushroom Template Card really shines. It’s incredibly flexible, looks modern, and lets you pack a surprising amount of intelligence into a small card.

    What I Wanted

    The goal was simple:

    • Show the wind speed in mph.
    • Display a directional icon based on wind direction.
    • Change the icon colour if wind speed is high.
    • Keep it compact, no charts, no clutter.

    All powered by the Tempest sensors I already had in place.

    The YAML

    Here’s the full YAML I ended up with:

    type: custom:mushroom-template-card
    entity: sensor.tempest_wind_speed
    primary: Wind
    secondary: "{{ states('sensor.tempest_wind_speed') | float | round(2) }} mph"

    The secondary line uses Jinja2 to pull the wind speed and round it to two decimal places. I like that this keeps it readable without unnecessary decimals.

    Directional Icon Logic

    The icon is where it gets a bit more clever. If the wind sensor is reporting a valid direction and the wind isn’t zero, we map it to a directional arrow.

    icon: >-
      {% set raw_dir = states('sensor.tempest_wind_direction') %}
      {% set raw_speed = states('sensor.tempest_wind_speed') %}
      {% if raw_dir in ['unknown', 'unavailable', 'none', ''] or raw_speed | float == 0 %}
        mdi:compass
      {% else %}
        {% set d_from = ((raw_dir | float) % 360 + 360) % 360 %}
        {% set d_to = (d_from + 180) % 360 %}
        {% set idx = (((d_to + 22.5) % 360) // 45) | int %}
        {% set icons = [
          'mdi:arrow-up-circle-outline',
          'mdi:arrow-top-right-thin-circle-outline',
          'mdi:arrow-right-circle-outline',
          'mdi:arrow-bottom-right-thin-circle-outline',
          'mdi:arrow-down-circle-outline',
          'mdi:arrow-bottom-left-thin-circle-outline',
          'mdi:arrow-left-circle-outline',
          'mdi:arrow-top-left-thin-circle-outline'
        ] %}
        {{ icons[idx] }}
      {% endif %}

    Let me break that down:

    • If the direction or speed is unknown or zero, we show a generic compass icon.
    • Otherwise, we:
      • Calculate the direction the wind is going to (not coming from).
      • Convert it into an index between 0–7.
      • Map that to one of eight arrow icons using Material Design Icons (MDI).

    This gives a visual indication of direction without needing to read degrees.

    Icon Colour Based on Wind Speed

    Just for a bit of extra UX, I wanted the icon to go red if the wind is high (10+ mph). Otherwise, it uses the theme’s default colour.

    icon_color: >-
      {% set raw_speed = states('sensor.tempest_wind_speed') | float %}
      {% if raw_speed >= 10 %}
        red
      {% else %}
        "var(text-primary-color)"
      {% endif %}

    This makes it easy to spot when it’s particularly gusty outside without taking up extra space or alerting.

    Final Touch

    Finally, I added a tap action to bring up the more-info panel when you tap the card.

    tap_action:
      action: more-info

    That way, if I want to see the raw data or history graph, it’s just a tap away.

    Result

    What I ended up with is a sleek little wind tile that blends into my dashboard, gives me useful info at a glance, and doesn’t crowd the interface.

    It’s one of those tiny details that makes the whole dashboard feel more alive.

    shows a dashboard of weather widgets, wind, rain, temperature

    Entire YAML

    type: custom:mushroom-template-card
    entity: sensor.tempest_wind_speed
    primary: Wind
    secondary: "{{ states('sensor.tempest_wind_speed') | float | round(2) }} mph"
    icon: >-
      {% set raw_dir = states('sensor.tempest_wind_direction') %}
      {% set raw_speed = states('sensor.tempest_wind_speed') %}
      {% if raw_dir in ['unknown', 'unavailable', 'none', ''] or raw_speed | float == 0 %}
        mdi:compass
      {% else %}
        {% set d_from = ((raw_dir | float) % 360 + 360) % 360 %}
        {% set d_to = (d_from + 180) % 360 %}
        {% set idx = (((d_to + 22.5) % 360) // 45) | int %}
        {% set icons = [
          'mdi:arrow-up-circle-outline',
          'mdi:arrow-top-right-thin-circle-outline',
          'mdi:arrow-right-circle-outline',
          'mdi:arrow-bottom-right-thin-circle-outline',
          'mdi:arrow-down-circle-outline',
          'mdi:arrow-bottom-left-thin-circle-outline',
          'mdi:arrow-left-circle-outline',
          'mdi:arrow-top-left-thin-circle-outline'
        ] %}
        {{ icons[idx] }}
      {% endif %}
    icon_color: >-
      {% set raw_speed = states('sensor.tempest_wind_speed') | float %}
      {% if raw_speed >= 10 %}
        red
      {% else %}
        "var(text-primary-color)"
      {% endif %}
    tap_action:
      action: more-info