🌞🔋 Since I last blogged about Home Assistant automations, I have taken a big step forward in sustainable living by installing a photo voltaic (PV) system with an energy storage unit. 🌱 This exciting upgrade has unlocked incredible opportunities for automating and optimizing energy usage in my home, making it smarter and greener than ever! 🏡✨

Energy Distribution

PV installation

I have a 6.3 kWp PV system with a 10 kWh battery storage unit installed in my utility room.

PV Installation

At the moment I am buying electricity at a flat rate of 1.2 PLN/kWh (0.28 €/kWh) including all taxes and fees. I can also sell excess energy back to the grid at dynamic hourly rates. The selling price depends on the time of the day and the current demand for energy. Charts below show the hourly rates for today and tomorrow, in PLN/MHh (to get the price in EUR or USD, just divide by ~4.3).

Price Forecast

Requirements

To make the most of my solar energy system, I have outlined the following key requirements for efficient energy management and automation:

⚡️ Prioritize self-consumption over exporting energy to the grid, as it’s the most cost-effective approach for my setup.

🌍 Export energy to the grid only when the current and forecasted household consumption is fully covered by on-site generation.

🚫 Limit energy export to the grid when hourly electricity prices are zero or negative. Exporting under such conditions provides no financial benefit and only generates unnecessary heat in the inverter.

Inverter remote control

The most important part of the setup is the ability to control the inverter remotely. I have a Solis hybrid inverter, which is compatible with the Solis Cloud API. Unfortunatelly, the existing solis-sensor integration covers only the monitoring part, the control part is experimental and unstable, see #437.

This project also provided an excellent opportunity to develop my own custom integration for Home Assistant, tailored specifically for controlling the Solis inverter. You can find the source code and detailed documentation here:

Solis Cloud Control Integration

At the time of writing, the integration allows for controlling the inverter in the following ways:

Solis Control

Energy Production Forecasting

An essential component of my setup is the ability to accurately forecast energy production. By integrating Solcast with Home Assistant, I can access detailed energy production forecasts tailored to my PV installation.

This integration provides valuable insights into expected solar generation, enabling better planning and optimization of energy usage and storage.

Production Forecast

Solis Inverter Modes of Operation

Solis inverters support three primary modes of operation:

🔋 Self-Use: In this mode, the inverter prioritizes using energy generated by the PV system to power the home and charge the battery. Any excess energy is exported to the grid.

In-Feed Priority: This mode prioritizes selling energy to the grid. The battery will neither charge nor discharge unless “Time Charging” is enabled and properly configured.

🌐 Off-Grid: Designed for installations without grid power, this mode isn’t relevant to this project.

Automation for inverter mode

By default, my installation operates in Self-Use mode, which effectively handles most scenarios. However, under specific conditions, automation switches the inverter to In-Feed Priority mode to maximize energy export. These conditions include:

  • The house is in “away mode,” indicating no one is home and excess energy is available.
  • The sun is above the horizon, ensuring sufficient energy production.
  • Hourly electricity prices are favorable and exceed the minimum price of the day.
  • The remaining energy production forecast for the day is sufficient to fully recharge the battery, ensuring optimal energy storage for later use.
  • Today’s temperature is high enough to minimize excessive power consumption for heating, considering the current state of charge (SOC) of the battery.
  • The current household power consumption is low, which aligns with the current battery SOC to optimize energy usage.

In all other scenarios, the inverter reverts to Self-Use mode to optimize energy usage and storage.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
- alias: Solar - mode optimization
  id: solar_01
  triggers:
    - trigger: state
      entity_id: input_boolean.away_mode
    - trigger: time_pattern
      minutes: 0
      seconds: 30 # to get up-to-date hourly statistics
    - trigger: time_pattern
      minutes: 30
  variables:
    # Battery SOC [%]
    battery_soc: "{{ states('sensor.solis_remaining_battery_capacity') | float }}"
    battery_reserve: "{{ states('sensor.inverter_control_battery_reserve_soc') | float }}"
    battery_max_charge: "{{ states('sensor.inverter_control_battery_max_charge_soc') | float }}"
    battery_threshold_low: "{{ [battery_reserve | float + 10, battery_max_charge] | min }}"
    battery_threshold_high: "{{ [battery_reserve | float + 40, battery_max_charge] | min }}"

    # Prices [PLN/kWh]
    price: "{{ states('sensor.solar_electricity_price_hourly_rate') | float }}"
    price_min: "{{ states('sensor.solar_electricity_price_hourly_rate_min') | float }}"
    price_valley_threshold: "{{ states('input_number.solar_electricity_price_valley_threshold') | float }}"
    price_threshold: "{{ [price_min, price_valley_threshold] | max }}"

    # PV forecast [kWh]
    pv_forecast: "{{ states('sensor.solcast_pv_forecast_forecast_remaining_today') | float }}"
    pv_forecast_threshold_low: 15
    pv_forecast_threshold_high: 25

    # Temperature forecast [°C]
    temp_forecast: "{{ states('sensor.weather_temperature_today') | float }}"
    temp_forecast_threshold: 5

    # Power consumption [W]
    power_consumption: "{{ states('sensor.solar_load_power') | float }}"
    power_consumption_threshold: 500

    # Export power [W]
    export_power_capped: 1000
    export_power_nominal: 13200
    export_power: "{{ export_power_nominal if price > 0.01 else export_power_capped }}"

  actions:
    choose:
      - conditions:
          - condition: state
            entity_id: input_boolean.away_mode
            state: "on"
          - condition: state
            entity_id: sun.sun
            state: above_horizon
          - condition: template
            alias: If price is decent
            value_template: "{{ price > price_threshold }}"
          - condition: template
            alias: If PV forecast meets battery SOC criteria
            value_template: >
              {{ (pv_forecast > pv_forecast_threshold_high and battery_soc > battery_threshold_low) or 
                  (pv_forecast > pv_forecast_threshold_low and battery_soc > battery_threshold_high) }}
          - condition: template
            alias: If temperature meets battery SOC criteria
            value_template: >
              {{ (temp_forecast > temp_forecast_threshold and battery_soc > battery_threshold_low) or
                  (temp_forecast <= temp_forecast_threshold and battery_soc > battery_threshold_high) }}
          - condition: template
            alias: If power consumption meets battery SOC criteria
            value_template:
              "{{ (power_consumption < power_consumption_threshold and battery_soc > battery_threshold_low) or
              (power_consumption >= power_consumption_threshold and battery_soc > battery_threshold_high) }}"
        sequence:
          - action: select.select_option
            entity_id: select.inverter_control_storage_mode
            data:
              option: Feed-In Priority
          - action: number.set_value
            entity_id: number.inverter_control_max_export_power
            data:
              value: "{{ export_power }}"
      default:
        - action: select.select_option
          entity_id: select.inverter_control_storage_mode
          data:
            option: Self-Use
        - action: number.set_value
          entity_id: number.inverter_control_max_export_power
          data:
            value: "{{ export_power }}"

Selling energy during peak hour

The second automation schedules the inverter to sell energy during peak hour, when the following conditions are met:

  • The house is working in eco mode. I have excess energy only when there is no one at home, or we’re going to leave soon.
  • The price is decent and maximum of the day.
  • Energy production forecast for tommorrow is excellent.
  • Temperature forecast for tomorrow is high enough to minimize excessive power consumption for heating.
  • Baterry is fully charged.
  • There will be enough energy for sale considering typical nighttime consumption.

Time Slots

