Rynkowe ceny energii elektrycznej

tak, pogubiłeś się :slight_smile:, i tak to opłaty stałe miesięczne spisane z poprzedniego rachunku. zakładam że przy taryfie dynamicznej będą takie same (stała sieciowa+abonamentowa+przejściowa+handlowa) + od stycznia mocowa.
Jeśli zsumujesz wszystkie opłaty i podzielisz przez ilość kWh w danej chwili miesiąca to obliczysz ile gr przypada na 1kWh. Te dwa sensory pokazują tylko chwilową cenę za kWh uwzględniając opłaty stałe i aktualne zużycie. dla jasności ten sensor nie nadaje sie do wpisania w panelu “Energia”.
Nie trać z nimi czasu, przygotuj do 1 listopada, poczekaj kilka dni i włącz wykresy. Poniżej niech przemówią moje. Ostatnie 10 dni bo robiłem porządek z sensorami przed publikacją

co do taryf dynamicznych. takie sa moje wnioski:
1 . dla PGE da się osiągnąć opłacalność powyżej 160kWh miesięcznie (w zależności od cen TGE, w październiku często za 0) (w tauronie szybciej)
2 . (40-50)% zużycia to urządzenia włączone w godzinach “0”

obecnie mam średnią cenę zakupu (codziennie spada) 0,9623PLN/kWh czyli 0,1356PLN zysku. przy 160kWh będzie 21,70PLN, jeszcze trochę i będzie na opłatę handlową :laughing:
salon to: (TV 12-14h, eko 80W), (miniPC,laptop, 2monitory,2routery) 24/7 niezarządzane, ups uruchomiony kilka dni temu
niech przemówi wykres:


i na koniec. to co probujesz osiągnąć jak najbardziej ma sens (tak mówią mi cyferki w głowie które latają w niej od miesiąca :slight_smile: ) i mam sugestie. zrób sensory sredniej ceny zakupu i sprzedaży, będziesz widział na bieżąco co i jak z finansami. ja mam np taki dla TV (obecnie 1.119pln/kWh):

template:
  - sensor:
    - name: "Monthly AVG TV Price"
      unique_id: 2024101700011
      unit_of_measurement: "PLN/kWh"
      state: >
        {% set cost = states('sensor.monthly_tv_cost') | float(0) %}
        {% set kwh  = states('sensor.monthly_tv_kwh')  | float(0) %}
        {% if kwh > 0 and cost > 0 %}
          {{ (cost/kwh) | float(0) }}
        {% endif %}
      icon: mdi:cash
1 polubienie

Dzięki za wyjaśnienia już czaję o co chodziło.
Zdałem sobie sprawę że z racji innych okoliczności realizujemy trochę inne cele.
Ty masz już taryfę dynamiczną i starasz się na niej jak najlepiej wyjść, świadomie zarządzając częścią urządzeń.
Ja od lipca mam fotowoltaikę z magazynem i obecnie taryfę G11. Robię obecnie symulację na jaką taryfę przejść czy G12W czy dynamiczną i staram się tak ogarnąć zarządzanie falownikiem/magazynem żeby w zimie kiedy nie ma słońca ładować magazyn z sieci. W G12W mógłbym wtedy zasilać dom w drugiej strefie (0,77 zł) całą dobę i jest to łatwe w konfiguracji. Z kolei w dynamicznej oszczędności mogły by być teoretycznie większe ale to wymaga większej gimnastyki.
Okoliczności zmienią się jednak po nowym roku bo obecne stawki taryfowe obowiązują do 31.12.2024, a później rząd nie będzie już pewnie dokładał się do ceny energii i stawki w G11 i G12 wzrosną. Tymczasem rynkowe ceny energii spadają obecnie (co obserwujesz u siebie).
Reasumując ja analizuję i czekam aż okoliczności się wyklarują :slight_smile:
Taryfę można zmienić raz w roku więc muszę się namyślić. Jeśli jednak będę się decydował na dynamiczne to zmienię sprzedawcę z PGE na Tauron bo tam jest zerowa opłata miesięczna.

Na razie się jednak organizuję i sporo czasu zajęło mi ogarnięcie sterowania falownikiem, prognoz produkcji energii itd.

Co do opłat stałych wolę zliczać je w ujęciu miesięcznym a nie dodawać do ceny kWh ale to kwestia gustu :slight_smile:

Walka trwa …

@mstepuch
image
Off-topic: czy ten rysunek sugeruje, że dany poziom domu jest zasilany z jednej fazy? Czy instalacja oświetlenia i gniazd zasilajacych np. na “II piętrze” jest na tej samej fazie “Faza 2”?

Mniej więcej tak. To budynek z przed 30 lat i niektóre obwody są źle zaprojektowane.
Inne podczas sukcesywnych remontów pozmieniałem.
Rozkład na fazach jest mniej więcej równy.
Oświetlenie niektórych pomieszczeń jest na tych samych fazach, a nawet zabezpieczeniach co gniazda. Na dzisiejsze standardy to herezja ale kiedyś tak się budowało :slight_smile:

2 polubienia

Analiza opłacalności taryf dynamicznych wcale nie jest prosta.
Zależy czy ma się magazyn żeby “zatankować” go tanią energią czy nie.
Jeśli nie to zależy czy procesy energochłonne można wysterować do działania kiedy jest taniej.

Na dzisiaj (do końca roku) to wychodzi mi że G12W jest lepsza,
kiedy jest taniej w dynamicznych to w G12 jest jeszcze taniej.
Ale to teraz bo w lecie było by inaczej, i teraz bo ceny energii są zamrożone na poziomie 500 zł MWH.

Na wykresie z Pstryk jest tylko cena energii bez dystrybucji i to pewnie netto.
Średnio wychodzi 540 zł MWh i i tak jest zamrożona do 500 w G12.

image

1 polubienie

dlatego mam symulację i czekam na rachunek żeby zrobić ewentualne poprawki. jak mnie najdzie to zrobie magazyn DIY 1kWh z aku do monitoringu żeby zobaczyć jak wychodzi, bo na chwilę obecną to prawie wszystko zrobiłem co mogę w bloku a najlepiej mi się pracuje na żywym organizmie. możesz uruchomić kopię HA do zasymulowania taryf u siebie.

edit: 01:30
!!! uwaga brak danych z energy instrat !!! zorientowałem się o północy :face_with_symbols_over_mouth:
utwórz plik new.json z zawartością:

[{"date":"2024-10-27T00:00:00Z","fixing_i":{"price":420}},{"date":"2024-10-27T01:00:00Z","fixing_i":{"price":392.07}},{"date":"2024-10-27T02:00:00Z","fixing_i":{"price":384}},{"date":"2024-10-27T03:00:00Z","fixing_i":{"prce":390}},{"date":"2024-10-27T04:00:00Z","fixing_i":{"price":386}},{"date":"2024-10-27T05:00:00Z","fixing_i":{"price":388}},{"date":"2024-10-27T06:00:00Z","fixing_i":{"price":398}},{"date":"2024-10-27T07:00:00Z","fixing_i":{"price":404}},{"date":"2024-10-27T08:00:00Z","fixing_i":{"price":416}},{"date":"2024-10-27T09:00:00Z","fixing_i":{"price":367.56}},{"date":"2024-10-27T10:00:00Z","fixing_i":{"price":300}},{"date":"2024-10-27T11:00:00Z","fixing_i":{"price":313.99}},{"date":"2024-10-27T12:00:00Z","fixing_i":{"price":309.99}},{"date":"2024-10-27T13:00:00Z","fixing_i":{"price":350}},{"date":"2024-10-27T14:00:00Z","fixing_i":{"price":445.18}},{"date":"2024-10-27T15:00:00Z","fixing_i":{"price":472}},{"date":"2024-10-27T16:00:00Z","fixing_i":{"price":568}},{"date":"2024-10-27T17:00:00Z","fixing_i":{"price":610}},{"date":"2024-10-27T18:00:00Z","fixing_i":{"price":587.8}},{"date":"2024-10-27T19:00:00Z","fixing_i":{"price":560.82}},{"date":"2024-10-27T20:00:00Z","fixing_i":{"price":472.7}},{"date":"2024-10-27T21:00:00Z","fixing_i":{"price":450}},{"date":"2024-10-27T22:00:00Z","fixing_i":{"price":420}},{"date":"2024-10-27T23:00:00Z","fixing_i":{"price":384}},{"date":"2024-10-28T00:00:00Z","fixing_i":{"price":396}},{"date":"2024-10-28T01:00:00Z","fixing_i":{"price":384.99}},{"date":"2024-10-28T02:00:00Z","fixing_i":{"price":378}},{"date":"2024-10-28T03:00:00Z","fixing_i":{"price":382}},{"date":"2024-10-28T04:00:00Z","fixing_i":{"price":404}},{"date":"2024-10-28T05:00:00Z","fixing_i":{"price":455}},{"date":"2024-10-28T06:00:00Z","fixing_i":{"price":649}},{"date":"2024-10-28T07:00:00Z","fixing_i":{"price":798.57}},{"date":"2024-10-28T08:00:00Z","fixing_i":{"price":671.42}},{"date":"2024-10-28T09:00:00Z","fixing_i":{"price":524}},{"date":"2024-10-28T10:00:00Z","fixing_i":{"price":454}},{"date":"2024-10-28T11:00:00Z","fixing_i":{"price":434.84}},{"date":"2024-10-28T12:00:00Z","fixing_i":{"price":430}},{"date":"2024-10-28T13:00:00Z","fixing_i":{"price":443.37}},{"date":"2024-10-28T14:00:00Z","fixing_i":{"price":501.91}},{"date":"2024-10-28T15:00:00Z","fixing_i":{"price":632}},{"date":"2024-10-28T16:00:00Z","fixing_i":{"price":948}},{"date":"2024-10-28T17:00:00Z","fixing_i":{"price":1100}},{"date":"2024-10-28T18:00:00Z","fixing_i":{"price":1044}},{"date":"2024-10-28T19:00:00Z","fixing_i":{"price":874.12}},{"date":"2024-10-28T20:00:00Z","fixing_i":{"price":670.72}},{"date":"2024-10-28T21:00:00Z","fixing_i":{"price":534.52}},{"date":"2024-10-28T22:00:00Z","fixing_i":{"price":490}},{"date":"2024-10-28T23:00:00Z","fixing_i":{"price":439}},{"date":"2024-10-29T00:00:00Z","fixing_i":{"price":444.00}},{"date":"2024-10-29T01:00:00Z","fixing_i":{"price":416.30}},{"date":"2024-10-29T02:00:00Z","fixing_i":{"price":404.00}},{"date":"2024-10-29T03:00:00Z","fixing_i":{"price":410.00}},{"date":"2024-10-29T04:00:00Z","fixing_i":{"price":430.00}},{"date":"2024-10-29T05:00:00Z","fixing_i":{"price":470.00}},{"date":"2024-10-29T06:00:00Z","fixing_i":{"price":642.96}},{"date":"2024-10-29T07:00:00Z","fixing_i":{"price":830.00}},{"date":"2024-10-29T08:00:00Z","fixing_i":{"price":802.01}},{"date":"2024-10-29T09:00:00Z","fixing_i":{"price":635.00}},{"date":"2024-10-29T10:00:00Z","fixing_i":{"price":576.00}},{"date":"2024-10-29T11:00:00Z","fixing_i":{"price":559.89}},{"date":"2024-10-29T12:00:00Z","fixing_i":{"price":555.00}},{"date":"2024-10-29T13:00:00Z","fixing_i":{"price":573.00}},{"date":"2024-10-29T14:00:00Z","fixing_i":{"price":621.10}},{"date":"2024-10-29T15:00:00Z","fixing_i":{"price":738.80}},{"date":"2024-10-29T16:00:00Z","fixing_i":{"price":1023.00}},{"date":"2024-10-29T17:00:00Z","fixing_i":{"price":1080.00}},{"date":"2024-10-29T18:00:00Z","fixing_i":{"price":1000.00}},{"date":"2024-10-29T19:00:00Z","fixing_i":{"price":870.00}},{"date":"2024-10-29T20:00:00Z","fixing_i":{"price":680.00}},{"date":"2024-10-29T21:00:00Z","fixing_i":{"price":551.37}},{"date":"2024-10-29T22:00:00Z","fixing_i":{"price":504.84}},{"date":"2024-10-29T23:00:00Z","fixing_i":{"price":444.00}},{"date":"2024-10-30T00:00:00Z","fixing_i":{"price":452.90}},{"date":"2024-10-30T01:00:00Z","fixing_i":{"price":427.30}},{"date":"2024-10-30T02:00:00Z","fixing_i":{"price":414.99}},{"date":"2024-10-30T03:00:00Z","fixing_i":{"price":413.38}},{"date":"2024-10-30T04:00:00Z","fixing_i":{"price":425.00}},{"date":"2024-10-30T05:00:00Z","fixing_i":{"price":456.85}},{"date":"2024-10-30T06:00:00Z","fixing_i":{"price":546.14}},{"date":"2024-10-30T07:00:00Z","fixing_i":{"price":630.00}},{"date":"2024-10-30T08:00:00Z","fixing_i":{"price":630.00}},{"date":"2024-10-30T09:00:00Z","fixing_i":{"price":546.00}},{"date":"2024-10-30T10:00:00Z","fixing_i":{"price":499.40}},{"date":"2024-10-30T11:00:00Z","fixing_i":{"price":475.11}},{"date":"2024-10-30T12:00:00Z","fixing_i":{"price":457.92}},{"date":"2024-10-30T13:00:00Z","fixing_i":{"price":460.00}},{"date":"2024-10-30T14:00:00Z","fixing_i":{"price":499.40}},{"date":"2024-10-30T15:00:00Z","fixing_i":{"price":570.00}},{"date":"2024-10-30T16:00:00Z","fixing_i":{"price":720.00}},{"date":"2024-10-30T17:00:00Z","fixing_i":{"price":841.20}},{"date":"2024-10-30T18:00:00Z","fixing_i":{"price":837.90}},{"date":"2024-10-30T19:00:00Z","fixing_i":{"price":745.00}},{"date":"2024-10-30T20:00:00Z","fixing_i":{"price":571.88}},{"date":"2024-10-30T21:00:00Z","fixing_i":{"price":500.11}},{"date":"2024-10-30T22:00:00Z","fixing_i":{"price":499.43}},{"date":"2024-10-30T23:00:00Z","fixing_i":{"price":442.37}}]

