How to estimate solar panel output in Home Assistant with a Lux/UV sensor
In this article, I'll share how I estimate solar panel output in Home Assistant using a simple Lux/UV sensor from my Bresser 7-in-1 weather station, after losing direct access to inverter data when I got a home battery system.
This was helpful for me to get back the solar production information that I had lost, and optimize my electricity usage again based on that - and while the home battery system ("Kotiakku" by Elisa) now does have an API, it seems to be a bit flaky, so I'm keeping the approximation as a backup.
I'll walk you through the background, the problem I faced, and the solution I implemented in Home Assistant to get a practical estimate of solar production from sensor data and sun position.
Quick answer: yes, you can estimate PV production without inverter data
If your inverter integration is missing, broken, or held hostage behind a flaky API, you can still estimate PV output in Home Assistant using:
- Solar radiation (W/m2) from a weather station
- Sun elevation and azimuth from
sun.sun - Panel tilt and azimuth
- A simple temperature derating factor
No, this is not billing-grade metering. But for automations (EV charging, water heating, load shifting), it's plenty good enough.
Background: Why I needed this (and why you might too)
If you don't like rants and background stories, feel free to skip to the section called "Solution" below. 🙈
As usual, little background (and ranting) to get us started on the right foot. I've been a pretty happy solar panel owner since 2019. I paid about 6 000 EUR for my 6 kWp system, that I got installed on the roof of my barn, including a 6 kW inverter. It's been producing almost 6000 kWh per year ever since, and while in Finland it produces absolutely nothing when I most need it (December-February), it's still been a great investment overall. For 9 months of the year, it covers some if not all of my electricity needs.
About 1.5 years ago I jumped on board the home battery bandwagon. A Finnish teleoperator, Elisa, offered a pretty attractively priced 28 kWh home battery system, and I figured that's going to make my solar panel system even more useful, as I can store the excess energy produced during the day and use it at night.
Coupled with Home Assistant optimizing the use of my electricity around the house pretty aggressively, I figured I'll get the cheap juice and can even store the extra solar in the battery to use during the night.
The reality has been a little bit more disappointing than that.
One of the key selling points of the home battery system ("Elisa Kotiakku") was its "Artificial Intelligence" (that's what an algorithm is called now), which would mean it learns from my usage patterns and is able to optimize electricity usage and storage in the battery.
And I'm sure it learns some patterns - but there's one pretty key pattern it does NOT learn: my house already optimizes electricity usage based on price!
What do I mean by that?
Well - the battery expects that we will always charge our cars at the same time. It thinks we will always use water heater at the same time. It likely thinks our floor heating is always on. It expects AC to always run on the same schedule. It expects us to turn the lights on and off based on the same pattern every single day. And it simply can't fathom that someone could even occasionally wash their clothes when it's cheap (the last thing is the only part that Home Assistant doesn't control in the house!)
And since this was a turnkey solution, there's absolutely nothing I can do about it.
Problems
That brings us to the problems at hand.
Problem 1: The Home Battery doesn't learn, and doesn't give me the data I need to optimize around it
At the very basic level, the battery's algorithm DOES work. It mostly charges when it's cheap, it often discharges when it's expensive, and for the most part it uses excess solar production to charge itself, which is great.
But it can not learn the pattern of "there's about 18kW of flexible load in the house which will turn on and off based on multiple factors". So it will end up competing with other flexible loads in the house for cheap electricity, sometimes discharge only to charge a car battery even if electricity was borderline free, charge when electricity costs 5c/kWh only to discharge 15 minutes later when electricity costs 2c/kWh, and so on.
To add insult to injury, the battery initially (for well over a year) took away any knowledge I had of my solar production, so that signal was removed from my other optimization workloads.
My old Fronius inverter had pretty good APIs that I used to optimize electricity usage based on solar production. Some of my automations ran on that information - for example, I had an automation that would turn on the water heater when there was excess solar production. And one that only enabled AC when there was excess solar production (we don't need AC for most of the year in Finland). And a bunch of others.
The infuriating part was that the inverter that Elisa is using actually DOES have pretty good APIs, and even a "guest" or "kiosk" mode that would allow me to pull the information and let Elisa be the owner of the system. But that was not an option for some reason. I had to choose between giving up all information about my solar production, or giving up the battery. I chose the former, but begrudgingly.
That was until earlier this year, when I got my Bresser 7-in-1 weather station, which includes a pretty good Lux/UV sensor. I figured that I could use the Lux/UV sensor to estimate the solar production, and use that information to optimize my electricity usage again.
Problem 2: The weather stations seem to be different levels of nightmares to set up
I had pondered getting a weather station anyway, and the need for a solar radiation sensor just gave me the final push to get one. My wife - ever the sleuth - took note and went and bought one for me!
Namely, she went and bought a TFA Dostmann weather station, which had the sensors I needed, and (according to Verkkokauppa.com) had great Home Assistant integration!
The configuration experience of the TFA device was very German in all the wrong ways:
- The configuration process had lot of steps
- The device came with detailed documentation (but only on paper)
- The documentation didn't always match the actual product
- The app did not support signing in (for privacy reasons of course), so no devices are persisted if the app has to be reset)
- The only way to try to fix a sync issue with a sensor was to reset the app (which of course also resets all the other sensors, so you have to set them up again)
- There was no logging of any kind (for privacy reasons I assume)
- Failing sensors are automatically and brutally removed from the system (any joke I could make about this would be so on the nose that I won't even make it)
- Home Assistant sync was local, which is great, but since none of the sensors worked, it was pretty much useless (kinda like having heated seats in your BMW but due to a timeout the car can't check if you paid for the subscription so the heated seats don't work)
TFA Dostmann support were at least very helpful, though, and after about 2 weeks of reinstallations and resets they just told me to return the device, and get a new one.
Which I did, it just wasn't a TFA Dostmann device. Got a Bresser for about theme price, and thank goodness it was slightly less German!
The configuration experience of the Bresser was still probably worth another blog post - but unlike the TFA Dostmann weather station I had before, I was eventually able to get it working and synchronizing with Home Assistant.
And the first thing I did (of course) was to use it to get back the Solar PV production information that I had lost when I got the battery. 😅
Solution: a Home Assistant template sensor that does the job
So I got hacking. Or, well, GitHub Copilot did.
The weather station has an irradiance sensor that returns a W/m2 value, and I can get the sun position from Home Assistant's built-in sun integration.

With those two pieces of information, I can make a pretty good estimate of the solar production, since I of course know the setup of my solar array, and can (with some help from Skynet) make some assumptions about the diffuse vs direct share of the irradiance, the panel heating, and the temperature derating.
- trigger:
- platform: state
entity_id:
- sensor.bresser_bresser_solar_radiation
- sun.sun
- platform: homeassistant
event: start
- platform: time_pattern
minutes: "/5"
sensor:
# Approximate PV production based on measured global horizontal irradiance,
# current sun position and a few fixed assumptions about the array.
- name: Approximate solar output
unique_id: approximate_solar_output
icon: mdi:solar-power-variant-outline
device_class: power
unit_of_measurement: "kW"
state_class: measurement
state: >
{% set irradiance = states('sensor.bresser_bresser_solar_radiation')|float(0) %}
{% set elevation = state_attr('sun.sun', 'elevation')|float(-90) %}
{% set azimuth = state_attr('sun.sun', 'azimuth')|float(0) %}
{% set bresser_temp_state = states('sensor.bresser_bresser_outdoor_temperature') %}
{% set bresser_temp = bresser_temp_state|float(0) %}
{% set fallback_temp = states('sensor.openweathermap_temperature')|float(0) %}
{% set ambient_air_temp = bresser_temp if bresser_temp_state not in ['unknown', 'unavailable', 'none', ''] else fallback_temp %}
{% set panel_kwp = 6 %}
{# Empirical system calibration: this SHOULD of course be under 1, but these panels produce way over their nameplate even years after installation; set to slightly above 1.0 to keep cold, clear peak conditions accurate. #}
{% set performance_ratio = 1.06 %}
{# Rough tilt estimate until the real mounting angle is known. #}
{% set panel_tilt_deg = 35 %}
{# Rough azimuth estimate for a South-South-East facing array. #}
{% set panel_azimuth_deg = 157.5 %}
{# Simplified diffuse-vs-direct irradiance split for cloudy Nordic conditions. #}
{% set diffuse_share = 0.35 %}
{# NOCT-style panel heating estimate: about 22 C rise per 1000 W/m2 on the panel plane. #}
{% set panel_heating_per_wm2 = 0.022 %}
{# Typical crystalline silicon power temperature coefficient. #}
{% set temp_coefficient_per_c = 0.0035 %}
{% set deg_to_rad = 0.017453292519943295 %}
{% if irradiance <= 0 or elevation <= 0 %}
0
{% else %}
{% set elevation_rad = elevation * deg_to_rad %}
{% set tilt_rad = panel_tilt_deg * deg_to_rad %}
{% set azimuth_delta_rad = ((azimuth - panel_azimuth_deg)|abs) * deg_to_rad %}
{% set cos_incidence =
(elevation_rad|sin * tilt_rad|cos)
+ (elevation_rad|cos * tilt_rad|sin * azimuth_delta_rad|cos)
%}
{% set horizontal_sun_component = [elevation_rad|sin, 0.17]|max %}
{% set direct_ratio = [cos_incidence / horizontal_sun_component, 0]|max %}
{% set sky_view_factor = (1 + tilt_rad|cos) / 2 %}
{% set poa_factor_raw = (diffuse_share * sky_view_factor) + ((1 - diffuse_share) * direct_ratio) %}
{% set poa_factor = [[poa_factor_raw, 0]|max, 1.35]|min %}
{% set poa_irradiance = irradiance * poa_factor %}
{% set estimated_cell_temp = ambient_air_temp + (poa_irradiance * panel_heating_per_wm2) %}
{% set temperature_factor = [1 - ((estimated_cell_temp - 25) * temp_coefficient_per_c), 0.88]|max %}
{{ (panel_kwp * performance_ratio * (irradiance / 1000) * poa_factor * temperature_factor)|round(2) }}
{% endif %}
attributes:
sampled_at: "{{ now().isoformat() }}"
source_sensor: sensor.bresser_bresser_solar_radiation
temperature_source_primary: sensor.bresser_bresser_outdoor_temperature
temperature_source_fallback: sensor.openweathermap_temperature
panel_peak_power_kwp: 6
panel_azimuth_degrees: 157.5
panel_tilt_degrees: 35
performance_ratio: 1.06
performance_ratio_note: Empirical calibration so cold, clear, well-aligned Finnish peak conditions can still land a bit over the 6 kWp nameplate after temperature derating.
panel_heating_per_wm2: 0.022
panel_heating_note: Simple NOCT-style cell-temperature estimate based on panel-plane irradiance.
temperature_coefficient_per_c: 0.0035
temperature_coefficient_note: About 0.35 percent power loss per degree C above the 25 C STC cell temperature.
Truth be told, I would've never created a template with this many variables myself; Copilot suggested the whole logic for figuring out the part of irradiance coming from diffuse vs direct sunlight, the panel heating estimate and the temperature derating, and all I had to do was to add some fixed assumptions about the array and a bit of empirical calibration to get it to match the actual production pretty well.
It has some magic numbers (like the floor for the "horizontal sun component" that is used to split the diffuse vs direct share), but since it works...
Yeah. It'll do for now.
Tuning this for your own setup
If you copy this approach, these are the four knobs you should touch first:
panel_kwp: installed panel capacity in kWppanel_tilt_deg: mounting tilt anglepanel_azimuth_deg: panel directionperformance_ratio: empirical calibration factor
Then compare against real inverter output for a few clear days and a few cloudy days. Change one thing at a time, unless chaos is your preferred debugging strategy.

Follow-up: approximation vs inverter API data
Elisa launched their first Kotiakku API version about a month ago. And 2 weeks BEFORE they did, an enthusiast had already hacked together a Home Assistant integration for it.
Nice.
At the time of writing, I've been pulling the data from their API for just enough time to validate that my approximation was actually quite good.
So next, I'll be flipping over from the approximation to using the actual data from the battery, and see how that goes.
The approximation stays as a backup - since it is so good, and (unlike Elisa's currently fairly flaky API) it is available all the time.
It is a local solution, after all, so it doesn't need to run to Chinese servers(*) to get the data. So even if Elisa's API is down, I can still optimize my electricity usage based on the approximation, and get pretty much the same result as with the real data.
And of course I wish Elisa had been faster with the API. I would've loved to have the real data for the past 1.5 years, instead of having to rely on an approximation that I only got working earlier this spring. But better late than never, I guess.
Footnotes
* I have no idea where Elisa's API is actually hosted, but the inverter IS a Huawei, so I'll just assume that they're first sending all of the data to Chairman Xi, who then checks the receiver's social score before deciding whether to send the data back to Finland or not. I mean, it is 2026, after all.
- This is how my automation for turning on the lights in my kitchen pantry looks like. A roundtrip to China takes about a second, so the lights turn on with a pretty noticeable delay, but it is what it is.
** Here's the madlad with the Home Assistant integration for the Elisa Kotiakku: https://github.com/Jarauvi/elisa_kotiakku
PS. How do you like the footless cover image? Figured mentioning that in the FOOTNOTES would be pretty fitting!
I don't usually use AI to generate feet, so I was surprised to learn how Copilot was seemingly unable to create any for the person in the picture, even after yours truly asking multiple times with different wording.
Must be some sort of new content guideline by Microsoft - no feet pics for y'all today!
Oh well, that'll just drive more traffic to Grok...
Comments
No comments yet.