Rynkowe ceny energii elektrycznej (mstepuch) - wyszukiwanie okienka najtańszej energii

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).

1 polubienie

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

  1. 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
  1. 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
  1. 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"
  1. 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] }}"
  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 }}"
  1. 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"
    })
  1. Automatyzacje do dodania do automations.yaml (2 pierwsze by @mstepuch, ostatnia by @Tomasz_K ):
# 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
  1. 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
  1. 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 :wink:

5 polubień

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

1 polubienie