How to Determine Whether It Is Sunny from Photovoltaic Power Output

Description

If you have a photovoltaic installation, you can use its current power output as an indicator for sunshine.

Using a fixed watt threshold is too simplistic because clear-sky PV output depends strongly on the sun’s elevation above the horizon. A practical approximation is to estimate the current clear-sky power generation (i.e., the potential maximum) from the sun elevation and then compare the actual PV power against that estimate.

This guide shows how to implement that logic in Home Assistant and then feed the resulting sunny/not-sunny signal into Smart Cover Automation via its Weather: sunny? (external control) switch.

Many systems are not perfectly symmetric around solar noon. East- or west-facing arrays, mixed roof orientations, and local shading can make the PV curve rise more gently in one half of the day and fall more steeply in the other. In that case, using one shape factor for the morning and another for the afternoon often gives a better estimate.

Requirements

  • A PV system whose power curve is reasonably smooth and has a single broad peak on a sunny day.
  • A Home Assistant sensor that reports your current PV power generation in watts.
  • The Home Assistant sun integration enabled so that sun.sun provides the current elevation and azimuth.
  • Smart Cover Automation installed.
  • The Smart Cover Automation entity Weather: sunny? (external control) enabled.

This setup works best if your PV system is not heavily shaded by nearby buildings or trees.

Implementation

Formula

Estimate the current clear-sky maximum PV power with this formula:

P_max(alpha) = P_peak * sin(alpha)^k

where:

  • alpha is the sun elevation above the horizon
  • P_peak is the absolute clear-sky maximum power of your PV system
  • k is a shape factor

Lower k makes the curve broader: the estimate rises earlier in the morning, stays higher in the shoulders, and falls later in the evening. Higher k makes the curve narrower: the estimate stays lower in the shoulders and drops faster away from the daily peak.

If your PV output is asymmetric, a single value of k may not fit both halves of the day well. A practical solution is to blend between a morning exponent and an afternoon exponent using the sun’s azimuth:

k_eff = blend(k_morning, k_afternoon, azimuth)

This keeps the model simple while allowing a broader morning curve and a steeper afternoon curve.

Then calculate the current normalized PV percentage as:

PV% = 100 * P_current / max(P_max(alpha), P_floor)

The minimum reference power P_floor avoids unrealistic percentages close to sunrise and sunset, where the theoretical output is near zero but your inverter may still report a small non-zero value.

Recommended starting values:

  • P_peak: highest power your PV system generates on sunny days in watts
  • k_morning: 1.0
  • k_afternoon: 1.0
  • Blend start azimuth: 170°
  • Blend end azimuth: 190°
  • Minimum sun elevation for the clear-sky estimate:
  • Minimum sun elevation for the sunny decision:
  • P_floor: 300 W
  • Sunny threshold: 75%

If your system is fairly symmetric, you can simply set k_morning and k_afternoon to the same value.

You should calibrate P_peak, k_morning, k_afternoon, the blend window, and the sunny threshold from a few clear days of your own data (see tips below).

Template Sensors

Paste the following into configuration.yaml.

Replace sensor.your_pv_power_generation with your PV power entity. Also adjust peak_power_w, morning_exponent, afternoon_exponent, the azimuth transition window, and the thresholds to match your installation.

template:
  - sensor:
      - name: "PV Clear-Sky Power Estimate"
        unique_id: pv_clear_sky_power_estimate
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set elevation_deg = state_attr('sun.sun', 'elevation') | float(0) %}
          {% set azimuth_deg = state_attr('sun.sun', 'azimuth') | float(0) %}
          {% set peak_power_w = 6000 %}
          {% set morning_exponent = 1.0 %}
          {% set afternoon_exponent = 1.0 %}
          {% set transition_start_azimuth_deg = 170 %}
          {% set transition_end_azimuth_deg = 190 %}
          {% set min_elevation_deg = 3 %}
          {% set deg_to_rad = 0.017453292519943295 %}
          {% if elevation_deg <= min_elevation_deg %}
            0
          {% else %}
            {% if azimuth_deg <= transition_start_azimuth_deg %}
              {% set blend_weight = 0 %}
            {% elif azimuth_deg >= transition_end_azimuth_deg %}
              {% set blend_weight = 1 %}
            {% else %}
              {% set blend_weight = (azimuth_deg - transition_start_azimuth_deg) / (transition_end_azimuth_deg - transition_start_azimuth_deg) %}
            {% endif %}
            {% set exponent = (morning_exponent * (1 - blend_weight)) + (afternoon_exponent * blend_weight) %}
            {{ (peak_power_w * ((sin(elevation_deg * deg_to_rad)) ** exponent)) | round(0) }}
          {% endif %}

      - name: "PV Utilization"
        unique_id: pv_utilization
        unit_of_measurement: "%"
        state_class: measurement
        state: >
          {% set current_power_w = states('sensor.your_pv_power_generation') | float(0) %}
          {% set estimated_max_w = states('sensor.pv_clear_sky_power_estimate') | float(0) %}
          {% set minimum_reference_power_w = 300 %}
          {% if estimated_max_w <= 0 %}
            0
          {% else %}
            {% set reference_power_w = [estimated_max_w, minimum_reference_power_w] | max %}
            {{ ([100, (100 * current_power_w / reference_power_w)] | min) | round(0) }}
          {% endif %}

  - binary_sensor:
      - name: "PV Sunny"
        unique_id: pv_sunny
        device_class: light
        delay_on:
          minutes: 5
        delay_off:
          minutes: 10
        state: >
          {% set elevation_deg = state_attr('sun.sun', 'elevation') | float(0) %}
          {% set sunny_threshold_percent = 75 %}
          {% set min_decision_elevation_deg = 8 %}
          {% set pv_utilization_percent = states('sensor.pv_utilization') | float(0) %}
          {{ elevation_deg >= min_decision_elevation_deg and pv_utilization_percent >= sunny_threshold_percent }}

