DevelopmentGuidelines: Unterschied zwischen den Versionen

Aus FHEMWiki
K (→‎Bezeichnungen: Remove dot in source.)
K (Added batteryReadings)
 
(5 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 5: Zeile 5:


===Allgemeines===
===Allgemeines===
Die Version von fhem, in der diese Guidelines umgesetzt werden sollen, wird mit fhem-NEU bezeichnet. Mit fhem 4.x wird die aktuelle Architektur bezeichnet, die auch fuer fhem 5.x zutrifft.  
Die Version von fhem, in der diese Guidelines umgesetzt werden sollen, wird mit fhem-NEU bezeichnet. Mit fhem 4.x wird die aktuelle Architektur bezeichnet, die auch für fhem 5.x zutrifft.  


===Readings===
===Readings===
Ein Reading (Ablesewert) ist jeglicher Wert, Zustand, etc., der von einem Geraet ausgelesen werden kann. Beispiele dafuer sind Messtemperatur, Luftfeuchtigkeit, Schaltzustand (an/aus), Firmwareversion, Empfangsstaerke.  
Ein Reading (Ablesewert) ist jeglicher Wert, Zustand, etc., der von einem Gerät ausgelesen werden kann. Beispiele dafür sind Messtemperatur, Luftfeuchtigkeit, Schaltzustand (an/aus), Firmwareversion, Empfangsstärke.  


=== I/O-Devices und Clients===
=== I/O-Devices und Clients===
Um Readings durch fhem verarbeiten zu koennen, muessen die Daten erst ueber ein I/O-Device in den Rechner kommen. Beispiele: FHZ1300, CUL, CM11, M232.
Um Readings durch fhem verarbeiten zu können, müssen die Daten erst über ein I/O-Device in den Rechner kommen. Beispiele: FHZ1300, CUL, CM11, M232.


Ein I/O-Device ist ueber einen Port (Beispiel fuer Unix: /dev/ttyS0) oder einen Socket ansprechbar.
Ein I/O-Device ist über einen Port (Beispiel für Unix: /dev/ttyS0) oder einen Socket ansprechbar.


Ein I/O-Device kann selbst ein Geraet sein, das Readings liefert (Beispiel: SCIVT, sowie die meisten anderen Schnittstellengeraete, wenn man die Geraeteversion oder die Uptime mit zu den Readings zaehlt), oder die Readings von anderen Geraeten (Clients) weiterleiten (Beispiel: FHZ1300).
Ein I/O-Device kann selbst ein Gerät sein, das Readings liefert (Beispiel: SCIVT, sowie die meisten anderen Schnittstellengeräte, wenn man die Geräteversion oder die Uptime mit zu den Readings zählt), oder die Readings von anderen Geräten (Clients) weiterleiten (Beispiel: FHZ1300).


==Requests for Proposal==
==Requests for Proposal==
Zeile 22: Zeile 22:


Es sind zwei Arten von Readings zu unterscheiden:
Es sind zwei Arten von Readings zu unterscheiden:
# Readings, die vom Geraet aktiv mitgeteilt werden, entweder ad-hoc oder in regelmäßigen Zeitabständen (aktive Readings). Beispiele: measuredTemp bei FHT80B, wind bei KS300
# Readings, die vom Gerät aktiv mitgeteilt werden, entweder ad-hoc oder in regelmäßigen Zeitabständen (aktive Readings). Beispiele: measuredTemp bei FHT80B, wind bei KS300
# Readings, nach denen fhem die Geräte fragen muss (passive Readings). Beispiele: energyDay bei EM1010PC, counter bei M232, temperature bei OWTEMP
# Readings, nach denen fhem die Geräte fragen muss (passive Readings). Beispiele: energyDay bei EM1010PC, counter bei M232, temperature bei OWTEMP


Dasselbe Geraet kann sowohl aktive als auch passive Readings beinhalten. Bei Geraeten mit aktiven Readings sind passive Readings meist Informationen wie Versionsnummer oder Uptime (CUL, CM11).
Dasselbe Gerät kann sowohl aktive als auch passive Readings beinhalten. Bei Geräten mit aktiven Readings sind passive Readings meist Informationen wie Versionsnummer oder Uptime (CUL, CM11).


Geraete, die mehrere verschiedene Readings haben, senden diese haeufig im Batch an fhem (Beispiel: FHT80b, KS300).  
Geräte, die mehrere verschiedene Readings haben, senden diese häufig im Batch an fhem (Beispiel: FHT80b, KS300).  


====Mechanismus fuer aktive Readings====
====Mechanismus für aktive Readings====
''TODO: Mechanismus mit ParseFn, GetFn, Match usw. beschreiben (wie in fhem 4.x)!''
''TODO: Mechanismus mit ParseFn, GetFn, Match usw. beschreiben (wie in fhem 4.x)!''


====Mechanismus fuer passive Readings====
====Mechanismus für passive Readings====
Fuer passive Geräte gibt es eine Polling-Infrastruktur.
Für passive Geräte gibt es eine Polling-Infrastruktur.


'''Status Quo in fhem 4.x:'''
'''Status Quo in fhem 4.x:'''
Zeile 60: Zeile 60:


Fragen:
Fragen:
* Kann/soll dieses Verfahren in einem Modul gekapselt werden? Geraete melden sich dann an der Polling-Infrastruktur an. Wenn ja, wie?
* Kann/soll dieses Verfahren in einem Modul gekapselt werden? Geräte melden sich dann an der Polling-Infrastruktur an. Wenn ja, wie?
* Wie wird waitIfInitNotDone korrekt eingesetzt?  
* Wie wird waitIfInitNotDone korrekt eingesetzt?  


====Mechanismus fuer langsame Readings====
====Mechanismus für langsame Readings====
'''Problemstellung:''' Laengere Verarbeitungsprozesse in einzelnen Modulen (z.B. aufgrund von Netzwerklatenzen oder toten Geraeten) halten fhem komplett an und verhindern die Verarbeitung von Events.
'''Problemstellung:''' Längere Verarbeitungsprozesse in einzelnen Modulen (z.B. aufgrund von Netzwerklatenzen oder toten Geräten) halten fhem komplett an und verhindern die Verarbeitung von Events.


'''Loesung:''' Multiprocessing
'''Lösung:''' Multiprocessing


Beim Multiprocessing wird der fhem-Prozess geforkt, wenn ein Reading vom Geraet eingelesen werden soll. Der Vaterprozess wird sofort fortgesetzt und erledigt weitere Aufgaben. Der Kindprozess holt die Readings vom Geraet, liefert sie an den Vaterprozess und verendet.
Beim Multiprocessing wird der fhem-Prozess geforkt, wenn ein Reading vom Gerät eingelesen werden soll. Der Vaterprozess wird sofort fortgesetzt und erledigt weitere Aufgaben. Der Kindprozess holt die Readings vom Gerät, liefert sie an den Vaterprozess und verendet.


Die Lieferung an den Vaterprozess erfolgt durch eine Verbindung zu fhem via Netzwerk und Aufruf des Befehls
Die Lieferung des Resultats an den Vaterprozess erfolgt durch eine Loopback Verbindung zu fhem via localhost.


set myDevice childupdate reading1:value1 reading2:value2 ...
Dieser Mechanismus wurde durch Rudolf König in ein entsprechendes Modul verpackt, so dass jedes andere FHEM-Modul diese Möglichkeiten direkt nutzen kann. Eine nähere Erklärung sowie eine Anleitung dazu findet man in dem Artikel → [[Blocking Call]]
 
Beispielcode:
 
DEVICE_StartUpdate wird periodisch aufgerufen.
 
<nowiki>sub
DEVICE_StartUpdate($)
{
      my ($hash) = @_;
      # %updates contains the names of the readings to be updated
      my %updates = (
            "reading1" => "",
            "reading2" => "",
            "reading3" => "",
      );
      # care for some prerequisites
      ...
      if($hash->{CHILDPID}) {
        # there is still the child process running => try again next time
        Log 2, "DEVICE: Child already forked: timeout too short?";
        return;
      }
      # fork a child process and return if we are the parent process
      return if(($hash->{CHILDPID} = fork));
      # if we are here, we are the child process
      my @ret;
      foreach my $r (sort keys %updates) {
        my $ret = ... # retrieve reading $r from device
        $ret = "" if(!defined($ret));
        last if($ret eq ""); # break on error
        push(@ret, $r . ":" . $ret); # append readingI:valueI to array
      } 
      # now connect to parent process and pass retrieved readings
      my @port = split(" ", $attr{global}{port});
      my $server = IO::Socket::INET->new(PeerAddr => "localhost:$port[0]");
      Log 0, "DEVICE: Can't connect to parent\n" if(!$server);
      syswrite($server, "set $hash->{NAME} childupdate ".join(" ",@ret)."\n");
      exit(0);
  } </nowiki>
 
 
Aus der Routine DEVICE_Set wird DEVICE_ChildUpdate aufgerufen, wenn das Kommando "set childupdate ..." lautet:
 
<nowiki>sub
DEVICE_ChildUpdate($$) {
  my ($hash, @ret)= @_;
  delete $hash->{CHILDPID};
  # evaluate readings
  ...
  # inform about changes
  ...
}</nowiki>


'''Fragen''':
'''Fragen''':
* Kann dieser Mechanismus in einem Modul gekapselt werden, damit nicht jedes Geraet den o.g. Kode replizieren muss?
* Soll es diesen Mechanismus nur passive Readings geben oder gibt es Anwendungsfälle für aktive Readings?  
* Soll es diesen Mechanismus nur passive Readings geben oder gibt es Anwendungsfaelle fuer aktive Readings?  


'''Verworfene Alternative''': Multithreading
'''Verworfene Alternative''': Multithreading


Argumente gegen Multithreading:
Argumente gegen Multithreading:
* Wird praktisch auf keiner kleinen Plattform unterstuetzt.
* Wird praktisch auf keiner kleinen Plattform unterstützt.
* Negative Erfahrungen (z.B. Probleme bei vielen 3rd-party-Komponenten)
* Negative Erfahrungen (z.B. Probleme bei vielen 3rd-party-Komponenten)
* Kaum Programmiererfahrung mit Multithreading in Perl.  
* Kaum Programmiererfahrung mit Multithreading in Perl.


==Verabschiedete Richtlinien==
==Verabschiedete Richtlinien==
===Zeichensatz===
===Zeichensatz===
Alle nach aussen sichtbaren Werte sind in der Zeichenkodierung ASCII (Codes 32..127). Dies vermeidet Konvertierungsprobleme bei der Bearbeitung des Quellkodes, bei der Ausgabe durch Perl, beim Transport in Internet-Protokollen und bei der Darstellung im Frontend/GUI.  
Alle nach aussen sichtbaren Werte sind in der Zeichenkodierung ASCII (Codes 32..127). Dies vermeidet Konvertierungsprobleme bei der Bearbeitung des Quellcodes, bei der Ausgabe durch Perl, beim Transport in Internet-Protokollen und bei der Darstellung im Frontend/GUI.  


===Klassifizierung der Attribute===
===Klassifizierung der Attribute===
Zeile 151: Zeile 98:
* zu definieren, wie ein Attribut im $hash->{} abgelegt wird (in $hash->{}, $hash->{READINGS}, $hash->{INTERNALS}, ...),
* zu definieren, wie ein Attribut im $hash->{} abgelegt wird (in $hash->{}, $hash->{READINGS}, $hash->{INTERNALS}, ...),
* zu gliedern, welche Namenskonventionen jeweils für Attribute verwendet werden (Kleinbuchstaben, Großbuchstaben, CamelCaps, ...),
* zu gliedern, welche Namenskonventionen jeweils für Attribute verwendet werden (Kleinbuchstaben, Großbuchstaben, CamelCaps, ...),
* abzugrenzen, wo der Entwickler sich an Vorgaben halten muß (z.B. bei System Internals) und wo er frei ist, Attribute zu erfinden oder wegzulassen, und
* abzugrenzen, wo der Entwickler sich an Vorgaben halten muss (z.B. bei System Internals) und wo er frei ist, Attribute zu erfinden oder wegzulassen, und
* festzulegen, wo bzgl. der Inhalte Standards notwendig sind und wo nicht (z.B. bei "Logical Readings", wenn diese im GUI angezeigt werden sollten).  
* festzulegen, wo bzgl. der Inhalte Standards notwendig sind und wo nicht (z.B. bei "Logical Readings", wenn diese im GUI angezeigt werden sollten).  


====Logische Rubriken====
====Logische Rubriken====
Die folgenden Rubriken stellen eine sehr feine Einteilung dar:
Die folgenden Rubriken stellen eine sehr feine Einteilung dar:
* System Internals: werden vom Framework (fhem.pl) benoetigt/verwendet/erkannt, z.B. NAME, NR, CHANGED  
* System Internals: werden vom Framework (fhem.pl) benötigt/verwendet/erkannt, z.B. NAME, NR, CHANGED  
* Device Readings: Rohdaten, die vom physischen Geraet ausgelesen werden (nicht interpretiert), z.B. rain_raw (Wippenschlaege des Regensensors) in KS300
* Device Readings: Rohdaten, die vom physischen Gerät ausgelesen werden (nicht interpretiert), z.B. rain_raw (Wippenschläge des Regensensors) in KS300
* Hilfsvariablen: z.B. rain_raw_adj (rain_raw, jedoch bereinigt um die bei einigen KS300 auftretenden erratischen Spruenge) in KS300, Zwischenergebnisse von Daten/Werten, welche zur Mittelwertberechnung benoetigt werden
* Hilfsvariablen: z.B. rain_raw_adj (rain_raw, jedoch bereinigt um die bei einigen KS300 auftretenden erratischen Sprünge) in KS300, Zwischenergebnisse von Daten/Werten, die zur Mittelwertberechnung benötigt werden
* Logical Readings: ausgewertete Messdaten und Zustaende, z.B. Temperatur, Niederschlag (nicht als Wippenschlaege sondern in l/qm)
* Logical Readings: ausgewertete Messdaten und Zustände, z.B. Temperatur, Niederschlag (nicht als Wippenschläge sondern in l/qm)
* Derived Readings: aus den Messdaten abgeleitete Werte, z.B. durchschnittliche Temperatur der laufenden Woche, Niederschlagsmenge des Tages
* Derived Readings: aus den Messdaten abgeleitete Werte, z.B. durchschnittliche Temperatur der laufenden Woche, Niederschlagsmenge des Tages
* Physical Device Parameter: z.B. Modell oder Housecode und Unitcode bei X10 oder die Sensornummer beim BS (brightness sensor)
* Physical Device Parameter: z.B. Modell oder Housecode und Unitcode bei X10 oder die Sensornummer beim BS (brightness sensor)
* Logical Device Parameter: statische Werte, die als Grundlage für Berechnungen genutzt werden, z.B. Korrekturfaktoren bei den Energiemessgeraeten, die Tankgeometrie beim USF1000
* Logical Device Parameter: statische Werte, die als Grundlage für Berechnungen genutzt werden, z.B. Korrekturfaktoren bei den Energiemessgeräten, die Tankgeometrie beim USF1000
* Messages: alle Nachrichten, die das Gerät betreffen, z.B. "AVG_Month erfolgreich berechnet", "Script XYZ am xx.xx.xx gestartet", LastIODEV, LastRAWMSG
* Messages: alle Nachrichten, die das Gerät betreffen, z.B. "AVG_Month erfolgreich berechnet", "Script XYZ am xx.xx.xx gestartet", LastIODEV, LastRAWMSG
* Trigger: wenn Trigger direkt am Geraet hinterlegt werden, muessen nicht mehr alle Notifies durchsucht werden, sondern es kann das Modul direkt ermitteln, ob ein Notify ausgeloest wird.  
* Trigger: wenn Trigger direkt am Gerät hinterlegt werden, müssen nicht mehr alle Notifies durchsucht werden, sondern es kann das Modul direkt ermitteln, ob ein Notify ausgelöst wird.  


====Ablage im Programm====
====Ablage im Programm====
Programmtechnisch werden die Attribute eines Moduls so in Behaeltern abgelegt, dass folgende Kriterien erfuellt sind:
Programmtechnisch werden die Attribute eines Moduls so in Behältern abgelegt, dass folgende Kriterien erfüllt sind:
* Die Aufteilung ist moeglichst einfach/minimalistisch.
* Die Aufteilung ist möglichst einfach/minimalistisch.
* Anhand des Behaelters laesst sich entscheiden,
* Anhand des Behälters lässt sich entscheiden,
** ob die Werte der darin enthaltene Attribute ueber das Programmende hinaus gespeichert oder nicht gespeichert werden,
** ob die Werte der darin enthaltenen Attribute über das Programmende hinaus gespeichert oder nicht gespeichert werden,
** welchen Anzeigestandards oder Programmierstandards die darin enthaltenen Attribute entsprechen, und
** welchen Anzeigestandards oder Programmierstandards die darin enthaltenen Attribute entsprechen, und
** ob es sich um fhem-interne Attribute, Readings oder Hilfsvariablen handelt.  
** ob es sich um fhem-interne Attribute, Readings oder Hilfsvariablen handelt.  


Es gibt folgende Behaelter:
Es gibt folgende Behälter:


readings
readings
* Logische Rubrik: Device Reading, Logical Reading, Derived Reading
* Logische Rubrik: Device Reading, Logical Reading, Derived Reading
* Wird gespeichert
* Wird gespeichert
* Alle vom Geraet gelieferten bzw. berechneten Werte, die den Endanwender interessieren. Keine Rohdaten vom Geraet, die erst interpretiert werden muessen (Wippenschlaege beim Regensensor).
* Alle vom Gerät gelieferten bzw. berechneten Werte, die den Endanwender interessieren. Keine Rohdaten vom Gerät, die erst interpretiert werden müssen (Wippenschläge beim Regensensor).
* Alle Daten haben einen Wert und ein Zeitstempel
* Alle Daten haben einen Wert und ein Zeitstempel
* Beispiele:  
* Beispiele:  
Zeile 187: Zeile 134:
helper
helper
* Logische Rubrik: Hilfsvariable, Device Reading
* Logische Rubrik: Hilfsvariable, Device Reading
* Wird gespeichert
* Wird nicht gespeichert
* Alle Werte, die fuer den Endanwender nicht direkt Sinn machen, aber vom Modul fuer unterschiedliche Zwecke benoetigt werden. Kann auch rohe Readings enthalten.
* Alle Werte, die für den Endanwender nicht direkt sinnvoll sind aber vom Modul für unterschiedliche Zwecke benötigt werden. Kann auch rohe Readings enthalten.
* Beispiele:  
* Beispiele:  


Zeile 197: Zeile 144:
* Logische Rubrik: System Internals, Physical Device Parameter, Logical Device Parameter
* Logische Rubrik: System Internals, Physical Device Parameter, Logical Device Parameter
* Wird nicht gespeichert
* Wird nicht gespeichert
* Alle Geraete-Werte, die nicht gespeichert werden muessen, weil entweder berechnet, oder aus der Definition hervorgehen.
* Alle Geräte-Werte, die nicht gespeichert werden müssen, weil entweder berechnet, oder aus der Definition hervorgehen.
* Beispiele  
* Beispiele  


Zeile 203: Zeile 150:
  $defs{emwz}{fhem}{lastIODev} = "CUL"
  $defs{emwz}{fhem}{lastIODev} = "CUL"


Verworfener Behaelter STATE
Verworfener Behälter STATE
* Logische Rubrik: Device Reading, Logical Reading, Derived Reading
* Logische Rubrik: Device Reading, Logical Reading, Derived Reading
* Wird gespeichert
* Wird gespeichert
* Kurze(!) Zusammenfassung der wichtigsten Information des Geraetes. Was man in einer Uebersicht ueber die Geraete sehen moechte, z.B. eine Warnung oder die aktuelle Temperatur beim FHT80 oder der Zeitpunkt der letzten Aktivierung beim PIRI. Falls es sowas nicht gibt, dann einheitlich "defined". GGf. ueber Attribute am Modul steuerbar. Eigentlich ist es kein Behaelter im ueblichen Sinne, da es keine Untereinheiten hat.  
* Kurze(!) Zusammenfassung der wichtigsten Information des Geräts. Was man in einer Übersicht über die Geräte sehen möchte, z.B. eine Warnung oder die aktuelle Temperatur beim FHT80 oder der Zeitpunkt der letzten Aktivierung beim PIRI. Falls es das nicht gibt, dann einheitlich "defined". GGf. über Attribute am Modul steuerbar. Eigentlich ist es kein Behälter im üblichen Sinne, da es keine Untereinheiten hat.  


Der Behaelter wurde verworfen, weil er redundant ist, an sich gar kein Behaelter, und einfacher und flexibler ueber einen Verweis auf das Reading realisiert werden kann, der per Default angezeigt werden soll.  
Der Behälter wurde verworfen, weil er redundant ist, an sich gar kein Behälter, und einfacher und flexibler über einen Verweis auf das Reading realisiert werden kann, der per Default angezeigt werden soll.


====Status====
====Status====
Kurze Zusammenfassung der wichtigsten Information des Geraetes. Was man in einer Uebersicht ueber die Geraete sehen moechte, z.B. eine Warnung oder die aktuelle Temperatur beim FHT80 oder der Zeitpunkt der letzten Aktivierung beim PIRI. Falls es sowas nicht gibt, dann einheitlich "defined". Modulseitig gibt es einen Default, der auf das relevante Reading verweist. Dieser ist ueber das Attribut defaultReading am individuellen Geraet steuerbar.
Kurze Zusammenfassung der wichtigsten Information des Geräts. Was man in einer Übersicht über die Geräte sehen möchte, z.B. eine Warnung oder die aktuelle Temperatur beim FHT80 oder der Zeitpunkt der letzten Aktivierung beim PIRI. Falls es sowas nicht gibt, dann einheitlich "defined". Modulseitig gibt es einen Default, der auf das relevante Reading verweist. Dieser ist über das Attribut defaultReading am individuellen Gerät steuerbar.


Beispiel: Definition in der Konfigurationsdatei: attr myDevice defaultReading measuredTemp Verwendung im Code:
Beispiel: Definition in der Konfigurationsdatei: attr myDevice defaultReading measuredTemp Verwendung im Code:
Zeile 220: Zeile 167:


===Bezeichnungen===
===Bezeichnungen===
Verwendung von lowerCamelCaps fuer alle vom Modul nach aussen sichtbaren Bezeichner: a) die Bezeichnungen der Behaelter fuer Readings, Fhem und Helper und der Untereintraege, b) die Bezeichnungen der Readings, c) die Bezeichnungen der Attribute.
Verwendung von lowerCamelCaps für alle vom Modul nach aussen sichtbaren Bezeichner: a) die Bezeichnungen der Behälter für Readings, Fhem und Helper und der Untereinträge, b) die Bezeichnungen der Readings, c) die Bezeichnungen der Attribute.


