Probleme mit sunrise, sunset, isday

Begonnen von dt2510, 06 April 2018, 09:23:39

Vorheriges Thema - Nächstes Thema

dt2510

Ich habe gestern meine Zeitsteuerung angepasst, um z.B. Lampen und Rolläden zu unterschiedlichen Zeiten zu schalten. Dazu habe ich bei den Aktoren (unter Anderem) ein User Attribut "Horizon" hinterlegt, in dem der Wert REAL, CIVIL, NAUTIC oder ASTRONOMIC steht:

define FGR222_ID19 ZWave xxxxxxxx 19
attr FGR222_ID19 userattr HomeDay HomeNight BedDay BedNight AwayDay AwayNight HolidayDay HolidayNight Horizon Param
attr FGR222_ID19 AwayDay dim 98
attr FGR222_ID19 AwayNight dim 0
attr FGR222_ID19 BedDay dim 98
attr FGR222_ID19 BedNight dim 0
attr FGR222_ID19 HolidayDay dim 98
attr FGR222_ID19 HolidayNight dim 0
attr FGR222_ID19 HomeDay dim 98
attr FGR222_ID19 HomeNight dim 0
attr FGR222_ID19 IODev AeonGen5
attr FGR222_ID19 Horizon CIVIL
attr FGR222_ID19 Param state
attr FGR222_ID19 alias Rollladen Anbau Fenster
attr FGR222_ID19 classes MULTI_CHANNEL_ASSOCIATION MANUFACTURER_SPECIFIC VERSION CONFIGURATION ASSOCIATION POWERLEVEL METER SWITCH_MULTILEVEL SENSOR_MULTILEVEL SWITCH_BINARY MANUFACTURER_PROPRIETARY PROTECTION MARK METER SENSOR_MULTILEVEL MANUFACTURER_PROPRIETARY SCENE_ACTIVATION SWITCH_MULTILEVEL SWITCH_BINARY
attr FGR222_ID19 vclasses ASSOCIATION:2 MANUFACTURER_PROPRIETARY:1 MANUFACTURER_SPECIFIC:1 METER:2 MULTI_CHANNEL_ASSOCIATION:2 POWERLEVEL:1 PROTECTION:2 SCENE_ACTIVATION:1 SENSOR_MULTILEVEL:2 SWITCH_BINARY:1 SWITCH_MULTILEVEL:3 VERSION:1


Weiterhin habe ich für die jeweiligen Sonnenauf- und untergänge Ereignisse definiert:

define SunriseREAL at *{sunrise("REAL")} {OnSunrise("REAL")}
define SunriseCIVIL at *{sunrise("CIVIL")} {OnSunrise("CIVIL")}
define SunriseNAUTIC at *{sunrise("NAUTIC")} {OnSunrise("NAUTIC")}
define SunriseASTRONOMIC at *{sunrise("ASTRONOMIC")} {OnSunrise("ASTRONOMIC")}
define SunsetREAL at *{sunset("REAL")} {OnSunset("REAL")}
define SunsetCIVIL at *{sunset("CIVIL")} {OnSunset("CIVIL")}
define SunsetNAUTIC at *{sunset("NAUTIC")} {OnSunset("NAUTIC")}
define SunsetASTRONOMIC at *{sunset("ASTRONOMIC")} {OnSunset("ASTRONOMIC")}


Es wird entsprechend die Funktion OnSunrise/OnSunset (in 99_myUtils.pm) mit dem jeweiligen Horizont  aufgerufen. Die Dummies FHEMMode und HomeStatus können die Inhalte Day,Night,Auto bzw. Home,Away,Bed,Holiday haben:

sub GetDeviceHorizon($) {
  my ($myDevice) = @_;
  return(AttrVal($myDevice,"Horizon","REAL"));
}

