diff --git a/ha-configs/151/automations.yaml b/ha-configs/151/automations.yaml index 66dab58..10d5d63 100644 --- a/ha-configs/151/automations.yaml +++ b/ha-configs/151/automations.yaml @@ -458,9 +458,10 @@ - alias: Solcast G12W - Włącz ładowanie dzienne 13:00 id: g12w_wlacz_ladowanie_dzienne description: > - 12:55: oblicza czy nadmiar PV w oknie 13:00-15:00 wystarczy do naładowania baterii. - Jeśli PV_nadmiar * 2h >= deficyt baterii → Battery First (bez sieci). - Jeśli nie → ładuj z sieci 100A (tania taryfa G12W). + 12:55: Solcast-based decision. Oblicza ile energii potrzeba z baterii od 15:00 do 22:00 + (droga taryfa) uwzględniając prognozę PV. Cel: target_soc_at_15 = pokrycie 15-22h. + Jeśli PV z okna 13-15h wystarczy → Battery First. Jeśli nie → Grid charging + tylko na brakującą ilość energii (nie zawsze 100A). Pojemność baterii: 14336 Wh (280Ah * 51.2V LFP). trigger: - platform: time @@ -478,16 +479,21 @@ below: 100 action: - choose: - # PV nadmiar przez 2h okna >= deficyt baterii → sieć niepotrzebna + # Solcast prognoza: PV w oknie 13-15h wystarczy na target SOC → Battery First - conditions: - condition: template value_template: > - {% set pv = states('sensor.inverter_deye_pv_power') | int(0) %} - {% set load = states('sensor.inverter_deye_load_power') | int(1000) %} - {% set soc = states('sensor.inverter_deye_battery') | int(0) %} - {% set bat_deficit_wh = (100 - soc) * 143.36 %} - {% set pv_surplus_2h = (pv - load) * 2 %} - {{ pv_surplus_2h >= bat_deficit_wh and pv > load }} + {%- set pv_next_wh = states('sensor.solcast_pv_forecast_prognoza_na_nastepna_godzine') | int(0) -%} + {%- set remaining_kwh = states('sensor.solcast_pv_forecast_pozostala_prognoza_na_dzis') | float(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set soc = states('sensor.inverter_deye_battery') | int(0) -%} + {%- set pv_13_15_wh = pv_next_wh * 1.8 -%} + {%- set bat_surplus_wh = [pv_13_15_wh * 0.94 - load_w * 2, 0] | max -%} + {%- set pv_after_15_wh = [remaining_kwh * 1000 - pv_13_15_wh, 0] | max -%} + {%- set needed_from_bat_wh = [load_w * 7 - pv_after_15_wh, 0] | max -%} + {%- set target_soc = [[needed_from_bat_wh / 143.36 + 15, 20] | max, 100] | min | int -%} + {%- set soc_at_15 = soc + bat_surplus_wh / 143.36 -%} + {{ soc_at_15 >= target_soc }} sequence: - service: select.select_option target: @@ -507,22 +513,29 @@ entity_id: number.inverter_deye_battery_max_charging_current data: value: > - {%- set pv = states('sensor.inverter_deye_pv_power') | int(0) -%} - {%- set load = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set pv_w = states('sensor.inverter_deye_pv_power') | int(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} {%- set vbat = states('sensor.inverter_deye_battery_voltage') | float(51.2) -%} - {%- set surplus_a = ((pv * 0.94 - load) / vbat) | int -%} + {%- set surplus_a = ((pv_w * 0.94 - load_w) / vbat) | int -%} {{ [5, [surplus_a, 100] | min] | max }} - service: notify.persistent_notification data: - title: '☀️ G12W: PV naładuje baterię bez sieci' + title: '☀️ G12W: PV (Solcast) wystarczy - Battery First' message: > - {% set pv = states('sensor.inverter_deye_pv_power') | int(0) %} - {% set load = states('sensor.inverter_deye_load_power') | int(1000) %} - {% set soc = states('sensor.inverter_deye_battery') | int(0) %} - {% set deficit = ((100 - soc) * 143.36) | round(0) %} - PV: {{ pv }}W, Zużycie: {{ load }}W, Nadmiar: {{ pv - load }}W. - Deficyt baterii: {{ deficit }} Wh. Nadmiar 2h: {{ ((pv - load) * 2) | round(0) }} Wh. SOC: {{ soc }}% - # PV niewystarczające → ładuj z sieci (tania taryfa G12W) + {%- set pv_next_wh = states('sensor.solcast_pv_forecast_prognoza_na_nastepna_godzine') | int(0) -%} + {%- set remaining_kwh = states('sensor.solcast_pv_forecast_pozostala_prognoza_na_dzis') | float(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set soc = states('sensor.inverter_deye_battery') | int(0) -%} + {%- set pv_13_15_wh = pv_next_wh * 1.8 -%} + {%- set bat_surplus_wh = [pv_13_15_wh * 0.94 - load_w * 2, 0] | max -%} + {%- set pv_after_15_wh = [remaining_kwh * 1000 - pv_13_15_wh, 0] | max -%} + {%- set needed_from_bat_wh = [load_w * 7 - pv_after_15_wh, 0] | max -%} + {%- set target_soc = [[needed_from_bat_wh / 143.36 + 15, 20] | max, 100] | min | int -%} + {%- set soc_at_15 = (soc + bat_surplus_wh / 143.36) | round(1) -%} + SOC: {{ soc }}% → ~{{ soc_at_15 }}% o 15:00 (target: {{ target_soc }}%). + PV 13-15h: {{ pv_13_15_wh | round(0) }} Wh, po 15:00: {{ pv_after_15_wh | round(0) }} Wh. + Zużycie 15-22h: {{ (load_w * 7) | round(0) }} Wh. Potrzeba z bat: {{ needed_from_bat_wh | round(0) }} Wh. + # Solcast: PV nie wystarczy → ładuj z sieci tylko brakującą ilość default: - service: switch.turn_on target: @@ -541,7 +554,21 @@ target: entity_id: number.inverter_deye_battery_grid_charging_current data: - value: 100 + value: > + {%- set pv_next_wh = states('sensor.solcast_pv_forecast_prognoza_na_nastepna_godzine') | int(0) -%} + {%- set remaining_kwh = states('sensor.solcast_pv_forecast_pozostala_prognoza_na_dzis') | float(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set soc = states('sensor.inverter_deye_battery') | int(0) -%} + {%- set vbat = states('sensor.inverter_deye_battery_voltage') | float(51.2) -%} + {%- set pv_13_15_wh = pv_next_wh * 1.8 -%} + {%- set bat_surplus_wh = [pv_13_15_wh * 0.94 - load_w * 2, 0] | max -%} + {%- set pv_after_15_wh = [remaining_kwh * 1000 - pv_13_15_wh, 0] | max -%} + {%- set needed_from_bat_wh = [load_w * 7 - pv_after_15_wh, 0] | max -%} + {%- set target_soc = [[needed_from_bat_wh / 143.36 + 15, 20] | max, 100] | min | int -%} + {%- set soc_at_15_no_grid = soc + bat_surplus_wh / 143.36 -%} + {%- set missing_wh = [(target_soc - soc_at_15_no_grid) * 143.36, 0] | max -%} + {%- set grid_a = [missing_wh / 2 / vbat, 100] | min | int -%} + {{ [grid_a, 5] | max }} - service: select.select_option target: entity_id: select.inverter_deye_program_4_charging @@ -551,17 +578,32 @@ target: entity_id: number.inverter_deye_program_4_soc data: - value: 100 + value: > + {%- set pv_next_wh = states('sensor.solcast_pv_forecast_prognoza_na_nastepna_godzine') | int(0) -%} + {%- set remaining_kwh = states('sensor.solcast_pv_forecast_pozostala_prognoza_na_dzis') | float(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set needed_from_bat_wh = [load_w * 7 - [remaining_kwh * 1000 - pv_next_wh * 1.8, 0] | max, 0] | max -%} + {{ [[needed_from_bat_wh / 143.36 + 15, 20] | max, 100] | min | int }} - service: notify.persistent_notification data: - title: '⚡ G12W: Ładowanie z sieci (PV niewystarczające)' + title: '⚡ G12W: Solcast - ładowanie z sieci do target SOC' message: > - {% set pv = states('sensor.inverter_deye_pv_power') | int(0) %} - {% set load = states('sensor.inverter_deye_load_power') | int(1000) %} - {% set soc = states('sensor.inverter_deye_battery') | int(0) %} - {% set deficit = ((100 - soc) * 143.36) | round(0) %} - PV: {{ pv }}W, Zużycie: {{ load }}W, Nadmiar: {{ pv - load }}W. - Deficyt: {{ deficit }} Wh > Nadmiar 2h: {{ ((pv - load) * 2) | round(0) }} Wh. SOC: {{ soc }}% + {%- set pv_next_wh = states('sensor.solcast_pv_forecast_prognoza_na_nastepna_godzine') | int(0) -%} + {%- set remaining_kwh = states('sensor.solcast_pv_forecast_pozostala_prognoza_na_dzis') | float(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set soc = states('sensor.inverter_deye_battery') | int(0) -%} + {%- set vbat = states('sensor.inverter_deye_battery_voltage') | float(51.2) -%} + {%- set pv_13_15_wh = pv_next_wh * 1.8 -%} + {%- set bat_surplus_wh = [pv_13_15_wh * 0.94 - load_w * 2, 0] | max -%} + {%- set pv_after_15_wh = [remaining_kwh * 1000 - pv_13_15_wh, 0] | max -%} + {%- set needed_from_bat_wh = [load_w * 7 - pv_after_15_wh, 0] | max -%} + {%- set target_soc = [[needed_from_bat_wh / 143.36 + 15, 20] | max, 100] | min | int -%} + {%- set soc_at_15_no_grid = soc + bat_surplus_wh / 143.36 -%} + {%- set missing_wh = [(target_soc - soc_at_15_no_grid) * 143.36, 0] | max -%} + {%- set grid_a = [[missing_wh / 2 / vbat, 100] | min | int, 5] | max -%} + SOC: {{ soc }}%, target na 15:00: {{ target_soc }}%. + PV 13-15h: {{ pv_13_15_wh | round(0) }} Wh, po 15:00: {{ pv_after_15_wh | round(0) }} Wh. + Zużycie 15-22h: {{ (load_w * 7) | round(0) }} Wh. Brakuje: {{ missing_wh | round(0) }} Wh → {{ grid_a }}A z sieci. - alias: Solcast G12W - Wyłącz ładowanie dzienne 15:00 description: Wyłącza ładowanie po taniej strefie dziennej, przywraca max prąd ładowania @@ -676,10 +718,9 @@ - alias: Solcast G12W - Optymalizacja PV ładowania dziennego id: g12w_pv_charging_optimization description: > - Co 15 min 13:00-15:00: oblicza czy PV zdąży naładować baterię do końca okna. - Formuła: PV_nadmiar * pozostały_czas_h >= deficyt_baterii_Wh - Jeśli TAK → Battery First (zero importu dla ładowania). - Jeśli NIE → Load First + Grid charging 100A (tania taryfa G12W). + Co 15 min 13:00-15:00: Solcast-based re-evaluation. Oblicza target SOC na 15:00 + potrzebny do pokrycia zużycia 15-22h (droga taryfa). Używa Solcast prognoza_na_biezaca_godzine + jako estymaty PV w pozostałym oknie. Pojemność baterii: 14336 Wh (280Ah LFP). trigger: - platform: time_pattern @@ -699,18 +740,23 @@ below: 100 action: - choose: - # PV zdąży naładować baterię do końca okna → nie potrzeba sieci + # Solcast: PV w pozostałym oknie wystarczy na target SOC → Battery First - conditions: - condition: template value_template: > - {% set pv = states('sensor.inverter_deye_pv_power') | int(0) %} - {% set load = states('sensor.inverter_deye_load_power') | int(1000) %} - {% set soc = states('sensor.inverter_deye_battery') | int(0) %} - {% set now_min = now().hour * 60 + now().minute %} - {% set remaining_h = ([15 * 60 - now_min, 0] | max) / 60 %} - {% set bat_deficit_wh = (100 - soc) * 143.36 %} - {% set pv_surplus_wh = (pv - load) * remaining_h %} - {{ pv > load and pv_surplus_wh >= bat_deficit_wh }} + {%- set pv_hour_wh = states('sensor.solcast_pv_forecast_prognoza_na_biezaca_godzine') | int(0) -%} + {%- set remaining_kwh = states('sensor.solcast_pv_forecast_pozostala_prognoza_na_dzis') | float(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set soc = states('sensor.inverter_deye_battery') | int(0) -%} + {%- set now_min = now().hour * 60 + now().minute -%} + {%- set remaining_window_h = ([15 * 60 - now_min, 0] | max) / 60 -%} + {%- set pv_remaining_window_wh = pv_hour_wh * remaining_window_h -%} + {%- set bat_surplus_wh = [pv_remaining_window_wh * 0.94 - load_w * remaining_window_h, 0] | max -%} + {%- set pv_after_15_wh = [remaining_kwh * 1000 - pv_remaining_window_wh, 0] | max -%} + {%- set needed_from_bat_wh = [load_w * 7 - pv_after_15_wh, 0] | max -%} + {%- set target_soc = [[needed_from_bat_wh / 143.36 + 15, 20] | max, 100] | min | int -%} + {%- set soc_at_15 = soc + bat_surplus_wh / 143.36 -%} + {{ soc_at_15 >= target_soc }} sequence: - service: switch.turn_off target: @@ -730,24 +776,29 @@ entity_id: number.inverter_deye_battery_max_charging_current data: value: > - {%- set pv = states('sensor.inverter_deye_pv_power') | int(0) -%} - {%- set load = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set pv_w = states('sensor.inverter_deye_pv_power') | int(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} {%- set vbat = states('sensor.inverter_deye_battery_voltage') | float(51.2) -%} - {%- set surplus_a = ((pv * 0.94 - load) / vbat) | int -%} + {%- set surplus_a = ((pv_w * 0.94 - load_w) / vbat) | int -%} {{ [5, [surplus_a, 100] | min] | max }} - # PV nie wystarczy → ładuj z sieci (tania G12W) + # Solcast: PV nie wystarczy → ładuj z sieci dokładnie brakującą ilość - conditions: - condition: template value_template: > - {% set pv = states('sensor.inverter_deye_pv_power') | int(0) %} - {% set load = states('sensor.inverter_deye_load_power') | int(1000) %} - {% set soc = states('sensor.inverter_deye_battery') | int(0) %} - {% set now_min = now().hour * 60 + now().minute %} - {% set remaining_h = ([15 * 60 - now_min, 0] | max) / 60 %} - {% set bat_deficit_wh = (100 - soc) * 143.36 %} - {% set pv_surplus_wh = (pv - load) * remaining_h %} - {{ (pv <= load or pv_surplus_wh < bat_deficit_wh) and soc < 95 }} + {%- set pv_hour_wh = states('sensor.solcast_pv_forecast_prognoza_na_biezaca_godzine') | int(0) -%} + {%- set remaining_kwh = states('sensor.solcast_pv_forecast_pozostala_prognoza_na_dzis') | float(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set soc = states('sensor.inverter_deye_battery') | int(0) -%} + {%- set now_min = now().hour * 60 + now().minute -%} + {%- set remaining_window_h = ([15 * 60 - now_min, 0] | max) / 60 -%} + {%- set pv_remaining_window_wh = pv_hour_wh * remaining_window_h -%} + {%- set bat_surplus_wh = [pv_remaining_window_wh * 0.94 - load_w * remaining_window_h, 0] | max -%} + {%- set pv_after_15_wh = [remaining_kwh * 1000 - pv_remaining_window_wh, 0] | max -%} + {%- set needed_from_bat_wh = [load_w * 7 - pv_after_15_wh, 0] | max -%} + {%- set target_soc = [[needed_from_bat_wh / 143.36 + 15, 20] | max, 100] | min | int -%} + {%- set soc_at_15 = soc + bat_surplus_wh / 143.36 -%} + {{ soc_at_15 < target_soc and soc < target_soc }} sequence: - service: switch.turn_on target: @@ -766,7 +817,23 @@ target: entity_id: number.inverter_deye_battery_grid_charging_current data: - value: 100 + value: > + {%- set pv_hour_wh = states('sensor.solcast_pv_forecast_prognoza_na_biezaca_godzine') | int(0) -%} + {%- set remaining_kwh = states('sensor.solcast_pv_forecast_pozostala_prognoza_na_dzis') | float(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set soc = states('sensor.inverter_deye_battery') | int(0) -%} + {%- set vbat = states('sensor.inverter_deye_battery_voltage') | float(51.2) -%} + {%- set now_min = now().hour * 60 + now().minute -%} + {%- set remaining_window_h = ([15 * 60 - now_min, 0.01] | max) / 60 -%} + {%- set pv_remaining_window_wh = pv_hour_wh * remaining_window_h -%} + {%- set bat_surplus_wh = [pv_remaining_window_wh * 0.94 - load_w * remaining_window_h, 0] | max -%} + {%- set pv_after_15_wh = [remaining_kwh * 1000 - pv_remaining_window_wh, 0] | max -%} + {%- set needed_from_bat_wh = [load_w * 7 - pv_after_15_wh, 0] | max -%} + {%- set target_soc = [[needed_from_bat_wh / 143.36 + 15, 20] | max, 100] | min | int -%} + {%- set soc_at_15_no_grid = soc + bat_surplus_wh / 143.36 -%} + {%- set missing_wh = [(target_soc - soc_at_15_no_grid) * 143.36, 0] | max -%} + {%- set grid_a = [missing_wh / remaining_window_h / vbat, 100] | min | int -%} + {{ [grid_a, 5] | max }} - service: select.select_option target: entity_id: select.inverter_deye_program_4_charging @@ -776,17 +843,35 @@ target: entity_id: number.inverter_deye_program_4_soc data: - value: 100 + value: > + {%- set pv_hour_wh = states('sensor.solcast_pv_forecast_prognoza_na_biezaca_godzine') | int(0) -%} + {%- set remaining_kwh = states('sensor.solcast_pv_forecast_pozostala_prognoza_na_dzis') | float(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set now_min = now().hour * 60 + now().minute -%} + {%- set remaining_window_h = ([15 * 60 - now_min, 0] | max) / 60 -%} + {%- set pv_rem_wh = pv_hour_wh * remaining_window_h -%} + {%- set pv_after_15_wh = [remaining_kwh * 1000 - pv_rem_wh, 0] | max -%} + {%- set needed_from_bat_wh = [load_w * 7 - pv_after_15_wh, 0] | max -%} + {{ [[needed_from_bat_wh / 143.36 + 15, 20] | max, 100] | min | int }} - service: notify.persistent_notification data: - title: '⚡ G12W: PV nie zdąży → ładowanie z sieci' + title: '⚡ G12W Optymalizacja: Solcast → sieć potrzebna' message: > - {% set pv = states('sensor.inverter_deye_pv_power') | int(0) %} - {% set load = states('sensor.inverter_deye_load_power') | int(1000) %} - {% set soc = states('sensor.inverter_deye_battery') | int(0) %} - {% set now_min = now().hour * 60 + now().minute %} - {% set remaining_h = ([15 * 60 - now_min, 0] | max) / 60 %} - {% set deficit = ((100 - soc) * 143.36) | round(0) %} - {% set surplus = ((pv - load) * remaining_h) | round(0) %} - Pozostało: {{ remaining_h | round(2) }}h, PV nadmiar: {{ surplus }} Wh < deficyt: {{ deficit }} Wh. SOC: {{ soc }}% + {%- set pv_hour_wh = states('sensor.solcast_pv_forecast_prognoza_na_biezaca_godzine') | int(0) -%} + {%- set remaining_kwh = states('sensor.solcast_pv_forecast_pozostala_prognoza_na_dzis') | float(0) -%} + {%- set load_w = states('sensor.inverter_deye_load_power') | int(1000) -%} + {%- set soc = states('sensor.inverter_deye_battery') | int(0) -%} + {%- set vbat = states('sensor.inverter_deye_battery_voltage') | float(51.2) -%} + {%- set now_min = now().hour * 60 + now().minute -%} + {%- set remaining_window_h = ([15 * 60 - now_min, 0.01] | max) / 60 -%} + {%- set pv_rem_wh = pv_hour_wh * remaining_window_h -%} + {%- set bat_surplus_wh = [pv_rem_wh * 0.94 - load_w * remaining_window_h, 0] | max -%} + {%- set pv_after_15_wh = [remaining_kwh * 1000 - pv_rem_wh, 0] | max -%} + {%- set needed_from_bat_wh = [load_w * 7 - pv_after_15_wh, 0] | max -%} + {%- set target_soc = [[needed_from_bat_wh / 143.36 + 15, 20] | max, 100] | min | int -%} + {%- set soc_at_15_no_grid = soc + bat_surplus_wh / 143.36 -%} + {%- set missing_wh = [(target_soc - soc_at_15_no_grid) * 143.36, 0] | max -%} + {%- set grid_a = [[missing_wh / remaining_window_h / vbat, 100] | min | int, 5] | max -%} + SOC: {{ soc }}%, target: {{ target_soc }}% ({{ remaining_window_h | round(2) }}h do końca). + Brakuje: {{ missing_wh | round(0) }} Wh → {{ grid_a }}A z sieci. mode: single