Beispiel:
Beispiel:
Zeile 228: Zeile 175:
===Readings===
===Readings===
====Standardisierung der Readings====
====Standardisierung der Readings====
Die Readings werden standardisiert, indem Geraeteklassen gebildet werden wie in DevelopmentInterfaces beschrieben. Die Definition der Interfaces ist explizit nicht Gegenstand dieser Entscheidungsvorlage und wird weiterentwickelt und angepasst, wie es sich bei der Entwicklung von fhem-NEU ergibt.
Die Readings werden standardisiert, indem Geräteklassen gebildet werden wie in DevelopmentInterfaces beschrieben. Die Definition der Interfaces ist explizit nicht Gegenstand dieser Entscheidungsvorlage und wird weiterentwickelt und angepasst, wie es sich bei der Entwicklung von fhem-NEU ergibt.
Struktur im Code
Struktur im Code


Zu jedem Reading gibt es die folgenden Unterbehaelter:
Zu jedem Reading gibt es die folgenden Unterbehälter:
* value: Wert der Messgroesse
* value: Wert der Messgröße
* time: Zeitpunkt, zu dem der Wert ermittelt wurde  
* time: Zeitpunkt, zu dem der Wert ermittelt wurde  


Zeile 240: Zeile 187:
  $defs{myFHT}{readings}{measuredTemp}{value}= 23.5;
  $defs{myFHT}{readings}{measuredTemp}{value}= 23.5;


Readings enthalten grundsaetzlich genau einen Wert und diesen ohne Einheit.
Readings enthalten grundsätzlich genau einen Wert und diesen ohne Einheit.


====Einheitendarstellung====
====Einheitendarstellung====
Die verwendete Einheit fuer ein Reading ergibt sich aus der Interfacespezifikation.
Die verwendete Einheit für ein Reading ergibt sich aus der Interfacespezifikation.


Beispiel: Gerät ist ein Thermometer => es gibt ein Reading "temperature" und dessen Einheit ist immer °C oder Celsius
Beispiel: Gerät ist ein Thermometer => es gibt ein Reading "temperature" und dessen Einheit ist immer °C oder Celsius
Zeile 250: Zeile 197:
* Einheit ergibt sich aus der Dokumentation
* Einheit ergibt sich aus der Dokumentation
* Einheit ergibt sich aus einem expliziten Untereintrag $defs{deviceName}{readings}{theReading}{unit}="°C"
* Einheit ergibt sich aus einem expliziten Untereintrag $defs{deviceName}{readings}{theReading}{unit}="°C"
* Einheit ergibt sich aus Festlegung gemaess "FHEM-Standard", z.B. immer SI-Einheiten, Temperaturen in Celsius, etc.  
* Einheit ergibt sich aus Festlegung gemäß "FHEM-Standard", z.B. immer SI-Einheiten, Temperaturen in Celsius, etc.
 
