I’m not using the ml forecaster.
Just a simple naive MPC method, with PV power forecast coming from forecast.solar, which I manually pass to EMHASS.
p_pv_forecast is actually created by EMHASS
Did you changed the set_use_pv
parameter to True?
Hello, again the same problem with optimization for negative prices. I think there must be some bug in profit cost function, plus I still don’t understand such high negative P_hybrid_inverter values - where should the power go?
Here is the optimization json, just for case if anyone can find some problem…
{
"load_cost_forecast":[4.82, 4.47, 4.15, 3.77, 3.65, 3.45, 3.32, 3.35, 3.48, 3.62, 3.61, 3.47, 2.62, 1.56, 2.1, 0.59, -0.68, -2.15, -1.24, 0.04, 1.41, 2.74, 4.36, 4.9],
"prod_price_forecast":[2.39, 2.1, 1.84, 1.53, 1.42, 1.26, 1.16, 1.18, 1.28, 1.4, 1.39, 1.27, 0.57, -0.3, -0.37, -1.11, -2.67, -3.37, -3.13, -1.56, -0.43, 0.16, 2.01, 2.46],
"prediction_horizon":24,
"pv_power_forecast":[11, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1230, 2498, 3337, 3779, 4384, 5440, 6110, 6437, 6337, 5793, 4891, 4116, 3421, 1660],
"load_power_forecast":[274, 420, 380, 220, 210, 210, 310, 300, 220, 200, 230, 280, 540, 360, 770, 640, 840, 790, 920, 760, 610, 500, 740, 260],
"delta_forecast":2,
"operating_hours_of_each_deferrable_load":[1,4,0,0,0],
"def_current_state":[0,0,0,0,0],
"soc_init":0.64,
"soc_final":0.78,
"battery_minimum_state_of_charge":0.4,
"start_timesteps_of_each_deferrable_load":[0,0,0,0,0],
"end_timesteps_of_each_deferrable_load":[4,0,0,0,0]
}
Allowing the wallbox (deferrable_1) higher power values helped. Like the inverter now knows what to do with the energy overflow. But this is still strange behavior, the full power from the grid is not a must, it is an option, so I would expect only the usable power from grid will be used and scheduled in P_grid_ variables.The profit cost function tries to do the profit even when it is not possible.
After some time, again very bad optimization - no load scheduled during cheapest hour. Unusable when there are negative prices during the day. @davidusb I guess we can consider it as a bug?
Maybe something familiar I see:
Negative feed-in, EMHASS wants to import 4000 W (My upper limit) and there is plenty available for the power load and deferrable from the PV. Where do these 4000W go then considering the battery is already 100% charged. With these very negative prices I run my Huawei inverter with zero export setting, but EMHASS doesn’t ‘see’ this. EDIT: using pv curtailment makes it sensible for EMHASS, so I guess all is fine here
Thanks for doing this - i will read through over the weekend and come back with any questions.
regards
Craig
The current ones i have are the SBP5000 units - i have 3 of them (one per phase) they are happy to export at about 4.6Kw and import at about 5Kw (they derate in the heat)
They have worked flawlessly for me for 4 years and are pushed hard as i use them for Grid Arbitrage
Initially i used them through the WIFI interface and the obsolete AA55 protocol (how the phone app communicates with them) - i suffer multiple failures of the WIFI adapters so in the end i moved to hard wired RS485 and modbus. My Node Red instance runs on a Virtual machine and talks TCP/IP to a 4 port RS485 to TCPIP converter (Modbus TCP to RTU)
Craig
Now that the defaults for
weight_battery_discharge
weight_battery_charge
have been set to 0 (by default), what is the impact on the optimization result when using a battery?
# Add more terms to the objective function in the case of battery use
if self.optim_conf["set_use_battery"]:
objective = objective + plp.lpSum(
-0.001
* self.timeStep
* (
self.optim_conf["weight_battery_discharge"] * P_sto_pos[i]
- self.optim_conf["weight_battery_charge"] * P_sto_neg[i]
)
for i in set_I
)
Is Emhass ready for 12th of June?
Most European countries will change to quarterly prices instead of hourly.
EMHASS can support different timesteps.
I’m using it here (AU) with 5 minute timesteps.
You will likely need to reshape your optimisation payload to include 15 minute price/ cost forecasts.
Indeed 15 min timesteps works fine! I modified my setup a few weeks ago. Nordpool HACS integration is still at 60minutes, so I copy those values 4 times for now. Solcast is 30minutes, so that one is copied twice.
Can you perhaps share your templates as reference?
In sensors.yaml
- platform: template
sensors:
current_quarter_of_the_day:
value_template: "{{ ((now().hour * 60 + now().minute) / 15) | round}}"
In template.yaml
- name: solcast_48hrs_forecast_15minutes_interval
state: none
attributes:
solar_power: >-
{%- set power = state_attr('sensor.solcast_pv_forecast_forecast_today', 'detailedForecast')| map(attribute='pv_estimate') | list + state_attr('sensor.solcast_pv_forecast_forecast_tomorrow', 'detailedForecast')| map(attribute='pv_estimate') | list %}
{%- set values_all = namespace(all=[]) %}
{% for i in range(power | length) %}
{%- set v = (power[i] | float |multiply(1000) ) | int(0) %}
{%- set values_all.all = values_all.all + [ v ] %}
{%- set values_all.all = values_all.all + [ v ] %}
{%- endfor %} {{ (values_all.all)[:192] }}
- name: solcast_48hrs_forecast_15minutes_interval_current_quarter
state: none
attributes:
solar_power: >-
{{ state_attr('sensor.solcast_48hrs_forecast_15minutes_interval','solar_power')[states('sensor.current_quarter_of_the_day') |int:192] }}
- name: solcast_48hrs_forecast_15minutes_interval_current_quarter_count
state: >-
{{ state_attr('sensor.solcast_48hrs_forecast_15minutes_interval_current_quarter','solar_power') | list | count }}
Is there a way force a Huawei sun inverter (sun2000-6ktl-m1) to take power from the grid instead the PV installation with negative buying prices? Don’t think it’s possible but maybe I am missing something here. thx
On my huawei i use Discharge = 0w. And huawei_solar.set_maximum_feed_grid_power_percent = 0
Do you perhaps can also share the Nordpool template? Many thanks in advance
alias: Kontrollera matning till nätet baserat på elpris
description: Stoppar export vid negativt elpris, tillåter vid positivt elpris
triggers:
- trigger: time_pattern
hours: /1
actions:
- choose:
- conditions:
- condition: numeric_state
entity_id: sensor.nordpool_kwh_se3_sek_3_10_0
below: -30
sequence:
- data:
device_id: 877400bca09c5e673ff27b201c71d371
power_percentage: 0
action: huawei_solar.set_maximum_feed_grid_power_percent
- conditions:
- condition: numeric_state
entity_id: sensor.nordpool_kwh_se3_sek_3_10_0
above: -30
sequence:
- data:
device_id: 877400bca09c5e673ff27b201c71d371
action: huawei_solar.reset_maximum_feed_grid_power
mode: single
Sure no problem!
In template.yaml:
#the prices as of now
- name: nordpool_upcoming_quarters
state: none
attributes:
costprices: >-
{%- set costprices = ((state_attr('sensor.nordpool_kwh_nl_eur_3_10_0', 'raw_today') | map(attribute='value') | list + state_attr('sensor.nordpool_kwh_nl_eur_3_10_0', 'raw_tomorrow') | map(attribute='value') | list))[now().hour:][:24] %}
{%- set values_all = namespace(all=[]) %}
{% for i in range(costprices | length) %}
{%- set v = (costprices[i] | float ) %}
{%- set values_all.all = values_all.all + [ v ] %}
{%- set values_all.all = values_all.all + [ v ] %}
{%- set values_all.all = values_all.all + [ v ] %}
{%- set values_all.all = values_all.all + [ v ] %}
{%- endfor %} {{ (values_all.all)[(states('sensor.index_of_current_quarter') | int):][:192] }}
In sensors.yaml:
# 0, 1, 2 or 3 depending on minutes in current hour
- platform: template
sensors:
index_of_current_quarter:
friendly_name: "Index of current quarter"
device_class: duration
value_template: >-
{{ ((now().minute / 15)) | int }}
@markpurcell Just got the email advising that I’m moving to 5 minute billing and I’m going overseas in a few days. Wouldn’t you know it.
I’ve implemented amber2mqtt and created the template below to replace my old one.
{
{#- Forecast of production price (feed-in tariff) -#}
"prod_price_forecast": {{
[states('sensor.amber_5min_current_feed_in_price')|float(0)] +
state_attr('sensor.amber_billing_interval_forecasts_feed_in_price', 'Forecasts')
| selectattr('advanced_price_predicted','is_number')
| map(attribute='advanced_price_predicted')
| map('multiply',-1)
| list
}},
{#- Determine if it's within the seasonal demand tariff period (March–August or November–December) -#}
{%- set current_month = now().month -%}
{%- if current_month in [11, 12, 1, 2, 3, 6, 7, 8] -%}
{#- Data Dictionary name inside if/then loop #}
"load_cost_forecast":
{%- set current_time = now() -%}
{#- Define demand tariff start and end times -#}
{%- set demand_tariff_start = "15:00:00" -%}
{%- set demand_tariff_end = "21:00:00" -%}
{#- Convert start and end times to datetime objects for today -#}
{%- set start_time = current_time.replace(hour=0, minute=0, second=0, microsecond=0) +
timedelta(hours=(demand_tariff_start.split(":")[0]|int), minutes=(demand_tariff_start.split(":")[1]|int))
-%}
{%- set end_time = current_time.replace(hour=0, minute=0, second=0, microsecond=0) +
timedelta(hours=(demand_tariff_end.split(":")[0]|int), minutes=(demand_tariff_end.split(":")[1]|int))
-%}
{#- Ensure the end time is after the start time (adjust for overnight scenarios) -#}
{%- if end_time <= start_time -%}
{%- set end_time = end_time + timedelta(days=1) %}
{%- endif %}
{#- Retrieve current and forecasted general price data -#}
{%- set values = (
[states('sensor.amber_5min_current_general_price')|float(0)] +
state_attr('sensor.amber_billing_interval_forecasts_general_price', 'Forecasts')
| selectattr('advanced_price_predicted','is_number')
| map(attribute='advanced_price_predicted')
| list
)
-%}
{%- set ns = namespace(x=[]) %}
{#- Iterate through forecasted values and adjust pricing during demand tariff window
If within demand tariff hours and price is below $1, apply a fixed cost of 1.0
-#}
{%- for i in range(values|length) %}
{%- set future_time = current_time + timedelta(minutes=i * 30) %}
{%- if start_time <= future_time < end_time and values[i] < 1 %}
{%- set ns.x = ns.x + [1.0] %}
{%- else %}
{%- set ns.x = ns.x + [values[i]] %}
{%- endif %}
{%- endfor %}
{{- ns.x | tojson }},
{%- else %}
{#- Retrieve current and forecasted general price data if not in the demand tariff period -#}
"load_cost_forecast": {{
[states('sensor.amber_5min_current_general_price')|float(0)] +
state_attr('sensor.amber_billing_interval_forecasts_general_price', 'Forecasts')
| selectattr('advanced_price_predicted','is_number')
| map(attribute='advanced_price_predicted')
| list
}},
{%- endif -%}
{#- Forecast of consumption price (demand tariff) -#}
"pv_power_forecast": {{
([states('sensor.sonnenbatterie_84324_production_w')|int(0)] +
state_attr('sensor.solcast_pv_forecast_forecast_today', 'detailedForecast')|selectattr('period_start','gt',utcnow()) | map(attribute='pv_estimate')|map('multiply',1000)|map('int')|list +
state_attr('sensor.solcast_pv_forecast_forecast_tomorrow', 'detailedForecast')|selectattr('period_start','gt',utcnow()) | map(attribute='pv_estimate')|map('multiply',1000)|map('int')|list
)| tojson
}},
{#- Current and Forecast consumption power derived from previoud 24h of consumption -#}
"load_power_forecast": {{
([states('sensor.house_power_consumption_less_deferrables')|int(0)] +
states('input_text.fifo_buffer').split(',') | map('int') | list)
}},
{#- Prediction horizon (number of forecast intervals available) -#}
"prediction_horizon": {{
state_attr('sensor.amber_billing_interval_forecasts_feed_in_price', 'Forecasts')
| selectattr('advanced_price_predicted','is_number')
| map(attribute='advanced_price_predicted')
| list | count
}},
{#- Define the number of deferrable loads and their respective total hours -#}
"num_def_loads": 2,
"def_total_hours": [
{%- if 'unny' in states('sensor.denistone_east_icon_descriptor_0') and states('sensor.cecil_st_general_price') | float(0) < 0.1 or states('sensor.cecil_st_general_price') | float(0) < 0.05 -%}
{%- if is_state('sensor.season', 'winter') -%}
{{2}}
{%- elif is_state('sensor.season', 'summer') -%}
{{4}}
{%- else -%}
{{3}}
{%- endif -%}
{%- else -%}
{{0}}
{%- endif -%},
{%- if is_state('device_tracker.ynot_location_tracker', ['home']) -%}
{%- if is_state('binary_sensor.ynot_charger', ['on']) -%}
{{ ((states('number.ynot_charge_limit')|int(80)-(states('sensor.ynot_battery')|int(0)))/30*3)|round(0) }}
{%- else -%}
0
{%- endif -%}
{%- else -%}
0
{%- endif -%}
],
{#- Define the nominal power of the deferrable loads -#}
"P_deferrable_nom": [1300, {{ (states('input_number.ev_amps') | int(0) * 230)|int(0) }}],
{#- Define the deferrable loads as semi-continuous -#}
"treat_def_as_semi_cont": [1, 0],
{#- Define the deferrable loads as constant -#}
"set_def_constant": [0, 0],
{#- Define the initial and final state of charge of the battery -#}
"soc_init": {{ (states('sensor.sonnenbatterie_84324_state_charge_user')|int(0))/100 }},
"soc_final": 0.08,
{#- Define the alpha and beta values giving higher weightings to the current states to make system more reactive -#}
"alpha": 1,
"beta": 0,
{#- Define the maximum charge and discharge power of the battery based on the current state of charge -#}
{%- set battery_state = states('sensor.sonnenbatterie_84324_state_charge_user')|int(0) %}
{%- if battery_state > 90 %}
"battery_charge_power_max": 850,
"battery_discharge_power_max": 3300
{%- elif battery_state > 7 %}
"battery_charge_power_max": 3300,
"battery_discharge_power_max": 3300
{%- else %}
"battery_charge_power_max": 3300,
"battery_discharge_power_max": 1200
{%- endif %}
}
Haven’t had time to look into what else is required to manage 5 min billing. How does the 5 minute billing change things? What minimal changes can I get away with?