Skip to content

Instantly share code, notes, and snippets.

@danielolsson100
Last active November 1, 2025 08:50
Show Gist options
  • Save danielolsson100/4ee739d864dc90dac144e4d026ae090f to your computer and use it in GitHub Desktop.
Save danielolsson100/4ee739d864dc90dac144e4d026ae090f to your computer and use it in GitHub Desktop.
ferroamp - charge or discharge battery sensor with home assistant and nordpool
binary_sensor:
- platform: template
sensors:
bs_charge_ferroamp_battery:
friendly_name: Charge Ferroamp Battery
unique_id: bs_charge_ferroamp_battery
value_template: >-
{# Charge logic (6 hours = 24 quarters at 15-min intervals) - v1.3 #}
{% set sensor = 'sensor.nordpool_kwh_se4_sek_2_10_025' %}
{% set today = state_attr(sensor, 'today') | default([]) %}
{% set tomorrow = state_attr(sensor, 'tomorrow') | default([]) %}
{# Combine today and tomorrow if tomorrow data is available and valid #}
{% set price_array = today + tomorrow if (tomorrow | count > 0 and tomorrow[0] != None) else today %}
{# Get current price and calculate threshold (6 hours = 24 quarters at 15-min intervals) #}
{% set current_price = states(sensor) | float(0) %}
{% set price_threshold = (price_array | sort)[23] if price_array | count >= 24 else (price_array | sort)[-1] %}
{# Return True if current price is at or below the threshold #}
{{ current_price <= price_threshold }}
{#--=[Charge battery logic]=--
----------------------------------
current_price.............: {{current_price}} kr
price_threshold...........: {{price_threshold}} kr #}
bs_discharge_ferroamp_battery:
friendly_name: Discharge Ferroamp Battery
unique_id: bs_discharge_ferroamp_battery
value_template: >-
{# Optimized Home Assistant Battery Discharge Logic - v1.2 #}
{# Calculate energy consumption #}
{% set total_energy = state_attr('sensor.load_energy_consumed_daily', 'last_period') | float(0) %}
{% set ev_energy = state_attr('sensor.easee_energy_daily', 'last_period') | float(0) %}
{% set house_energy = total_energy - ev_energy %}
{% set solar_energy_high = states('binary_sensor.solar_energy_high') | bool %}
{% set solar1 = states('sensor.energy_production_today_remaining') %}
{% set solar2 = states('sensor.energy_production_today_remaining_2') %}
{% set solar3 = states('sensor.energy_production_today_remaining_3') %}
{% set calc_solar_energy_today =
(solar1 not in ['unavailable', 'unknown', None] and solar1|float(0) or 0) +
(solar2 not in ['unavailable', 'unknown', None] and solar2|float(0) or 0) +
(solar3 not in ['unavailable', 'unknown', None] and solar3|float(0) or 0)
%}
{# Battery state #}
{% set battery_soc = states('sensor.ferroamp_eso_19110078_state_of_charge') | float(0) %}
{% set battery_capacity = states('sensor.ferroamp_esm_74210273_rated_capacity') | float(0) / 1000 %}
{% set usable_battery = (battery_capacity * battery_soc / 100) - 1.2 %}
{% set battery_charging = states('binary_sensor.bs_charge_ferroamp_battery') | bool %}
{# Time calculations - use single now() call for performance #}
{% set current_time = now() %}
{% set current_quarter = ((current_time.hour * 4 + ((now().minute // 15) + 1) | int)) %}
{# Energy consumption calculations #}
{% set avg_consumption = house_energy / 24 %}
{% set battery_runtime_quarters = (((usable_battery + calc_solar_energy_today) / avg_consumption) * 4) | int if avg_consumption > 0 else 0 %}
{# Nordpool price data #}
{% set nordpool_prices = state_attr('sensor.nordpool_kwh_se4_sek_2_10_025', 'today') | default([]) %}
{% set remaining_prices = nordpool_prices[current_quarter:] if nordpool_prices is not none else [] %}
{% set remaining_count = remaining_prices | count %}
{# Logic: Discharge battery if current price is high relative to remaining prices #}
{% if battery_charging %}
{# The battery is still charging, let's wait for it to complete #}
False
{% elif solar_energy_high %}
{# Sun energy excess: auto mode to store sun energy #}
True
{% elif battery_runtime_quarters <= 0 %}
{# No runtime estimated or invalid value: don't discharge by price logic #}
False
{% elif remaining_count <= battery_runtime_quarters %}
{# Battery can last all remaining hours: discharge #}
True
{% else %}
{# Check if current price is in top N most expensive remaining prices #}
{% set sorted_remaining = remaining_prices | reject('equalto', None) | list | sort %}
{% set sr_count = sorted_remaining | count %}
{% if sr_count == 0 %}
{# No valid remaining price data -> don't discharge #}
False
{% else %}
{# Compute zero-based index and clamp it to valid range #}
{% set idx = remaining_count - battery_runtime_quarters - 1 %}
{% if idx < 0 %}{% set idx = 0 %}{% endif %}
{% if idx >= sr_count %}{% set idx = sr_count - 1 %}{% endif %}
{% set threshold_price = sorted_remaining[idx] %}
{% set current_price = nordpool_prices[current_quarter] if (nordpool_prices | count) > current_quarter else none %}
{% if current_price in [none, 'unknown', 'unavailable'] %}
False
{% else %}
{{ (current_price | float(0)) >= (threshold_price | float(0)) }}
{% endif %}
{% endif %}
{% endif %}
{#--=[Discharge/AutoCharge logic]=--
----------------------------------
total_energy..............: {{total_energy | round(2)}} kWh
ev_energy.................: {{ev_energy | round(2)}} kWh
house_energy..............: {{house_energy | round(2)}} kWh
avg_consumption...........: {{avg_consumption | round(2)}} kWh
battery_capacity..........: {{battery_capacity | round(2)}} kWh
usable_battery............: {{usable_battery | round(2)}} kWh
calc_solar_energy_today...: {{calc_solar_energy_today | round(2)}} kWh
battery_soc...............: {{battery_soc | round(1)}} %
nordpool_current_price....: {{nordpool_prices[current_quarter-1]}} kr
current_quarter...........: {{current_quarter}} quarters
battery_runtime_quarters..: {{battery_runtime_quarters}} quarters
calc_autocharge_runtime...: {{(battery_runtime_quarters / 4)| round(1)}} h
remaining_count...........: {{remaining_count}} quarters
battery_charging..........: {{battery_charging}} #}
@danielolsson100
Copy link
Author

This binary sensor now requires integrations from Easee/NordPool/Ferroamp and the use of utility meter in HomeAssistant to work
use at own risk ;)

utility_meter:
  easee_energy_hourly:
    source: sensor.easee_lifetime_energy
    cycle: hourly
  easee_energy_daily:
    source: sensor.easee_lifetime_energy
    cycle: daily
  easee_energy_monthly:
    source: sensor.easee_lifetime_energy
    cycle: monthly
  load_energy_consumed_hourly:
    source: sensor.ferroamp_load_energy_consumed
    cycle: hourly
  load_energy_consumed_daily:
    source: sensor.ferroamp_load_energy_consumed
    cycle: daily
  load_energy_consumed_monthly:
    source: sensor.ferroamp_load_energy_consumed
    cycle: monthly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment