defmod Stromboerse DOIF ## Startup Befehle für den WebSocket vom EVU_Tibber_connect\
init\
{ \
  Log3("$SELF",0,sprintf("%-16s init      : ", "$SELF")."▶️ Start init") ;;\
  if ( [?$SELF_connect:conf_EVU,"null"] eq "Tibber_Live" ) {\
    fhem("setreading $SELF_connect ws_cmd connect");; \
    Log3("$SELF",0,sprintf("%-16s init      : ", "$SELF")."▶️ Start Websocket $SELF_connect") ;;\
  } else {\
    Log3("$SELF",0,sprintf("%-16s init      : ", "$SELF")."No Tibber_Live configured") ;;\
  } \
  Log3("$SELF",0,sprintf("%-16s init      : ", "$SELF")."🏁 Init done for ".[?$SELF_connect:conf_EVU,"null"]) ;;\
}\
\
################################################################################################################\
## EVU_Tibber_connect start/stop WebSocket\
0_$SELF_connect_Tibber_Live_ws\
{if( !([$SELF:state] eq "off")                                           ## DOIF enabled\
    and [?$SELF_connect:conf_EVU,"null"] eq "Tibber_Live"\
    or  [$SELF:ui_command_2]                                             ## Hier wird das uiTable select ausgewertet\
    and [?$SELF:ui_command_2] ne "---"\
   ) {\
    if ( [?$SELF_connect:conf_EVU,"null"] eq "Tibber_Live" ) {\
      fhem("setreading $SELF_connect ws_cmd ".[?$SELF:ui_command_2]);; \
      Log3("$SELF",3,sprintf("%-16s connect   : ", "$SELF")."Connect Tibber_Live") ;;\
    } else {\
      Log3("$SELF",3,sprintf("%-16s connect   : ", "$SELF")."No Tibber_Live configured") ;;\
    }\
    set_Reading("ui_command_2","---");;                                   ## Hier wird das uiTable select wieder zurückgesetzt, ansonsten\
                                                                         ## kann das Kommando nicht sofort wiederholt werden\
  }\
}\
\
################################################################################################################\
## Tibber_Live Kosten abholen\
0_$SELF_connect_Tibber_Live_Kosten\
{if( !([$SELF:state] eq "off")                                           ## DOIF enabled\
    and [:03]                                                            ##\
    and [?$SELF_connect:conf_EVU,"null"] eq "Tibber_Live"\
    or  [$SELF:ui_command_2]                                             ## Hier wird das uiTable select ausgewertet\
    and [?$SELF:ui_command_2] ne "---"\
   ) {\
    if ( [?$SELF_connect:conf_EVU,"null"] eq "Tibber_Live" ) {\
      ::CommandGet(undef, "$SELF_connect 03_consumption_hour");;\
      Log3("$SELF",3,sprintf("%-16s cost      : ", "$SELF")."Get cost of last hour for ".[?$SELF_connect:conf_EVU,"null"]) ;;\
    } else {\
      Log3("$SELF",3,sprintf("%-16s cost      : ", "$SELF")."No Tibber_Live configured") ;;\
    }\
    set_Reading("ui_command_2","---");;                                   ## Hier wird das uiTable select wieder zurückgesetzt, ansonsten\
                                                                         ## kann das Kommando nicht sofort wiederholt werden\
  }\
}\
\
#################################### Ende Tibber_Live ##########################################################\
\
\
################################################################################################################\
## 1 Scheduling für das setzten des aktuellen Preises, ohne erneute Abfrage des EPEX_Spot\
1_$SELF_PriceInfo\
{if( !([$SELF:state] eq "off")                                           ## DOIF enabled\
    and  ![00:00]\
    and ( [:00] or [:15] or [:30] or [:45] )                             ## jede 15 Minuten\
    or    [$SELF:ui_command_1] eq "1_Aktueller_Preis"                    ## Hier wird das uiTable select ausgewertet\
   ) {\
\
    fhem("setreading $SELF_connect fc0_0000_total ".[?$SELF_connect:fc0_0000_total]);;  ## Preis für die aktuelle Stunde triggern\
    Log3("$SELF",3,sprintf("%-16s set price : ", "$SELF")."Setzen aktueller Preis") ;;\
    set_Reading("ui_command_1","---");;                                   ## Hier wird das uiTable select wieder zurückgesetzt, ansonsten\
                                                                         ## kann das Kommando nicht sofort wiederholt werden\
  }\
}\
\
################################################################################################################\
## 2 Scheduling für das Abholen der Börsenpreise\
2_$SELF_PriceInfo_Schedule\
{if( !([$SELF:state] eq "off")                                           ## DOIF enabled\
    and\
    (    [00:00]                                                         ## Ab 00:00 Uhr gibt es die Preise für den Tag\
     or ( [00:00-01:00] and [+:10]                                       ## Bei Problemen alle 10 Minuten nochmals abfragen\
     and  [$SELF_connect:fc0_0000_startsAt,"null"] ne "null"             ##       solange bis das Datum aktuell ist\
     and  [?$SELF_connect:fc0_0000_startsAt,"0000-00-00 00:00:00"] lt strftime("%Y-%m-%d 00:00:00", localtime)\
        )\
     or [13:00-16:00] and [+:20] and [?$SELF_connect:fc1_0000_startsAt,"null"] eq "null"  ## Ab 14:00 Uhr gibt es die Preise für den nächsten Tag\
    )\
    or [$SELF:ui_command_1] eq "2_Preis_Info"                            ## Hier wird das uiTable select ausgewertet\
   ) {\
\
  Log3("$SELF",3,sprintf("%-16s schedule  : ", "$SELF")."Abfrage von ".[?$SELF_connect:conf_EVU]) ;;\
  if ([?$SELF_connect:fc1_0000_startsAt,"null"] eq "null") {             ## Der nächste Tag ist noch nicht da\
    Log3("$SELF",3,sprintf("%-16s schedule  : ", "$SELF")."Daten für fc1 sind noch nicht da") ;;\
  }\
  my $rc = "";;\
  if ([$SELF_connect:conf_EVU] ne "Tibber_Live") {\
    $rc = ::CommandGet(undef, "$SELF_connect 01_Preis_Info_EPEX_Spot");;  ## Preisbasis für Tibber und aWATTar\
    Log3("$SELF",3,sprintf("%-16s schedule  : ", "$SELF")."$rc") ;;\
  }\
  set_Reading("ui_command_1","---");;                                     ## Hier wird das uiTable select wieder zurückgesetzt, ansonsten\
                                                                         ## kann das Kommando nicht sofort wiederholt werden\
  }\
}\
\
################################################################################################################\
## 3 Erstellen des Diagramms im uiTable\
3_$SELF_Diagramm\
{if( !([$SELF:state] eq "off")                                           # DOIF enabled\
    and [$SELF_connect:fc0_0000_total]\
     or [$SELF:ui_command_1] eq "3_Diagramm"                             # Hier wird das uiTable select ausgewertet\
   ) {\
\
  my (@out,$timestamp,$date);;\
\
  # Ist der nächste Tag schon da?\
  my $stop_fc = (ReadingsVal("$SELF_connect","fc1_0000_startsAt","null") eq "null") ? 0 : 1;;\
\
  for (my $loop_fc = 0;; $loop_fc <= $stop_fc;; $loop_fc++) {\
    for (my $loop_hour = 0;; $loop_hour <= 23;; $loop_hour++) {\
      for (my $loop_quarter =  0;; $loop_quarter <= 3;; $loop_quarter++) {\
        $timestamp      = sprintf("%s_%02d:%02d:00", POSIX::strftime("%Y-%m-%d",localtime(time+86400*$loop_fc)), $loop_hour, $loop_quarter*15);;\
        $out[$loop_fc] .= "$timestamp ".::round(ReadingsVal("$SELF_connect",sprintf("fc%d_%02d%02d_total", $loop_fc, $loop_hour, $loop_quarter*15), 0),1)."\n";;\
      } # end $loop_quarter\
    } # end $loop_hour\
    Log3("$SELF",3,sprintf("%-16s diagram   : ", "$SELF").sprintf("fc%d Daten wurden im Diagramm gesetzt", $loop_fc)) ;;\
  } # end $loop_fc\
\
  if ($stop_fc == 0) {                                                   # Daten für fc1 sind noch nicht da\
    my $loop_fc = 1;;\
       $date    = POSIX::strftime("%Y-%m-%d",localtime(time+86400));;     # Setze das Datum auf morgen\
    for (my $loop_hour = 0;; $loop_hour <= 23;; $loop_hour++) {\
      for (my $loop_quarter = 0;; $loop_quarter <= 3;; $loop_quarter++) {\
        $out[$loop_fc] .= sprintf("%s_%02d:%02d:00 0.0\n", $date, $loop_hour, $loop_quarter*15);;      # Die Daten mit 0 ergänzen\
      } # end $loop_quarter\
    } # end $loop_hour\
    Log3("$SELF",3,sprintf("%-16s diagram   : ", "$SELF").sprintf("fc%d Daten sind noch nicht da", $loop_fc)) ;;\
  }\
\
  if (AttrVal("$SELF","verbose",0) >=4) {\
    my @fc0 = split /\n/, $out[0];; my @fc1 = split /\n/, $out[1];; my $log;;\
    for (my $i = 0;; $i < 96;; $i++) { $log .= sprintf("%-24s | %-24s\n", $fc0[$i], $fc1[$i]) } ;;      # Log Daten nebeneinander\
    Log3("$SELF",4,sprintf("%-16s diagram   : ", "$SELF")."Werte für die Diagramme\n$log") ;;\
  }\
\
  # Die readings current_price und current_level dienen hier nur als Trigger Events, und müssen für\
  # jedes Diagramm unterschiedlich sein, die Daten werden über $out[] bereit gestellt !!\
  ::DOIF_modify_card_data("$SELF","$SELF_connect","current_price","bar1day",0,$out[0]);;\
  ::DOIF_modify_card_data("$SELF","$SELF_connect","current_level","bar1day",-86400,$out[1]);;\
\
  set_Reading("ui_command_1","---");;                                   # Hier wird das uiTable select wieder zurückgesetzt, ansonsten\
                                                                       # kann das Kommando nicht sofort wiederholt werden\
  }\
}\