a w terminalu wykonujemy polecenia:

cp energy_instrat.json old_energy_instrat.json
jq -sc '.[0] + .[1] | unique_by(.date)' old_energy_instrat.json new.json > energy_instrat.json

można też zawartość pliku skopiować ręcznie do istniejącego, najlepiej w jakimś edytorze np. jsonpathfinder.com
dane będę wrzucał do tego wpisu
18:30 dodane dane na 28.10. do jutra skoncze parser ze strony tge bo tak nie może być
dodane dane na 29.10
dodane dane na 30.10

edit: 31.10
z powodu kolejnego braku danych z energy instrat napisałem w końcu pobieranie danych z innych źródeł. skrypt pobiera dane z trzech źródeł (do wyboru) i tworzy z nich pliki JSON w jedynym właściwym formacie (jak w energy instrat). dane pse_rce pobiera z api pse a tge_rdn2 z tge.pl z html. wszystko pobierane jest poleceniem ‘curl’ i przetwarzane poleceniami ‘jq grep sed cut’. jesli macie HAOS to w skrypcie są linijki polecenia ‘pup’ (są oznaczone, a pup trzeba doinstalować) możecie sprawdzić czy działa bo w dockerze działa w terminalu a w ‘shell_command’ już nie. oprócz energy_energy instrat dane pobierane są przyrostowo, a parametr drugi pilnuje ilości danych w plikach.

do dzieła:
w skrypcie, w pierwszych linijkach możecie zmienić kilka parametrów: nazwy plików, atrybutow json, pobrać wcześniejsze dane
skrypt uruchamia się z parametrami:
1 . nazwa danych lub lista po przecinkach: energy_instrat,tge_rdn,pse_rce
2 . ilość danych w dniach: 7days (wystarczy sama cyfra) wczesniejsze dane można pobrać odkomentowując linijkę #25 #data_day=“2024-10-27” i uruchamiając ręcznie automatyzację kilka razy
3. 4. kolejność dowolna: merge i verbose
merge: łączy dane z pierwszego parametru w jeden plik do wykorzystania np w jednej encjii z atrybutami (poszczególne pliki nadal będą dostępne)
edit: okazało się że merge nie jest potrzebne. robi się to tworząc sensor command_line. można też wczytać z pliku energy_prices.json metodą jak wyżej
verbose: tylko do używania w ręcznym uruchamianiu skryptu. wypisuje wszystko na terminal.
podstawowe informacje po każdym uruchomieniu znajdują się w pliku .log tam gdzie skrypt

w configuration.yaml:

shell_command:
  energy_prices_scrapper: bash /config/downloader/energy_prices/energy_prices_scrapper.sh energy_instrat,tge_rdn,pse_rce 7days
command_line:
  - sensor:
      unique_id: 20241031000001
      name: TGE RDN
      command: "cat /config/downloader/energy_prices/instrat.json"
      value_template: "{{ value_json.tge_rdn[-1].date | as_timestamp |  | timestamp_custom('%Y-%m-%dT%H:%M:%SZ', False) }}"
#      value_template: "{{ value_json.tge_rdn[0].price }}"
#      unit_of_measurement: "PLN/MWh"
      json_attributes:
        - tge_rdn
  - sensor:
      unique_id: 20241031000002
      name: TGE RDN2
      command: "cat /config/downloader/energy_prices/tge_rdn.json"
      value_template: "{{ value_json.tge_rdn2[-1].date | as_timestamp | timestamp_custom('%Y-%m-%dT%H:%M:%SZ', False) }}"
#      value_template: "{{ value_json.tge_rdn2[0].price }}"
#      unit_of_measurement: "PLN/MWh"
      json_attributes:
        - tge_rdn2
  - sensor:
      unique_id: 20241031000003
      name: PSE RCE
      command: "cat /config/downloader/energy_prices/pse_rce.json"
#nizej zamienna linijka ktora laczy ze soba wszystkie okresy po 15min bo przy 7dniach jest warning w logach o rozmiarze 
#      command: "cd /config/downloader/energy_prices/ && jq -c '.pse_rce[] | {date: \"\\(.date[0:14])00:00Z\", price: .price}' pse_rce.json | jq -sc 'unique_by(.date) | {pse_rce: .}'"
      value_template: "{{ value_json.pse_rce[-1].date | as_timestamp | timestamp_custom('%Y-%m-%dT%H:%M:%SZ', False) }}"
#      value_template: "{{ value_json.pse_rce[0].price }}"
#      unit_of_measurement: "PLN/MWh"
      json_attributes:
        - pse_rce
  - sensor:
      unique_id: 20241031000010
      name: Energy Prices
      command: "cd /config/downloader/energy_prices/ && jq -sc '.[0] +.[1] + .[2]' instrat.json tge_rdn.json pse_rce.json"
      value_template: >
        "{{ value_json.tge_rdn[-1].date | as_timestamp | timestamp_custom('%Y-%m-%dT%H:%MZ', False) ~ '/'
        ~ value_json.tge_rdn2[-1].date | as_timestamp | timestamp_custom('%Y-%m-%dT%H:%MZ', False) ~ '/'
        ~ value_json.pse_rce[-1].date | as_timestamp | timestamp_custom('%Y-%m-%dT%H:%MZ', False)}}"
#      value_template: "{{ value_json.tge_rdn[0].price }}"
#      unit_of_measurement: "PLN/MWh"
      json_attributes:
        - tge_rdn
        - tge_rdn2
        - pse_rce

edit: znalazłem właśnie taki zapis (timestamp konca), pomoże w rozpoznawaniu awarii źrodła danych

value_template: "{{ value_json.tge_rdn[-1].date | as_timestamp | timestamp_custom('%Y-%m-%dT%H:%M:%SZ', False) }}"

dane publikowane są:
energy_intrat po 16
tge_rdn na stronie po 14
pse_rce nie wiem
w skrypcie w linijce #28 if [ $verify_h -lt 16 ] , 16 to godzina zmiany odczytu danych z dzisiaj na jutro, więc w automations.yaml jest po 16

- id: '2024103100020'
  alias: EnergyPrices
  description: ''
  triggers:
  - hours: '16'
    minutes: '0'
    seconds: '5'
    trigger: time_pattern
  actions:
  - action: shell_command.energy_prices_scrapper
    data: {}
  mode: single

w katalogu downloader/energy_prices tworzymy plik energy_prices_scrapper.sh i wrzucamy poniższą zawartość. restart HA i zrobione. automatyczne przełączanie się pomiędzy danymi w przypadku awarii któregoś źródła zróbcie we własnym zakresie bo jeszcze tego nie umiem.(jest niżej), ale bash’a jak najbardziej i go lubie :). tak na marginesie wypasione są komendy użyte razem (grep sed cut), bo wyciągnięcie danych ze strony tge.pl to 3 linijki :grin:
duży edit: 03.11
optymalizacja całości (chyba ze zmęczenia wkradł się mały chaos), 03.11 albo pse się zacinało albo u mnie było coś z netem. dodane: pse_ilosc_prob_pobrania i zmiana formatu danych z M15 na H1, dodana weryfikacja nowych danych. skrypt może być gdziekolwiek, DATAPATH załatwia temat. całość działa jak wcześniej

#!/bin/bash
SCRIPTPATH=$(dirname `realpath "$0"`)
DATAPATH="/homeassistant/downloader/energy_prices/"
cd $DATAPATH

attrib=(tge_rdn tge_rdn2 pse_rce)
file_name=(instrat.json tge_rdn.json pse_rce.json)
pse_rce_format=1 # M15=4, H1=1
pse_ilosc_prob_pobrania=5
merge_filename=energy_prices.json
echo "-------------------------------"  >  $0.log
echo "script path/name: "$(realpath $0) >> $0.log
echo "       data path: "$DATAPATH      >> $0.log
echo "     executed at: "`date`         >> $0.log

if [[ ! $1 == *"energy_instrat"* && ! $1 == *"tge_rdn"* && ! $1 == *"pse_rce"* ]]; then
  echo error: blad parametru \"'$1'\"=$1 powinien byc jeden: z energy_instrat,tge_rdn,pse_rce lub wszystkie oddzielone przecinkami >> $0.log
  cat $0.log
  exit 1
fi
if [[ ! ${2//[!0-9]} =~ ^-?[0-9]+$ ]]; then
  echo error: zmienna \"'$2'\"=$2, nie ma cyfry >> $0.log
  cat $0.log
  exit 1
fi

verify_h=`date +%H`
if [ $verify_h -lt 16 ]; then
  data_day=`date +%Y-%m-%d -d @$(($(date +%s)+(0*24*3600)))` #  end=`date +%Y-%m-%d -d @$(($(date +%s)+(0*24*3600)))`
  start_range=`date +%Y-%m-%d -d @$(($(date +%s)-((${2//[!0-9]}-1)*24*3600)))`
else
  data_day=`date +%Y-%m-%d -d @$(($(date +%s)+(1*24*3600)))` #  end=`date +%Y-%m-%d -d @$(($(date +%s)+(1*24*3600)))`
  start_range=`date +%Y-%m-%d -d @$(($(date +%s)-((${2//[!0-9]}-2)*24*3600)))`
fi
#data_day="2024-11-03"  #pobieranie zaleglych danych
#echo "* date range: "$start_range do $data_day
####################################################################################################################################################
if [[ $1 == *"energy_instrat"* ]]; then
  start_range_dd=`date -d $start_range +%d-%m-%YT00:00:00Z`
  end_range_dd=`date -d $data_day +%d-%m-%YT23:59:59Z`
  energy_instrat_url="https://energy-instrat-api.azurewebsites.net/api/prices/energy_price_rdn_hourly?&date_from=$start_range_dd&date_to=$end_range_dd"

  result1=`curl -H "Accept: application/json" -s $energy_instrat_url | jq 'del (..|objects|.fixing_ii, .volume) | (.[] | {date: (.date),price: (.fixing_i.price)})' | jq -sc --arg attr "${attrib[0]}" '{$attr: .}'` #> ${file_name[0]}

  if [[ `jq -c '.[] | length' <<< $result1` -ne 0 ]]; then
    echo $result1 > ${file_name[0]}
    if [[ $3 == *"merge"* || $4 == *"merge"* ]]; then tomerge1=$result1
    fi
    error_msg="no error"
  else
    error_msg="blad danych"
  fi

   msg="-------------------------------"
  msg+=" json attribute: "${attrib[0]}" filename: "${file_name[0]}" filesize: "`wc -c ${file_name[0]} | cut -d ' ' -f1`
  msg+=" date range: "$start_range" to "$data_day
  msg+=" data source: "$energy_instrat_url
  msg+=" error: "$error_msg
  msg+=" -------------------------------"
  if [[ $3 == *"verbose"* || $4 == *"verbose"* ]]; then
    printf "%s\n%s %s %s\n%15s %s\n%15s %s bytes\n%8s %s %s %s %s\n%7s %s %s\n%15s %s %s \n%s%s\n" $msg
    jq -sc '.[]' <<< $result1
  fi
  printf "%s\n%s %s %s\n%15s %s\n%15s %s bytes\n%8s %s %s %s %s\n%7s %s %s\n%15s %s %s \n%s%s\n" $msg >> $0.log
fi
#=================================================================================================================================================
if [[ $1 == *"tge_rdn"* ]]; then
  data_day_dd=`date -d $data_day +%d-%m-%Y`
  tge_rdn_url="https://tge.pl/energia-elektryczna-rdn?dateShow=$data_day_dd"
  curl -s $tge_rdn_url > .tge_rdn.tmp

  date_from_file=`cat .tge_rdn.tmp | grep "dostawy w dniu" | sed 's/<small>dla dostawy w dniu /x/g;s/<\/small><\/h4>/x/g' | cut -d 'x' -f2 | grep '[0:9]'`
  date_convert=`date -d $(awk -F'-' '{printf("%04d-%02d-%02d\n",$3,$2,$1)}' <<< $date_from_file) +%Y-%m-%d`
  hours=(`cat .tge_rdn.tmp | grep "\"date\":" | grep -v parseInt | cut -d ' ' -f7 | grep '[0:9]' | sed 's/\",//g'`)
  prices=(`cat .tge_rdn.tmp | grep kurs1 | sed 's/\t\t\t\t\t\t\t\t/x/g;s/'\''/_/g' | grep -v 'x\|value' | cut -d '_' -f2`)
#  date_from_file=`cat .tge_rdn.tmp | pup 'h4 :contains("dostawy") text{}' | grep -v '^ ' | cut -d ' ' -f5`                                         #pup
#  hours=(` cat .tge_rdn.tmp | pup 'div table#footable_kontrakty_godzinowe tr :nth-of-type(1) text{}' | grep '[0-9]'`)                              #pup
#  prices=(`cat .tge_rdn.tmp | pup 'div table#footable_kontrakty_godzinowe tr :nth-of-type(2) text{}' | sed 's/,/./g;s/-/9999/' | grep '[0-9]'`)    #pup

  for index in ${!hours[*]}; do
    if [[ ${hours[$index]} == *"a"* ]]; then
      unset "hours[$index]"
#      if [[ ${hours[$index]} != "9999" ]]; then                                                                                                    #pup
      if [[ ${#hours[@]} != ${#prices[@]} ]]; then
        unset "prices[$index]"
        prices=( ${prices[@]} )
      fi
      hours=( ${hours[@]} )
    fi
  done
  for index in ${!hours[*]}; do
    date_for_json=`date -d "$date_convert $index" +%Y-%m-%dT%H:%M:%SZ`
    j_fixing_i+=` jq -nc --arg d "$date_for_json" --argjson p "${prices[$index]}" '{"date": $d, "price": $p}'`
    html_data+=`echo ${hours[$index]} " - " ${prices[$index]} "("${#hours[@]}")" - "("${#prices[@]}") "`
  done
  new=`jq -c '.' <<< $j_fixing_i | jq -sc '.'`
#-------------------------------------------------------------------------------------------------------------------------------------------------
  if [[ -f ${file_name[1]} ]]; then
    old=`jq -c '.[]' ${file_name[1]}`
    end_range=`date +%Y-%m-%d -d @$(($(date +%s)+(1*24*3600)))`
    if [[ `jq '. | length' <<< $new` -eq 24 ]]; then
      result2=`jq -sc 'add | unique_by(.date) | .[]' <<< $old$new | jq -sc --arg s $start_range --arg e $end_range  'map(select(.date | . >= $s and . <= $e + "z"))' | jq -sc --arg attr "${attrib[1]}" '{$attr: .[]}'` #to z dolu
      echo $result2 > ${file_name[1]}
      if [[ $3 == *"merge"* || $4 == *"merge"* ]]; then tomerge2=$result2
      fi
      error_msg="no error2"
    else
      error_msg="blad danych2"
    fi
  else
    if [[ `jq '. | length' <<< $new` -eq 24 ]]; then
      result2=`jq -sc --arg attr "${attrib[1]}" ' {$attr: .[]}' <<< $new`
      echo $result2 > ${file_name[1]}
      if [[ $3 == *"merge"* || $4 == *"merge"* ]]; then tomerge2=$result2
      fi
      error_msg="no error1"
    else
      error_msg="blad danych1"
    fi
  fi
   msg="-------------------------------"
  msg+=" json attribute: "${attrib[1]}" filename: "${file_name[1]}" filesize: "`wc -c ${file_name[1]} | cut -d ' ' -f1`
  msg+=" data day: "$data_day_dd" date from html: "$date_from_file" date converted: "$date_convert" date range: "$start_range" to "$end_range
  msg+=" data source: "$tge_rdn_url
  msg+=" error: "$error_msg
  msg+=" -------------------------------"
  if [[ $3 == *"verbose"* || $4 == *"verbose"* ]]; then
    printf "%s\n%s %s %s\n%15s %s\n%15s %s bytes\n%10s %s %s\n%s %s %s %s\n%s %s %s\n%8s %s %s %s %s\n%7s %s %s\n%15s %s %s\n%s\n" $msg
    printf "%8s %2s %7s  %s %s %s \n" $html_data-------------------------------
    jq -sc '.[]' <<< $result2
  fi
  printf "%s\n%s %s %s\n%15s %s\n%15s %s bytes\n%10s %s %s\n%s %s %s %s\n%s %s %s\n%8s %s %s %s %s\n%7s %s %s\n%15s %s %s\n%s\n" $msg >> $0.log
  printf "%8s %2s %7s  %s %s %s \n" $html_data-------------------------------                                        >> $0.log
  rm .tge_rdn.tmp
fi
# #=================================================================================================================================================
if [[ $1 == *"pse_rce"* ]]; then
  operator=eq
  select="%24select=doba,rce_pln,udtczas_oreb"
  filter="%24filter=doba%20$operator%20%27$data_day%27"
  pse_rce_url="https://api.raporty.pse.pl/api/rce-pln?$select&$filter"

  for i in $(seq 1 $pse_ilosc_prob_pobrania); do
    case $pse_rce_format in
      4) new=`curl -H "Accept: application/json" -s $pse_rce_url | sed 's/a:/:/g' | jq -c '.value[] | {date: "\(.doba)T\(.udtczas_oreb[0:5]):00Z", price: (.rce_pln*100 | round/100)}' | jq -sc '.'`
        data_format=M15 ;;
      1) new=`curl -H "Accept: application/json" -s $pse_rce_url | sed 's/a:/:/g' | jq -c '.value[] | {date: "\(.doba)T\(.udtczas_oreb[0:2]):00:00Z", price: (.rce_pln*100 | round/100)}' | jq -sc '. | unique_by(.date)'`
        data_format=H1 ;;
      *) new=`curl -H "Accept: application/json" -s $pse_rce_url | sed 's/a:/:/g' | jq -c '.value[] | {date: "\(.doba)T\(.udtczas_oreb[0:5]):00Z", price: (.rce_pln*100 | round/100)}' | jq -sc '.'`
        data_format=M15 ;;
    esac
    if [[ `jq '. | length' <<< $new` -ne 0 ]]; then break
    fi
    sleep 2
  done

  if [[ -f ${file_name[2]} ]]; then
    old=`jq -c '.[]' ${file_name[2]}`
    if [[ `jq '. | length' <<< $new` -eq pse_rce_format*24 ]]; then
      result3=`jq -sc 'add | unique_by(.date) | .[]' <<< $old$new | jq -sc --arg s $start_range --arg e $data_day  'map(select(.date | . >= $s and . <= $e + "z"))' | jq -sc --arg attr "${attrib[2]}" '{$attr: .[]}'`
      echo $result3 > ${file_name[2]}
      if [[ $3 == *"merge"* || $4 == *"merge"* ]]; then tomerge3=$result3
      fi
      error_msg="no error2"
    else
      error_msg="blad danych2"
    fi
  else
    if [[ `jq '. | length' <<< $new` -eq pse_rce_format*24 ]]; then      #result3=`jq -c '.' <<< $new | jq -sc --arg attr "${attrib[2]}" ' {$attr: .[]}'` #to jest ok
      result3=`jq -sc --arg attr "${attrib[2]}" ' {$attr: .[]}' <<< $new`
      echo $result3 > ${file_name[2]}
      if [[ $3 == *"merge"* || $4 == *"merge"* ]]; then tomerge3=$result3
      fi
      error_msg="no error1"
    else
      error_msg="blad danych1"
    fi
  fi

   msg="-------------------------------"
  msg+=" json attribute: "${attrib[2]}" filename: "${file_name[2]}" filesize: "`wc -c ${file_name[2]} | cut -d ' ' -f1`
  msg+=" data format: "$data_format
  msg+=" data day: "$data_day" date range: "$start_range" to "$data_day
  msg+=" data source: "$pse_rce_url
  msg+=" error: "$error_msg
  msg+=" -------------------------------"
  if [[ $3 == *"verbose"* || $4 == *"verbose"* ]]; then
    printf "%s\n%s %s %s\n%15s %s\n%15s %s bytes\n%7s %s %s\n%10s %s %s\n%8s %s %s %s %s\n%7s %s %s\n%15s %s %s\n%s\n" $msg
     jq -sc '.[]' <<< $result3
  fi
  printf "%s\n%s %s %s\n%15s %s\n%15s %s bytes\n%7s %s %s\n%10s %s %s\n%8s %s %s %s %s\n%7s %s %s\n%15s %s %s\n%s\n" $msg >> $0.log
fi
# #=================================================================================================================================================
if [[ $3 == *"merge"* || $4 == *"merge"* ]]; then
  jq -sc 'add' <<< $tomerge1$tomerge2$tomerge3 > $merge_filename
  if [[ $3 == *"verbose"* || $4 == *"verbose"* ]]; then
    echo -e '\n'"#################################################### merged ####################################################"'\n'
    jq -sc '.[]' $merge_filename
  fi
fi
#script by © bialy
#https://forum.arturhome.pl/t/rynkowe-ceny-energii-elektrycznej/12051/117
#https://stackoverflow.com/questions/49181898/jq-replace-part-of-value-in-json
#new=`curl -H "Accept: application/json" -s $pse_rce_url | jq '.value[]' | jq '{date: "\(.doba)T\(.udtczas_oreb[0:6] | sub("a"; "")):00Z", price: .rce_pln}' | jq -nc '. |= [inputs]'`

efekt końcowy

edit:
poniżej sensor przełączający się pomiędzy źródłami: energy_instrat > tge_rdn > pse_rce (6h przed północą). dla pse_rce w formacie danych M15 jest parametr pse_rce_format = 4, dla H1 pse_rce_format=1. dla H1 nie trzeba tego zmieniać, ale wtedy zakres wczytanych danych jest spoza zakresu. i pomimo że działa to nigdy nie wiadomo. sprawdzałem działanie ręcznie usuwając dane i było ok. przydałoby się jakieś powiadomienie do jeśli nastąpi przełączenie, no ale tego już chyba nie ogarnę
w configuration.yaml

template:
  - sensor:
    - name: "Crurrent TGE Prices extended"
      unique_id: 20241002000001
      unit_of_measurement: "PLN/MWh"
      state: >
        {% set current_time = now().strftime('%Y-%m-%dT%H:00:00Z')     %}
        {% set delta = ((states('sensor.tge_rdn') | as_timestamp - current_time | as_timestamp)/3600 ) | int +1 %}
        {% set dates = state_attr('sensor.tge_rdn', 'tge_rdn')[-delta:]  %}
        {% if delta <= 6 %}
          {% set delta = ((states('sensor.tge_rdn2') | as_timestamp - current_time | as_timestamp)/3600 ) | int +1 %}
          {% set dates = state_attr('sensor.tge_rdn2', 'tge_rdn2')[-delta:]  %}
          {% if delta <= 6 %}
            {% set pse_rce_format = 4 %}
            {% set delta = (pse_rce_format * (states('sensor.pse_rce') | as_timestamp - current_time | as_timestamp) / 3600) | int+1  %}
            {% set current_quarter = ( now().strftime('%M') | int / 15) | int  %}
            {% set delta = delta-current_quarter %}
            {% set dates = state_attr('sensor.pse_rce', 'pse_rce')[-delta:]  %}
          {% endif %}
        {% endif %}
        {% for date in dates %}
          {% set start_time = date.date %}
          {% if start_time >= current_time %}
            {{ date.price }}
            {% break %}
          {% endif %}
        {% endfor %}
      icon: mdi:cash-100

poprzedni skrypt który tu był HighestLowest będę teraz dostosowywał do danych powyżej, wrzuciłem tylko do wglądu i ewentualnych uwag

1 polubienie

Dzięki za integrację!

Niestety, po iluś godzinach spędzonych nad tematem, doszedłem do tego, że RCE nie jest 1:1 ceną używaną w taryfach dynamicznych w Polsce.
Integracja Nordpool ani ENTSO-E też nie są idealne, gdyż Nordpool to inna giełda niż TGE i ich ceny są podobne, ale nie identyczne. ENTSO-E bazuje na TGE Fixing 2, więc uwzględnia przepływy międzynarodowe.

Nasi sprzedawcy energii elektrycznej bazują na TGE Fixing 1.
Udało mi się nawiązać współpracę z Piotrem Machowskim i napisał on odpowiednią integrację dla Home Assistant, która odzwierciedla mechanizm używany w taryfach dynamicznych.
Mam do Was dużą prośbę. Piotr spędził dobrych kilka godzin nad zrobieniem tego kodu, puśćcie mu donate’a po 5 czy 10pln bo chłopak zrobił to na konkretne zlecenie.

W ramach integracji, po ustaleniu interesującej Was jednostki miary, można ustalić makro Jinja, które będzie służyło do modyfikacji wyświetlanej wartości.
Ja w cenę energii doliczam od razu opłatę dystrybucyjną G12W Tauronu i VAT.
Wpiszcie to w Value template for Fixing 1 - Rate

{% set s = {
    "VAT": 1.23,
    "peak_distribution_rate": states('input_number.dystrybucja_g12w_szczyt')|float,
    "offpeak_distribution_rate": states('input_number.dystrybucja_g12w_pozaszczyt')|float,
} %}

{# Define peak periods for G12W tariff as a list of tuples (start_hour, stop_hour) #}
{% set peaks = [(6, 13), (15, 22)] %}

{# Determine if it's a weekend (offpeak all day) #}
{% set is_weekend = now().weekday() >= 5 %}

{# Check if the current time falls within any peak period #}
{% set is_peak = False %}
{% if not is_weekend %}
    {% for peak in peaks %}
        {% if now().hour >= peak[0] and now().hour < peak[1] %}
            {% set is_peak = True %}
        {% endif %}
    {% endfor %}
{% endif %}

{# Set distribution rate based on peak or offpeak time #}
{% set distribution_rate = s.peak_distribution_rate if is_peak else s.offpeak_distribution_rate %}

{# Final price calculation: convert current_price to PLN, add distribution rate, market access fee, and apply VAT #}
{{ (fixing1_rate + distribution_rate) * s.VAT | float }}
2 polubienia

Dodatkowo, integracja TGE, ENTSO-E jak i Nordpool wystawiają ustandaryzowany format danych, którym można nakarmić kolejne integracje.

Ja używam EV Smart Charging, który wyszukuje najtańsze godziny w ciągu najbliższego X czasu i pozwala załączać encje switch

Jest też przegenialne makro Jinja, które pozwala wyszukiwać najtańsze, najdroższe, jako jeden duży blok, jako kilka bloków, do wyboru do koloru.

Załączam moje przykłady:

template:
  - binary_sensor:
    - unique_id: phahw3ra8aiShohphaibiegee9ahf6Cu
      name: 4 cheapest consecutive hours
      device_class: battery_charging
      state: >
        {% from 'cheapest_energy_hours.jinja' import cheapest_energy_hours -%}
        {% set sensor = 'sensor.tge_fixing_1_rate' -%}
        {% set start = states('input_datetime.find_cheapest_most_expensive_hours_since') -%}
        {% set end = states('input_datetime.find_cheapest_hours_until') -%}
        {% set hours = 4 -%}
        {{
          cheapest_energy_hours(
            sensor=sensor,
            attr_today='prices_today',
            attr_tomorrow='prices_tomorrow',
            time_key='time',
            value_key='price',
            start=start,
            end=end,
            include_tomorrow=true,
            hours=4,
            split=false,
            mode='is_now'
          )
        }}
      availability: >
        {{
          (states('sensor.tge_fixing_1_rate') | is_number)
        }}
      attributes:
        start: "{{ states('input_datetime.find_cheapest_most_expensive_hours_since') }}"
        end: "{{ states('input_datetime.find_cheapest_hours_until') }}"
        hours: "4"
        debug: >
          {% from 'cheapest_energy_hours.jinja' import cheapest_energy_hours -%}
          {% set sensor = 'sensor.tge_fixing_1_rate' -%}
          {% set start = states('input_datetime.find_cheapest_most_expensive_hours_since') -%}
          {% set end = states('input_datetime.find_cheapest_hours_until') -%}
          {% set hours = 4 -%}
          {{
            cheapest_energy_hours(
              sensor=sensor,
              attr_today='prices_today',
              attr_tomorrow='prices_tomorrow',
              time_key='time',
              value_key='price',
              start=start,
              end=end,
              include_tomorrow=true,
              hours=hours,
              split=false,
              mode='all'
            )
          }}
    - unique_id: Aesouthaico3ic7oowa4agahiJeeghoo
      name: 4 cheapest non-consecutive hours
      device_class: battery_charging
      state: >
        {% from 'cheapest_energy_hours.jinja' import cheapest_energy_hours -%}
        {% set sensor = 'sensor.tge_fixing_1_rate' -%}
        {% set start = states('input_datetime.find_cheapest_most_expensive_hours_since') -%}
        {% set end = states('input_datetime.find_cheapest_hours_until') -%}
        {% set hours = 4 -%}
        {{
          cheapest_energy_hours(
            sensor=sensor,
            attr_today='prices_today',
            attr_tomorrow='prices_tomorrow',
            time_key='time',
            value_key='price',
            start=start,
            end=end,
            include_tomorrow=true,
            hours=4,
            split=true,
            mode='is_now'
          )
        }}
      availability: >
        {{
          (states('sensor.tge_fixing_1_rate') | is_number)
        }}
      attributes:
        start: "{{ states('input_datetime.find_cheapest_most_expensive_hours_since') }}"
        end: "{{ states('input_datetime.find_cheapest_hours_until') }}"
        hours: "4"
        debug: >
          {% from 'cheapest_energy_hours.jinja' import cheapest_energy_hours -%}
          {% set sensor = 'sensor.tge_fixing_1_rate' -%}
          {% set start = states('input_datetime.find_cheapest_most_expensive_hours_since') -%}
          {% set end = states('input_datetime.find_cheapest_hours_until') -%}
          {% set hours = 4 -%}
          {{
            cheapest_energy_hours(
              sensor=sensor,
              attr_today='prices_today',
              attr_tomorrow='prices_tomorrow',
              time_key='time',
              value_key='price',
              start=start,
              end=end,
              include_tomorrow=true,
              hours=hours,
              split=true,
              mode='all'
            )
          }}
    - unique_id: yaesat9Phai3wu4eishuThofietoo8Sh
      name: 4 most expensive consecutive hours
      device_class: battery_charging
      state: >
        {% from 'cheapest_energy_hours.jinja' import cheapest_energy_hours -%}
        {% set sensor = 'sensor.tge_fixing_1_rate' -%}
        {% set start = states('input_datetime.find_cheapest_most_expensive_hours_since') -%}
        {% set end = states('input_datetime.find_cheapest_hours_until') -%}
        {% set hours = 4 -%}
        {{
          cheapest_energy_hours(
            sensor=sensor,
            attr_today='prices_today',
            attr_tomorrow='prices_tomorrow',
            time_key='time',
            value_key='price',
            start=start,
            end=end,
            include_tomorrow=true,
            hours=4,
            split=false,
            lowest=false,
            mode='is_now'
          )
        }}
      availability: >
        {{
          (states('sensor.tge_fixing_1_rate') | is_number)
        }}
      attributes:
        start: "{{ states('input_datetime.find_cheapest_most_expensive_hours_since') }}"
        end: "{{ states('input_datetime.find_cheapest_hours_until') }}"
        hours: "4"
        debug: >
          {% from 'cheapest_energy_hours.jinja' import cheapest_energy_hours -%}
          {% set sensor = 'sensor.tge_fixing_1_rate' -%}
          {% set start = states('input_datetime.find_cheapest_most_expensive_hours_since') -%}
          {% set end = states('input_datetime.find_cheapest_hours_until') -%}
          {% set hours = 4 -%}
          {{
            cheapest_energy_hours(
              sensor=sensor,
              attr_today='prices_today',
              attr_tomorrow='prices_tomorrow',
              time_key='time',
              value_key='price',
              start=start,
              end=end,
              include_tomorrow=true,
              hours=hours,
              split=false,
              lowest=false,
              mode='all'
            )
          }}
    - unique_id: fuecooPhaizavoh0ShooSeequu8echee
      name: 4 most expensive non-consecutive hours
      device_class: battery_charging
      state: >
        {% from 'cheapest_energy_hours.jinja' import cheapest_energy_hours -%}
        {% set sensor = 'sensor.tge_fixing_1_rate' -%}
        {% set start = states('input_datetime.find_cheapest_most_expensive_hours_since') -%}
        {% set end = states('input_datetime.find_cheapest_hours_until') -%}
        {% set hours = 4 -%}
        {{
          cheapest_energy_hours(
            sensor=sensor,
            attr_today='prices_today',
            attr_tomorrow='prices_tomorrow',
            time_key='time',
            value_key='price',
            start=start,
            end=end,
            include_tomorrow=true,
            hours=4,
            split=true,
            lowest=false,
            mode='is_now'
          )
        }}
      availability: >
        {{
          (states('sensor.tge_fixing_1_rate') | is_number)
        }}
      attributes:
        start: "{{ states('input_datetime.find_cheapest_most_expensive_hours_since') }}"
        end: "{{ states('input_datetime.find_cheapest_hours_until') }}"
        hours: "4"
        debug: >
          {% from 'cheapest_energy_hours.jinja' import cheapest_energy_hours -%}
          {% set sensor = 'sensor.tge_fixing_1_rate' -%}
          {% set start = states('input_datetime.find_cheapest_most_expensive_hours_since') -%}
          {% set end = states('input_datetime.find_cheapest_hours_until') -%}
          {% set hours = 4 -%}
          {{
            cheapest_energy_hours(
              sensor=sensor,
              attr_today='prices_today',
              attr_tomorrow='prices_tomorrow',
              time_key='time',
              value_key='price',
              start=start,
              end=end,
              include_tomorrow=true,
              hours=hours,
              split=true,
              lowest=false,
              mode='all'
            )
          }}


Odpowiada to na pytania:

2 polubienia

Gdzie można to ustalić ?

skrypt w bash’u HighestLowest:
może nie wszyscy wiedzą. system rozliczania energii kiedyś w 2025 ma przejść na rozliczanie M15 i wszystko co jest poniżej jest już dostosowane (w dniu zmiany wystarczy usunąć linijkę #26, jest podpisana), poprzedni skrypt pobierający dane dostosuję jak bedę miał dane :slight_smile:
Skrypt pisałem ucząc się narzędzia jq jeszcze przed skryptem do pobierania danych. teraz dokończyłem, a tak mi się spodobał format JSON i narzędzie że nie mogłem przestać i może wyszło nadmiarowo, ale co chwila miałem nowe pomysły. z tego co widzę to nauka generowania własnego pliku JSON rozwiąże wiele problemów w przyszłości. na razie nie mam nawet urządzeń do zarządzania, jedyne co mogę to odłączać od sieci ups’a (komputery to ponad 20% całego zużycia).
celem jaki chciałem osiągnąć było utworzenie pliku z parametrami znajdujący szczyty i dołki w 4ch okresach doby i dodatkowo żeby miał oddzielne konfiguracje dla różnych urządzeń. potem wczytanie całości do jednego sensora lub oddzielnych.

do dzieła:
w linijkach 40+ ustawiamy parametry urządzeń, wszystkie parametry są w tablicach (w bashu separatorem elementow tablicy jest spacja). okno czasowe jest podawane w h, jak zmienią rozliczanie to zmienię rozdzielczość okna na M15

skrypt uruchamia się z parametrami:
1 . nazwa pliku z danymi: np instrat.json
2 . tryb danych: simple, detailed, debug (najlepiej zacząć od debug, żeby zobaczyć co jest dostępne)
3 . verbose, tylko do używania w ręcznym uruchamianiu skryptu, lub without_file, nie tworzy pliku json, wczytuje dane bezpośrednio do sensora
podstawowe informacje po każdym uruchomieniu znajdują się w pliku .log tam gdzie skrypt, szczegółowość logów w linijce #6 detailed_log=false,true
recznie uruchamiamy w terminalu lub ssh po wykonaniu /chmod +x/ ./HighestLowest.sh
skrypt tworzymy gdziekolwiek, ścieżkę do danych podajemy w skrypcie

#!/bin/bash
SCRIPTPATH=$(dirname `realpath "$0"`)
DATAPATH="/config/downloader/energy_prices"
cd $DATAPATH
file_name="TGE_HighsLows.json"
detailed_log=false
#echo `date` "parametry:" $1 $2 $3 >> timestamp.log

next_day=`date +%Y-%m-%d -d @$(($(date +%s)+(0*3600)))` #dodawanie dni w poleceniu date w obecnym busybox dziala tylko na sekundach
#poniżej dla json bezpośrednio z energy instrat, zostawiam na wszelki wypadek
#prepared_json=`jq 'del (..|objects|.fixing_ii, .volume) | (.[] | {date: (.date),price: (.fixing_i.price)})' energy_instrat_orig.json | jq -sc --arg s $next_day --arg e $next_day  'map(select(.date | . >= $s and . <= $e + "z"))'`

if [ -f $1 ]; then
  err_msg="no errors"
  prepared_json=`jq -c --arg s $next_day --arg e $next_day  '.[] | map(select(.date | . >= $s and . <= $e + "z"))' $1`
  data_length=$(($(jq -c '. | length' <<< $prepared_json)/24))
  if [[ $data_length -eq 0 ]]; then
    err_msg="blad danych "$1", brak danych na "$next_day
  fi
else
  err_msg="blad parametru "'$1'", brak pliku "$1
  data_length=0
fi

############# ponizsza linijka do usuniecia z dniem przejscia systemu rozliczania na M15 (kiedys w 2015) chyba ze swiadomie wybieramy H1 ##############
if [[ $data_length -eq 4 ]]; then  prepared_json=`jq -c '.[] | {date: "\(.date[0:14])00:00Z", price: .price}' <<< $prepared_json | jq -sc 'unique_by(.date)'`; fi

#########################################################################################################################################
if [[ $3 != "without_file" ]]; then
  echo --------------------------------------------------------------------------- > $0.log
  echo "script path/name: "$(realpath $0)                   >> $0.log
  echo "       data path: "$DATAPATH                        >> $0.log
  echo "     executed at: "`date`                           >> $0.log
  echo "        data for: "$next_day" from "$1              >> $0.log
  echo "        filename: "$file_name                       >> $0.log
  echo "           error: "$err_msg                         >> $0.log
  echo --------------------------------------------------------------------------- >> $0.log
fi
#########################################################################################################################################
# ---------------------------------------------------------------------------------------------
       list_devices=(urzadzenie1             internet                urzadzenie3)
    def_lows_states=(ON                      ON                      ON)
   def_highs_states=(OFF                     OFF                     OFF)
 def_neutral_states=(ON                      ON                      ON)
         night_lows=(00:00-05:59             01:00-05:59             02:00-05:59)
        midday_lows=(09:00-16:59             09:00-16:59             09:00-16:59)
      morning_highs=(05:00-10:59             05:00-10:59             06:00-10:59)
      evening_highs=(16:00-22:59             15:00-22:59             15:00-22:59)
   night_low_window=(1                       3                       1)
  midday_low_window=(1                       3                       1)
morning_high_window=(2                       4                       0)
evening_high_window=(2                       4                       4)
# ---------------------------------------------------------------------------------------------
declare -a result_array
day_lowest=(`jq '[.[] | select(.price)] | min_by(.price)' <<< $prepared_json | jq '.price, .date'`)
day_highest=(`jq '[.[] | select(.price)] | max_by(.price)' <<< $prepared_json | jq '.price, .date'`)
#########################################################################################################################################
for index in ${!list_devices[*]}; do
  start_range1=`date -d $next_day +%Y-%m-%dT$(echo ${night_lows[$index]} | cut -d '-' -f1)`
    end_range1=`date -d $next_day +%Y-%m-%dT$(echo ${night_lows[$index]} | cut -d '-' -f2)`
  start_range2=`date -d $next_day +%Y-%m-%dT$(echo ${midday_lows[$index]} | cut -d '-' -f1)`
    end_range2=`date -d $next_day +%Y-%m-%dT$(echo ${midday_lows[$index]} | cut -d '-' -f2)`
  start_range3=`date -d $next_day +%Y-%m-%dT$(echo ${morning_highs[$index]} | cut -d '-' -f1)`
    end_range3=`date -d $next_day +%Y-%m-%dT$(echo ${morning_highs[$index]} | cut -d '-' -f2)`
  start_range4=`date -d $next_day +%Y-%m-%dT$(echo ${evening_highs[$index]} | cut -d '-' -f1)`
    end_range4=`date -d $next_day +%Y-%m-%dT$(echo ${evening_highs[$index]} | cut -d '-' -f2)`
# ---------------------------------------------------------------------------------------------
  diff=`expr $(date -d "$(echo ${night_lows[$index]} | cut -d '-' -f2)" -u +%s) - $(date -d "$(echo ${night_lows[$index]} | cut -d '-' -f1)" -u +%s)`
  n_range1=`date -d "@$diff" -u +%Hh:%Mm | sed 's/^0//'`
  diff=`expr $(date -d "$(echo ${midday_lows[$index]} | cut -d '-' -f2)" -u +%s) - $(date -d "$(echo ${midday_lows[$index]} | cut -d '-' -f1)" -u +%s)`
  n_range2=`date -d "@$diff" -u +%Hh:%Mm | sed 's/^0//'`
  diff=`expr $(date -d "$(echo ${morning_highs[$index]} | cut -d '-' -f2)" -u +%s) - $(date -d "$(echo ${morning_highs[$index]} | cut -d '-' -f1)" -u +%s)`
  n_range3=`date -d "@$diff" -u +%Hh:%Mm | sed 's/^0//'`
  diff=`expr $(date -d "$(echo ${evening_highs[$index]} | cut -d '-' -f2)" -u +%s) - $(date -d "$(echo ${evening_highs[$index]} | cut -d '-' -f1)" -u +%s)`
  n_range4=`date -d "@$diff" -u +%Hh:%Mm | sed 's/^0//'`
# ---------------------------------------------------------------------------------------------
  if [[ $3 != "without_file" && $detailed_log == true ]]; then
    echo ${list_devices[$index]}:                                                                                                    >> $0.log
    echo "night:   "$start_range1 do $end_range1 "|" ${night_lows[$index]}    "("$n_range1") (W:"${night_low_window[$index]}"h)"     >> $0.log
    echo "morning: "$start_range3 do $end_range3 "|" ${morning_highs[$index]} "("$n_range3") (W:"${morning_high_window[$index]}"h)"  >> $0.log
    echo "midday:  "$start_range2 do $end_range2 "|" ${midday_lows[$index]}   "("$n_range2") (W:"${midday_low_window[$index]}"h)"    >> $0.log
    echo "evening: "$start_range4 do $end_range4 "|" ${evening_highs[$index]} "("$n_range4") (W:"${evening_high_window[$index]}"h)"  >> $0.log
    echo "==========================================================================="                                               >> $0.log
  fi
# ============================================================================================
  device_name=`jq -nc --arg name "${list_devices[$index]}" '. += {"Name": $name}'`
  default_states=`jq -nc --arg err "$err_msg"    '. += { "error": $err }'`
  default_states+=`jq -nc --arg lows "${def_lows_states[$index]}" --arg highs "${def_highs_states[$index]}" --arg neutral "${def_neutral_states[$index]}" '. += { "Default_Lows_state": $lows, "Default_Highs_state": $highs, "Default_Neutral_state": $neutral }'`

  unset range1; unset range2; unset range3; unset range4
  if [[ ${night_low_window[$index]} != 0 ]]; then
    range1=`jq --argjson min "$(($data_length*${night_low_window[$index]}))"    --arg s $start_range1 --arg e $end_range1 'map(select(.date | . >= $s and . <= $e + "z")) | sort_by(.price)[:$min] | sort_by(.date)' <<< $prepared_json | jq -sc '{"Lows": .[]}'`;   fi
  if [[ ${midday_low_window[$index]} != 0 ]]; then
    range2=`jq --argjson min "$(($data_length*${midday_low_window[$index]}))"   --arg s $start_range2 --arg e $end_range2 'map(select(.date | . >= $s and . <= $e + "z")) | sort_by(.price)[:$min] | sort_by(.date)' <<< $prepared_json | jq -sc '{"Lows": .[]}'`;   fi
  if [[ ${morning_high_window[$index]} != 0 ]]; then
    range3=`jq --argjson max "$(($data_length*${morning_high_window[$index]}))" --arg s $start_range3 --arg e $end_range3 'map(select(.date | . >= $s and . <= $e + "z")) | sort_by(.price)[-$max:] | sort_by(.date)' <<< $prepared_json | jq -sc '{"Highs": .[]}'`; fi
  if [[ ${evening_high_window[$index]} != 0 ]]; then
    range4=`jq --argjson max "$(($data_length*${evening_high_window[$index]}))" --arg s $start_range4 --arg e $end_range4 'map(select(.date | . >= $s and . <= $e + "z")) | sort_by(.price)[-$max:] | sort_by(.date)' <<< $prepared_json | jq -sc '{"Highs": .[]}'`; fi
# ---------------------------------------------------------------------------------------------
  if [[ $2 == "detailed" || $2 == "debug" ]]; then
    unset day_price_extrems
    day_price_extrems=` jq -nc --argjson min_p "${day_lowest[0]}"          --argjson min_d "${day_lowest[1]}"          '. += { "Day_Lowest_price":      $min_p,     "Day_Lowest_time":      $min_d }'`
    day_price_extrems+=`jq -nc --argjson max_p "${day_highest[0]}"         --argjson max_d "${day_highest[1]}"         '. += { "Day_Highest_price":     $max_p,     "Day_Highest_time":     $max_d }'`

    unset range_low_extrems
    if [[ ${night_low_window[$index]} != 0 ]];    then
      night_lowest=(`jq '[.Lows[]  | select(.price)] | min_by(.price)' <<< $range1 | jq '.price, .date'`)
      range_low_extrems=`jq -nc --argjson night_p "${night_lowest[0]}"      --argjson night_d "${night_lowest[1]}"      '. += { "Night_Lowest_price":    $night_p,   "Night_Lowest_time":    $night_d }'`;    fi
    if [[ ${midday_low_window[$index]} != 0 ]];   then
      midday_lowest=(`jq '[.Lows[]  | select(.price)] | min_by(.price)' <<< $range2 | jq '.price, .date'`)
      range_low_extrems+=`jq -nc --argjson midday_p "${midday_lowest[0]}"    --argjson midday_d "${midday_lowest[1]}"    '. += { "Midday_Lowest_price":   $midday_p,  "Midday_Lowest_time":   $midday_d }'`;  fi

    unset range_high_extrems
    if [[ ${morning_high_window[$index]} != 0 ]]; then
      morning_highest=(`jq '[.Highs[] | select(.price)] | max_by(.price)' <<< $range3 | jq '.price, .date'`)
      range_high_extrems=`jq -nc --argjson morning_p "${morning_highest[0]}" --argjson morning_d "${morning_highest[1]}" '. += { "Morning_Highest_price": $morning_p, "Morning_Highest_time": $morning_d }'`;  fi
    if [[ ${evening_high_window[$index]} != 0 ]]; then
      evening_highest=(`jq '[.Highs[] | select(.price)] | max_by(.price)' <<< $range4 | jq '.price, .date'`)
      range_high_extrems+=`jq -nc --argjson evening_p "${evening_highest[0]}" --argjson evening_d "${evening_highest[1]}" '. += { "Evening_Highest_price": $evening_p, "Evening_Highest_time": $evening_d }'`; fi
# ---------------------------------------------------------------------------------------------
    if [[ $2 == "debug" ]]; then
      unset script_params
      script_params=` jq -nc --arg range1 "${night_lows[$index]} ($n_range1) (window:${night_low_window[$index]}h)"       '. += { "Night_Lows_hours":    $range1 }'`
      script_params+=`jq -nc --arg range2 "${midday_lows[$index]} ($n_range2) (window:${midday_low_window[$index]}h)"     '. += { "Midday_Lows_hours":   $range2 }'`
      script_params+=`jq -nc --arg range3 "${morning_highs[$index]} ($n_range3) (window:${morning_high_window[$index]}h)" '. += { "Morning_Highs_hours": $range3 }'`
      script_params+=`jq -nc --arg range4 "${evening_highs[$index]} ($n_range4) (window:${evening_high_window[$index]}h)" '. += { "Evening_Highs_hours": $range4 }'`
      script_params+=`jq -nc --arg data_file "$1" '. += { "data_from_file": $data_file }'`
      if [[ $data_length -eq 4 ]]; then     script_params+=`jq -nc '. += { "data_format": "M15" }'`
      else                                  script_params+=`jq -nc '. += { "data_format": "H1" }'`
      fi
      if [[ $3 == "without_file" ]]; then   script_params+=`jq -nc --arg json_file "$3"         '. += { "created_json_file": $json_file }'`
      else                                  script_params+=`jq -nc --arg json_file "$file_name" '. += { "created_json_file": $json_file }'`
      fi
    fi
  fi
# ---------------------------------------------------------------------------------------------
  result_array+=`jq -s '[ (.[] | keys[]) as $k | reduce .[] as $item (null; .[$k] += $item[$k]) ] | add' \
  <<< "$device_name$default_states$day_price_extrems$range_low_extrems$range_high_extrems$script_params$range1$range2$range3$range4" | jq -sc --arg name "device$(($index+1))" '{$name: .[]}'`
done
HighsLows_result=`jq -sc '[ (.[] | keys[]) as $k | reduce .[] as $item (null; .[$k] += $item[$k]) ] | add' <<< ${result_array[*]}`
#########################################################################################################################################
if [[ $3 == "without_file" ]]; then  jq -c '.' <<< $HighsLows_result
else jq -c '.' <<< $HighsLows_result > $file_name && if [[ $3 == "verbose" ]]; then      cat $0.log && jq '.[]' -sc $file_name;  fi fi
#########################################################################################################################################
#========================================================================================================================================
:<<'END'
#jq -s add <<< "$range1$range2$range3$range4" #jesli takie same tablice np. highs[] lows[] nie działa
#https://stackoverflow.com/questions/47105490/can-i-pass-a-string-variable-to-jq-rather-than-passing-a-filea=
#https://jsonpath.com/
#https://jsonpathfinder.com/
state:                 {{ states('sensor.highestlowest') }}
device_name1:          {{ state_attr('sensor.highestlowest','device1').Name }}
device_name2:          {{ state_attr('sensor.highestlowest','device2').Name }}
device_name3:          {{ state_attr('sensor.highestlowest','device3').Name }}
Default_Highs_state:   {{ state_attr('sensor.highestlowest','device1').Default_Highs_state }}
Default_Lows_state:    {{ state_attr('sensor.highestlowest','device1').Default_Lows_state }}
Default_Neutral_state: {{ state_attr('sensor.highestlowest','device1').Default_Neutral_state }}
error:                 {{ state_attr('sensor.highestlowest','device1').error }}
lows[0]:               {{ state_attr('sensor.highestlowest','device1').Lows[0].price }}
lows[0]:               {{ state_attr('sensor.highestlowest','device1').Lows[0].date }}
highs[0]:              {{ state_attr('sensor.highestlowest','device1').Highs[0].price }}
highs[0]:              {{ state_attr('sensor.highestlowest','device1').Highs[0].date }}
highs list:            {{ state_attr('sensor.highestlowest','device1').Highs }}
lows list:             {{ state_attr('sensor.highestlowest','device1').Lows }}
=================================
state:                 {{ states('sensor.internet_highs') }}
device_name:           {{ state_attr('sensor.internet_highs','Name') }}
Default_Highs_state:   {{ state_attr('sensor.internet_highs','Default_Highs_state') }}
Default_Lows_state:    {{ state_attr('sensor.internet_highs','Default_Lows_state') }}
Default_Neutral_state: {{ state_attr('sensor.internet_highs','Default_Neutral_state') }}
error:                 {{ state_attr('sensor.internet_highs','error') }}
lows[0]:               {{ state_attr('sensor.internet_highs','Lows')[0].price }}
lows[0]:               {{ state_attr('sensor.internet_highs','Lows')[0].date }}
highs[0]:              {{ state_attr('sensor.internet_highs','Highs')[0].price }}
highs[0]:              {{ state_attr('sensor.internet_highs','Highs')[0].date }}
highs list:            {{ state_attr('sensor.internet_highs','Highs') }}
lows list:             {{ state_attr('sensor.internet_highs','Lows') }}
END
#script by © bialy
#https://forum.arturhome.pl/t/rynkowe-ceny-energii-elektrycznej/12051/121

wczytywanie danych:

  1. wersja z tworzonym plikiem .json:
    w configuration.yaml i automations.yaml
####### configuration.yaml #######
shell_command:
  energy_prices_scrapper: bash /config/downloader/energy_prices/energy_prices_scrapper.sh energy_instrat,tge_rdn,pse_rce 10days merge
  highest_lowest: bash /config/downloader/energy_prices/HighestLowest.sh instrat.json simple

command_line:
  - sensor:
      unique_id: 20241110000001
      name: HighestLowest1
      command: "cat /config/downloader/energy_prices/TGE_HighsLows.json"
      value_template: "{{ value_json.device1.Name | string }},{{ value_json.device2.Name | string }},{{ value_json.device3.Name | string }}"
      json_attributes:
        - device1
        - device2
        - device3
      icon: mdi:cash-100

####### automations.yaml #######
- id: '1731378052863'
  alias: HighestLowest
  description: ''
  triggers:
  - trigger: time
    at: 00:00:00
  - trigger: homeassistant
    event: start
  conditions: []
  actions:
  - action: shell_command.highest_lowest
    data: {}
  mode: single
  1. wersja z wczytywaniem całości bezpośrednio do sensora:
####### configuration.yaml #######
command_line:
  - sensor:
      unique_id: 20241110000002
      name: HighestLowest2
      command: "bash /config/downloader/energy_prices/HighestLowest.sh instrat.json simple without_file"
      scan_interval: 86400
      value_template: "{{ value_json.device1.Name | string }},{{ value_json.device2.Name | string }},{{ value_json.device3.Name | string }}"
      json_attributes:
        - device1
        - device2
        - device3
      icon: mdi:cash-100

####### automations.yaml #######
- id: '1731436212633'
  alias: HighestLowest2
  description: ''
  triggers:
  - trigger: time
    at: 00:00:00
  - trigger: homeassistant
    event: start
  conditions: []
  actions:
  - action: homeassistant.update_entity
    data:
      entity_id:
      - sensor.highestlowest2
  mode: single

dalej z powstałymi sensorami robimy co chcemy :slight_smile:
przykładowy sensor którego używam i testuje od kilku dni (już zautomatyzowany). jesli chcecie wyciągać dane z sensora “without_file” podmieniacie “np value_json.error” na właściwe nazwy np. state_attr(‘sensor.highestlowest’,‘error’) i robicie z tego template sensor. przykłady state_attr są w skrypcie
configurations.yaml:
edit: zrezygnowałem z json_attributes_path i przeniosłem do command, zmieniła się reszta sensora

command_line:
  - sensor:
      unique_id: 20241108000002
      name: Internet Highs
      command: "jq -c '.device2' /config/downloader/energy_prices/TGE_HighsLows.json"
      value_template: >
        {% set current_quarter = ( now().strftime('%M') | int / 15) | int  %}
        {% set current_time_m15 = (now().strftime('%Y-%m-%dT%H:') + ((current_quarter * 15) | string + 'Z')) | as_timestamp | timestamp_custom('%Y-%m-%dT%H:%M:%SZ', false) %}
        {% set current_time_h1 = now().strftime('%Y-%m-%dT%H:00:00Z') %}
        {% if value_json.error == "no errors" %}
          {% set dates = value_json.Highs %}
          {% if (dates | length) > 0 %}
            {% set state = namespace(value=value_json.Default_Lows_state) %}
            {% for date in dates %}
              {% set start_time = date.date %}
              {% if start_time == current_time_m15 or start_time == current_time_h1 %}
                {% set state.value = value_json.Default_Highs_state %}
                {% break %}
              {% else %}
                {% set state.value = value_json.Default_Lows_state %}
                {% continue %}
              {% endif %}
            {% endfor %}
            {{state.value}}
          {% else %}
            {{ value_json.Default_Lows_state }}
          {% endif%}
        {% else %}
          {{ value_json.Default_Lows_state }}
        {% endif %}
      json_attributes:
        - Name
        - Default_Highs_state
        - Default_Lows_state
        - Default_Neutral_state
        - error
        - Lows
        - Highs
      icon: mdi:cash-100

do powyższego tylko wykonawca w automations.yaml (wszystko jest z powiadomieniami, łącznie z błędem danych). upewnijcie się co do poniższego bo mam wrażenie że zacina a skupiam się na precyzyjnym przedstawieniu całości

- id: '1731031668988'
  alias: Internet Auto
  description: ""
  triggers:
    - trigger: time_pattern
      hours: /1
      minutes: '0'
      seconds: '3'
    - trigger: homeassistant
      event: start
  conditions: []
  actions:
    - action: homeassistant.update_entity
      data:
        entity_id:
          - sensor.internet_highs
    - delay:
        hours: 0
        minutes: 0
        seconds: 1
        milliseconds: 0
    - if:
        - condition: template
          value_template: >-
            {% if state_attr('sensor.internet_highs', 'error') == 'no errors' %}
            true {% endif %}
      then:
        - if:
            - condition: template
              value_template: >-
                {% if is_state('sensor.internet_highs', 'OFF') %}  true  {% endif
                %}
          then:
            - type: turn_off
              device_id: a149c0e49349e4bb748a8172a81d151d
              entity_id: 890ad5d29402efa7299e97ecc0de103b
              domain: switch
            - action: notify.mobile_app_iphone8
              metadata: {}
              data:
                message: >-
                  Internet Auto OFF, current TGE price:
                  {{states('sensor.current_tge_price')}}
          else:
            - type: turn_on
              device_id: a149c0e49349e4bb748a8172a81d151d
              entity_id: 890ad5d29402efa7299e97ecc0de103b
              domain: switch
            - action: notify.mobile_app_iphone8
              metadata: {}
              data:
                message: >-
                  Internet Auto ON, current TGE price:
                  {{states('sensor.current_tge_price')}}
      else:
        - action: notify.mobile_app_iphone8
          metadata: {}
          data:
            message: >-
              Internet Automat: HighestLowest.sh,
              {{state_attr('sensor.internet_highs','error')}}
  mode: single

na koniec apexcharts (nie wiem dlaczego w data_generator musi być tak jak jest ale nie wykres był celem). w sumie to wygląda dobrze

type: custom:apexcharts-card
graph_span: 1d
span:
  start: day
cache: true
all_series_config:
  stroke_width: 1
  float_precision: 2
  type: column
  show:
    legend_value: false
    datalabels: true
  group_by:
    func: raw
    fill: zero
    duration: 1h
series:
  - entity: sensor.internet_highs
    attribute: Highs
    name: Highs
    yaxis_id: TGE
    color: "#dd2020"
    data_generator: |
      var highs = entity.attributes.Highs.map((hour) => {
        const price = hour.price;
        return [new Date(hour.date), price];
      });
      return highs
  - entity: sensor.internet_highs
    attribute: Lows
    name: Lows
    yaxis_id: TGE
    color: "#338833"
    data_generator: |
      var lows = entity.attributes.Lows.map((hour) => {
        const price = hour.price;
        const UTC_offset = Math.abs(new Date(hour.date).getTimezoneOffset())*60*1000;
        return [new Date(hour.date)-UTC_offset, price];
      });
      return lows
header:
  show: false
  show_states: true
  colorize_states: true
now:
  show: true
  label: Now
  color: red
apex_config:
  chart:
    height: 800px
  fill:
    opacity: 0.9
    type: gradient
    gradient:
      type: vertical
      shadeIntensity: 0.1
      opacityFrom: 0.3
      opacityTo: 0.8
      stops:
        - 0
        - 70
        - 100
  plotOptions:
    scales:
      x:
        grid:
          offset: true
    bar:
      columnWidth: 200%
      borderRadius: 3
      borderRadiusApplication: end
      dataLabels:
        position: top
        hideOverflowingLabels: true
        orientation: horizontal
  annotations:
    position: back
    yAxisIndex: 0
    yaxis:
      - "y": 418.8
        strokeDashArray: 3
        borderColor: red
        borderWidth: 1
        label:
          text: PGE "0"
          borderColor: "#ff0000"
          position: left
          style:
            color: "#ffff00"
            background: "#707070"
            fontSize: 10px
  tooltip:
    shared: true
    intersect: false
    x:
      format: dd MMM (HH:00 - HH:59)
      show: true
  grid:
    borderColor: rgb(50, 50, 50)
    position: back
    row:
      colors:
        - rgb(55, 55, 55)
        - transparent
      opacity: 0.3
    xaxis:
      lines:
        show: true
    yaxis:
      lines:
        show: true
  legend:
    show: true
  xaxis:
    axisBorder:
      color: "#505050"
    axisTicks:
      show: true
      width: 4
      color: "#909090"
    crosshairs:
      show: true
      position: back
      stroke:
        color: "#505050"
        width: 1
        dashArray: 10
    labels:
      datetimeUTC: false
      style:
        colors: "#bebebe"
        fontSize: 12px
        fontFamily: Arial Narrow
  yaxis:
    - id: TGE
      forceNiceScale: true
      decimalsInFloat: 0
      opposite: false
      tooltip:
        enabled: true
      axisTicks:
        show: true
        width: 4
        color: "#909090"
      axisBorder:
        show: true
        color: "#505050"
      crosshairs:
        show: true
        position: back
        stroke:
          color: "#505050"
          width: 1
          dashArray: 10
      labels:
        style:
          colors: "#bebebe"
          fontSize: 12px
          fontFamily: Arial Narrow

kończąc, dokładnie takie miałem założenia, ale w trakcie pisania nie miałem pomysłu co dalej :slight_smile:
jeśli będą jakieś błędy, zgłaszajcie
PZDR
szybki podgląd cen na telefon. jeśli znacie html to można zrobić lepiej. karta ‘markdown’

<h1>
<a href="https://www.gkpge.pl/dla-domu/oferta/dynamiczna-energia-z-pge">PGE</a>&nbsp;&nbsp;
<a href="https://www.tge.pl/energia-elektryczna-rdn">TGE</a>&nbsp;&nbsp;
<a href="https://raporty.pse.pl/?report=RCE-PLN&state=Funkcjonowanie%20RB,Raporty%20dobowe%20z%20funkcjonowania%20RB,Podstawowe%20wska%C5%BAniki%20cenowe%20i%20kosztowe&date=2024-11-14&type=table&search=&">RCE</a>
</h1>
<center><h1>
{{ now().strftime('%a&nbsp;%d.%m.%Y&nbsp;<br>%H:%M:%S') | lower
   |replace('mon','Poniedziałek')
   |replace('tue','Wtorek')
   |replace('wed','Środa')
   |replace('thu','Czwartek')
   |replace('fri','Piątek')
   |replace('sat','Sobota')
   |replace('sun','Niedziela')}}
</h1>
{%- set current_time = now().strftime('%Y-%m-%dT%H:00:00Z') -%}
{%- set delta = ((states('sensor.tge_prices_today') | as_timestamp - current_time | as_timestamp)/3600 ) | int +1 -%}
{%- set dates = state_attr('sensor.tge_prices_today', 'tge_rdn')[-delta:] -%}
<center><h1> 
{%- for date in dates -%}
  {%- set start_time = date.date %}
  {%- if start_time >= current_time %}
    {%- set data_hour = (date.date | as_timestamp | timestamp_custom('%H:%M', false)) | string -%}
    {{ data_hour + '&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;' + '%.2f' | format(date.price) | string +'<br>' }} 
  {%- endif -%}
{%- endfor -%}
</h1>```
2 polubienia

Cześć

Może temat nie bezpośrednio powiązany z HA ale ma ktoś z was jakiś skrypt albo widział dodatek dla HA który umożliwiał by eksport cen z całego miesiąca do jednego pliku? Niestety ze strony https://raporty.pse.pl/ da się eksportować tylko pojedynczy dzień co do np wyliczenia depozytu w arkuszu jest bardzo czasochłonne.

skrypt i opis masz kilka postów wyżej “energy_prices_scrapper.sh”
wystarczy uruchomić skrypt w pętli. przykładowy skrypt pisany teraz bez sprawdzania
trzeba by jeszcze dopisać parametr do skryptu zmieniający linijkę #41
#data_day=“2024-11-03” #pobieranie zaleglych danych
(w skrypcie data_day=$3)

#!/bin/bash
start_date_s=`date -d '2024-11-01' '+%s'`
for dni in {0..30}
do
#  dni trzeba zaminić na date np:
  nowa_data_day=`date +%Y-%m-%d -d @$(($start_date_s+($dni*24*3600)))`
  echo "$nowa_data_day"
  bash energy_prices_scrapper.sh pse_rce 31days $nowa_data_day
  sleep 2 # lepiej dać chwile przerwy
done
jq -r '.pse_rce[] | "date:\(.date), price:\(.price)"' pse_rce.json >  dane_bez_nawiasow.csv

możliwe że powyższe zadziała od razu

1 polubienie

Jeśli nie powiązany z HA, to mam skrypt w powershellu, formatowanie wyjścia jest okrojone, bo wstawiam to potem do excela, ale możesz sobie rozszerzyć co ma być wyświetlone (wstaw # po $wynik.value w ostatniej linii):

$year="2024"
$month="09"
$day_start=1
$day_end=30

for ($day = $day_start; $day -le $day_end; $day++)
{
    if ($day -lt 10)
    {$day_txt="0"+$day}
    else
    {$day_txt=$day}
    $doba=$year+"-"+$month+"-"+$day_txt
    $URI = "https://api.raporty.pse.pl/api/rce-pln?%24filter=doba%20eq%20'" + $doba + "'&%24select=rce_pln,udtczas"
    $Method = “Get”
    $wynik = Invoke-RestMethod -Method $Method -URI $URI
    $wynik.value | select-string ":15" | %{($_ -split ";\s+")[0]} | %{($_ -split "=")[1]}
}
1 polubienie

Dziękuje, bardzo proste użycie i w sumie wykonuje wszystko co potzrebuje. Jedyne co to dla wrzesnia i pazdziernika pokazuje mi przy jedny mz dni błąd przez co jeden dzien ma powtórzone ceny z poprzedniego ale latwo to wylapac i poprawic. Troche wyglada jak problem na ich serwerze :slight_smile:

Invoke-RestMethod :


        body {
            font-family: Arial;
            margin-left: 40px;
        }
        img {
            border: 0 none;
        }
        #content {
            margin-left: auto;
            margin-right: auto
        }
        #message h2 {
            font-size: 20px;
            font-weight: normal;
            color: #000000;
            margin: 34px 0px 0px 0px
        }
        #message p {
            font-size: 13px;
            color: #000000;
            margin: 7px 0px 0px0px
        }
        #errorref {
            font-size: 11px;
            color: #737373;
            margin-top: 41px
        }

    Service unavailable


            Our services aren't available right now
            We're working to restore all services as soon as possible. Please check back soon.


            20241119T202326Z-179d85bf68c7w66chC1FRA006000000005pg00000000xnu0


At line:10 char:14
+     $wynik = Invoke-RestMethod -Method $Method -URI $URI
+              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebExc
   eption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

cześć, przyklejam sie do tematu bo aktualnie konfiguruje juz HA w swoim nowym domu. Nie będę ukrywał że nie jestem programistą , ale coś tam ogarniam i w dużej mierze bazuje na tym co robicie. Natomiast przegryzam sie przez zawiłości rozliczeń taurona i może Wam się przyda ta wiedza że:

  1. jesli chodzi o netbilling, tauron nie rozlicza oddanej anergi po cenach godzinowych bie (jak twierdzi) nie jest na to gotowy. (kij że ustawa obowiązuje od lipca. Jakl zapytałem co z optymalizacjami co ludzie mają to mi powiedzieli że kiedyś jak już zacznie rozliczać to skorygują faktury według godzinowych stawek…
  2. Jeśli chodzi o taryfy dynamiczne to na iich stronie jest dokument i tam wzór jak do cen RDN dodać, uśrednić i wypluć stawkę która w efekcie jest wyższa niż zamrożone przez tarcze 500zł/MWh, a niżej nie może być… No i są to średnie ważone z całego okresu. zostawiam link. Prąd z Cenami Dynamicznymi od TAURONA

Testowałeś obie? Która lepsza?

Może to oczywiste ale usystematyzuję.
Czytając artykuły w prasie tej branżowej i tej codziennej oraz analizując niełatwe do rozszyfrowania taryfy operatorów i nakładając na to politykę rządu w postaci mrożenia i odmrażania - nie trudno w tym zgłupieć.
Należało by zdefiniować co z perspektywy odbiorcy rozumiemy przez “cenę energii”.
W domu, rozumiemy jako cena energii to ile płacimy za kWh, a ta cena zawiera cenę energii (jako takiej) i cenę dystrybucji, o VAT nie wspominając :slight_smile:
TGE obraca energią netto za MWh.
Taryfa dynamiczna to cena z TGE (fixing I) + marża operatora nazywana K (w PGE to 0,0812 zł). Do tego jeszcze opłata miesięczna.

Czyli na pełną cenę energii w taryfie dynamicznej PGE składa się obecnie:
energia_czynna = TGE fixing I =
marża k = 0,0812
dystrybucja_sieciowa = 0,35000
dystrybucja_jakosciowa = 0,03140
opłata kogeneracyjna = 0,00618
akcyza = 0,005
vat = 23%

Taryfy dynamiczne zostały wprowadzone bo był taki przymus i operatorzy musieli. Jedynie Tauron ma rozsądne ceny i względnie PGE.

Na dzisiaj ni jak się to nie opłaca! Do puki (koniec września 2025) rząd mrozi cenę energii na poziomie 500 zł netto za MWh to dużo bardziej opłacalna jest taryfa G12 czy G12W gdzie cena energii jest również na poziomie 500 zł, a dystrybucja w drugiej strefie jest dużo tańsza.

Na wykresie widać stawki brutto za kWh. Linia wznosząca i opadająca to cena w taryfie dynamicznej, linia niebieska to G11, linia żółto-czerwona to I i II strefa taryfy G12W.

image

Będąc na taryfie dynamicznej masz taryfę dystrybucyjną G12W. Krążą plotki, że w styczniu powstanie ciekawsze rozwiązanie w styczniu.

U tego samego operatora?

Tak, przejście na sprzedaż w taryfie dynamicznej wymusza zmianę dystrybucji na G12W. W styczniu będzie to ciekawsze rozwiązanie.