====BatteryReadings====
Es gibt '''nur''' diese drei Readings für den Batteriestatus:
* batteryState
* batteryPercent
* batteryVoltage
 
Wertebereich:
*batteryState: ok|low
*batteryPercent: \d{1,2}|100
*batteryVoltage: \d+.\d+
 
Wichtig:
das jeweilige Modul setzt '''nur''' die Readings, die es aus den aktuellen Daten vom Geraet bestimmen kann. Konkret: niemand kann sich darauf verlassen, welche der drei battery Readings vorhanden sind (es gibt nicht überall ein batteryState). Wenn das Gerät früher ein Percent gemeldet hat, aber in der letzten Nachricht nur state, dann wird das Percent Reading nicht angefasst.
Aktuell wird noch diskutiert, was mit dem Reading für Laden ist. Mehr dazu hier:  {{Link2Forum|Topic=87575|Message=62960|LinkText=Beitrag im FHEM Forum}}


===Zeitdarstellung in fhem===
===Zeitdarstellung in fhem===
Zeiten werden im Programm grundsaetzlich immer maschinenlesbar abgelegt, also sowohl in $defs{deviceName}{readings}{time} als auch z.B. bei der Speicherung des Zeitpunkts der Ausfuehrung des naechsten at-Kommandos.
Zeiten werden im Programm grundsätzlich immer maschinenlesbar abgelegt, also sowohl in $defs{deviceName}{readings}{time} als auch z.B. bei der Speicherung des Zeitpunkts der Ausführung des nächsten at-Kommandos.


Es gibt uebergreifende Hilfsfunktionen, die die maschinenlesbare in eine menschenlesbare Darstellung umwandeln.
Es gibt übergreifende Hilfsfunktionen, die die maschinenlesbare in eine menschenlesbare Darstellung umwandeln.


Argumente fuer maschinenlesbar:
Argumente für maschinenlesbar:
* vereinfacht Datumsarithmetik, z.B. die Berechnung der Zeitdauer zwischen zwei Ereignissen
* vereinfacht Datumsarithmetik, z.B. die Berechnung der Zeitdauer zwischen zwei Ereignissen
* kann vom Frontend in die regionale Darstellung des Anwenders uebersetzt werden
* kann vom Frontend in die regionale Darstellung des Anwenders übersetzt werden
* hat keine Probleme mit doppelt auftretenden Zeitpunkten bei der Umstellung von Sommer- auf Winterzeit  
* hat keine Probleme mit doppelt auftretenden Zeitpunkten bei der Umstellung von Sommer- auf Winterzeit  


Folgende Zeitdarstellungen werden ausschliesslich verwendet:
Folgende Zeitdarstellungen werden ausschliesslich verwendet:


A. Zahl der Sekunden seit der Unix-Epoche (was time liefert); auch wenn time eine Ganzzahl liefert, muss der Verwender damit rechnen, eine Gleitkommazahl vorzufinden. Das ist z.B. dann der Fall, wenn fuer die Zeibestimmung hoeheraufloesende Funktionen (z.B. Time::HiRes) zum Einsatz kamen und Sekundenbruchteile mitgespeichert wurden.
A. Zahl der Sekunden seit der Unix-Epoche (was time liefert); auch wenn time eine Ganzzahl liefert, muss der Verwender damit rechnen, eine Gleitkommazahl vorzufinden. Das ist z.B. dann der Fall, wenn für die Zeitbestimmung höher auflösende Funktionen (z.B. Time::HiRes) zum Einsatz kamen und Sekundenbruchteile mitgespeichert wurden.


Hinweis zu potentiellen Problemen:
Hinweis zu potentiellen Problemen:
* Ab perl 5.12 ist das Jahr-2038-Problem in Perl beseitigt.
* Ab perl 5.12 ist das Jahr-2038-Problem in Perl beseitigt.
* Ab MacOS X liefert time auch auf Macs die Zahl der Sekunden seit der Unix-Epoche (statt seit Anfang 1904)  
* Ab Mac OS X liefert time auch auf Macs die Zahl der Sekunden seit der Unix-Epoche (statt seit Anfang 1904)  


B. ISO8601 mit optionaler Zeitzonenangabe, wobei bei fehlender Zeitzone die lokale Zeitzone des fhem-Servers gilt
B. ISO8601 mit optionaler Zeitzonenangabe, wobei bei fehlender Zeitzone die lokale Zeitzone des fhem-Servers gilt
Zeile 275: Zeile 237:
* Zur Programmlaufzeit in Variablen immer A
* Zur Programmlaufzeit in Variablen immer A
* In Konfigurationsdateien (fhem.conf, fhem.save) immer B
* In Konfigurationsdateien (fhem.conf, fhem.save) immer B
* In Logs, die fuer den Menschen bestimmt sind, B
* In Logs, die für den Menschen bestimmt sind, B
* In Logs, die fuer die Maschine bestimmt sind, also z.B. bei Weiterverarbeitung in einem GUI, A
* In Logs, die für die Maschine bestimmt sind, also z.B. bei Weiterverarbeitung in einem GUI, A
* In Listen (list, xmllist friendly), die fuer den Menschen bestimmt sind, B
* In Listen (list, xmllist friendly), die für den Menschen bestimmt sind, B
* In Listen (xmllist), die fuer die Maschine bestimmt sind, also z.B. bei Weiterverarbeitung in einem GUI, A  
* In Listen (xmllist), die für die Maschine bestimmt sind, also z.B. bei Weiterverarbeitung in einem GUI, A  


Die Unterscheidung bei den Logs ist noch zu diskutieren.
Die Unterscheidung bei den Logs ist noch zu diskutieren.
Zeile 286: Zeile 248:
  <nowiki>set timefmt '%Y-%m-%dT%H:%M:%S' </nowiki>
  <nowiki>set timefmt '%Y-%m-%dT%H:%M:%S' </nowiki>


(ISO8601) funktioniert, und zwar unabhaengig davon, ob die optionale Zeitzonenangabe anhaengt oder nicht (getestet mit Gnuplot v4.2 pl3). Fuer die Sekunden seit der Unix-Epoche:
(ISO8601) funktioniert, und zwar unabhängig davon, ob die optionale Zeitzonenangabe anhängt oder nicht (getestet mit Gnuplot v4.2 pl3). Für die Sekunden seit der Unix-Epoche:


  <nowiki>set timefmt '%s' </nowiki>
  <nowiki>set timefmt '%s' </nowiki>
Zeile 295: Zeile 257:
===Events, Filter, Notify===
===Events, Filter, Notify===
====Event====
====Event====
Ein Event tritt ein, wenn ein oder mehrere Readings eines Geraetes aktualisiert werden, weil z.B. ein Datagramm empfangen oder zeitgesteuert Werte eingelesen wurden. Es wird durch drei Angaben
Ein Event tritt ein, wenn ein oder mehrere Readings eines Geräts aktualisiert werden, weil z.B. ein Datagramm empfangen oder zeitgesteuert Werte eingelesen wurden. Es wird durch drei Angaben


  <nowiki>(device, timestamp, { reading1, ..., readingN } )</nowiki>
  <nowiki>(device, timestamp, { reading1, ..., readingN } )</nowiki>


beschrieben:
beschrieben:
* das Geraet device
* das Gerät device
* der Zeitpunkt der Aktualisierung timestamp
* der Zeitpunkt der Aktualisierung timestamp
* die Menge der N>= 1 geaenderten Readings reading1, .. readingN  
* die Menge der N>= 1 geaenderten Readings reading1, .. readingN  
Zeile 318: Zeile 280:


====Mechanismus====
====Mechanismus====
0. Fuer das Geraet device namens deviceName wird ein Datagramm empfangen oder es werden zeitgesteuert Werte eingelesen.
0. Für das Gerät device namens deviceName wird ein Datagramm empfangen oder es werden zeitgesteuert Werte eingelesen.


1. Die Readings werden aktualisiert. Fuer X= 1..N:
1. Die Readings werden aktualisiert. Für X= 1..N:


  $defs{deviceName}{readings}{readingX}{value}= valueX;
  $defs{deviceName}{readings}{readingX}{value}= valueX;
  $defs{deviceName}{readings}{readingX}{time}= timestamp;
  $defs{deviceName}{readings}{readingX}{time}= timestamp;


2. Das Event wird erstellt und an DoTrigger uebergeben.
2. Das Event wird erstellt und an DoTrigger übergeben.


3. DoTrigger informiert zunaechst alle Clients mit inform-Wunsch.
3. DoTrigger informiert zunächst alle Clients mit inform-Wunsch.


4. Das Event wird sodann von DoTrigger nacheinander an alle NotifyFn in der alphabetischen Reihenfolge der Geraetenamen gereicht. Darunter auch jene von Logs.
4. Das Event wird sodann von DoTrigger nacheinander an alle NotifyFn in der alphabetischen Reihenfolge der Gerätenamen gereicht. Darunter auch jene von Logs.


5. Wenn der Filter des Logs auf das Event passt, wird ein Log-Eintrag der Form
5. Wenn der Filter des Logs auf das Event passt, wird ein Log-Eintrag der Form
Zeile 337: Zeile 299:
erzeugt.  
erzeugt.  


==Zurueckgestellte Entscheidungen==
==Zurückgestellte Entscheidungen==
===Attribute vs. Internals===
===Attribute vs. Internals===
Die Unterscheidung zwischen Attributen und Internals ist nicht eindeutig. Manche define-Parameter sind optional, und man kann define-Parameter mit "modify" aendern. Insofern koennte man theoretisch einen der beiden Verfahren (modify vs. attribute) abloesen.
Die Unterscheidung zwischen Attributen und Internals ist nicht eindeutig. Manche define-Parameter sind optional, und man kann define-Parameter mit "modify" ändern. Insofern könnte man theoretisch einen der beiden Verfahren (modify vs. attribute) ablösen.


1. Idee: define-Parameter duerfen nachtraeglich nicht geaendert werden, auch wenn es sich um optionale Parameter handelt => nicht-modifizierbare Internals
1. Idee: define-Parameter dürfen nachträglich nicht geändert werden, auch wenn es sich um optionale Parameter handelt => nicht-modifizierbare Internals


Pros:
Pros:
* define-Parameter sind elementar fuer den Betrieb des Geraetes und koennen nicht sinnvoll zur Programmlaufzeit geaendert werden (z.B. Hauskode bei X10, FHTId bei FHT80b)
* define-Parameter sind elementar für den Betrieb des Geräts und können nicht sinnvoll zur Programmlaufzeit geändert werden (z.B. Hauskode bei X10, FHTId bei FHT80b)
* Aus den define-Parametern werden bei der Initialisierung des Geraets weitere Helper und Internals abgeleitet und gespeichert. Eine nachtraegliche Aenderung zieht Aenderungen der Helper und Internals nach sich mit ggf. schwer durchschaubaren Nebeneffekten (z.B. corr1..corr4 bei EM, rainadjustment bei KS300). Auch die Logs aendern sich nicht nachtraeglich.  
* Aus den define-Parametern werden bei der Initialisierung des Geräts weitere Helper und Internals abgeleitet und gespeichert. Eine nachträgliche Änderung zieht Änderungen der Helper und Internals nach sich mit ggf. schwer durchschaubaren Nebeneffekten (z.B. corr1..corr4 bei EM, rainadjustment bei KS300). Auch die Logs ändern sich nicht nachträglich.  


Contras:
Contras:
* Man will nicht ein Geraet loeschen und neu anlegen, wenn es ausgetauscht wird.
* Man will nicht ein Gerät löschen und neu anlegen, wenn es ausgetauscht wird.
* modify sollte bleiben, weil es nuetzlich ist.  
* modify sollte bleiben, weil es nützlich ist.  


2. Idee: Attribute enthalten Werte, die ohne Nebeneffekte zur Laufzeit geaendert werden koennen, weil sie z.B. ad-hoc ausgewertet werden.
2. Idee: Attribute enthalten Werte, die ohne Nebeneffekte zur Laufzeit geändert werden können, weil sie z.B. ad-hoc ausgewertet werden.
* follow-on-for-timer
* follow-on-for-timer
* retrycount
* retrycount
* lazy  
* lazy  


3. Idee: Attribute beinhalten geraeteunabhaengige Meta-Informationen:
3. Idee: Attribute beinhalten geräteunabhängige Meta-Informationen:
* room
* room
* defaultReading  
* defaultReading  


Contras:
Contras:
* Nach 2. und 3. muesste
* Nach 2. und 3. müsste


  attr my_at disabled
  attr my_at disabled
Zeile 368: Zeile 330:




Spezialitaeten
Spezialitäten
* beim FS20 bleibt alles, wie es ist, also model als Attribut, was Auswirkung auf die moeglichen set Befehle hat.
* beim FS20 bleibt alles, wie es ist, also model als Attribut, was Auswirkung auf die möglichen set Befehle hat.
* 1wire setzt nach define das model Attribut, laesst aber eine Aenderung nicht zu, oder wenn doch, dann muss sich dementsprechend verhalten, es macht ja evtl. Sinn es zu aendern. Was machbar ist, entscheidet der Modul-Author.  
* 1wire setzt nach define das model Attribut, lässt aber eine Änderung nicht zu, oder wenn doch, dann muss sich dementsprechend verhalten, es macht ja evtl. Sinn es zu ändern. Was machbar ist, entscheidet der Modul-Author.  


Damit ist "model" immer ein Attribut.
Damit ist "model" immer ein Attribut.
Zeile 376: Zeile 338:
Fazit
Fazit


Da es keine ueberzeugende Alternative zum Ist-Zustand in fhem 4.x gibt, wird b.a.w. nicht hierueber entschieden.  
Da es keine überzeugende Alternative zum Ist-Zustand in fhem 4.x gibt, wird b.a.w. nicht hierüber entschieden.  


===notify===
===notify===
Die Frage, ob der notify-Mechanismus, der weiter oben dokumentiert ist, aus Performancegruenden optimiert werden sollte, ist offen.
Die Frage, ob der notify-Mechanismus, der weiter oben dokumentiert ist, aus Performancegründen optimiert werden sollte, ist offen.


In fhem 4.x bekommt jedes Geraet mit NotifyFn jedes Event mit. Alternativ koennte sich ein Geraet als EventListener mit einem Filter bei fhem anmelden. Dann koennten die Events nur an die NotifyFn verteilt werden, fuer die es ein Match auf den Filter gaebe.
In fhem 4.x bekommt jedes Gerät mit NotifyFn jedes Event mit. Alternativ könnte sich ein Gerät als EventListener mit einem Filter bei fhem anmelden. Dann könnten die Events nur an die NotifyFn verteilt werden, für die es ein Match auf den Filter gaebe.


Der Punkt wurde zurueckgestellt, bis nachgewiesen ist, dass sich aus dieser Vorgehensweise relevante Auswirkungen auf die Systemlast ergeben.  
Der Punkt wurde zurückgestellt, bis nachgewiesen ist, dass sich aus dieser Vorgehensweise relevante Auswirkungen auf die Systemlast ergeben.  