attr Stromboerse DbLogExclude .*
attr Stromboerse comment Version 2026.01.25 12:00 \
Dieses Device benötigt EPEX_Spot_connect als Verbindung zu EPEX_Spot.
attr Stromboerse disable 0
attr Stromboerse group PV Steuerung EVU
attr Stromboerse icon stromzaehler_icon
attr Stromboerse room Strom->Boerse
attr Stromboerse sortby 415
attr Stromboerse uiState {\
package ui_Table;;\
  $TABLE = "style='width:100%;;'";;\
\
  $TD{0..6}{0} = "style='border-top-style:solid;;border-bottom-style:solid;;border-left-style:solid;;border-left-width:2px;;border-right-style:solid;;border-color:darkgreen;;border-top-width:2px;;border-bottom-width:2px;;border-right-width:1px;;width:36%;;font-weight:bold;;'";;\
  $TD{0..6}{1..4} = "style='border-top-style:solid;;border-bottom-style:solid;;border-right-style:solid;;border-color:darkgreen;;border-top-width:2px;;border-bottom-width:2px;;border-right-width:1px;;width:8%;;text-align:center;;'";;\
  $TD{0..6}{5} = "style='border-top-style:solid;;border-bottom-style:solid;;border-right-style:solid;;border-color:darkgreen;;border-top-width:2px;;border-bottom-width:2px;;border-right-width:2px;;width:8%;;text-align:center;;'";;\
}\
\
"Kommando<dd>Auswahl</dd>" |widget([$SELF:ui_command_1],"uzsuDropDown,---,1_Aktueller_Preis,2_Preis_Info,3_Diagramm") |"Börse <br>".widget([$SELF_connect:conf_EVU],"uzsuDropDown,EPEX_Spot,Tibber,Tibber_Live,aWATTar")| "Tibber_Live<br>".widget([$SELF:ui_command_2],"uzsuDropDown,---,connect,disconnect") |[$SELF_connect:ws_cmd] \
\
"Strompreis<br>".card([$SELF_connect:current_price:bar1day],undef,undef,0,60,90,0,"fc0 ".[$SELF_connect:current_currency],undef,"1","130,,,,,,220").card([$SELF_connect:current_level:bar1day],undef,undef,0,60,90,0,"fc1 ".[$SELF_connect:current_currency],undef,"1","130,,,,,,220")|\
"<span style=font-weight:bold>avg nächste 3h</span><br><br>".$SELF_Price(1)."<br>".$SELF_Price(2)."<br>".$SELF_Price(3)|"<span style=font-weight:bold>Statistik fc0</span><br><br>".[$SELF_connect:fc_min]." min<br>".[$SELF_connect:fc_avg]." avg<br>".[$SELF_connect:fc_med]." med<br>".[$SELF_connect:fc_max1]." max"|\
"<span style=font-weight:bold>Trigger fc0</span><br>Basis ".widget([$SELF_connect:compensation_grid],"selectnumbers,0,0.1,12,1,lin")."<br>".$SELF_Format("trigger_0")|\
"<span style=font-weight:bold>Trigger fc1</span><br><br>".$SELF_Format("trigger_1")
attr Stromboerse uiTable {\
package ui_Table;;\
  $TABLE = "style='width:100%;;'";;\
\
   $TD{0..18}{0} = "style='border-top-style:solid;;border-bottom-style:solid;;border-left-style:solid;;border-left-width:2px;;border-right-style:solid;;border-color:darkgreen;;border-top-width:2px;;border-bottom-width:2px;;border-right-width:1px;;width:36%;;font-weight:bold;;'";;\
  $TD{0..18}{1..5} = "style='border-top-style:solid;;border-bottom-style:solid;;border-right-style:solid;;border-color:darkgreen;;border-top-width:2px;;border-bottom-width:2px;;border-right-width:1px;;width:8%;;text-align:center;;'";;\
  $TD{0..18}{6} = "style='border-top-style:solid;;border-bottom-style:solid;;border-right-style:solid;;border-color:darkgreen;;border-top-width:2px;;border-bottom-width:2px;;border-right-width:2px;;width:8%;;text-align:center;;'";;\
\
sub $SELF_FUNC_Status {\
    my($value, $min, $colorMin,  $statusMin,  $colorMiddel, $statusMiddle, $max, $colorMax, $statusMax)=@_;;\
    my $ret = ($value < $min)? '<span style="color:'.$colorMin.'">'.$statusMin.'</span>' : ($value > $max)? '<span style="color:'.$colorMax.'">'.$statusMax.'</span>' : '<span style="color:'.$colorMiddel.'">'.$statusMiddle.'</span>';;\
    return $ret;;\
  }\
\
sub $SELF_Cost {\
  my($i)=@_;;\
  my $currency = (::ReadingsVal("$SELF_connect","current_currency","") eq "EUR")? " €" : "";;\
\
  return ::ReadingsVal("$SELF_connect","total_cost_".$i,0).$currency;;\
}\
\
sub $SELF_Price {\
  my($i)=@_;;\
  my $j;;\
  my $value;;\
  my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);; $year += 1900;; $mon += 1 ;;\
\
  if ($i == 0) {\
    $value = ::round(::ReadingsVal("$SELF_connect","current_price",0),2);;\
    $value = ( ::ReadingsVal("$SELF_connect","fc0_trigger","off") eq "on" ) ?\
         "<span style='color:green'>".$value."</span>" :\
         "<span style='color:red'>".$value."</span>" ;;\
    $value .= " ct/kWh";;\
  } else {\
    $j       = $i+$hour;;\
    if ($j < 24) {\
      for (my $quarter = 0;; $quarter <= 3;; $quarter++) {\
        $value += ::ReadingsVal("$SELF_connect","fc0_".sprintf("%02d%02d",$j, $quarter*15)."_total",0);;\
      }\
    } else {\
      $j = $j - 24;;\
      for (my $quarter = 0;; $quarter <= 3;; $quarter++) {\
        $value += ::ReadingsVal("$SELF_connect","fc1_".sprintf("%02d%02d",$j, $quarter*15)."_total",0);;\
      }\
    }\
    $value = ::round($value/4,1);;\
    $value = ( ::ReadingsVal("$SELF_connect","fc_trigger_price","0") > $value ) ?\
         "<span style='color:green'>".$value."</span>" :\
         "<span style='color:red'>".$value."</span>" ;;\
    $value = sprintf("%02d",$j)." :  ".$value ;;\
  }\
  return  $value;;\
 }\
