Category: Home Assistant

  • Create a Mini Predbat Card for Dashboards

    A small tweak to the Predbat Table Card has enabled a really cool new use-case – MINI Predbat table cards!

    Github user @stiplady made a request to add a limit to the number of rows the plan returns, for "quick look" style functionality. A pretty easy change, but has unlocked some very cool functionality.

    Predbat mini card on a home assistant dashboard
    Predbat mini card on a home assistant dashboard

    By limiting the number of rows, users can now combine this functionality with custom columns and font size to create a mini Predbat table card on bigger dashboards.

    Previous to this, while you could limit the columns, the plan was usually 24 hours long, so at least 48 rows of data – which was not great for dashboards with other cards in place.

    Screenshot 2025-09-18 at 09.10.49
    Close up of the Predbat Table Card MINI

    As you can see in the images above, placing the table card nicely in a dashboard is now super easy!

    Here’s the YAML you can use to get started – obviously feel free to customise the YAML to meet your needs according to the spec.

    type: custom:predbat-table-card
    entity: predbat.plan_html
    columns:
      - time-column
      - state-column
      - pv-column
      - load-column
      - soc-column
    table_width: 100
    fill_empty_cells: true
    row_limit: 5
    font_size: 13
    old_skool_columns:
      - state-column
    hide_last_update: true
  • 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
  • TADO API Limits: Home Assistant Users Should Be Concerned

    TADO has introduced strict new API limits that will seriously impact anyone integrating their smart heating system with Home Assistant or other automation platforms.

    Screenshot 2025-09-05 at 10.07.35

    For years, smart home enthusiasts have relied on TADO’s API for near real-time heating and hot water control. But with the recent changes, users are now restricted to just 100 API calls per day unless they pay for a subscription.


    TADO API Limits Explained

    TADO has reduced the default polling frequency of their home assistant integration to every 15 minutes, which works out to roughly 100 calls per day (their new API limit). That may sound reasonable at first glance, but in a smart home environment, it’s far from enough.

    Automations such as pre-heating rooms, adjusting heating when doors and windows are open, or monitoring hot water require near real-time updates. Waiting up to 15 minutes for an update simply breaks the purpose of automation.

    TADO now offers a paid subscription (currently $29.99 per year) to increase limits to 20,000 calls per day. While not expensive in isolation, it feels like a hidden cost on top of the hundreds of pounds many of us have already invested in TADO hardware, and a bit of a rug pull / bait and switch.


    Local API Access Through HomeKit

    TADO’s official “alternative” is to use local access via HomeKit on V3+ bridges (or Matter on Tado X). But this approach has serious drawbacks:

    • Requires additional Apple hardware like an Apple TV or HomePod to act as a hub on V3+.
    • No hot water support. Local access covers TRVs, temperature, and humidity – but not hot water control.

    For many households, hot water is just as important as heating. Limiting its control to cloud-only updates every 15 minutes makes the system feel incomplete.


    TADO Integration Problems in Home Assistant

    Home Assistant users face another major issue: the TADO integration is unstable. Sessions frequently log out at random, breaking automations and leaving users without reliable control.

    The integration is community-maintained, not officially developed by TADO, and the company shows little interest in fixing these problems. It leaves power users caught between unreliable cloud access and incomplete local control.


    TADO X and Matter Support

    TADO’s latest hardware, TADO X, includes Matter support – a promising step towards local integration without needing Apple’s HomeKit ecosystem.

    But once again, there’s a catch: no hot water support. And so far, TADO has made no public commitment to add it in future.

    For many, this undermines Matter’s potential to finally deliver reliable, subscription-free local access.


    Why This Matters for Smart Home Owners

    The whole point of a smart heating system is flexibility and integration. TADO’s decision to restrict API access feels like a step backwards:

    • It penalises the most engaged users who automate beyond TADO’s app.
    • It introduces unnecessary subscription costs.
    • It limits functionality by excluding hot water from local integrations.

    Rather than blaming users for “overusing” the API, TADO should re-evaluate its infrastructure. A well-designed API can scale. Instead, customers are left with broken integrations, missing features, and a sense that they’re being upsold to fix problems they didn’t create.


    Final Thoughts

    As someone who has spent over £700 on TADO hardware, I find these changes disappointing. Smart home enthusiasts chose TADO because it promised openness, flexibility, and control. Now, that vision feels compromised.

    Until TADO re-thinks its approach, many Home Assistant users may start looking elsewhere for heating solutions that truly embrace the principles of the smart home.

  • Template Sensor and Attribute issues in Home Assistant

    I came across an interesting issue today and thought it worth documenting.

    I have setup a template sensor which is triggered by the state change of an entity. This template sensor saves a generic state (a string saying OK), storing a maximum or minimum value of the referenced entity as an attribute. Its a daily maximum, so I reset it at midnight.

    Screenshot 2025-09-04 at 13.40.11

    Template Sensor Setup

    In order to calculate if for example the "new" state of the entity was higher than the previous maximum value stored in the attribute, I have this logic in the template sensor YAML:

    attributes:
        max_rain: >
            {% set r_new = states('sensor.tempest_precipitation_intensity') | float(-1) %}
            {% set prev = this.attributes.max_rain | float(-1) %}
            {{ [r_new, prev] | max if trigger.platform != 'time' else r_new }} 

    So, create a variable r_new of the current state of the precipitation intensity entity, otherwise return -1.

    Next, set a variable prev to get the previous value of the current template sensor attribute (max_rain), otherwise return -1.

    Finally set the max_rain attribute to the largest of the two variables (r_new or prev), or reset to the current state (r_new) at midnight.

    The Problem

    If you test out this YAML logic in the developer tools, it works without error. E.g.

    {% set r_new = states('sensor.tempest_precipitation_intensity') | float(-1) %}
    {% set prev = state_attr('sensor.today_weather_statistics', 'max_rain') | float(-1) %}
    {{ [r_new, prev] | max }}
    Screenshot 2025-09-04 at 13.27.27
    Screenshot 2025-09-04 at 13.27.35

    The result here is 0.0, which reflects the current state from sensor.tempest_precipitation_intensity. In this case prev is returning -1 because the attribute max_rain does not exist on sensor.today_weather_statistics. All expected so far.

    The REAL problem

    The issue comes in when we’re trying to create this template sensor for the first time and establish the max_rain attribute. Because the logic to create and update the max_rain attribute references itself, something internally seems to fail. This all happens despite the YAML being safe in the developer tools. E.g.

    {% set prev = state_attr('sensor.today_weather_statistics', 'max_rain') | float(-1) %}

    Returns -1 safely in the developer tools. But it seems this kind of reference to itself cannot work on the first initialisation of the attribute itself. What you’ll find is the attribute never appears in your template sensor.

    Solution

    So instead of creating the self referencing logic in the first place, the easiest thing to do is something like this, where you create the attribute and assign a default value:

    - trigger:
      - platform: state
        entity_id: 
            - sensor.tempest_precipitation_intensity
      - platform: time
        at: "00:00:00"
      sensor:
        - name: 'Today Weather Statistics'
          unique_id: today_weather_statistics
          state: "OK"
          attributes:
            max_rain: >
                {{ float(0.0) }}

    Restart Home Assistant, go check out your new sensor, and confirm the max_rain attribute appears, with a value of 0.0.

    Now you’re ready for the self-referencing logic to work and not fail. Go back to your template sensor YAML, and update the max_rain logic:

    - trigger:
      - platform: state
        entity_id: 
            - sensor.tempest_precipitation_intensity
      - platform: time
        at: "00:00:00"
      sensor:
        - name: 'Today Weather Statistics'
          unique_id: today_weather_statistics
          state: "OK"
          attributes:
            max_rain: >
                        {% set r_new = states('sensor.tempest_precipitation_intensity') | float(-1) %}
                        {% set prev = this.attributes.max_rain | float(-1) %}
                        {{ [r_new, prev] | max if trigger.platform != 'time' else r_new }} 

    Restart Home Assistant – this time you should still see your attribute, but most importantly, see the value increase from 0.0, should the state of your reference entity be higher.

  • 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.

  • Tracking EV Tesla Lease Mileage in Home Assistant

    As part of my growing Home Assistant dashboard, I’ve built a custom section to track the mileage of my electric vehicle (EV), a Tesla Model Y. It helps me visualise how I’m progressing against my lease’s mileage allowance – not just the total driven, but also how I’m pacing day to day.

    car lease dashboard in home assistant showing daily stats and charts

    This dashboard has become one of the most valuable tools in my setup, especially with a leased EV where tracking your usage is critical to avoiding end-of-term penalties.


    Data Source: Tessie Integration

    I’m using the excellent Tessie integration to pull live data from my Tesla. It provides an entity for the odometer (sensor.none_odometer in my case), which powers all the mileage tracking. none here is the car’s name – I hadn’t named it when I installed the integration, and now its stuck there in the entity names.


    Lease Parameters

    • Start date: 10 February 2024
    • End date: 9 February 2027
    • Total allowed: 45,000 miles (15,000 miles per year)

    What I Show on the Dashboard

    My Tesla mileage dashboard includes:

    • Odometer: current total mileage (live from Tessie)
    • Daily Avg: my average daily mileage since delivery
    • Days Owned: how many days since I took delivery
    • Target Daily Avg: how many miles I can drive per day from today to stay within my lease cap
    • ApexCharts Graph: daily mileage (bars) vs. target daily average (dotted line)

    This gives me a clear visual indication of whether I’m tracking well or over-driving. The Target Daily Avg is based on how many miles I have used so far, and calculating the number of days remaining in the lease, so dividing mileage remaining / days left.


    ApexCharts Graph

    I use the apexcharts-card custom card to plot two data series:

    • Daily mileage from a helper or automation that tracks daily odometer deltas
    • Target average from a template sensor (see below)

    The bar chart gives a good at-a-glance view, with the dotted line acting as a benchmark to compare against.


    Template Sensors

    These sensors do the logic for tracking averages.

    1. Daily Average Since Delivery

    - sensor:
        - name: "Average Tesla Mileage"
          unique_id: average_tesla_mileage
          state_class: "total"
          device_class: "distance"
          unit_of_measurement: "mi"
          state: >
            {% set specific_date = "2024-02-10" %}
            {% set current_date = now().date() %}
            {% set specific_date_obj = as_timestamp(specific_date) %}
            {% set current_date_obj = as_timestamp(current_date) %}
            {% set days_since_specific_date = ((current_date_obj - specific_date_obj) / 86400) | round(0, 'ceil') %}
            {% set mileage = states('sensor.none_odometer') | int %}
            {{ (mileage / days_since_specific_date) }}
          icon: mdi:car

    2. Target Daily Allowance Until Lease Ends

    - sensor:
        - name: "Average Tesla Mileage Remaining"
          unique_id: average_tesla_mileage_remaining
          state_class: "total"
          device_class: "distance"
          unit_of_measurement: "mi"
          state: >
            {% set specific_date = "2027-02-09" %}
            {% set current_date = now().date() %}
            {% set specific_date_obj = as_timestamp(specific_date) %}
            {% set current_date_obj = as_timestamp(current_date) %}
            {% set days_until_specific_date = ((specific_date_obj - current_date_obj) / 86400) | round(0, 'ceil') %}
            {% set mileage = states('sensor.none_odometer') | int %}
            {% set mileage_remaining = 45000 - mileage %}
            {{ (mileage_remaining / days_until_specific_date) }}
          icon: mdi:car

    Markdown Card: Days Owned

    This card is created using a simple markdown card that calculates the number of days since delivery:

    - type: markdown
      content: >-
        #### Days Owned
    
        {% set specific_date = "2024-02-10" %}
        {% set current_date = now().date() %}
        {% set specific_date_obj = as_timestamp(specific_date) %}
        {% set current_date_obj = as_timestamp(current_date) %}
        {% set days_since_specific_date = ((current_date_obj - specific_date_obj) / 86400) | round(0, 'ceil') %}
    
        # {{ days_since_specific_date }}

    It displays a nice clean numeric summary alongside the stats and graph.


    Example Stats (as of today)

    • Odometer: 20,946 miles
    • Daily Avg: 36.75 miles
    • Target Avg: 45.73 miles
    • Days Owned: 570 days

    So I’m under the target, which gives me some breathing room for long trips.

  • Home Assistant Dashboard for Solar PV tracking

    One of the most satisfying parts of running a home solar setup is being able to visualise how your system performs compared to the forecast, how its performing financially, and how might it perform in the next few days.

    home assistant dashboard previewing solar pv

    In this walkthrough I’ll explain at a high level how I’ve combined Solcast, Predbat & Octopus Energy integrations, with Bar Card, Apex Charts and more to get a really great looking dashboard. I’ll do a deeper dive on how its all possible soon.


    Live PV Overview Using Bar Card

    Screenshot 2025-09-01 at 17.36.56

    At the top of this section, I use a compact bar-card layout to display:

    • PV Power: current output from my solar inverter
    • PV Max: the peak output so far today
    • Income: estimated real-time value in £/hour based on grid interaction

    These are not standard stat cards. They are styled bar-card elements, which give me better layout control and a consistent visual design.

    The live PV data (including PV Power and Max) comes from my GivEnergy system, using the excellent GivTCP integration. GivTCP is a local MQTT-based integration that makes real-time data from both the GivEnergy battery and inverter available in Home Assistant. It is fast, reliable, and fully local, which is ideal for anyone running GivEnergy hardware.

    The income figure is calculated using a template sensor that combines live import/export power with live tariff rates from the Octopus Energy integration. This gives a real-time estimate of financial benefit, whether from exported power or avoided import during peak rates. It updates every few seconds and is one of the most useful numbers on the dashboard. The bar card also dynamically changes colour, red for import, green for export.


    Solar PV Forecast vs Actual Chart

    Screenshot 2025-09-01 at 17.36.56

    This section is powered by ApexCharts Card and visualises:

    • Solcast Forecast (grey)
    • 10% Confidence Range (dark grey)
    • Actual PV Today (orange)
    • PV from Yesterday (white dotted line)

    It provides a live comparison throughout the day. I can instantly see whether production is tracking above or below expectations, and by how much.

    The forecast data is retrieved via Predbat. Predbat serves as the bridge between Solcast and Home Assistant, pulling in high-resolution forecasts and exposing them as sensors. I then feed these directly into the chart.


    5-Day Forecast Using Bar Card

    Screenshot 2025-09-01 at 17.36.56

    Just below the chart, I show a 5-day solar forecast using bar-card again. This includes:

    • The Forecast for today and the next 4 days
    • Colour-coded bars based on expected total generation

    Each bar represents total energy (kWh) for the day, with dynamic colouring applied using templates. Lower days show in red, wheras higher days in orange. I’ve configured these in such a way that it uses entities for max PV values (35 kWh in my case) as well as my "threshold" for minimum (around your average use). This enables the dynamic colour coding of the bars.

    If you want to replicate this setup, I’ve created a full walkthrough: PV Card Preview GitHub Repo

    The guide includes:

    • How to access and format forecast data from Predbat
    • Template examples for value display and colour thresholds
    • Complete YAML for the 5-day bar-card layout

    Forecast vs Reality Delta

    Screenshot 2025-09-01 at 17.36.56

    At the bottom of this section, I include a summary of:

    • Forecasted PV power for right now
    • Forecasted PV energy for right now
    • Percentage difference between the forecast and reality

    For example, as of this image I am 38% up on the forecasted PV energy generation today. These numbers dynamically change as the hours go during the day, giving me a strong signal for how much better or worse reality is compared to the prediction.