This creates three entities:

  • PV Clear-Sky Power Estimate: estimated maximum current PV power for the present sun elevation, with a smooth morning-to-afternoon change in curve shape.
  • PV Utilization: current PV generation as a percentage of the estimated potential maximum for the present sun elevation.
  • PV Sunny: debounced binary sensor that reports whether it is sunny enough according to your threshold. The delay_on and delay_off settings stabilize the sensor and avoid flapping when a cloud passes over, for example.

Connect the Result to Smart Cover Automation

Smart Cover Automation does not read the template binary sensor directly. Instead, enable its entity Weather: sunny? (external control) and drive that switch from the template binary sensor.

Create an automation in Home Assistant and paste the following YAML. Replace switch.smart_cover_automation_weather_sunny_external_control with the actual entity ID of your Smart Cover Automation switch.

alias: SCA weather sunny external control from PV
description: "Set the Smart Cover Automation sunny state according to photovoltaic output."
triggers:
  - trigger: state
    entity_id:
      - binary_sensor.pv_sunny
conditions: []
actions:
  - choose:
      - conditions:
          - condition: state
            entity_id: binary_sensor.pv_sunny
            state: "on"
        sequence:
          - action: switch.turn_on
            metadata: {}
            target:
              entity_id: switch.smart_cover_automation_weather_sunny_external_control
    default:
      - action: switch.turn_off
        metadata: {}
        target:
          entity_id: switch.smart_cover_automation_weather_sunny_external_control
mode: single

After adding the automation, run it manually to set the current state of the Weather: sunny? (external control) entity as the automation is only triggered on state changes.

Calibration

To improve accuracy, calibrate the constants from your own data:

  • Wait for a mostly cloudless day.
  • In Home Assistant’s History viewer, add the actual power generation and the PV clear-sky estimate.
  • The estimate’s graph should trace the general outline of the actual power generation graph.
  • If the estimate’s graph is overall too low, increase peak_power_w (and vice-versa).
  • If the estimate is too low in the morning, decrease morning_exponent slightly. If it is too high, increase morning_exponent.
  • If the estimate falls too slowly in the afternoon, increase afternoon_exponent. If it falls too quickly, decrease afternoon_exponent.
  • If the turnover from morning to afternoon happens too early or too late, move the azimuth blend window accordingly.
  • Adjust sunny_threshold_percent until the PV Sunny sensor matches your local observation.

Typical useful ranges:

  • peak_power_w: your system-specific value
  • morning_exponent and afternoon_exponent: 0.5 to 1.5
  • transition_start_azimuth_deg: 160 to 180
  • transition_end_azimuth_deg: 180 to 200
  • sunny_threshold_percent: 60 to 85
  • minimum_reference_power_w: 200 to 500

Test

To test the setup:

  1. Verify that PV Clear-Sky Power Estimate rises and falls smoothly through the day.
  2. Check that PV Utilization is high on clear days and low during cloudy periods.
  3. Confirm that PV Sunny does not flap rapidly when passing clouds move through.
  4. Enable Smart Cover Automation’s Weather: sunny? (external control) entity and verify that the switch follows binary_sensor.pv_sunny.

If the sunny state flips too often around sunrise or sunset, raise either min_decision_elevation_deg or minimum_reference_power_w.


This site uses Just the Docs, a documentation theme for Jekyll.