\
sub $SELF_Format {\
  my($i)=@_;;\
\
  my $MonthBefore   = "LogDBRep_Statistic_previous_Month";;\
  my $MonthPrevious = ::ReadingsTimestamp("$MonthBefore","$SELF_connect_nodes_consumption_month","null");;\
       $MonthPrevious = ($MonthPrevious ne "null") ?    POSIX::strftime("%Y",localtime(::time_str2num(::ReadingsTimestamp("$MonthBefore","$SELF_connect_nodes_consumption_month","null")))) : "null";;\
\
  my $YearBefore   = "LogDBRep_Statistic_previous_Year";;\
  my $YearPrevious = ::ReadingsTimestamp("$YearBefore","$SELF_connect_nodes_consumption_year","null");;\
       $YearPrevious = ($YearPrevious ne "null") ? POSIX::strftime("%Y",localtime(::time_str2num(::ReadingsTimestamp("$YearBefore","$SELF_connect_nodes_consumption_year","null")))) : "null";;\
\
  if ($i eq "day") {\
      return sprintf("%04d",::ReadingsVal("$SELF_connect","nodes_consumption_day",0));;\
    } elsif ($i eq "month") {\
      my $evu_em = sprintf("%04d",::ReadingsVal("$SELF_connect","nodes_consumption_month",0));;\
      $evu_em .= ($MonthPrevious ne "null") ? sprintf(" / %04d", ::ReadingsVal("$MonthBefore","$SELF_connect_nodes_consumption_month",0) ) : "";;\
      return $evu_em;;\
    } elsif ($i eq "year") {\
      my $evu_ey = sprintf("%04d",::ReadingsVal("$SELF_connect","nodes_consumption_year",0));;\
      $evu_ey .= ($YearPrevious ne "null") ? sprintf(" / %04d", ::ReadingsVal("$YearBefore","$SELF_connect_nodes_consumption_month",0) ) : "";;\
      return $evu_ey;;\
    } elsif ($i eq "trigger_0") {\
      return ((::ReadingsVal("$SELF_connect","fc0_trigger_net_start","") eq "null" ) ?\
            "<span style='color:red'>Heute kein Trigger <br>mehr unter ".\
                 ::ReadingsVal("$SELF_connect","fc_trigger_net_price","null")." ct</span>" :\
            "<span style='color:green'>Trigger von<br>".\
                 ::ReadingsVal("$SELF_connect","fc0_trigger_net_start","00:00")." bis ".::ReadingsVal("$SELF_connect","fc0_trigger_net_stop","00:00")."<br>unter ".\
                 ::ReadingsVal("$SELF_connect","fc_trigger_net_price","null")." ct</span>" );;\
    } elsif ($i eq "trigger_1") {\
      if (::ReadingsVal("$SELF_connect","fc1_0000_startsAt","null") ne "null") {\
        return ((::ReadingsVal("$SELF_connect","fc1_trigger_net_start","null") eq "null" ) ?\
              "<span style='color:red'>Morgen kein Trigger <br>mehr unter ".\
                   ::ReadingsVal("$SELF_connect","fc_trigger_net_price","null")." ct</span>" :\
              "<span style='color:green'>Morgen ein Trigger von<br>".\
                   ::ReadingsVal("$SELF_connect","fc1_trigger_net_start","00:00")." bis ".::ReadingsVal("$SELF_connect","fc1_trigger_net_stop","00:00")."<br>unter ".\
                   ::ReadingsVal("$SELF_connect","fc_trigger_net_price","null")." ct</span>" );;\
      } else {\
        return "Morgen noch kein Trigger vorhanden";;\
      }\
    }\
  return "null";;\
}\
\
}\
\
##"Statistiken ".::ReadingsVal("$SELF_connect","current_date",0)." in kWh"|"<span style=font-weight:bold>aktuell</span>"|"<span style=font-weight:bold>heute</span>"|"<span style=font-weight:bold>Monat</span>"|"<span style=font-weight:bold>Jahr</span>"\
"Statistiken ".[$SELF_connect:current_date]." in kWh"|"<span style=font-weight:bold>aktuell</span>"|"<span style=font-weight:bold>heute</span>"|"<span style=font-weight:bold>Monat</span>"|"<span style=font-weight:bold>Jahr</span>"\
\
"Strom<dd>Preis / Kosten</dd>"|$SELF_Price(0)|$SELF_Cost("day")|$SELF_Cost("month")|$SELF_Cost("year")\
\
## Wenn man einen eigenes SmartMeter verwendet\
"Bezug vom Netz"|sprintf("%04d W",([WR_0_KSEM:M_AC_Power] >= 0 ? ::round(::ReadingsVal("WR_0_KSEM","M_AC_Power",0),0) : 0) )|$SELF_Format("day")|$SELF_Format("month")|$SELF_Format("year")\
"Einspeisung ins Netz"|sprintf("%04d W",([WR_0_KSEM:M_AC_Power] <= 0 ? abs(::round(::ReadingsVal("WR_0_KSEM","M_AC_Power",0),0)) :  0) )|""|""|""
attr Stromboerse verbose 3



setstate Stromboerse 2026-01-09 17:15:02 ui_command_1 ---
setstate Stromboerse 2026-01-09 17:03:00 ui_command_2 ---