sub GetDayNight($) {
  my ($myDevice) = @_;
  my $myDayNight = Value("FHEMMode");
  if ($myDayNight ne "Auto") {return($myDayNight);}
  return(isday(GetDeviceHorizon($myDevice)) ? "Day" : "Night");


sub GetDeviceParam($) {
  my ($myDevice) = @_;
  return(AttrVal($myDevice,"Param","state"));
}

sub GetDeviceReading($$) {
  my ($myDevice,$myReading) = @_;
  return(ReadingsVal($myDevice,$myReading,""));


sub SetDeviceReading($$$) {
  my ($myDevice,$myReading,$myValue) = @_;
  if ($myReading ne "state") {$myValue = $myReading." ".$myValue;}
  fhem("set $myDevice $myValue");


sub GetDeviceState($) {
  my ($myDevice) = @_;
  return(GetDeviceReading($myDevice,GetDeviceParam($myDevice)));


sub SetDeviceState($$) {
  my ($myDevice,$myValue) = @_;
  SetDeviceReading($myDevice,GetDeviceParam($myDevice),$myValue);


sub SetDevice($$) {
  my ($myDevice,$myHorizon) = @_;
  if ($myHorizon ne GetDeviceHorizon($myDevice)) {return();} # Geräte Horizont <> Horizont ? -> Ende
  my $myHomeStatus = Value("HomeStatus"); # Homestatus
  my $myDayNight = GetDayNight($myDevice); # Tag/Nacht
  my $myDefault = AttrVal($myDevice,$myHomeStatus.$myDayNight,""); # Standardzustand laut Anlagenmodus
  my $myDay = AttrVal($myDevice,$myHomeStatus."Day",""); # Standardzustand tagsüber
  my $myNight = AttrVal($myDevice,$myHomeStatus."Night",""); # Standardzustand nachts
  my $myState = GetDeviceState($myDevice); # aktueller Inhalt des "Param" Readings
  if (($myDayNight eq "Day") && ($myState ne $myNight)) {return();} # Wechsel Nacht->Tag und nicht mehr Standardzustand Nacht -> Ende
  if (($myDayNight eq "Night") && ($myState ne $myDay)) {return();} # Wechsel Tag->Nacht und nicht mehr Standardzustand Tag -> Ende
  SetDeviceState($myDevice,$myDefault); # Standardzustand setzen
}

sub SetDevices($) {
  my ($myHorizon) = @_;
  # EDIT: ... hier werden noch mehr Aktoren geschaltet, wurde auf das wesentliche gekürzt
  SetDevice("FGR222_ID19",$myHorizon);
}

sub OnSunrise($) {
  my ($myHorizon) = @_;
  SetDevices($myHorizon);
}


Ich vermute den Fehler in der Funktion SetDevice bzw. im aufgerufenen isday. Normalerweise müsste z.B. bei sunrise("CIVIL") der Rolladen nach oben gehen (HomeDay ist dim 98).

betateilchen

Zitat von: dt2510 am 06 April 2018, 09:23:39


sub GetDeviceHorizon($) {
  my ($myDevice) = @_;
  return(AttrVal($myDevice,"Horizon","REAL"));
}


Nur mal interessehalber: Wieso baust Du einen völlig überflüssigen Wrapper um eine FHEM-Standardfunktion?

-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

dt2510

Macht der Gewohnheit ... ich bin Programmierer (IBM/370 Assembler, Delphi, C++, Java usw...) und da hat man sich in weit über 20 Jahren angewöhnt alles in Funktionen/Prozeduren zu packen ;) Zudem macht es die einzelnen Zeilen etwas kürzer und die Kommentare passen besser dahinter ...

dt2510

alternativ zu isday könnte ich auch einen (schon vorhandenen) Dummy verwenden:

# ##########################################################################
# Sonnenaufgang
# ##########################################################################

sub OnSunrise($) {
  my ($myHorizon) = @_;
  SetDeviceUserReading("DayNight",$myHorizon,"Day"); # DayNight Reading für Horizont auf "Day"
  SetDevices($myHorizon); # Nacht/Tag Wechsel
}

# ##########################################################################
# Sonnenuntergang
# ##########################################################################

sub OnSunset($) {
  my ($myHorizon) = @_;
  SetDeviceUserReading("DayNight",$myHorizon,"Night"); # DayNight Reading für Horizont auf "Night"
  SetDevices($myHorizon); # Tag/Nacht Wechsel
}


Dort habe ich Day/Night (zur Anzeige in TABLETUI) schon hinterlegt - isday sollte eigentlich zum gleichen Ergebnis kommen ...

Internals:
   NAME       DayNight
   NR         238
   STATE      ???
   TYPE       dummy
   READINGS:
     2018-04-06 05:20:11   ASTRONOMIC      Day
     2018-04-06 06:27:16   CIVIL           Day
     2018-04-06 05:47:53   NAUTIC          Day
     2018-04-06 07:05:06   REAL            Day
Attributes:
   group      Zeitsteuerung
   room       _ Variablen
   userReadings REAL CIVIL NAUTIC ASTRONOMIC

dt2510

ich protokolliere heute Abend mal den Aufruf im Logfile um zu sehen, was genau passiert ... Das Ergebnis sieht dann in der Art aus:

2018.04.06 11:41:04 3: SetDevice: *** ANFANG ***
2018.04.06 11:41:04 3: SetDevice: Aktor            = HUEGroup2
2018.04.06 11:41:04 3: SetDevice: Horizont aktuell = CIVIL
2018.04.06 11:41:04 3: SetDevice: Horizont Aktor   = REAL
2018.04.06 11:41:04 3: SetDevice: Horizont Aktor abweichend => zu diesem Zeitpunkt nicht schalten
2018.04.06 11:41:04 3: SetDevice: *** ENDE ***
2018.04.06 11:41:04 3: SetDevice:
2018.04.06 11:41:04 3: SetDevice: *** ANFANG ***
2018.04.06 11:41:04 3: SetDevice: Aktor            = FGR222_ID17
2018.04.06 11:41:04 3: SetDevice: Horizont aktuell = CIVIL
2018.04.06 11:41:04 3: SetDevice: Horizont Aktor   = CIVIL
2018.04.06 11:41:04 3: SetDevice: Aktor soll zu diesem Zeitpunkt geschaltet werden
2018.04.06 11:41:04 3: SetDevice: HomeStatus       = Away
2018.04.06 11:41:04 3: SetDevice: Wechsel auf      = Day
2018.04.06 11:41:04 3: SetDevice: Status Tag       = dim 98
2018.04.06 11:41:04 3: SetDevice: Status Nacht     = dim 0
2018.04.06 11:41:04 3: SetDevice: Status aktuell   = dim 98
2018.04.06 11:41:04 3: SetDevice: Status neu       = dim 98
2018.04.06 11:41:04 3: SetDevice: Status aktuell weicht von Status Nacht ab => nicht schalten, wurde manuell geschaltet
2018.04.06 11:41:04 3: SetDevice: *** ENDE ***

dt2510

Das Ergebnis ist ziemlich eindeutig ... Laut List des Events war Sonnenuntergang (CIVIL) um exakt 20:42:27

Internals:
   COMMAND    {OnSunset("CIVIL")}
   DEF        *{sunset("CIVIL")} {OnSunset("CIVIL")}
   NAME       SunsetCIVIL
   NR         250
   NTM        20:44:04
   PERIODIC   yes
   RELATIVE   no
   REP        -1
   STATE      Next: 20:44:04
   TIMESPEC   {sunset("CIVIL")}
   TRIGGERTIME 1523126644
   TRIGGERTIME_FMT 2018-04-07 20:44:04
   TYPE       at
   READINGS:
     2018-04-06 20:42:27   state           Next: 20:44:04
Attributes:
   group      Zeitsteuerung
   room       _ Events


Genau zur gleichen Zeit wurde auch isday aufgerufen (hier: "Wechsel auf ..."). Eigentlich hätte ich hier "Night" erwartet, aber sunrise/sunset und isday scheinen auf unterschiedliche Ergebnisse zu kommen.

2018.04.06 20:42:27 3: SetDevice: *** ANFANG ***
2018.04.06 20:42:27 3: SetDevice: Aktor            = FGR222_ID18
2018.04.06 20:42:27 3: SetDevice: Horizont aktuell = CIVIL
2018.04.06 20:42:27 3: SetDevice: Horizont Aktor   = CIVIL
2018.04.06 20:42:27 3: SetDevice: Aktor soll zu diesem Zeitpunkt geschaltet werden
2018.04.06 20:42:27 3: SetDevice: HomeStatus       = Away
2018.04.06 20:42:27 3: SetDevice: Wechsel auf      = Day
2018.04.06 20:42:27 3: SetDevice: Status Tag       = dim 98
2018.04.06 20:42:27 3: SetDevice: Status Nacht     = dim 0
2018.04.06 20:42:27 3: SetDevice: Status aktuell   = dim 98
2018.04.06 20:42:27 3: SetDevice: Status neu       = dim 98
2018.04.06 20:42:27 3: SetDevice: Status aktuell weicht von Status Nacht ab => nicht schalten, wurde manuell geschaltet
2018.04.06 20:42:27 3: SetDevice: *** ENDE ***


Da ich aber ja durch den Aufruf OnSunrise/OnSunset weiß, ob ein Wechsel auf Tag oder Nacht erfolgt, übernehme ich einfach diesen Wert.

betateilchen

Zitat von: dt2510 am 06 April 2018, 09:48:00
da hat man sich in weit über 20 Jahren angewöhnt alles in Funktionen/Prozeduren zu packen

Ich programmiere seit über 40 Jahren und käme trotzdem nicht auf die Idee, eine bereits in eine vorhandene Funktion gepackte Berechnung nochmal in eine Funktion zu verpacken.

Davon abgesehen, sind in meiner FHEM Installation die Werte von sunrise/sunset/isday absolut synchron. Irgendwo hast Du in Deiner Planung noch einen Logik- oder einen Verständnisfehler.

-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

dt2510

Jeder Programmierer hat seinen eigenen Stil ... bei den Programmen meiner Kollegen bekomme ich des öfteren graue Haare, wie etwas gelöst wurde.

Also sunset und isday ist bei mir nicht 100% synchron

Zum Zeitpunkt sunset("CIVIL") wird mein Ereignis aufgerufen

define SunsetCIVIL at *{sunset("CIVIL")} {OnSunset("CIVIL")}

Der Aufruf von OnSunset erfolgte um exakt 20:42:27

2018.04.06 20:42:27 3: ******************************
2018.04.06 20:42:27 3: *** OnSunset
2018.04.06 20:42:27 3: *** CIVIL
2018.04.06 20:42:27 3: ******************************


Die isday Abfrage erfolgte innerhalb des OnSunset ebenfalls um exakt 20:42:27

return(isday(GetDeviceHorizon($myDevice)) ? "Day" : "Night");

Laut Protokoll sieht man, daß isday hier eindeutig noch "Day" (Wechsel auf ...) geliefert hat:

2018.04.06 20:42:27 3: SetDevice: *** ANFANG ***
2018.04.06 20:42:27 3: SetDevice: Aktor            = FGR222_ID18
2018.04.06 20:42:27 3: SetDevice: Horizont aktuell = CIVIL
2018.04.06 20:42:27 3: SetDevice: Horizont Aktor   = CIVIL
2018.04.06 20:42:27 3: SetDevice: Aktor soll zu diesem Zeitpunkt geschaltet werden
2018.04.06 20:42:27 3: SetDevice: HomeStatus       = Away
2018.04.06 20:42:27 3: SetDevice: Wechsel auf      = Day
2018.04.06 20:42:27 3: SetDevice: Status Tag       = dim 98
2018.04.06 20:42:27 3: SetDevice: Status Nacht     = dim 0
2018.04.06 20:42:27 3: SetDevice: Status aktuell   = dim 98
2018.04.06 20:42:27 3: SetDevice: Status neu       = dim 98
2018.04.06 20:42:27 3: SetDevice: Status aktuell weicht von Status Nacht ab => nicht schalten, wurde manuell geschaltet
2018.04.06 20:42:27 3: SetDevice: *** ENDE ***


Frage ich isday jetzt aktuell ab, bekomme ich korrekterweise "Night"

dt2510

#8
Zitat von: betateilchen am 06 April 2018, 09:32:27
Nur mal interessehalber: Wieso baust Du einen völlig überflüssigen Wrapper um eine FHEM-Standardfunktion?

Davon mal abgesehen finde ich das gar nicht überflüssig. Klar, der Befehl

AttrVal(Gerätename,"Horizon","REAL");

liefert das gleiche Ergebnis. Allerdings muss ich an jeder Stelle, an der ich den Wert brauche, den Devicenamen, den Namen des Attributes und den Defaultwert "REAL" angeben. Durch das Verpacken in die Funktion

sub GetDeviceHorizon($) {
  my ($myDevice) = @_;
  return(AttrVal($myDevice,"Horizon","REAL"));
}


rufe ich nur noch

GetDeviceHorizon(Gerätename);

auf. Der Name des Attributes und der Default-Wert brauchen mich dann weder zu interessieren noch müssen sie angegeben werden. Statt dreier Parameter brauche ich nur noch einen. Und in meinen Augen - das ist natürlich alles Geschmackssache - erhöht es die Lesbarkeit innerhalb des Programms.

betateilchen

sunset() und isday() rufen innerhalb von FHEM die exakt gleiche Funktion auf, deshalb können bei identischem Aufruf keine unterschiedlichen Ergebnisse zurückkommen.

Du solltest mal loggen, mit welchem Parameter Du die Funktion isday() tatsächlich aufrufst. Vermutlich liegt nämlich da der Hund begraben.




Zitat von: dt2510 am 06 April 2018, 23:58:24
Davon mal abgesehen finde ich das gar nicht überflüssig. Klar, der Befehl

AttrVal(Gerätename,"Horizon","REAL");

... ist überhaupt kein Befehl, sondern ein Funktionsaufruf.




-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

dt2510

#10
Zitat von: betateilchen am 07 April 2018, 00:02:37
... ist überhaupt kein Befehl, sondern ein Funktionsaufruf.

das ist jetzt Haarspalterei ...

isday wird mit dem korrekten Parameter aufgerufen, da sowohl in dem isday Aufrauf als auch in der Prozedur SetDevice mein überflüssiger ;) Wrapper GetDeviceHorizon aufgerufen wird und dessen Funktionsergebnis wird ja protokolliert.

return(isday(GetDeviceHorizon($myDevice)) ? "Day" : "Night");

Log(3,"SetDevice: Horizont Aktor   = ".GetDeviceHorizon($myDevice));

2018.04.06 20:42:27 3: SetDevice: Horizont Aktor   = CIVIL 


dt2510

#11
Hier mal die Funktion sr_alt  aus 99_SUNRISE_EL.pm (wird für sunrise, sunset  und isday aufgerufen)

sr_alt($$$$$$$$$)
{
  my $nt=shift;
  my $rise=shift;
  my $isrel=shift;
  my $daycheck=shift;
  my $nextDay=shift;
  my $altit = defined($_[0]) ? $_[0] : "";
  if(exists $alti{uc($altit)}) {
      $altit=$alti{uc($altit)};
      shift;
  } elsif($altit =~ /HORIZON=([\-\+]*[0-9\.]+)/i) {
      $altit=$1;
      shift;
  } else {
      $altit=-6; #default
  }
  my($seconds, $min, $max)=@_;
  my $needrise = ($rise || $daycheck) ? 1 : 0;
  my $needset = (!$rise || $daycheck) ? 1 : 0;
  $seconds = 0 if(!$seconds);

   ############################
   # If set in global, use longitude/latitude
   # from global, otherwise set Frankfurt/Germany as
   # default
   $long = AttrVal("global", "longitude", "8.686");
   $lat  = AttrVal("global", "latitude", "50.112");
   Log3 undef, 5, "Compute sunrise/sunset for latitude $lat , longitude $long";


  #my $nt = time;
  my @lt = localtime($nt);
  my $gmtoff = _calctz($nt,@lt); # in hour

  my ($rt,$st) = _sr_alt($altit,$needrise,$needset,
                        $lt[5]+1900,$lt[4]+1,$lt[3], $gmtoff);
  my $sst = ($rise ? $rt : $st) + ($seconds/3600);

  my $nh = $lt[2] + $lt[1]/60 + $lt[0]/3600;    # Current hour since midnight
  if($daycheck) {
    if(defined($min) && defined($max)) { #Forum #43742
      $min = hms2h($min); $max = hms2h($max);
      if($min < $max) {
        $rt = $min if($rt < $min);
        $st = $max if($st > $max);
      } else {
        $rt = $max if($rt > $max);
        $st = $min if($st < $min);
      }
    }
    return 1 if($rt <= $nh && $nh <= $st);
    return 0;
  }

  $sst = hms2h($min) if(defined($min) && (hms2h($min) > $sst));
  $sst = hms2h($max) if(defined($max) && (hms2h($max) < $sst));

  my $diff = 0;
  if (($data{AT_RECOMPUTE} ||                     # compute it for tommorow
    int(($nh-$sst)*3600) >= 0) && $nextDay)  {    # if called a subsec earlier
    $nt += 86400;
    @lt = localtime($nt);
    my $ngmtoff = _calctz($nt,@lt); # in hour
    $diff = 24;

    ($rt,$st) = _sr_alt($altit,$needrise,$needset,
                        $lt[5]+1900,$lt[4]+1,$lt[3], $ngmtoff);
    $sst = ($rise ? $rt : $st) + ($seconds/3600);

    $sst = hms2h($min) if(defined($min) && (hms2h($min) > $sst));
    $sst = hms2h($max) if(defined($max) && (hms2h($max) < $sst));
  }

  $sst += $diff if($isrel);
  $sst -= $nh if($isrel == 1);

  return h2hms_fmt($sst);
}


Beim überfliegen würde ich sagen, daß dies die Stelle ist, bei der Tag/Nacht bestimmt wird ($rt Sonnenaufgang, $st Sonnenuntergang und $nh aktuelle Zeit)

    return 1 if($rt <= $nh && $nh <= $st);
    return 0;


Demnach wäre Tag immer inklusive Sonnenauf- bzw. untergang.

isday kann ich demnach nicht verwenden, da ich davon ausgegangen bin, daß bei Sonnenuntergang auch schon Nacht geliefert wird.

dt2510

Ich habe jetzt den Aufruf von isday rausgenommen und lese stattdessen das userReading meines Dummy's aus, der schon bei Sonnenauf-/untergang zur Auswertung im TABLETUI gesetzt wird - und schon funktioniert alles wie es soll  8)

return(GetDeviceReading("DayNight",GetDeviceHorizon($myDevice)));