==Entscheidungen==
==Entscheidungen==
Zeile 391: Zeile 353:
:Es wird ein Attribut defaultReading verwendet.
:Es wird ein Attribut defaultReading verwendet.
;E3
;E3
:Verwendung von lowerCamelCaps fuer a) die Bezeichnungen der Behaelter fuer Readings, Fhem und Helper und der Untereintraege, b) die Bezeichnungen der Readings, c) die Bezeichnungen der Attribute.
:Verwendung von lowerCamelCaps für a) die Bezeichnungen der Behälter für Readings, Fhem und Helper und der Untereintraege, b) die Bezeichnungen der Readings, c) die Bezeichnungen der Attribute.
;E4
;E4
:Verwendung von value und time.
:Verwendung von value und time.
;E5
;E5
:Zeitdarstellung im Programm grundsaetzlich maschinenlesbar
:Zeitdarstellung im Programm grundsätzlich maschinenlesbar
;E6
;E6
:Zulaessige Zeitdarstellungen
:Zulässige Zeitdarstellungen
* Sekunden seit der Unix-Epoche
* Sekunden seit der Unix-Epoche
* ISO8601 mit optionaler Zeitzonenangabe
* ISO8601 mit optionaler Zeitzonenangabe
ISO8601 mit _ statt T war nicht mehrheitsfaehig.
ISO8601 mit _ statt T war nicht mehrheitsfähig.
;E7
;E7
:Verwendung der Zeitdarstellungen
:Verwendung der Zeitdarstellungen
;E8
;E8
:Readings enthalten grundsaetzlich genau einen Wert und diesen ohne Einheit.
:Readings enthalten grundsätzlich genau einen Wert und diesen ohne Einheit.
;E9
;E9
:Einheiten ergeben sich aus der Interfacespezifikation.
:Einheiten ergeben sich aus der Interfacespezifikation.
;E10
;E10
:Entscheidung fuer ASCII
:Entscheidung für ASCII
;E11
;E11
:Die Readings werden standardisiert, indem Geraeteklassen gebildet werden wie in DevelopmentInterfaces beschrieben. Die Definition der Interfaces ist explizit nicht Gegenstand dieser Entscheidungsvorlage und wird weiterentwickelt und angepasst, wie es sich bei der Entwicklung von fhem-NEU ergibt.
:Die Readings werden standardisiert, indem Geräteklassen gebildet werden wie in DevelopmentInterfaces beschrieben. Die Definition der Interfaces ist explizit nicht Gegenstand dieser Entscheidungsvorlage und wird weiterentwickelt und angepasst, wie es sich bei der Entwicklung von fhem-NEU ergibt.


[[Kategorie:Development]]
[[Kategorie:Development (Archive)]]

Aktuelle Version vom 2. Juni 2018, 13:05 Uhr

Wichtiger Hinweis: Diese Seite enthält die Ideen der fhem-Entwickler, wie fhem in einer künftigen Version funktionieren soll. Es beschreibt nicht den Zustand der aktuellen Umsetzung in den Release- oder den SVN-Versionen.

Definitionen

TODO: Rudis kanonische Begriffe verwenden!

Allgemeines

Die Version von fhem, in der diese Guidelines umgesetzt werden sollen, wird mit fhem-NEU bezeichnet. Mit fhem 4.x wird die aktuelle Architektur bezeichnet, die auch für fhem 5.x zutrifft.

Readings

Ein Reading (Ablesewert) ist jeglicher Wert, Zustand, etc., der von einem Gerät ausgelesen werden kann. Beispiele dafür sind Messtemperatur, Luftfeuchtigkeit, Schaltzustand (an/aus), Firmwareversion, Empfangsstärke.

I/O-Devices und Clients

Um Readings durch fhem verarbeiten zu können, müssen die Daten erst über ein I/O-Device in den Rechner kommen. Beispiele: FHZ1300, CUL, CM11, M232.

Ein I/O-Device ist über einen Port (Beispiel für Unix: /dev/ttyS0) oder einen Socket ansprechbar.

Ein I/O-Device kann selbst ein Gerät sein, das Readings liefert (Beispiel: SCIVT, sowie die meisten anderen Schnittstellengeräte, wenn man die Geräteversion oder die Uptime mit zu den Readings zählt), oder die Readings von anderen Geräten (Clients) weiterleiten (Beispiel: FHZ1300).

Requests for Proposal

Aktualisierung der Readings

Aktive und passive Readings

Es sind zwei Arten von Readings zu unterscheiden:

  1. Readings, die vom Gerät aktiv mitgeteilt werden, entweder ad-hoc oder in regelmäßigen Zeitabständen (aktive Readings). Beispiele: measuredTemp bei FHT80B, wind bei KS300
  2. Readings, nach denen fhem die Geräte fragen muss (passive Readings). Beispiele: energyDay bei EM1010PC, counter bei M232, temperature bei OWTEMP

Dasselbe Gerät kann sowohl aktive als auch passive Readings beinhalten. Bei Geräten mit aktiven Readings sind passive Readings meist Informationen wie Versionsnummer oder Uptime (CUL, CM11).

Geräte, die mehrere verschiedene Readings haben, senden diese häufig im Batch an fhem (Beispiel: FHT80b, KS300).

Mechanismus für aktive Readings

TODO: Mechanismus mit ParseFn, GetFn, Match usw. beschreiben (wie in fhem 4.x)!

Mechanismus für passive Readings

Für passive Geräte gibt es eine Polling-Infrastruktur.

Status Quo in fhem 4.x:

In der Routine DEVICE_Define wird ein interner Timer gestartet, der die Updatefunktion aufruft. INTERVAL ist die Periode in Sekunden.

sub
DEVICE_Define($$) {
...
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "DEVICE_GetUpdate", $hash, 0);
...
}

In der Routine DEVICE_GetUpdate werden dann die Readings vom Gerät geholt und der Timer wird mit dem gleichen Befehl erneut gestartet.

sub
DEVICE_GetUpdate($$) {
...
# start internal timer; do it at the beginning to achieve equal intervals no matter how long it takes to gather data
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "DEVICE_GetUpdate", $hash, 1);
# gather data
...
}

Der letzte Paramater 0 oder 1 ist waitIfInitNotDone.

Fragen:

  • Kann/soll dieses Verfahren in einem Modul gekapselt werden? Geräte melden sich dann an der Polling-Infrastruktur an. Wenn ja, wie?
  • Wie wird waitIfInitNotDone korrekt eingesetzt?

Mechanismus für langsame Readings

Problemstellung: Längere Verarbeitungsprozesse in einzelnen Modulen (z.B. aufgrund von Netzwerklatenzen oder toten Geräten) halten fhem komplett an und verhindern die Verarbeitung von Events.

Lösung: Multiprocessing

Beim Multiprocessing wird der fhem-Prozess geforkt, wenn ein Reading vom Gerät eingelesen werden soll. Der Vaterprozess wird sofort fortgesetzt und erledigt weitere Aufgaben. Der Kindprozess holt die Readings vom Gerät, liefert sie an den Vaterprozess und verendet.

Die Lieferung des Resultats an den Vaterprozess erfolgt durch eine Loopback Verbindung zu fhem via localhost.

Dieser Mechanismus wurde durch Rudolf König in ein entsprechendes Modul verpackt, so dass jedes andere FHEM-Modul diese Möglichkeiten direkt nutzen kann. Eine nähere Erklärung sowie eine Anleitung dazu findet man in dem Artikel → Blocking Call

Fragen:

  • Soll es diesen Mechanismus nur passive Readings geben oder gibt es Anwendungsfälle für aktive Readings?

Verworfene Alternative: Multithreading

Argumente gegen Multithreading:

  • Wird praktisch auf keiner kleinen Plattform unterstützt.
  • Negative Erfahrungen (z.B. Probleme bei vielen 3rd-party-Komponenten)
  • Kaum Programmiererfahrung mit Multithreading in Perl.

Verabschiedete Richtlinien

Zeichensatz

Alle nach aussen sichtbaren Werte sind in der Zeichenkodierung ASCII (Codes 32..127). Dies vermeidet Konvertierungsprobleme bei der Bearbeitung des Quellcodes, bei der Ausgabe durch Perl, beim Transport in Internet-Protokollen und bei der Darstellung im Frontend/GUI.

Klassifizierung der Attribute

Die Attribute eines Moduls werden in logische Klassen (Rubriken) eingeteilt.

Sinn und Zweck

Die Rubriken helfen...

  • zu sortieren, welche Attribute bei (xml)list gezeigt werden und welche nicht,
  • zu definieren, welche Werte per save in die Sicherung kommen und welche nicht,
  • zu vereinbaren, welche Änderungen ein notify auslösen und welche nicht,
  • zu definieren, wie ein Attribut im $hash->{} abgelegt wird (in $hash->{}, $hash->{READINGS}, $hash->{INTERNALS}, ...),
  • zu gliedern, welche Namenskonventionen jeweils für Attribute verwendet werden (Kleinbuchstaben, Großbuchstaben, CamelCaps, ...),
  • abzugrenzen, wo der Entwickler sich an Vorgaben halten muss (z.B. bei System Internals) und wo er frei ist, Attribute zu erfinden oder wegzulassen, und
  • festzulegen, wo bzgl. der Inhalte Standards notwendig sind und wo nicht (z.B. bei "Logical Readings", wenn diese im GUI angezeigt werden sollten).

Logische Rubriken