The most tricky part of the automation is to calculate the amount of energy that can be sold. Ask LLM if you need more details about the alghoritm 😜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
- alias: Solar - schedule discharge slot
  id: solar_02
  triggers:
    - trigger: state
      entity_id: input_boolean.eco_mode
    - trigger: time
      at: "15:30:00" # when prices for the next day are available
    - trigger: time
      at: "16:00:00" # backup call
  variables:
    # Battery SOC [%]
    battery_soc: "{{ states('sensor.solis_remaining_battery_capacity') | float }}"
    battery_reserve: "{{ states('sensor.inverter_control_battery_reserve_soc') | float }}"
    battery_max_charge: "{{ states('sensor.inverter_control_battery_max_charge_soc') | float }}"
    battery_threshold_low: "{{ [battery_reserve | float + 15, battery_max_charge] | min }}"
    battery_threshold_high: "{{ [battery_max_charge | float - 10, battery_threshold_low] | max }}"

    battery_capacity: 10000 # Wh
    battery_voltage: "{{ states('sensor.solis_battery_voltage') | float }}" # V

    # PV forecast [kWh]
    pv_forecast: "{{ states('sensor.solcast_pv_forecast_forecast_tomorrow') | float }}"
    pv_forecast_threshold: 25

    # Temperature forecast [°C]
    temp_forecast: "{{ states('sensor.weather_temperature_tomorrow') | float }}"
    temp_forecast_threshold: 5

    # Peak price and time
    peak_raw: "{{ states('sensor.solar_electricity_price_hourly_rate_max_next_raw') | from_json }}"

    peak_price: "{{ peak_raw.price }}"
    peak_price_threshold: >
      {{ states('input_number.solar_electricity_price_peak_threshold') | float }}

    peak_time_hours: 1
    peak_time: >
      {% set from = '%02d:00' | format(peak_raw.hour) %}
      {% set to = '%02d:00' | format((peak_raw.hour + peak_time_hours) % 24) %}
      {{ from ~ "-" ~ to }}

    # Export energy [#Wh]
    energy_to_export_threshold: 1000
    energy_to_export: >
      {% set energy_available = (battery_soc - battery_threshold_low) / 100 * battery_capacity %}

      {% set sunset = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
      {% set sunrise = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
      {% if sunset > sunrise %}
          {% set sunset = sunset - 86400 %}
      {% endif %}
      {% set night_duration = sunrise - sunset  %}

      {% set night_avg_consumption = 300 %}
      {% set energy_night_consumption = (night_duration / 3600) * night_avg_consumption %}

      {{ [energy_available - energy_night_consumption, 0] | max }}

    # Export current [A]
    export_current_max: 100
    export_current: "{{ [energy_to_export / battery_voltage / peak_time_hours, export_current_max] | min }}"

  actions:
    choose:
      - conditions:
          - condition: state
            entity_id: input_boolean.eco_mode
            state: "on"
          - condition: template
            alias: If price is decent
            value_template: "{{ peak_price > peak_price_threshold }}"
          - condition: template
            alias: If PV forecast is good
            value_template: "{{ pv_forecast > pv_forecast_threshold }}"
          - condition: template
            alias: If temperature forecast is good
            value_template: "{{ temp_forecast > temp_forecast_threshold }}"
          - condition: template
            alias: If battery SOC is high
            value_template: "{{ battery_soc > battery_threshold_high }}"
          - condition: template
            alias: If there is enough energy to export
            value_template: "{{ energy_to_export > energy_to_export_threshold }}"
        sequence:
          - action: text.set_value
            entity_id: text.inverter_control_slot1_discharge_time
            data:
              value: "{{ peak_time }}"
          - action: number.set_value
            entity_id: number.inverter_control_slot1_discharge_current
            data:
              value: "{{ export_current }}"
          - action: switch.turn_on
            entity_id: switch.inverter_control_slot1_discharge
      default:
        - action: switch.turn_off
          entity_id: switch.inverter_control_slot1_discharge

Expose excess energy mode

The third automation introduces an “excess energy” mode, which can be utilized by other automations to determine when surplus energy is available for consumption. For instance, a bathroom heater automation can leverage this mode to activate the electric heater only when excess energy is detected.

Excess power sensor is calculated as follows:

1
2
3
4
5
6
7
8
9
10
11
12
template:
  - sensor:
      - name: Solar Excess Power
        unit_of_measurement: W
        device_class: power
        icon: mdi:lightning-bolt-outline
        state: >
          {% set pv = states('sensor.solar_pv_power') | float %}
          {% set battery = states('sensor.solar_battery_power') | float %}
          {% set load = states('sensor.solar_load_power') | float %}
          {% set excess = pv - (load + battery) if battery >= 0 else 0 %}
          {{ [excess, 0] | max }}

Automation for setting the excess energy mode is triggered by the following conditions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- alias: Solar - set excess energy mode "on"
  id: solar_03
  triggers:
    - trigger: numeric_state
      entity_id: sensor.solar_electricity_price_hourly_rate
      below: input_number.solar_electricity_price_valley_threshold
    - trigger: numeric_state
      entity_id: sensor.solar_excess_power
      above: 500
      for:
        minutes: 10
  conditions:
    - condition: numeric_state
      entity_id: sensor.solar_electricity_price_hourly_rate
      below: input_number.solar_electricity_price_valley_threshold
  actions:
    - action: input_boolean.turn_on
      entity_id: input_boolean.solar_excess_energy_mode

- alias: Solar - set excess energy mode "off"
  id: solar_04
  triggers:
    - trigger: numeric_state
      entity_id: sensor.solar_electricity_price_hourly_rate
      above: input_number.solar_electricity_price_valley_threshold
    - trigger: numeric_state
      entity_id: sensor.solar_excess_power
      below: 100
      for:
        minutes: 10
  conditions:
    - condition: numeric_state
      entity_id: sensor.solar_electricity_price_hourly_rate
      above: input_number.solar_electricity_price_valley_threshold
  actions:
    - action: input_boolean.turn_off
      entity_id: input_boolean.solar_excess_energy_mode

Summary

With a PV system of this size, I could have simply left the inverter in “Self-Use” mode, and the overall results wouldn’t differ significantly. However, I approached this entire experiment as an excellent opportunity to learn and have fun. By diving into the intricacies of energy management and automation, I gained valuable insights and hands-on experience that not only enhanced my technical skills but also made my home smarter and more efficient.

Energy Dashboard

Don’t forget to add a ⭐️ to my project on GitHub if you find it useful! https://github.com/mkuthan/solis-cloud-control

Updated:

Comments