Byłoby fajnie gdybyś stworzył wątek opisujący wyłącznie swoje rozwiązanie (szczególnie jeśli jesteś w stanie zapewniać też jakieś wsparcie dla użytkowników swojego rozwiązania), bo niestety ten wątek gdzie to już opisałeś to takie “mydło i powidło” (jak zwykle gdy jest kilka różnych rozwiązań w jednym wątku to jest z tego bałagan dlatego też odciąłem ten kawałek do osobnego wątku).
Spróbuję, a więc:
Opis działania projektu
Projekt ma na celu automatyczne pobieranie i monitorowanie cen RCE na dzisiaj i jutro z API PSE, wyświetlanie aktualnej ceny energii na dany moment, wyliczenie okna czasowego (3h) kiedy średnia energia jest najniższa oraz wysłanie powiadomienia (lub inne działanie) kiedy to okno się rozpoczyna.
Sensory do wklejenia do sensors.yaml
- Sensor pobierający dzisiejsze ceny RCE (by @mstepuch )
# Pobieranie dzisiejszych cen RCE z PSE
- platform: rest
name: RCE Prices Today
resource_template: "https://api.raporty.pse.pl/api/rce-pln?$select=rce_pln,udtczas_oreb&$filter=doba eq '{{ now().strftime('%Y-%m-%d') }}'"
method: GET
headers:
accept: "application/json"
value_template: "{{ value_json.value[0]['rce_pln'] }}"
json_attributes:
- value
scan_interval: 86400
- Sensor pobierający jutrzejsze ceny RCE (by @mstepuch)
# Pobieranie jutrzejszych cen RCE z PSE
- platform: rest
name: RCE Prices Tomorrow
resource_template: "https://api.raporty.pse.pl/api/rce-pln?$filter=doba eq '{{ (now() + timedelta(days=1)).strftime('%Y-%m-%d') }}'"
method: GET
headers:
accept: "application/json"
value_template: "{{ value_json.value[0]['rce_pln'] }}"
json_attributes:
- value
scan_interval: 3600
- Template sensor z bieżącą ceną RCE (by @mstepuch )
# Sensor z bieżącą ceną
- platform: template
sensors:
current_rce_price:
friendly_name: "Current RCE Price"
value_template: >
{% set current_time = now().strftime('%H:%M') %}
{% set prices = state_attr('sensor.rce_prices_today', 'value') %}
{% for price in prices %}
{% set start_time = price.udtczas_oreb.split(' - ')[0] %}
{% set end_time = price.udtczas_oreb.split(' - ')[1] %}
{% if start_time <= current_time < end_time %}
{{ price.rce_pln }}
{% endif %}
{% endfor %}
unit_of_measurement: "PLN/MWh"
- Template sensor okna najniższej ceny (by @mstepuch )
# Okno czasowe najniższej ceny RCE w dzień
- platform: template
sensors:
lowest_price_window_daytime:
value_template: "{{ states('sensor.lowest_price_window_daytime').split('/')[0] }} - {{ states('sensor.lowest_price_window_daytime').split('/')[1] }}"
friendly_name: "Lowest Price Window Daytime"
attribute_templates:
min_avg: "{{ state_attr('sensor.lowest_price_window_daytime', 'min_avg') }}"
start_time: "{{ states('sensor.lowest_price_window_daytime').split('/')[0] }}"
end_time: "{{ states('sensor.lowest_price_window_daytime').split('/')[1] }}"
- Template sensor czasu początku tego okna (by @Tomasz_K ) - sensor można ustawić np. na 10 minut przed rozpoczęciem tego okna. Aby sensor wskazywał dokładna godzinę wystarczy usunąć “-600”.
# Start okna czasowego najniższych cen
- platform: template
sensors:
lowest_price_start_time:
friendly_name: "Lowest price start time"
device_class: 'timestamp'
value_template: "{{ ( today_at(states.sensor.lowest_price_window_daytime.attributes.start_time) | as_timestamp -600 ) | as_datetime }}"
- Skrypt python find_lowest_price_window_daytime.py do dodania do folderu python_scripts (by @mstepuch )
# Pobierz ceny energii z sensora
prices = hass.states.get('sensor.rce_prices_today').attributes['value']
# Filtruj ceny, aby uwzględnić tylko przedział od 06:00 do 18:00
filtered_prices = [entry for entry in prices if '06:00' <= entry['udtczas_oreb'].split(' - ')[0] <= '18:00']
# Lista cen energii (rce_pln) w przefiltrowanym przedziale
price_list = [entry['rce_pln'] for entry in filtered_prices]
# Liczba interwałów w 3 godzinach (12 * 15 minut)
window_size = 12
# Inicjalizacja zmiennych do przechowywania minimalnej średniej i jej indeksu
min_avg = float('inf')
min_index = 0
# Przesuwanie okna po liście cen
for i in range(len(price_list) - window_size + 1):
# Obliczenie średniej cen w bieżącym oknie
current_avg = sum(price_list[i:i + window_size]) / window_size
# Sprawdzenie, czy bieżąca średnia jest najmniejsza
if current_avg < min_avg:
min_avg = current_avg
min_index = i
# Zwrócenie indeksu początkowego okna o najniższej średniej oraz tę średnią
start_time = filtered_prices[min_index]['udtczas_oreb'].split(' - ')[0]
end_time = filtered_prices[min_index + window_size - 1]['udtczas_oreb'].split(' - ')[1]
hass.states.set('sensor.lowest_price_window_daytime', f'{start_time}/{end_time}', {
'min_avg': min_avg,
'start_time': start_time+":00",
'end_time': end_time+":00"
})
# Update Current RCE Price at midnight
- id: '1718249833122'
alias: Update Current RCE Price
trigger:
- platform: time
at: 00:01:00
action:
- service: homeassistant.update_entity
target:
entity_id: sensor.current_rce_price
data: {}
# Check lowest price window
- id: '1720605043249'
alias: Check lowest price window
description: ''
trigger:
- platform: time
at: 00:02:00
condition: []
action:
- service: python_script.find_lowest_price_window_daytime
data: {}
# Powiadomienie o rozpoczęciu okna najtańszej energii
- id: '1722323598536'
alias: Okno najtanszej energii
description: ''
trigger:
- platform: time
at: sensor.lowest_price_start_time
condition: []
action:
# Powiadomienie w HA
- service: notify.persistent_notification
metadata: {}
data:
message: Okno najtańszej energii
title: Okno najtańszej energii za 10 minut
# Powiadomienie na telefon
- service: notify.mobile_app_sm_g781b
metadata: {}
data:
message: Okno najtańszej energii
title: Okno najtańszej energii za 10 minut
- Kody wykresów Apex Charts (Trzeba zainstalować ApexCharts Card przez HACS) - by @mstepuch
Ceny energii dzisiaj:
type: custom:apexcharts-card
graph_span: 24h
span:
start: day
update_interval: 15min
experimental:
color_threshold: true
series:
- entity: sensor.rce_prices_today
attribute: value
data_generator: |
const startOfDay = new Date();
startOfDay.setHours(0, 0, 0, 0);
return entity.attributes.value.map(item => {
const timeRange = item.udtczas_oreb.split(" - ")[0];
const [hours, minutes] = timeRange.split(":");
const timestamp = new Date(startOfDay);
timestamp.setHours(hours, minutes);
return [timestamp.getTime(), item.rce_pln];
});
type: line
name: Cena energii
curve: smooth
stroke_width: 4
color_threshold:
- value: 0
color: red
- value: 300
color: orange
- value: 500
color: yellow
- value: 700
color: green
header:
show: true
title: Ceny energii (PLN)
show_states: false
colorize_states: true
now:
show: true
label: Teraz
color: red
Ceny na jutro:
type: custom:apexcharts-card
graph_span: 24h
span:
start: day
offset: +1d
update_interval: 15min
experimental:
color_threshold: true
series:
- entity: sensor.rce_prices_tomorrow
attribute: value
data_generator: |
return entity.attributes.value.map(item => {
return [new Date(item.udtczas).getTime(), item.rce_pln];
});
type: line
name: Cena energii (jutro)
curve: smooth
stroke_width: 4
color_threshold:
- value: 0
color: red
- value: 300
color: orange
- value: 500
color: yellow
- value: 700
color: green
header:
show: true
title: Ceny energii (PLN) - Jutro
show_states: false
- Na koniec ode mnie 2 kafelki na ekran główny
Aktualna cena:
type: gauge
entity: sensor.current_rce_price
unit: PLN/MWh
name: Cena aktualna
needle: false
severity:
green: 500
yellow: 0
red: -2000
min: -500
max: 2000
Okno z najniższą średnią:
type: entity
entity: sensor.lowest_price_window_daytime
name: Okno 3h z najniższą ceną
Chyba tyle
Dziękuję !!!. Świetna robota.
Hej.
Pięknie to wygląda.
Czy aby zwiększyć okno do np. 4 godzin wystarczy zmienić w skrypcie Pythona z 12 na 16?
Druga kwestia nieco bardziej skomplikowana.
Czy jesteś w stanie dodać sensor wyszukujący najwyższe ceny?
Chodzi o sprzedaż nadwyżek prądu z magazanu.
Tak, zwiększasz okno do 4 godzin czyli window_size = 16
Sam jesteś w stanie to zrobić, masz przykład z ceną minimalną, powinieneś zmienić warunek …
# Sprawdzenie, czy bieżąca średnia jest najmniejsza
if current_avg < min_avg:
min_avg = current_avg
min_index = i
na taki jak chcesz …
# Sprawdzenie, czy bieżąca średnia jest największą
if current_avg > max_avg:
max_avg = current_avg
max_index = i
… i zmienić w kodzie min_avg
i min_index
na nowy …
Tu masz zmodyfikowany kod pythona, poniewaz zeby liczyl on poprawnie min i maks trzeba dokonac w nim znacznie wiecej zmian. Pamietaj zeby dostosowac zmienne rce_ceny_dzisiaj
, rce_okno_najnizszych_cen
, rce_okno_najwyzszych_cen
do takich jakich uzywasz w swoim kodzie.
# Pobierz ceny energii z sensora
prices = hass.states.get('sensor.rce_ceny_dzisiaj').attributes['value']
# Filtruj ceny, aby uwzględnić tylko przedział od 06:00 do 18:00
# ma to sens jesli tylko liczymy ceny najnizsze
#filtered_prices = [entry for entry in prices if '06:00' <= entry['udtczas_oreb'].split(' - ')[0] <= '18:00']
#w przeciwnym wypadku trzeba uzyc calej tablicy cen
filtered_prices = prices
# Lista cen energii (rce_pln) w przefiltrowanym przedziale
price_list = [entry['rce_pln'] for entry in filtered_prices]
# Liczba interwałów w 3 godzinach (12 * 15 minut)
window_size = 12
# Inicjalizacja zmiennych do przechowywania minimalnej/maksymalnej średniej i jej indeksu
min_avg = float('inf')
min_index = 0
max_avg = float('-inf') #Minus nieskończoność inaczej warunek się nie spełni
max_index = 0
# Przesuwanie okna po liście cen - cena minimalna
for i in range(len(price_list) - window_size + 1):
# Obliczenie średniej cen w bieżącym oknie
current_avg = sum(price_list[i:i + window_size]) / window_size
# Sprawdzenie, czy bieżąca średnia jest najmniejsza
if current_avg < min_avg:
min_avg = current_avg
min_index = i
# Sprawdzenie, czy bieżąca średnia jest największa
if current_avg > max_avg:
max_avg = current_avg
max_index = i
# Zwrócenie indeksu początkowego okna o najniższej średniej oraz tę średnią dla ceny minimalnej
start_time = filtered_prices[min_index]['udtczas_oreb'].split(' - ')[0]
end_time = filtered_prices[min_index + window_size - 1]['udtczas_oreb'].split(' - ')[1]
hass.states.set('sensor.rce_okno_najnizszych_cen', f'{start_time}/{end_time}', {
'min_avg': min_avg,
'start_time': start_time+":00",
'end_time': end_time+":00"
})
# Zwrócenie indeksu początkowego okna o najniższej średniej oraz tę średnią dla ceny maksymalnej
start_time = filtered_prices[max_index]['udtczas_oreb'].split(' - ')[0]
end_time = filtered_prices[max_index + window_size - 1]['udtczas_oreb'].split(' - ')[1]
hass.states.set('sensor.rce_okno_najwyzszych_cen', f'{start_time}/{end_time}', {
'max_avg': max_avg,
'start_time': start_time+":00",
'end_time': end_time+":00"
})
Template sobie powielasz z tego dla najnizszej ceny tylko ustawiasz w nim zmienna z najwyzsza cena i masz w ten sposob oba okienka