Die folgenden Rubriken stellen eine sehr feine Einteilung dar:

  • System Internals: werden vom Framework (fhem.pl) benötigt/verwendet/erkannt, z.B. NAME, NR, CHANGED
  • Device Readings: Rohdaten, die vom physischen Gerät ausgelesen werden (nicht interpretiert), z.B. rain_raw (Wippenschläge des Regensensors) in KS300
  • Hilfsvariablen: z.B. rain_raw_adj (rain_raw, jedoch bereinigt um die bei einigen KS300 auftretenden erratischen Sprünge) in KS300, Zwischenergebnisse von Daten/Werten, die zur Mittelwertberechnung benötigt werden
  • Logical Readings: ausgewertete Messdaten und Zustände, z.B. Temperatur, Niederschlag (nicht als Wippenschläge sondern in l/qm)
  • Derived Readings: aus den Messdaten abgeleitete Werte, z.B. durchschnittliche Temperatur der laufenden Woche, Niederschlagsmenge des Tages
  • Physical Device Parameter: z.B. Modell oder Housecode und Unitcode bei X10 oder die Sensornummer beim BS (brightness sensor)
  • Logical Device Parameter: statische Werte, die als Grundlage für Berechnungen genutzt werden, z.B. Korrekturfaktoren bei den Energiemessgeräten, die Tankgeometrie beim USF1000
  • Messages: alle Nachrichten, die das Gerät betreffen, z.B. "AVG_Month erfolgreich berechnet", "Script XYZ am xx.xx.xx gestartet", LastIODEV, LastRAWMSG
  • Trigger: wenn Trigger direkt am Gerät hinterlegt werden, müssen nicht mehr alle Notifies durchsucht werden, sondern es kann das Modul direkt ermitteln, ob ein Notify ausgelöst wird.

Ablage im Programm

Programmtechnisch werden die Attribute eines Moduls so in Behältern abgelegt, dass folgende Kriterien erfüllt sind:

  • Die Aufteilung ist möglichst einfach/minimalistisch.
  • Anhand des Behälters lässt sich entscheiden,
    • ob die Werte der darin enthaltenen Attribute über das Programmende hinaus gespeichert oder nicht gespeichert werden,
    • welchen Anzeigestandards oder Programmierstandards die darin enthaltenen Attribute entsprechen, und
    • ob es sich um fhem-interne Attribute, Readings oder Hilfsvariablen handelt.

Es gibt folgende Behälter:

readings

  • Logische Rubrik: Device Reading, Logical Reading, Derived Reading
  • Wird gespeichert
  • Alle vom Gerät gelieferten bzw. berechneten Werte, die den Endanwender interessieren. Keine Rohdaten vom Gerät, die erst interpretiert werden müssen (Wippenschläge beim Regensensor).
  • Alle Daten haben einen Wert und ein Zeitstempel
  • Beispiele:
$defs{Lampe}{readings}{switchedTo}{value} = "on"; $defs{Lampe}{readings}{switchedTo}{time} = "2010-03-29 23:32:26"
$defs{FHToben}{readings}{measuredTemp}{value} = "23"; $defs{FHToben}{readings}{switchedTo}{time} = "2010-03-29 21:58:36"

helper

  • Logische Rubrik: Hilfsvariable, Device Reading
  • Wird nicht gespeichert
  • Alle Werte, die für den Endanwender nicht direkt sinnvoll sind aber vom Modul für unterschiedliche Zwecke benötigt werden. Kann auch rohe Readings enthalten.
  • Beispiele:
$defs{ks300}{helper}{cumMonth} = "23 T: 131.4  H: 816  W: 775.1  R: -651.1"
$defs{emwz}{helper}{basis} = "4776493"

fhem

  • Logische Rubrik: System Internals, Physical Device Parameter, Logical Device Parameter
  • Wird nicht gespeichert
  • Alle Geräte-Werte, die nicht gespeichert werden müssen, weil entweder berechnet, oder aus der Definition hervorgehen.
  • Beispiele
$defs{emwz}{fhem}{def} = "1 75 900" 
$defs{emwz}{fhem}{lastIODev} = "CUL"

Verworfener Behälter STATE

  • Logische Rubrik: Device Reading, Logical Reading, Derived Reading
  • Wird gespeichert
  • Kurze(!) Zusammenfassung der wichtigsten Information des Geräts. Was man in einer Übersicht über die Geräte sehen möchte, z.B. eine Warnung oder die aktuelle Temperatur beim FHT80 oder der Zeitpunkt der letzten Aktivierung beim PIRI. Falls es das nicht gibt, dann einheitlich "defined". GGf. über Attribute am Modul steuerbar. Eigentlich ist es kein Behälter im üblichen Sinne, da es keine Untereinheiten hat.

Der Behälter wurde verworfen, weil er redundant ist, an sich gar kein Behälter, und einfacher und flexibler über einen Verweis auf das Reading realisiert werden kann, der per Default angezeigt werden soll.

Status

Kurze Zusammenfassung der wichtigsten Information des Geräts. Was man in einer Übersicht über die Geräte sehen möchte, z.B. eine Warnung oder die aktuelle Temperatur beim FHT80 oder der Zeitpunkt der letzten Aktivierung beim PIRI. Falls es sowas nicht gibt, dann einheitlich "defined". Modulseitig gibt es einen Default, der auf das relevante Reading verweist. Dieser ist über das Attribut defaultReading am individuellen Gerät steuerbar.

Beispiel: Definition in der Konfigurationsdatei: attr myDevice defaultReading measuredTemp Verwendung im Code:

$defs{myDevice}{readings}{$attr{defaultReading}}

Die logischen Rubriken Messages und Trigger wurden noch nicht diskutiert.

Bezeichnungen

Verwendung von lowerCamelCaps für alle vom Modul nach aussen sichtbaren Bezeichner: a) die Bezeichnungen der Behälter für Readings, Fhem und Helper und der Untereinträge, b) die Bezeichnungen der Readings, c) die Bezeichnungen der Attribute.

Beispiel:

$defs{myFHT}{readings}{measuredTemp}{time}

Readings

Standardisierung der Readings

Die Readings werden standardisiert, indem Geräteklassen gebildet werden wie in DevelopmentInterfaces beschrieben. Die Definition der Interfaces ist explizit nicht Gegenstand dieser Entscheidungsvorlage und wird weiterentwickelt und angepasst, wie es sich bei der Entwicklung von fhem-NEU ergibt. Struktur im Code

Zu jedem Reading gibt es die folgenden Unterbehälter:

  • value: Wert der Messgröße
  • time: Zeitpunkt, zu dem der Wert ermittelt wurde

Beispiel:

$defs{myFHT}{readings}{measuredTemp}{time}= timestamp;
$defs{myFHT}{readings}{measuredTemp}{value}= 23.5;

Readings enthalten grundsätzlich genau einen Wert und diesen ohne Einheit.

Einheitendarstellung

Die verwendete Einheit für ein Reading ergibt sich aus der Interfacespezifikation.

Beispiel: Gerät ist ein Thermometer => es gibt ein Reading "temperature" und dessen Einheit ist immer °C oder Celsius

Verworfene Alternative:

  • Einheit ergibt sich aus der Dokumentation
  • Einheit ergibt sich aus einem expliziten Untereintrag $defs{deviceName}{readings}{theReading}{unit}="°C"
  • Einheit ergibt sich aus Festlegung gemäß "FHEM-Standard", z.B. immer SI-Einheiten, Temperaturen in Celsius, etc.

BatteryReadings

Es gibt nur diese drei Readings für den Batteriestatus:

  • batteryState
  • batteryPercent
  • batteryVoltage

Wertebereich:

  • batteryState: ok|low
  • batteryPercent: \d{1,2}|100
  • batteryVoltage: \d+.\d+

Wichtig: das jeweilige Modul setzt nur die Readings, die es aus den aktuellen Daten vom Geraet bestimmen kann. Konkret: niemand kann sich darauf verlassen, welche der drei battery Readings vorhanden sind (es gibt nicht überall ein batteryState). Wenn das Gerät früher ein Percent gemeldet hat, aber in der letzten Nachricht nur state, dann wird das Percent Reading nicht angefasst. Aktuell wird noch diskutiert, was mit dem Reading für Laden ist. Mehr dazu hier: Beitrag im FHEM Forum

Zeitdarstellung in fhem

Zeiten werden im Programm grundsätzlich immer maschinenlesbar abgelegt, also sowohl in $defs{deviceName}{readings}{time} als auch z.B. bei der Speicherung des Zeitpunkts der Ausführung des nächsten at-Kommandos.

Es gibt übergreifende Hilfsfunktionen, die die maschinenlesbare in eine menschenlesbare Darstellung umwandeln.

Argumente für maschinenlesbar:

  • vereinfacht Datumsarithmetik, z.B. die Berechnung der Zeitdauer zwischen zwei Ereignissen
  • kann vom Frontend in die regionale Darstellung des Anwenders übersetzt werden
  • hat keine Probleme mit doppelt auftretenden Zeitpunkten bei der Umstellung von Sommer- auf Winterzeit

Folgende Zeitdarstellungen werden ausschliesslich verwendet:

A. Zahl der Sekunden seit der Unix-Epoche (was time liefert); auch wenn time eine Ganzzahl liefert, muss der Verwender damit rechnen, eine Gleitkommazahl vorzufinden. Das ist z.B. dann der Fall, wenn für die Zeitbestimmung höher auflösende Funktionen (z.B. Time::HiRes) zum Einsatz kamen und Sekundenbruchteile mitgespeichert wurden.

Hinweis zu potentiellen Problemen:

  • Ab perl 5.12 ist das Jahr-2038-Problem in Perl beseitigt.
  • Ab Mac OS X liefert time auch auf Macs die Zahl der Sekunden seit der Unix-Epoche (statt seit Anfang 1904)

B. ISO8601 mit optionaler Zeitzonenangabe, wobei bei fehlender Zeitzone die lokale Zeitzone des fhem-Servers gilt

Die Zeitdarstellungen werden wie folgt verwendet:

  • Zur Programmlaufzeit in Variablen immer A
  • In Konfigurationsdateien (fhem.conf, fhem.save) immer B
  • In Logs, die für den Menschen bestimmt sind, B
  • In Logs, die für die Maschine bestimmt sind, also z.B. bei Weiterverarbeitung in einem GUI, A
  • In Listen (list, xmllist friendly), die für den Menschen bestimmt sind, B
  • In Listen (xmllist), die für die Maschine bestimmt sind, also z.B. bei Weiterverarbeitung in einem GUI, A

Die Unterscheidung bei den Logs ist noch zu diskutieren.

Hinweise zu gnuplot:

set timefmt '%Y-%m-%dT%H:%M:%S' 

(ISO8601) funktioniert, und zwar unabhängig davon, ob die optionale Zeitzonenangabe anhängt oder nicht (getestet mit Gnuplot v4.2 pl3). Für die Sekunden seit der Unix-Epoche:

set timefmt '%s' 

(nicht getestet).

Allgemeine Dokumentation

Events, Filter, Notify

Event

Ein Event tritt ein, wenn ein oder mehrere Readings eines Geräts aktualisiert werden, weil z.B. ein Datagramm empfangen oder zeitgesteuert Werte eingelesen wurden. Es wird durch drei Angaben

(device, timestamp, { reading1, ..., readingN } )

beschrieben:

  • das Gerät device
  • der Zeitpunkt der Aktualisierung timestamp
  • die Menge der N>= 1 geaenderten Readings reading1, .. readingN

Wenn N> 1, dann handelt es sich um ein Sammelevent, wie es z.B. von KS300 generiert wird.

Filter

Ein Filter filtert Events. Er hat die Form

deviceNamePattern

bzw.

deviceNamePattern:readingNamePattern 

Ein Filter passt auf ein Event, wenn deviceNamePattern den Namen des device matcht und, sofern vorhanden, readingNamePattern mindestens irgendeinen Namen von reading1 bis readingN matcht.

Mechanismus

0. Für das Gerät device namens deviceName wird ein Datagramm empfangen oder es werden zeitgesteuert Werte eingelesen.

1. Die Readings werden aktualisiert. Für X= 1..N:

$defs{deviceName}{readings}{readingX}{value}= valueX;
$defs{deviceName}{readings}{readingX}{time}= timestamp;

2. Das Event wird erstellt und an DoTrigger übergeben.

3. DoTrigger informiert zunächst alle Clients mit inform-Wunsch.

4. Das Event wird sodann von DoTrigger nacheinander an alle NotifyFn in der alphabetischen Reihenfolge der Gerätenamen gereicht. Darunter auch jene von Logs.

5. Wenn der Filter des Logs auf das Event passt, wird ein Log-Eintrag der Form

timestamp deviceName reading1: value1 reading2: value2 ... readingN: valueN

erzeugt.

Zurückgestellte Entscheidungen

Attribute vs. Internals

Die Unterscheidung zwischen Attributen und Internals ist nicht eindeutig. Manche define-Parameter sind optional, und man kann define-Parameter mit "modify" ändern. Insofern könnte man theoretisch einen der beiden Verfahren (modify vs. attribute) ablösen.

1. Idee: define-Parameter dürfen nachträglich nicht geändert werden, auch wenn es sich um optionale Parameter handelt => nicht-modifizierbare Internals

Pros:

  • define-Parameter sind elementar für den Betrieb des Geräts und können nicht sinnvoll zur Programmlaufzeit geändert werden (z.B. Hauskode bei X10, FHTId bei FHT80b)
  • Aus den define-Parametern werden bei der Initialisierung des Geräts weitere Helper und Internals abgeleitet und gespeichert. Eine nachträgliche Änderung zieht Änderungen der Helper und Internals nach sich mit ggf. schwer durchschaubaren Nebeneffekten (z.B. corr1..corr4 bei EM, rainadjustment bei KS300). Auch die Logs ändern sich nicht nachträglich.

Contras:

  • Man will nicht ein Gerät löschen und neu anlegen, wenn es ausgetauscht wird.
  • modify sollte bleiben, weil es nützlich ist.

2. Idee: Attribute enthalten Werte, die ohne Nebeneffekte zur Laufzeit geändert werden können, weil sie z.B. ad-hoc ausgewertet werden.

  • follow-on-for-timer
  • retrycount
  • lazy

3. Idee: Attribute beinhalten geräteunabhängige Meta-Informationen:

  • room
  • defaultReading

Contras:

  • Nach 2. und 3. müsste
attr my_at disabled

doch zu Definition gehoeren. Und "skip_next" auch.


Spezialitäten

  • beim FS20 bleibt alles, wie es ist, also model als Attribut, was Auswirkung auf die möglichen set Befehle hat.
  • 1wire setzt nach define das model Attribut, lässt aber eine Änderung nicht zu, oder wenn doch, dann muss sich dementsprechend verhalten, es macht ja evtl. Sinn es zu ändern. Was machbar ist, entscheidet der Modul-Author.

Damit ist "model" immer ein Attribut.

Fazit

Da es keine überzeugende Alternative zum Ist-Zustand in fhem 4.x gibt, wird b.a.w. nicht hierüber entschieden.

notify

Die Frage, ob der notify-Mechanismus, der weiter oben dokumentiert ist, aus Performancegründen optimiert werden sollte, ist offen.

In fhem 4.x bekommt jedes Gerät mit NotifyFn jedes Event mit. Alternativ könnte sich ein Gerät als EventListener mit einem Filter bei fhem anmelden. Dann könnten die Events nur an die NotifyFn verteilt werden, für die es ein Match auf den Filter gaebe.

Der Punkt wurde zurückgestellt, bis nachgewiesen ist, dass sich aus dieser Vorgehensweise relevante Auswirkungen auf die Systemlast ergeben.

Entscheidungen

E1
Es werden die Container fhem, readings und helper verwendet.
E2
Es wird ein Attribut defaultReading verwendet.
E3
Verwendung von lowerCamelCaps für a) die Bezeichnungen der Behälter für Readings, Fhem und Helper und der Untereintraege, b) die Bezeichnungen der Readings, c) die Bezeichnungen der Attribute.
E4
Verwendung von value und time.
E5
Zeitdarstellung im Programm grundsätzlich maschinenlesbar
E6
Zulässige Zeitdarstellungen
  • Sekunden seit der Unix-Epoche
  • ISO8601 mit optionaler Zeitzonenangabe

ISO8601 mit _ statt T war nicht mehrheitsfähig.

E7
Verwendung der Zeitdarstellungen
E8
Readings enthalten grundsätzlich genau einen Wert und diesen ohne Einheit.
E9
Einheiten ergeben sich aus der Interfacespezifikation.
E10
Entscheidung für ASCII
E11
Die Readings werden standardisiert, indem Geräteklassen gebildet werden wie in DevelopmentInterfaces beschrieben. Die Definition der Interfaces ist explizit nicht Gegenstand dieser Entscheidungsvorlage und wird weiterentwickelt und angepasst, wie es sich bei der Entwicklung von fhem-NEU ergibt.