DevIo: Unterschied zwischen den Versionen
K (→Referenzen: Websocket ergänzt) |
|||
(58 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
{{Infobox Modul | {{Infobox Modul | ||
|ModPurpose= | |ModPurpose=Dienstfunktionen für die Kommunikation per serieller Schnittstelle (USB/RS232), TCP/IP-Verbindung oder UNIX-Socket | ||
|ModType= | |ModType=u | ||
|ModForumArea=FHEM Development | |ModForumArea=FHEM Development | ||
|ModTechName=DevIo.pm | |ModTechName=DevIo.pm | ||
Zeile 11: | Zeile 10: | ||
Es dient dabei lediglich dem Zweck einen Kommunikationskanal für eine Definition in FHEM zu etablieren und Daten darüber auszutauschen. Die Interpretation der empfangenen Daten obliegt dem Modul, welches die Verbindung via DevIo geöffnet hat. Die ausgetauschten Daten werden durch DevIo nicht verändert. | Es dient dabei lediglich dem Zweck einen Kommunikationskanal für eine Definition in FHEM zu etablieren und Daten darüber auszutauschen. Die Interpretation der empfangenen Daten obliegt dem Modul, welches die Verbindung via DevIo geöffnet hat. Die ausgetauschten Daten werden durch DevIo nicht verändert. | ||
= Allgemeine Funktionsweise = | = Allgemeine Funktionsweise = | ||
DevIo hat das Ziel eine besonders einfache Möglichkeit für Modulentwickler zu schaffen, um einen dauerhaften Kommunikationskanal zu etablieren. Um eine Verbindung aufzubauen muss zunächst die Gegenstelle bekannt sein. Dazu muss vor dem Verbindungsaufbau in dem Internal <code>$hash->{DeviceName}</code> der jeweiligen Definition das Ziel hinterlegt werden. Dies kann bspw. eine serielle Schnittstelle sein (z.B. "/dev/ttyUSB0" oder "COM1" unter Windows) oder eine TCP/IP-Gegenstelle (z.B. "192.168.1.100:1012") sein. Eine detaillierte Aufstellung der möglichen Verbindungsarten und deren Angabe in <code>$hash->{DeviceName}</code> gibt es im folgenden Kapitel [[#Unterstützte Verbindungsarten|Unterstützte Verbindungsarten]]. | DevIo hat das Ziel eine besonders einfache Möglichkeit für Modulentwickler zu schaffen, um einen dauerhaften Kommunikationskanal mit einem Hardware- oder Netzwerk-Gerät bzw. Service zu etablieren. Um eine Verbindung aufzubauen muss zunächst die Gegenstelle bekannt sein. Dazu muss vor dem Verbindungsaufbau in dem Internal <code>$hash->{DeviceName}</code> der jeweiligen Definition das Ziel hinterlegt werden. Dies kann bspw. eine serielle Schnittstelle sein (z.B. "/dev/ttyUSB0" oder "COM1" unter Windows) oder eine TCP/IP-Gegenstelle (z.B. "192.168.1.100:1012") sein. Eine detaillierte Aufstellung der möglichen Verbindungsarten und deren Angabe in <code>$hash->{DeviceName}</code> gibt es im folgenden Kapitel [[#Unterstützte Verbindungsarten|Unterstützte Verbindungsarten]]. | ||
Die Funktion [[#DevIo_OpenDev|DevIo_OpenDev()]] baut dabei die entsprechende Verbindung für eine einzelne Definition in Form eines Filedeskriptors auf und registriert diesen in dem globalen Hash <code>%selectlist</code><ref name="selectlist">[[DevelopmentModuleIntro#Wichtige_globale_Variablen_aus_fhem.pl|Development Module Introduction]] - Wichtige globale Variablen aus fhem.pl</ref>. Sobald die Verbindung erfolgreich aufgebaut wurde, kann | Die Funktion [[#DevIo_OpenDev()|DevIo_OpenDev()]] baut dabei die entsprechende Verbindung für eine einzelne Definition in Form eines Filedeskriptors auf und registriert diesen in dem globalen Hash <code>%selectlist</code><ref name="selectlist">[[DevelopmentModuleIntro#Wichtige_globale_Variablen_aus_fhem.pl|Development Module Introduction]] - Wichtige globale Variablen aus fhem.pl</ref>. Sobald die Verbindung erfolgreich aufgebaut wurde, kann eine, durch den Modulautor mitgelieferte, Initialisierungs-Funktion ausgeführt werden, um die Kommunikation zu initialiseren (bspw. das Senden einer Authentifizierungs-/Loginsequenz oder aktivieren der Hardware, etc.). | ||
FHEM (respektive fhem.pl) prüft nun regelmäßig, ob Daten zum Lesen bereitstehen (also Daten empfangen wurden). Ist dies der Fall, so wird die [[DevelopmentModuleIntro#X_Read|X_Read()]]-Funktion des zugehörigen Moduls für die hinterlegte Definition aufgerufen. Hier können die Daten durch den Aufruf von [[#DevIo_SimpleRead|DevIo_SimpleRead()]] nun eingelesen und verarbeitet werden. Das Senden von Daten ist durch den Aufruf von [[#DevIo_SimpleWrite|DevIo_SimpleWrite()]] sehr einfach möglich. | FHEM (respektive fhem.pl) prüft nun regelmäßig, ob Daten zum Lesen bereitstehen (also Daten empfangen wurden). Ist dies der Fall, so wird die [[DevelopmentModuleIntro#X_Read|X_Read()]]-Funktion des zugehörigen Moduls für die hinterlegte Definition aufgerufen. Hier können die Daten durch den Aufruf von [[#DevIo_SimpleRead()|DevIo_SimpleRead()]] nun eingelesen und verarbeitet werden. Das Senden von Daten ist durch den Aufruf von [[#DevIo_SimpleWrite()|DevIo_SimpleWrite()]] sehr einfach möglich. | ||
Sollte die Verbindung zusammenbrechen (USB-Gerät abgezogen, Gerät per Netzwerk nicht mehr erreichbar, etc.), so erkennt dies | Sollte die Verbindung zusammenbrechen (USB-Gerät abgezogen, Gerät per Netzwerk nicht mehr erreichbar, etc.), so erkennt dies DevIo und registriert die Definition in <code>%readyfnlist</code><ref name="selectlist" />. FHEM führt nun regelmäßig die [[DevelopmentModuleIntro#X_Ready|X_Ready()]]-Funktion des zugehörigen Moduls | ||
aus um zu prüfen, ob die Verbindung wieder aufgebaut werden kann. Hier wird nun | aus um zu prüfen, ob die Verbindung wieder aufgebaut werden kann. Hier wird nun durch die Ausführung von [[#DevIo_OpenDev()|DevIo_OpenDev()]] versucht die Verbindung wieder herzustellen. | ||
Sobald die Verbindung nicht mehr benötigt wird, oder FHEM bspw. beendet wird, kann die Verbindung via [[# | Sobald die Verbindung nicht mehr benötigt wird, oder FHEM bspw. beendet wird, kann die Verbindung via [[#DevIo_CloseDev()|DevIo_CloseDev()]] sauber geschlossen werden. | ||
= Unterstützte Verbindungsarten = | = Unterstützte Verbindungsarten = | ||
Zeile 33: | Zeile 31: | ||
! Verbindungsart !! Beispielangabe in<br><code>$hash->{DeviceName}</code> !! Beschreibung | ! Verbindungsart !! Beispielangabe in<br><code>$hash->{DeviceName}</code> !! Beschreibung | ||
|- | |- | ||
| '''Serielle Schnittstelle''' | | style="vertical-align:top" | '''Serielle Schnittstelle''' | ||
|| | | style="vertical-align:top" | | ||
* <code>/dev/ttyUSB0</code> | * <code>/dev/ttyUSB0</code> | ||
* <code>/dev/ttyUSB0@9600</code> | * <code>/dev/ttyUSB0@9600</code> | ||
Zeile 43: | Zeile 41: | ||
Durch Angabe eines Geräts in Form eines Gerätepfad (UNIX-basierte Betriebssysteme) oder der Schnittstellenbezeichnung aus Windows kann eine serielle Verbindung geöffnet werden. Der Gerätename kann zusätzliche Angaben zu Baudrate, Datenbits, Parität und Stoppbits enthalten um die Verbindung entsprechend zu konfiguieren. Diese Angaben sind durch ein "@" getrennt an die Gerätebezeichnung angehangen: | Durch Angabe eines Geräts in Form eines Gerätepfad (UNIX-basierte Betriebssysteme) oder der Schnittstellenbezeichnung aus Windows kann eine serielle Verbindung geöffnet werden. Der Gerätename kann zusätzliche Angaben zu Baudrate, Datenbits, Parität und Stoppbits enthalten um die Verbindung entsprechend zu konfiguieren. Diese Angaben sind durch ein "@" getrennt an die Gerätebezeichnung angehangen: | ||
Schematische Syntax: <code>''<font color="grey"><Gerät></font>'''''@'''''<Baudrate>''''','''''<Datenbits>''''','''''<Parität>''''','''''<Stopbits></code> | Schematische Syntax: <code>''<font color="grey"><Gerät></font>'''''@'''''<Baudrate>''''','''''<Datenbits>''''','''''<Parität>''''','''''<Stopbits>''</code> | ||
* <code><Baudrate></code> - Eine gültige Taktfrequenz (Symbole pro Sekunde) mit der die Schnittstelle geöffnet werden soll (Beispiel: <code>9600</code>, <code>14400</code>, <code>115200</code>, ...) | * <code><Baudrate></code> - Eine gültige Taktfrequenz (Symbole pro Sekunde) mit der die Schnittstelle geöffnet werden soll (Beispiel: <code>9600</code>, <code>14400</code>, <code>115200</code>, ...) | ||
Zeile 56: | Zeile 54: | ||
Unter Windows verwendet man als Gerätename die entsprechende Schnittstellenbezeichung wie bspw. <code>COM1</code>, <code>COM2</code>, usw. | Unter Windows verwendet man als Gerätename die entsprechende Schnittstellenbezeichung wie bspw. <code>COM1</code>, <code>COM2</code>, usw. | ||
|- | |- | ||
| '''TCP/IP-Verbindung''' | | style="vertical-align:top" | '''TCP/IP-Verbindung''' | ||
|| | | style="vertical-align:top" | | ||
* <code>192.168.1.2:1012</code> | * <code>192.168.1.2:1012</code> | ||
* <code>raspberry:5000</code> | * <code>raspberry:5000</code> | ||
Zeile 70: | Zeile 68: | ||
Die Verbindung kann optional verschlüsselt via SSL/TLS aufgebaut werden. Dazu muss das Internal <code>$hash->{SSL}</code> auf 1 gesetzt werden. | Die Verbindung kann optional verschlüsselt via SSL/TLS aufgebaut werden. Dazu muss das Internal <code>$hash->{SSL}</code> auf 1 gesetzt werden. | ||
|- | |- | ||
| '''UNIX-Socket''' | |'''Websocket''' | ||
|| | | | ||
* ws:echo.websocket.org | |||
* wss:example.org:443/path/to/something | |||
|Durch Angabe von ws: oder wss: signalisiert man, dass DevIo eine Websocket öffnen soll. Details sind auf der Seite [[Websocket]] zu finden. | |||
Syntax: <code>ws:</code> oder <code>wss:</code> | |||
* <code>ws:example.org</code> | |||
* <code>wss:exmaple.org:443</code> | |||
* <code>wss:exmaple.org:443/path/to/API</code> | |||
|- | |||
| style="vertical-align:top" | '''UNIX-Socket''' | |||
| style="vertical-align:top" | | |||
* <code>UNIX:SEQPACKET:/var/tmp/me_avm_home_external.ctl</code> | * <code>UNIX:SEQPACKET:/var/tmp/me_avm_home_external.ctl</code> | ||
* <code>UNIX:STREAM:/var/socket/con</code> | * <code>UNIX:STREAM:/var/socket/con</code> | ||
|| | || | ||
Durch Angabe eines Pfads zu einem UNIX-Domain-Socket kann eine Kommunikation mit einem anderen Prozess aufgebaut werden (Inter-Prozess-Kommunikation). Man kann den Socket dabei paketorientiert ("SEQPACKET") oder als Stream ("STREAM") öffnen. | |||
Schematische Syntax: <code>UNIX:''<Typ>'':''<Pfad>''</code> | |||
* <code><Typ></code> - Der Typ des Sockets. Für einen paketorientierten Socket ist hier <code>SEQPACKET</code> zu verwenden, für einen streamorientierten Socket <code>STREAM</code>. Der Typ muss immer mit angegeben werden. | |||
* <code><Pfad></code> - Der Pfad im Dateisystem zu dem gewünschten UNIX-Socket | |||
|- | |- | ||
| '''FHEM IO-Modul''' | | style="vertical-align:top" | '''FHEM IO-Modul''' | ||
|| | | style="vertical-align:top" | | ||
* <code>FHEM:DEVIO:Firmata_SerielleSchnittstelle@9600</code> | * <code>FHEM:DEVIO:Firmata_SerielleSchnittstelle@9600</code> | ||
|| | || | ||
Beschreibung siehe {{Link2Forum|Topic=46276}} | |||
|} | |||
= Wichtige Internals zur Konfiguration = | |||
Da DevIo ausschließlich definitionsbezogen arbeitet, erfolgt eine Konfiguration von DevIo über Internals, die im übergebenen <code>$hash</code> gesetzt werden müssen (oder können). Hiermit lässt sich das Verhalten von DevIo entsprechend beeinflussen. | |||
Hier eine Auflistung von allen Internals, die DevIo beeinflussen: | |||
{| class="wikitable" | |||
|- | |||
! style="min-width: 13em;" | Internal !! Beschreibung | |||
|- | |||
| style="vertical-align:top; white-space:nowrap;" | <code>$hash->{DeviceName}</code> | |||
''mandatory'' | |||
| style="vertical-align:top" | Die Gegenstelle zu der eine Verbindung aufgebaut werden soll. Die möglichen Werte und deren Syntax ist im Kapitel [[#Unterstützte Verbindungsarten|Unterstützte Verbindungsarten]] genauer beschrieben. | |||
|- | |||
| style="vertical-align:top; white-space:nowrap;" | <code>$hash->{nextOpenDelay}</code> | |||
''optional'' | |||
|| Die Zeit in Sekunden, welche im Falle eines Verbindungsabbruchs gewartet werden soll, bevor ein erneuter Verbindungsversuch stattfindet. | |||
Standardwert: 60 Sekunden | |||
|- | |||
| style="vertical-align:top; white-space:nowrap;" | <code>$hash->{TIMEOUT}</code> | |||
''optional'' | |||
|| Die maximale Zeit in Sekunden für den Aufbau einer TCP/IP-Verbindung. Sollte diese Zeit überschritten werden, bricht der Verbindungsaufbau mit einer Fehlermeldung ab. | |||
'''WICHTIG:''' Sollte beim Aufruf von [[#DevIo_OpenDev|DevIo_OpenDev()]] keine Callback-Funktion parametrisiert sein und die Gegenstelle antwortet beim Verbindungsaufbau nicht, so wird FHEM für die Dauer von <code>$hash->{TIMEOUT}</code> blockiert. | |||
Standardwert: 3 Sekunden | |||
|- | |||
| style="vertical-align:top; white-space:nowrap;" | <code>$hash->{SSL}</code> | |||
''optional'' | |||
|| Flag (0 oder 1), ob eine TCP/IP-Verbindung verschlüsselt (via SSL/TLS) aufgebaut werden soll. Wenn dieses Flag auf 1 gesetzt ist, wird nach erfolgtem Verbindungsaufbau eine SSL-Session initiiert. | |||
Standardwert: 0 (keine Verschlüsselung) | |||
|- | |||
| style="vertical-align:top; white-space:nowrap;" | <code>$hash->{devioLoglevel}</code> | |||
''optional'' | |||
|| Das Loglevel in dem <code>disconnected</code>/<code>reappeared</code> Meldungen geloggt werden sollen. Standardmäßig werden solche Verbindungsabbrüche (<code>disconnected</code>/<code>reappeared</code>) im Loglevel 1 geloggt. Die erfolgreiche Erstverbindung wird standardmäßig im Loglevel 3 geloggt. Durch das Setzen von <code>$hash->{devioLoglevel}</code> werden diese Meldungen allesamt in dem gesetzten Loglevel ausgegeben. Details dazu siehe {{Link2Forum|Topic=61970}}. | |||
Standardwert: ''[leer]'' | |||
|- | |||
| style="vertical-align:top; white-space:nowrap;" | <code>$hash->{devioNoSTATE}</code> | |||
''optional'' | |||
|| Flag (0 oder 1) - Sofern aktiviert, verändert DevIo das Internal <code>STATE</code> nicht direkt, sondern setzt nur das Reading "state" unter Berücksichtigung eines gesetzten <code>stateFormat</code> Attribut der jeweiligen Definition. Details dazu siehe {{Link2Forum|Topic=120940}} | |||
Standardwert: 0 | |||
|} | |} | ||
= Die Funktionen = | = Die Funktionen = | ||
== DevIo_OpenDev == | == DevIo_OpenDev() == | ||
:<syntaxhighlight lang="perl">$error = DevIo_OpenDev($hash, $reopen, $initfn, $callback);</syntaxhighlight> | :<syntaxhighlight lang="perl">$error = DevIo_OpenDev($hash, $reopen, $initfn); | ||
$error = DevIo_OpenDev($hash, $reopen, $initfn, $callback);</syntaxhighlight> | |||
Die Funktion DevIo_OpenDev() öffnet eine Verbindung zu dem Endpunkt der in <code>$hash->{DeviceName}</code> hinterlegt ist. Sobald die Verbindung erfolgreich hergestellt wurde, wird die Funktion <code>$initfn</code> ausgeführt, sofern gesetzt, um die Verbindung zu initialisieren. Sofern eine TCP/IP-Verbindung hergestellt wird, kann eine optionale Callback-Funktion <code>$callback</code> übergeben werden um den Verbindungsaufbau non-blocking durchzuführen. | |||
Der Rückgabewert enthält im Fehlerfall eine entsprechende Fehlermeldung. Im Erfolgsfall wird <code>undef</code> zurückgegeben. Sofern eine TCP/IP-Verbindung hergestellt wird und eine Callback-Funktion <code>$callback</code> übergeben wurde, wird immer <code>undef</code> zurückgegeben, da ein evtl. Fehler an diese Callback-Funktion nach dem erfolgten Verbindungsversuch mitgeteilt wird. | |||
Parameter: | Parameter: | ||
Zeile 95: | Zeile 167: | ||
''mandatory'' | ''mandatory'' | ||
|| Die Hash-Referenz der Definition, für die eine Verbindung geöffnet werden soll | | style="vertical-align:top" | Die Hash-Referenz der Definition, für die eine Verbindung geöffnet werden soll | ||
|- | |- | ||
| style="vertical-align:top" | '''<code>$reopen</code>''' | | style="vertical-align:top" | '''<code>$reopen</code>''' | ||
''mandatory<br>can be <code>undef</code>'' | ''mandatory<br>can be <code>undef</code>'' | ||
|| Flag (0 oder 1), ob es sich um einen erneuten Verbindungsversuch handelt (im Rahmen der [[DevelopmentModuleIntro#X_Ready|X_Ready()]]-Funktion). Sollte es der erste Verbindungsversuch sein, so muss dieser Parameter den Wert 0 besitzen. | | style="vertical-align:top" | Flag (0 oder 1), ob es sich um einen erneuten Verbindungsversuch handelt (im Rahmen der [[DevelopmentModuleIntro#X_Ready|X_Ready()]]-Funktion). Sollte es der erste Verbindungsversuch sein, so muss dieser Parameter den Wert 0 besitzen. | ||
|- | |- | ||
| style="vertical-align:top" | '''<code>$initfn</code>''' | | style="vertical-align:top" | '''<code>$initfn</code>''' | ||
Zeile 120: | Zeile 192: | ||
''optional'' | ''optional'' | ||
|| Der Name (als Zeichenkette) oder die Referenz auf eine Modulfunktion, welche aufgerufen werden soll um evtl. Fehlermeldungen beim Verbindungsaufbau an das Modul zurückzuliefern. Wenn eine Callback-Funktion gesetzt ist, erfolgt der Verbindungsaufbau non-blocking. Andernfalls wartet DevIo_OpenDev() bis die Verbindung steht bzw. ein Fehler auftritt (z.B. Timeout) | || Der Name (als Zeichenkette) oder die Referenz auf eine Modulfunktion, welche aufgerufen werden soll um evtl. Fehlermeldungen beim Verbindungsaufbau einer TCP/IP-Verbindung an das Modul zurückzuliefern. Wenn eine Callback-Funktion gesetzt ist, erfolgt der Verbindungsaufbau non-blocking. Andernfalls wartet DevIo_OpenDev() bis die Verbindung steht bzw. ein Fehler auftritt (z.B. Timeout). | ||
Die Funktion welche in <code>$callback</code> angegeben wurde, wird mit folgenden Parametern aufgerufen: | Die Funktion welche in <code>$callback</code> angegeben wurde, wird mit folgenden Parametern aufgerufen: | ||
Zeile 138: | Zeile 210: | ||
! Rückgabe!! Bedeutung | ! Rückgabe!! Bedeutung | ||
|- | |- | ||
| | | style="vertical-align:top" | '''<code>$error </code>''' || Eine Fehlermeldung als Zeichenkette, sollte der Verbindungsaufbau fehlschlagen. Im Erfolgsfall wird <code>undef</code> zurückgegeben. | ||
Wenn es sich um eine TCP/IP-Verbindung handelt und eine Callback-Funktion als Parameter <code>$callback</code> angegeben wurde, wird immer <code>undef</code> zurückgegeben, andernfalls erfolgt der Verbindungsaufbau blocking und eine evtl. Fehlermeldung wird als Rückgabewert zurückgegeben. Im Erfolgsfall wird <code>undef</code> zurückgegeben. | |||
|} | |||
== DevIo_IsOpen() == | |||
:<syntaxhighlight lang="perl">$status = DevIo_IsOpen($hash);</syntaxhighlight> | |||
Die Funktion DevIo_IsOpen() prüft, ob eine Verbindung für <code>$hash</code> geöffnet ist. Falls ja, wird das zugehörige IO-Objekt zurückgegeben, andernfalls <code>undef</code>. | |||
Diese Funktion kann dabei direkt in typischen if-Konstrukten verwendet werden um zu prüfen, ob eine Verbindung für die jeweilige Definition geöffnet oder geschlossen ist. | |||
Parameter: | |||
{| class="wikitable" | |||
|- | |||
! Parameter !! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$hash</code>''' | |||
''mandatory'' | |||
|| Die Hash-Referenz der Definition, deren Verbindung geprüft werden soll. | |||
|} | |||
Rückgabewerte: | |||
{| class="wikitable" | |||
|- | |||
! Rückgabewert !! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$status</code>''' || Der Status der Verbindung. Wenn eine Verbindung besteht, wird das zugehörige IO-Objekt zurückgegeben. Falls keine Verbindung besteht, wird <code>undef</code> zurückgegeben. | |||
|} | |||
== DevIo_SimpleRead() == | |||
:<syntaxhighlight lang="perl">$buf = DevIo_SimpleRead($hash);</syntaxhighlight> | |||
Die Funktion DevIo_SimpleRead() liest anstehende Daten für die Verbindung von <code>$hash</code> ein und gibt diese zurück. | |||
Sollte beim Versuch Daten zu lesen eine geschlossene Verbindung erkannt werden, so wird <code>undef</code> zurückgegeben und die Verbindung geschlossen. Es erfolgt zu einem späteren Zeitpunkt (siehe Internal <code>$hash->{nextOpenDelay}</code> aus Kapitel [[#Wichtige Internals zur Konfiguration|Wichtige Internals zur Konfiguration]]) ein neuer Verbindungsversuch. | |||
Parameter: | |||
{| class="wikitable" | |||
|- | |||
! Parameter !! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$hash</code>''' | |||
''mandatory'' | |||
|| Die Hash-Referenz der Definition, für deren Verbindung aktuell anstehende Daten gelesen werden sollen. | |||
|} | |||
Rückgabewert: | |||
{| class="wikitable" | |||
|- | |||
! Rückgabe!! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$buf</code>''' || Die zu lesenden Daten als Zeichenkette. Im Falle eines Verbindungsabruchs wird <code>undef</code> zurückgegeben. | |||
|} | |||
== DevIo_SimpleReadWithTimeout() == | |||
{{Randnotiz|RNTyp=Warn|RNText=<u>'''ACHTUNG:'''</u> | |||
Bei der Benutzung von DevIo_SimpleReadWithTimeout() wird FHEM für die Dauer von bis zu <code>$timeout</code> Sekunden blockiert, sollten keine Daten bis dahin zum Lesen bereitstehen. -> DevIo_SimpleReadWithTimeout() NICHT NUTZEN!}} | |||
:<syntaxhighlight lang="perl">$buf = DevIo_SimpleReadWithTimeout($hash, $timeout);</syntaxhighlight> | |||
Die Funktion DevIo_SimpleReadWithTimeout() wartet maximal <code>$timeout</code> Sekunden bis die Verbindung von <code>$hash</code> Daten zum einlesen bereitstellt und gibt diese zurück. Sollte nach dem Warten von <code>$timeout</code> noch immer keine Daten zum Lesen bereitstehen, so wird ein Leerstring zurückgegeben. | |||
Parameter: | |||
{| class="wikitable" | |||
|- | |||
! Parameter !! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$hash</code>''' | |||
''mandatory'' | |||
|| Die Hash-Referenz der Definition, für deren Verbindung aktuell anstehende Daten gelesen werden sollen. | |||
|- | |||
| style="vertical-align:top" | '''<code>$timeout</code>''' | |||
''mandatory'' | |||
|| Die maximale Wartezeit in Sekunden. | |||
|} | |||
Rückgabewert: | |||
{| class="wikitable" | |||
|- | |||
! Rückgabe!! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$buf</code>''' || Die zu lesenden Daten als Zeichenkette. | |||
|} | |} | ||
== | == DevIo_TimeoutRead() == | ||
{{Randnotiz|RNTyp=Warn|RNText=<u>'''ACHTUNG:'''</u> | |||
Bei der Benutzung von DevIo_TimeoutRead() wird FHEM für die Dauer von <code>$timeout</code> Sekunden blockiert. -> DevIo_TimeoutRead() NICHT NUTZEN!}} | |||
:<syntaxhighlight lang="perl">$buf = DevIo_TimeoutRead($hash, $timeout);</syntaxhighlight> | |||
== | |||
= Beispiel = | Die Funktion DevIo_SimpleReadWithTimeout() wartet <code>$timeout</code> Sekunden und liest sämtliche Daten für die Verbindung von <code>$hash</code> ein, die in dieser Zeit eintreffen. Sollten keinerlei Daten während der Wartezeit eintreffen, so wird ein Leerstring zurückgegeben. | ||
Parameter: | |||
{| class="wikitable" | |||
|- | |||
! Parameter !! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$hash</code>''' | |||
''mandatory'' | |||
|| Die Hash-Referenz der Definition, für deren Verbindung Daten eingelesen werden sollen. | |||
|- | |||
| style="vertical-align:top" | '''<code>$timeout</code>''' | |||
''mandatory'' | |||
|| Die Wartezeit in Sekunden. | |||
|} | |||
Rückgabewert: | |||
{| class="wikitable" | |||
|- | |||
! Rückgabe!! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$buf</code>''' || Die eingelesenen Daten als Zeichenkette. | |||
|} | |||
== DevIo_SimpleWrite() == | |||
:<syntaxhighlight lang="perl">DevIo_SimpleWrite($hash, $msg, $type); | |||
DevIo_SimpleWrite($hash, $msg, $type, $addnl);</syntaxhighlight> | |||
Die Funktion DevIo_SimpleWrite() sendet den Inhalt von <code>$msg</code> über die Verbindung von <code>$hash</code>. Mit den beiden Argumenten <code>$type</code> und <code>$addnl</code> kann die Formatierung von <code>$msg</code> beeinflusst werden bevor die Daten tatsächlich gesendet werden. | |||
Parameter: | |||
{| class="wikitable" | |||
|- | |||
! Parameter !! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$hash</code>''' | |||
''mandatory'' | |||
|| Die Hash-Referenz der Definition, über deren Verbindung die Daten gesendet werden sollen. | |||
|- | |||
| style="vertical-align:top" | '''<code>$msg</code>''' | |||
''mandatory'' | |||
|| Die zu schreibenden Daten als Zeichenkette. Abhängig von <code>$type</code> kann <code>$msg</code> Byte-Characters, HEX-Darstellungen oder normale ASCII-Zeichen enthalten. | |||
|- | |||
| style="vertical-align:top" | '''<code>$type</code>''' | |||
''mandatory'' | |||
|| Die Art des Inhalts von <code>$msg</code>. Abhängig von dem Inhalt von <code>$type</code> wird <code>$msg</code> entsprechend geändert oder nicht. Desweiteren werden die Daten für Logausgaben evtl. leserlich gemacht. | |||
Folgende Werte sind möglich: | |||
* <code>0</code> - <code>$msg</code> enthält Daten in Byteform. Der Inhalt von <code>$msg</code> wird 1:1 gesendet. Zur besseren Lesbarkeit werden die Daten bei Logausgaben in HEX-Darstellung umgewandelt. | |||
* <code>1</code> - <code>$msg</code> enthält Binärdaten in HEX-Darstellung (0-9/A-F). Der Inhalt von <code>$msg</code> wird vorher in Byteform umgewandelt und anschließend gesendet. In Logausgaben wird die HEX-Darstellung wie übergeben verwendet. | |||
* <code>2</code> - <code>$msg</code> enthält normale ASCII-Textzeichen. Der Inhalt von <code>$msg</code> wird 1:1 gesendet. In Logausgaben wird <code>$msg</code> wie übergeben ausgegeben. | |||
|- | |||
| style="vertical-align:top" | '''<code>$addnl</code>''' | |||
''optional'' | |||
|| Ein Flag (0/1) welches, sofern aktiviert, einen Zeilenumbruch (<code>\n</code>) an <code>$msg</code> anfügt, bevor es gesendet wird. | |||
Standardwert: 0 | |||
|} | |||
== DevIo_Expect() == | |||
{{Randnotiz|RNTyp=Warn|RNText=<u>'''ACHTUNG:'''</u> | |||
# Bei der Benutzung von DevIo_Expect() wird FHEM für die Dauer von bis zu <code>$timeout</code> Sekunden blockiert. | |||
# Sollte im ersten Versuch keine Antwort auf die zuvor gesendeten Daten innerhalb von <code>$timeout</code> Sekunden eintreffen, wird die Verbindung geschlossen und neu aufgebaut. Es erfolgt dabei '''KEINE INITIALISIERUNG''' durch <code>$initfn</code> aus [[#DevIo_OpenDev()|DevIo_OpenDev()]]. | |||
-> DevIo_Expect() NICHT NUTZEN! | |||
}} | |||
:<syntaxhighlight lang="perl">$buf = DevIo_Expect($hash, $msg, $timeout);</syntaxhighlight> | |||
Die Funktion DevIo_Expect() sendet den Inhalt von <code>$msg</code> und wartet bis zu <code>$timeout</code> Sekunden auf eine Antwort. Sollte in dieser Zeit keine Antwort eintreffen, so wird die Verbindung einmalig geschlossen, erneut geöffnet (ohne Aufruf einer Initialisierungs-Funktion) und der Vorgang wiederholt. Die empfangene Antwort wird anschließend als Funktionsergebnis zurückgegeben. | |||
Wenn beim ersten Versuch keine Antwort innerhalb von <code>$timeout</code> Sekunden eintrifft, wird das Event <code>FAILED</code> generiert (siehe [[#Generierte Events|Generierte Events]]). Anschließend wird die Verbindung geschlossen, neu geöffnet (ohne Initialisierung) und <code>$msg</code> erneut gesendet. Sollte nun eine Antwort eintreffen, so wird das Event <code>CONNECTED</code> generiert und die empfangene Antwort zurückgegeben. Sollte dennoch keine Antwort eintreffen, so wird das Event <code>DISCONNECTED</code> getriggert. | |||
Parameter: | |||
{| class="wikitable" | |||
|- | |||
! Parameter !! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$hash</code>''' | |||
''mandatory'' | |||
|| Die Hash-Referenz der Definition, für deren Verbindung Daten gesendet und anschließen gelesen werden sollen. | |||
|- | |||
| style="vertical-align:top" | '''<code>$msg</code>''' | |||
''mandatory'' | |||
|| Die zu sendenden Daten auf die eine Antwort erwartet wird. Die Daten werden ohne Konvertierung direkt gesendet (entspricht <code>$type</code> gleich <code>0</code> bei [[#DevIo_SimpleWrite()|DevIo_SimpleWrite]]). | |||
|- | |||
| style="vertical-align:top" | '''<code>$timeout</code>''' | |||
''mandatory'' | |||
|| Die maximale Wartezeit in Sekunden bis zum Eintreffen einer Antwort. | |||
|} | |||
Rückgabewert: | |||
{| class="wikitable" | |||
|- | |||
! Rückgabe!! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$buf</code>''' || Die empfangene Antwort. Sollte es zu einem Fehler gekommen sein (Senden von <code>$msg</code> fehlgeschlagen, keine Antwort innerhalb von <code>$timeout</code> Sekunden erhalten, etc.), so wird <code>undef</code> zurückgegeben. | |||
|} | |||
== DevIo_CloseDev() == | |||
:<syntaxhighlight lang="perl">DevIo_CloseDev($hash); | |||
DevIo_CloseDev($hash, $isFork);</syntaxhighlight> | |||
Die Funktion DevIo_CloseDev() schließt eine evtl. geöffnete Verbindung für die Definition <code>$hash</code>. | |||
Optional kann man mit dem Flag <code>$isFork</code> angeben, dass man sich aktuell in einem Fork vom Hauptprozess befindet. Dadurch werden beim Schließen einer seriellen Verbindung die Kommunikationsparameter nicht zurückgesetzt. Dies verhindert einen Ausfall der nachwievor bestehenden Verbindung im Elternprozess. | |||
Parameter: | |||
{| class="wikitable" | |||
|- | |||
! Parameter !! Bedeutung | |||
|- | |||
| style="vertical-align:top" | '''<code>$hash</code>''' | |||
''mandatory'' | |||
|| Die Hash-Referenz der Definition, deren Verbindung geschlossen werden soll. | |||
|- | |||
| style="vertical-align:top" | '''<code>$isFork</code>''' | |||
''optional'' | |||
|| Ein Flag (0 oder 1), welches angibt, dass DevIo_CloseDev() innerhalb eines geforkten Kindprozess ausgeführt wird. Dadurch werden die Kommunikationsparameter im Falle einer seriellen Verbindung (Baudrate, Datenbits, Parität, etc.) nicht zurückgesetzt. Dieses Flag wird primär von dem Modul [[Blocking_Call|Blocking.pm]] verwendet um Verbindungen in einem geforkten Kindprozess zu schließen, ohne die Verbindung im Hauptprozess zu beeinträchtigen. | |||
Standardwert: 0 (Verbindung wird im Hauptprozess geschlossen) | |||
|} | |||
= Generierte Events = | |||
Die Funktionen von DevIo generieren verschiedene Events für die entsprechende Definition (parametrisiert durch <code>$hash</code>). Diese können bspw. in der [[DevelopmentModuleIntro#X_Notify|Notify]]-Funktion des entsprechenden Moduls verarbeitet werden. | |||
Hier eine Übersicht der generierten Events: | |||
{| class="wikitable" | |||
|- | |||
! Event !! Bedeutung | |||
|- | |||
| '''<code>CONNECTED</code>''' || Die Verbindung wurde nach einem Verbindungsabbruch erfolgreich wieder aufgebaut. Dieses Event wird nur nach einem erfolgreichen Reconnect von einer zuvor verlorenen Verbindung generiert. Es wird '''nicht''' nach der erfolgreichen Initialverbindung generiert. | |||
<u>Hinweis:</u><br>Seit [https://svn.fhem.de/trac/changeset/18985/ SVN-Revision 18985] wird das <code>CONNECTED</code>-Event auch beim erfolgreichen Aufbau der initialen Verbindung getriggert. Dies gilt jedoch nur für TCP-Verbindungen. Bei allen anderen Verbindungsarten bleibt das Verhalten unverändert. (siehe {{Link2Forum|Topic=98809|Message=921911}}) | |||
|- | |||
| '''<code>DISCONNECTED</code>''' || Die zuvor erfolgreich aufgebaute Verbindung ist zusammengebrochen. Es wird ein neuer Verbindungsversuch nach <code>$hash->{nextOpenDelay}</code> Sekunden erfolgen (siehe [[#Wichtige Internals zur Konfiguration|Wichtige Internals zur Konfiguration]]). | |||
<u>Hinweis:</u><br>Seit [https://svn.fhem.de/trac/changeset/18985/ SVN-Revision 18985] wird das <code>DISCONNECTED</code>-Event auch nach dem ersten initialem erfolglosen Verbindungsversuch getriggert. Dies gilt jedoch nur für TCP-Verbindungen. Bei allen anderen Verbindungsarten bleibt das Verhalten unverändert. (siehe {{Link2Forum|Topic=98809|Message=921911}}) | |||
|- | |||
| '''<code>FAILED</code>''' || Dieses Event wird nur bei der Nutzung der Funktion [[#DevIo_Expect|DevIo_Expect()]] generiert, sofern es keine Antwort auf die zuvor gesendeten Daten gibt. Dies bedeutet konkret, dass Gerät hat nicht auf die eigene Anfrage geantwortet und die Verbindung wird daher neu aufgebaut. Details dazu, siehe [[#DevIo_Expect()|DevIo_Expect()]]. | |||
|} | |||
= Hinweis bei der Datenverarbeitung (Buffering) = | |||
DevIo beschränkt sich ausschließlich auf die Verbindungsverwaltung. Sobald die Verbindung etabliert wurde, übernimmt FHEM (fhem.pl) die Verarbeitung von eingehenden Daten und informiert das entsprechende Modul durch Aufruf der [[DevelopmentModuleIntro#X_Read|Read]]-Funktion. Dabei hat weder DevIo, noch FHEM selber Kenntnis von der Struktur der Daten. Sowohl DevIo, als auch FHEM selber können nicht erkennen, ob die empfangenen Daten weder vollständig, noch valide in ihrem Aufbau sind. Dies alles obliegt dem Modul, welches die Verbindung initiiert hat. Man sollte daher immer davon ausgehen, dass beim Lesen von Daten (in [[DevelopmentModuleIntro#X_Read|X_Read()]]) diese unvollständig sein können, oder sogar mehrere Datagramme enthalten sein können. | |||
Dazu stellt DevIo dem Modulentwickler das Internal <code>$hash->{PARTIAL}</code> in der jeweiligen Definition zur Verfügung um dort Daten zwischenzuspeichern, bis ein vollständiges Datagramm daraus verarbeitet werden kann. Alle eingelesenen Daten werden dazu in der [[DevelopmentModuleIntro#X_Read|Read]]-Funktion immer an <code>$hash->{PARTIAL}</code> hinten angehangen. Sobald ein vollständiges Datagramm in <code>$hash->{PARTIAL}</code> erkannt wurde, wird dieses herausgenommen und verarbeitet, solange bis kein vollständiges Datagramm in <code>$hash->{PARTIAL}</code> erkennbar ist. Hierbei ist jedoch entscheidend, woran man ein abgeschlossenes Datagramm erkennen kann. Dies kann je nach Protokoll sehr unterschiedlich sein (bspw. Zeilenumbruch <code>\r\n</code> oder ein Semikolon <code>;</code>, etc.). | |||
DevIo initialisiert <code>$hash->{PARTIAL}</code> nach dem erfolgreichen Verbindungsaufbau mit einem Leerstring (<code>""</code>). Sobald eine Verbindung mit [[#DevIo_CloseDev()|DevIo_CloseDev()]] geschlossen wird, wird <code>$hash->{PARTIAL}</code> gelöscht. | |||
Hier ein Beispiel, wie man ein solches Buffering verwendet für ein Protokoll, welches Datagramme durch einen Zeilenumbruch <code>\n</code> (Newline) abtrennt: | |||
<syntaxhighlight lang="perl"> | |||
sub MY_MODULE_Read($) | |||
{ | |||
my ($hash) = @_; | |||
my $name = $hash->{NAME}; | |||
my $data = DevIo_SimpleRead($hash); | |||
return if(!defined($data)); # connection lost | |||
my $buffer = $hash->{PARTIAL}; | |||
Log3 $name, 5, "MY_MODULE ($name) - received $data (buffer contains: $buffer)"; | |||
# concat received data to $buffer | |||
$buffer .= $data; | |||
# as long as the buffer contains newlines (complete datagramm) | |||
while($buffer =~ m/\n/) | |||
{ | |||
my $msg; | |||
# extract the complete message ($msg), everything else is assigned to $buffer | |||
($msg, $buffer) = split("\n", $buffer, 2); | |||
# remove trailing whitespaces | |||
chomp $msg; | |||
# parse the extracted message | |||
MY_MODULE_ParseMessage($hash, $msg); | |||
} | |||
# update $hash->{PARTIAL} with the current buffer content | |||
$hash->{PARTIAL} = $buffer; | |||
} | |||
</syntaxhighlight> | |||
= Beispielimplementierung in einem Modul = | |||
== serielle Verbindung == | |||
<syntaxhighlight lang="perl"> | |||
package main; | |||
use strict; | |||
use warnings; | |||
use DevIo; # load DevIo.pm if not already loaded | |||
# called upon loading the module MY_MODULE | |||
sub MY_MODULE_Initialize($) | |||
{ | |||
my ($hash) = @_; | |||
$hash->{DefFn} = "MY_MODULE_Define"; | |||
$hash->{UndefFn} = "MY_MODULE_Undef"; | |||
$hash->{SetFn} = "MY_MODULE_Set"; | |||
$hash->{ReadFn} = "MY_MODULE_Read"; | |||
$hash->{ReadyFn} = "MY_MODULE_Ready"; | |||
} | |||
# called when a new definition is created (by hand or from configuration read on FHEM startup) | |||
sub MY_MODULE_Define($$) | |||
{ | |||
my ($hash, $def) = @_; | |||
my @a = split("[ \t]+", $def); | |||
my $name = $a[0]; | |||
# $a[1] is always equals the module name "MY_MODULE" | |||
# first argument is a serial device (e.g. "/dev/ttyUSB0@9600") | |||
my $dev = $a[2]; | |||
return "no device given" unless($dev); | |||
# close connection if maybe open (on definition modify) | |||
DevIo_CloseDev($hash) if(DevIo_IsOpen($hash)); | |||
# add a default baud rate (9600), if not given by user | |||
$dev .= '@9600' if(not $dev =~ m/\@\d+$/); | |||
# set the device to open | |||
$hash->{DeviceName} = $dev; | |||
# open connection with custom init function | |||
my $ret = DevIo_OpenDev($hash, 0, "MY_MODULE_Init"); | |||
return undef; | |||
} | |||
# called when definition is undefined | |||
# (config reload, shutdown or delete of definition) | |||
sub MY_MODULE_Undef($$) | |||
{ | |||
my ($hash, $name) = @_; | |||
# close the connection | |||
DevIo_CloseDev($hash); | |||
return undef; | |||
} | |||
# called repeatedly if device disappeared | |||
sub MY_MODULE_Ready($) | |||
{ | |||
my ($hash) = @_; | |||
# try to reopen the connection in case the connection is lost | |||
return DevIo_OpenDev($hash, 1, "MY_MODULE_Init"); | |||
} | |||
# called when data was received | |||
sub MY_MODULE_Read($) | |||
{ | |||
my ($hash) = @_; | |||
my $name = $hash->{NAME}; | |||
# read the available data | |||
my $buf = DevIo_SimpleRead($hash); | |||
# stop processing if no data is available (device disconnected) | |||
return if(!defined($buf)); | |||
Log3 $name, 5, "MY_MODULE ($name) - received: $buf"; | |||
# | |||
# do something with $buf, e.g. generate readings, send answers via DevIo_SimpleWrite(), ... | |||
# | |||
} | |||
# called if set command is executed | |||
sub MY_MODULE_Set($$@) | |||
{ | |||
my ($hash, $name, $cmd) = @_; | |||
my $usage = "unknown argument $cmd, choose one of statusRequest:noArg on:noArg off:noArg"; | |||
if($cmd eq "statusRequest") | |||
{ | |||
DevIo_SimpleWrite($hash, "get_status\r\n", 2); | |||
} | |||
elsif($cmd eq "on") | |||
{ | |||
DevIo_SimpleWrite($hash, "on\r\n", 2); | |||
} | |||
elsif($cmd eq "off") | |||
{ | |||
DevIo_SimpleWrite($hash, "off\r\n", 2); | |||
} | |||
else | |||
{ | |||
return $usage; | |||
} | |||
} | |||
# will be executed upon successful connection establishment (see DevIo_OpenDev()) | |||
sub MY_MODULE_Init($) | |||
{ | |||
my ($hash) = @_; | |||
# send a status request to the device | |||
DevIo_SimpleWrite($hash, "get_status\r\n", 2); | |||
return undef; | |||
} | |||
1; | |||
</syntaxhighlight> | |||
== TCP/IP-Verbindung == | |||
<syntaxhighlight lang="perl"> | |||
package main; | |||
use strict; | |||
use warnings; | |||
use DevIo; # load DevIo.pm if not already loaded | |||
# called upon loading the module MY_MODULE | |||
sub MY_MODULE_Initialize($) | |||
{ | |||
my ($hash) = @_; | |||
$hash->{DefFn} = "MY_MODULE_Define"; | |||
$hash->{UndefFn} = "MY_MODULE_Undef"; | |||
$hash->{SetFn} = "MY_MODULE_Set"; | |||
$hash->{ReadFn} = "MY_MODULE_Read"; | |||
$hash->{ReadyFn} = "MY_MODULE_Ready"; | |||
} | |||
# called when a new definition is created (by hand or from configuration read on FHEM startup) | |||
sub MY_MODULE_Define($$) | |||
{ | |||
my ($hash, $def) = @_; | |||
my @a = split("[ \t]+", $def); | |||
my $name = $a[0]; | |||
# $a[1] is always equals the module name "MY_MODULE" | |||
# first argument is the hostname or IP address of the device (e.g. "192.168.1.120") | |||
my $dev = $a[2]; | |||
return "no device given" unless($dev); | |||
# close connection if maybe open (on definition modify) | |||
DevIo_CloseDev($hash) if(DevIo_IsOpen($hash)); | |||
# add a default port (1012), if not explicitly given by user | |||
$dev .= ':1012' if(not $dev =~ m/:\d+$/); | |||
# set the IP/Port for DevIo | |||
$hash->{DeviceName} = $dev; | |||
# open connection with custom init and error callback function (non-blocking connection establishment) | |||
DevIo_OpenDev($hash, 0, "MY_MODULE_Init", "MY_MODULE_Callback"); | |||
return undef; | |||
} | |||
# called when definition is undefined | |||
# (config reload, shutdown or delete of definition) | |||
sub MY_MODULE_Undef($$) | |||
{ | |||
my ($hash, $name) = @_; | |||
# close the connection | |||
DevIo_CloseDev($hash); | |||
return undef; | |||
} | |||
# called repeatedly if device disappeared | |||
sub MY_MODULE_Ready($) | |||
{ | |||
my ($hash) = @_; | |||
# try to reopen the connection in case the connection is lost | |||
return DevIo_OpenDev($hash, 1, "MY_MODULE_Init", "MY_MODULE_Callback"); | |||
} | |||
# called when data was received | |||
sub MY_MODULE_Read($) | |||
{ | |||
my ($hash) = @_; | |||
my $name = $hash->{NAME}; | |||
# read the available data | |||
my $buf = DevIo_SimpleRead($hash); | |||
# stop processing if no data is available (device disconnected) | |||
return if(!defined($buf)); | |||
Log3 $name, 5, "MY_MODULE ($name) - received: $buf"; | |||
# | |||
# do something with $buf, e.g. generate readings, send answers via DevIo_SimpleWrite(), ... | |||
# | |||
} | |||
# called if set command is executed | |||
sub MY_MODULE_Set($$@) | |||
{ | |||
my ($hash, $name, $cmd) = @_; | |||
my $usage = "unknown argument $cmd, choose one of statusRequest:noArg on:noArg off:noArg"; | |||
if($cmd eq "statusRequest") | |||
{ | |||
DevIo_SimpleWrite($hash, "get_status\r\n", 2); | |||
} | |||
elsif($cmd eq "on") | |||
{ | |||
DevIo_SimpleWrite($hash, "on\r\n", 2); | |||
} | |||
elsif($cmd eq "off") | |||
{ | |||
DevIo_SimpleWrite($hash, "off\r\n", 2); | |||
} | |||
else | |||
{ | |||
return $usage; | |||
} | |||
} | |||
# will be executed upon successful connection establishment (see DevIo_OpenDev()) | |||
sub MY_MODULE_Init($) | |||
{ | |||
my ($hash) = @_; | |||
# send a status request to the device | |||
DevIo_SimpleWrite($hash, "get_status\r\n", 2); | |||
return undef; | |||
} | |||
# will be executed if connection establishment fails (see DevIo_OpenDev()) | |||
sub MY_MODULE_Callback($) | |||
{ | |||
my ($hash, $error) = @_; | |||
my $name = $hash->{NAME}; | |||
# create a log emtry with the error message | |||
Log3 $name, 5, "MY_MODULE ($name) - error while connecting: $error"; | |||
return undef; | |||
} | |||
1; | |||
</syntaxhighlight> | |||
= Referenzen = | = Referenzen = | ||
<references /> | <references /> | ||
[[Kategorie:Development]] | [[Kategorie:Development]] |
Aktuelle Version vom 11. Januar 2024, 19:38 Uhr
DevIo | |
---|---|
Zweck / Funktion | |
Dienstfunktionen für die Kommunikation per serieller Schnittstelle (USB/RS232), TCP/IP-Verbindung oder UNIX-Socket | |
Allgemein | |
Typ | Utilities |
Details | |
Dokumentation | siehe Forum |
Support (Forum) | FHEM Development |
Modulname | DevIo.pm |
Ersteller | rudolfkoenig (Forum / Wiki) |
Wichtig: sofern vorhanden, gilt im Zweifel immer die (englische) Beschreibung in der commandref! |
Das Modul DevIo(.pm) ist für Modulentwickler gedacht, um Daten zwischen einem FHEM-Modul und bspw. einer seriellen Schnittstelle, einer TCP/IP-Verbindung oder einem UNIX-Socket auszutauschen. Es übernimmt dabei die gesamte Verbindungsverwaltung und Aufrechterhaltung innerhalb von FHEM und nimmt dem Modulentwickler daher die gesamte Verbindungsverwaltung (Aufbau, Initialisierung, Neu-Verbindung bei Abbruch, etc.) ab. Es berücksichtigt dabei Besonderheiten zwischen Unix-basierten Betriebssystemen und Windows.
Es dient dabei lediglich dem Zweck einen Kommunikationskanal für eine Definition in FHEM zu etablieren und Daten darüber auszutauschen. Die Interpretation der empfangenen Daten obliegt dem Modul, welches die Verbindung via DevIo geöffnet hat. Die ausgetauschten Daten werden durch DevIo nicht verändert.
Allgemeine Funktionsweise
DevIo hat das Ziel eine besonders einfache Möglichkeit für Modulentwickler zu schaffen, um einen dauerhaften Kommunikationskanal mit einem Hardware- oder Netzwerk-Gerät bzw. Service zu etablieren. Um eine Verbindung aufzubauen muss zunächst die Gegenstelle bekannt sein. Dazu muss vor dem Verbindungsaufbau in dem Internal $hash->{DeviceName}
der jeweiligen Definition das Ziel hinterlegt werden. Dies kann bspw. eine serielle Schnittstelle sein (z.B. "/dev/ttyUSB0" oder "COM1" unter Windows) oder eine TCP/IP-Gegenstelle (z.B. "192.168.1.100:1012") sein. Eine detaillierte Aufstellung der möglichen Verbindungsarten und deren Angabe in $hash->{DeviceName}
gibt es im folgenden Kapitel Unterstützte Verbindungsarten.
Die Funktion DevIo_OpenDev() baut dabei die entsprechende Verbindung für eine einzelne Definition in Form eines Filedeskriptors auf und registriert diesen in dem globalen Hash %selectlist
[1]. Sobald die Verbindung erfolgreich aufgebaut wurde, kann eine, durch den Modulautor mitgelieferte, Initialisierungs-Funktion ausgeführt werden, um die Kommunikation zu initialiseren (bspw. das Senden einer Authentifizierungs-/Loginsequenz oder aktivieren der Hardware, etc.).
FHEM (respektive fhem.pl) prüft nun regelmäßig, ob Daten zum Lesen bereitstehen (also Daten empfangen wurden). Ist dies der Fall, so wird die X_Read()-Funktion des zugehörigen Moduls für die hinterlegte Definition aufgerufen. Hier können die Daten durch den Aufruf von DevIo_SimpleRead() nun eingelesen und verarbeitet werden. Das Senden von Daten ist durch den Aufruf von DevIo_SimpleWrite() sehr einfach möglich.
Sollte die Verbindung zusammenbrechen (USB-Gerät abgezogen, Gerät per Netzwerk nicht mehr erreichbar, etc.), so erkennt dies DevIo und registriert die Definition in %readyfnlist
[1]. FHEM führt nun regelmäßig die X_Ready()-Funktion des zugehörigen Moduls
aus um zu prüfen, ob die Verbindung wieder aufgebaut werden kann. Hier wird nun durch die Ausführung von DevIo_OpenDev() versucht die Verbindung wieder herzustellen.
Sobald die Verbindung nicht mehr benötigt wird, oder FHEM bspw. beendet wird, kann die Verbindung via DevIo_CloseDev() sauber geschlossen werden.
Unterstützte Verbindungsarten
Die folgenden Verbindungsarten können via DevIo realisiert werden.
Verbindungsart | Beispielangabe in$hash->{DeviceName} |
Beschreibung |
---|---|---|
Serielle Schnittstelle |
|
Durch Angabe eines Geräts in Form eines Gerätepfad (UNIX-basierte Betriebssysteme) oder der Schnittstellenbezeichnung aus Windows kann eine serielle Verbindung geöffnet werden. Der Gerätename kann zusätzliche Angaben zu Baudrate, Datenbits, Parität und Stoppbits enthalten um die Verbindung entsprechend zu konfiguieren. Diese Angaben sind durch ein "@" getrennt an die Gerätebezeichnung angehangen: Schematische Syntax:
Wenn man unter Unix-basierten Betriebssystemen die Schnittstelle nicht explizit konfiguriert öffnen möchte, sondern das Gerät direkt öffnen möchte (und damit die OS-Einstellungen verwendet), kann man durch Angabe von Bsp: Unter Windows verwendet man als Gerätename die entsprechende Schnittstellenbezeichung wie bspw. |
TCP/IP-Verbindung |
|
Durch Angabe eines Hostnamen oder IP-Adresse und einem Port, kann eine TCP-Verbindung aufgebaut werden. Dazu muss Hostname/IP-Adresse und Port in folgendem Schema angegeben werden: Schematische Syntax:
Die Verbindung kann optional verschlüsselt via SSL/TLS aufgebaut werden. Dazu muss das Internal |
Websocket |
|
Durch Angabe von ws: oder wss: signalisiert man, dass DevIo eine Websocket öffnen soll. Details sind auf der Seite Websocket zu finden.
Syntax:
|
UNIX-Socket |
|
Durch Angabe eines Pfads zu einem UNIX-Domain-Socket kann eine Kommunikation mit einem anderen Prozess aufgebaut werden (Inter-Prozess-Kommunikation). Man kann den Socket dabei paketorientiert ("SEQPACKET") oder als Stream ("STREAM") öffnen. Schematische Syntax:
|
FHEM IO-Modul |
|
Beschreibung siehe Thema |
Wichtige Internals zur Konfiguration
Da DevIo ausschließlich definitionsbezogen arbeitet, erfolgt eine Konfiguration von DevIo über Internals, die im übergebenen $hash
gesetzt werden müssen (oder können). Hiermit lässt sich das Verhalten von DevIo entsprechend beeinflussen.
Hier eine Auflistung von allen Internals, die DevIo beeinflussen:
Internal | Beschreibung |
---|---|
$hash->{DeviceName}
mandatory |
Die Gegenstelle zu der eine Verbindung aufgebaut werden soll. Die möglichen Werte und deren Syntax ist im Kapitel Unterstützte Verbindungsarten genauer beschrieben. |
$hash->{nextOpenDelay}
optional |
Die Zeit in Sekunden, welche im Falle eines Verbindungsabbruchs gewartet werden soll, bevor ein erneuter Verbindungsversuch stattfindet.
Standardwert: 60 Sekunden |
$hash->{TIMEOUT}
optional |
Die maximale Zeit in Sekunden für den Aufbau einer TCP/IP-Verbindung. Sollte diese Zeit überschritten werden, bricht der Verbindungsaufbau mit einer Fehlermeldung ab.
WICHTIG: Sollte beim Aufruf von DevIo_OpenDev() keine Callback-Funktion parametrisiert sein und die Gegenstelle antwortet beim Verbindungsaufbau nicht, so wird FHEM für die Dauer von Standardwert: 3 Sekunden |
$hash->{SSL}
optional |
Flag (0 oder 1), ob eine TCP/IP-Verbindung verschlüsselt (via SSL/TLS) aufgebaut werden soll. Wenn dieses Flag auf 1 gesetzt ist, wird nach erfolgtem Verbindungsaufbau eine SSL-Session initiiert.
Standardwert: 0 (keine Verschlüsselung) |
$hash->{devioLoglevel}
optional |
Das Loglevel in dem disconnected /reappeared Meldungen geloggt werden sollen. Standardmäßig werden solche Verbindungsabbrüche (disconnected /reappeared ) im Loglevel 1 geloggt. Die erfolgreiche Erstverbindung wird standardmäßig im Loglevel 3 geloggt. Durch das Setzen von $hash->{devioLoglevel} werden diese Meldungen allesamt in dem gesetzten Loglevel ausgegeben. Details dazu siehe Thema.
Standardwert: [leer] |
$hash->{devioNoSTATE}
optional |
Flag (0 oder 1) - Sofern aktiviert, verändert DevIo das Internal STATE nicht direkt, sondern setzt nur das Reading "state" unter Berücksichtigung eines gesetzten stateFormat Attribut der jeweiligen Definition. Details dazu siehe Thema
Standardwert: 0 |
Die Funktionen
DevIo_OpenDev()
$error = DevIo_OpenDev($hash, $reopen, $initfn); $error = DevIo_OpenDev($hash, $reopen, $initfn, $callback);
Die Funktion DevIo_OpenDev() öffnet eine Verbindung zu dem Endpunkt der in $hash->{DeviceName}
hinterlegt ist. Sobald die Verbindung erfolgreich hergestellt wurde, wird die Funktion $initfn
ausgeführt, sofern gesetzt, um die Verbindung zu initialisieren. Sofern eine TCP/IP-Verbindung hergestellt wird, kann eine optionale Callback-Funktion $callback
übergeben werden um den Verbindungsaufbau non-blocking durchzuführen.
Der Rückgabewert enthält im Fehlerfall eine entsprechende Fehlermeldung. Im Erfolgsfall wird undef
zurückgegeben. Sofern eine TCP/IP-Verbindung hergestellt wird und eine Callback-Funktion $callback
übergeben wurde, wird immer undef
zurückgegeben, da ein evtl. Fehler an diese Callback-Funktion nach dem erfolgten Verbindungsversuch mitgeteilt wird.
Parameter:
Parameter | Bedeutung |
---|---|
$hash
mandatory |
Die Hash-Referenz der Definition, für die eine Verbindung geöffnet werden soll |
$reopen
mandatory |
Flag (0 oder 1), ob es sich um einen erneuten Verbindungsversuch handelt (im Rahmen der X_Ready()-Funktion). Sollte es der erste Verbindungsversuch sein, so muss dieser Parameter den Wert 0 besitzen. |
$initfn
mandatory |
Der Name (als Zeichenkette) oder die Referenz auf eine Modulfunktion, welche optional nach dem erfolgreichen Aufbau/Wiederaufbau der Verbindung ausgeführt werden soll. Im Rahmen dieser Funktion kann weiterführende Kommunikation über die aufgebaute Verbindung erfolgen um zum Beispiel eine Loginsequenz oder eine Konfiguration der Gegenseite vorzunehmen, bevor die Verbindung allgemein benutzt werden kann.
Die Funktion welche in $ret = MYMODULE_InitFn($hash)
Wenn der Rückgabewert Beispiel:
|
$callback
optional |
Der Name (als Zeichenkette) oder die Referenz auf eine Modulfunktion, welche aufgerufen werden soll um evtl. Fehlermeldungen beim Verbindungsaufbau einer TCP/IP-Verbindung an das Modul zurückzuliefern. Wenn eine Callback-Funktion gesetzt ist, erfolgt der Verbindungsaufbau non-blocking. Andernfalls wartet DevIo_OpenDev() bis die Verbindung steht bzw. ein Fehler auftritt (z.B. Timeout).
Die Funktion welche in MYMODULE_ConnectCallbackFn($hash, $error)
Das Argument Beispiel:
|
Rückgabewert:
Rückgabe | Bedeutung |
---|---|
$error |
Eine Fehlermeldung als Zeichenkette, sollte der Verbindungsaufbau fehlschlagen. Im Erfolgsfall wird undef zurückgegeben.
Wenn es sich um eine TCP/IP-Verbindung handelt und eine Callback-Funktion als Parameter |
DevIo_IsOpen()
$status = DevIo_IsOpen($hash);
Die Funktion DevIo_IsOpen() prüft, ob eine Verbindung für $hash
geöffnet ist. Falls ja, wird das zugehörige IO-Objekt zurückgegeben, andernfalls undef
.
Diese Funktion kann dabei direkt in typischen if-Konstrukten verwendet werden um zu prüfen, ob eine Verbindung für die jeweilige Definition geöffnet oder geschlossen ist.
Parameter:
Parameter | Bedeutung |
---|---|
$hash
mandatory |
Die Hash-Referenz der Definition, deren Verbindung geprüft werden soll. |
Rückgabewerte:
Rückgabewert | Bedeutung |
---|---|
$status |
Der Status der Verbindung. Wenn eine Verbindung besteht, wird das zugehörige IO-Objekt zurückgegeben. Falls keine Verbindung besteht, wird undef zurückgegeben.
|
DevIo_SimpleRead()
$buf = DevIo_SimpleRead($hash);
Die Funktion DevIo_SimpleRead() liest anstehende Daten für die Verbindung von $hash
ein und gibt diese zurück.
Sollte beim Versuch Daten zu lesen eine geschlossene Verbindung erkannt werden, so wird undef
zurückgegeben und die Verbindung geschlossen. Es erfolgt zu einem späteren Zeitpunkt (siehe Internal $hash->{nextOpenDelay}
aus Kapitel Wichtige Internals zur Konfiguration) ein neuer Verbindungsversuch.
Parameter:
Parameter | Bedeutung |
---|---|
$hash
mandatory |
Die Hash-Referenz der Definition, für deren Verbindung aktuell anstehende Daten gelesen werden sollen. |
Rückgabewert:
Rückgabe | Bedeutung |
---|---|
$buf |
Die zu lesenden Daten als Zeichenkette. Im Falle eines Verbindungsabruchs wird undef zurückgegeben.
|
DevIo_SimpleReadWithTimeout()
$timeout
Sekunden blockiert, sollten keine Daten bis dahin zum Lesen bereitstehen. -> DevIo_SimpleReadWithTimeout() NICHT NUTZEN!$buf = DevIo_SimpleReadWithTimeout($hash, $timeout);
Die Funktion DevIo_SimpleReadWithTimeout() wartet maximal $timeout
Sekunden bis die Verbindung von $hash
Daten zum einlesen bereitstellt und gibt diese zurück. Sollte nach dem Warten von $timeout
noch immer keine Daten zum Lesen bereitstehen, so wird ein Leerstring zurückgegeben.
Parameter:
Parameter | Bedeutung |
---|---|
$hash
mandatory |
Die Hash-Referenz der Definition, für deren Verbindung aktuell anstehende Daten gelesen werden sollen. |
$timeout
mandatory |
Die maximale Wartezeit in Sekunden. |
Rückgabewert:
Rückgabe | Bedeutung |
---|---|
$buf |
Die zu lesenden Daten als Zeichenkette. |
DevIo_TimeoutRead()
$timeout
Sekunden blockiert. -> DevIo_TimeoutRead() NICHT NUTZEN!$buf = DevIo_TimeoutRead($hash, $timeout);
Die Funktion DevIo_SimpleReadWithTimeout() wartet $timeout
Sekunden und liest sämtliche Daten für die Verbindung von $hash
ein, die in dieser Zeit eintreffen. Sollten keinerlei Daten während der Wartezeit eintreffen, so wird ein Leerstring zurückgegeben.
Parameter:
Parameter | Bedeutung |
---|---|
$hash
mandatory |
Die Hash-Referenz der Definition, für deren Verbindung Daten eingelesen werden sollen. |
$timeout
mandatory |
Die Wartezeit in Sekunden. |
Rückgabewert:
Rückgabe | Bedeutung |
---|---|
$buf |
Die eingelesenen Daten als Zeichenkette. |
DevIo_SimpleWrite()
DevIo_SimpleWrite($hash, $msg, $type); DevIo_SimpleWrite($hash, $msg, $type, $addnl);
Die Funktion DevIo_SimpleWrite() sendet den Inhalt von $msg
über die Verbindung von $hash
. Mit den beiden Argumenten $type
und $addnl
kann die Formatierung von $msg
beeinflusst werden bevor die Daten tatsächlich gesendet werden.
Parameter:
Parameter | Bedeutung |
---|---|
$hash
mandatory |
Die Hash-Referenz der Definition, über deren Verbindung die Daten gesendet werden sollen. |
$msg
mandatory |
Die zu schreibenden Daten als Zeichenkette. Abhängig von $type kann $msg Byte-Characters, HEX-Darstellungen oder normale ASCII-Zeichen enthalten.
|
$type
mandatory |
Die Art des Inhalts von $msg . Abhängig von dem Inhalt von $type wird $msg entsprechend geändert oder nicht. Desweiteren werden die Daten für Logausgaben evtl. leserlich gemacht.
Folgende Werte sind möglich:
|
$addnl
optional |
Ein Flag (0/1) welches, sofern aktiviert, einen Zeilenumbruch (\n ) an $msg anfügt, bevor es gesendet wird.
Standardwert: 0 |
DevIo_Expect()
- Bei der Benutzung von DevIo_Expect() wird FHEM für die Dauer von bis zu
$timeout
Sekunden blockiert. - Sollte im ersten Versuch keine Antwort auf die zuvor gesendeten Daten innerhalb von
$timeout
Sekunden eintreffen, wird die Verbindung geschlossen und neu aufgebaut. Es erfolgt dabei KEINE INITIALISIERUNG durch$initfn
aus DevIo_OpenDev().
$buf = DevIo_Expect($hash, $msg, $timeout);
Die Funktion DevIo_Expect() sendet den Inhalt von $msg
und wartet bis zu $timeout
Sekunden auf eine Antwort. Sollte in dieser Zeit keine Antwort eintreffen, so wird die Verbindung einmalig geschlossen, erneut geöffnet (ohne Aufruf einer Initialisierungs-Funktion) und der Vorgang wiederholt. Die empfangene Antwort wird anschließend als Funktionsergebnis zurückgegeben.
Wenn beim ersten Versuch keine Antwort innerhalb von $timeout
Sekunden eintrifft, wird das Event FAILED
generiert (siehe Generierte Events). Anschließend wird die Verbindung geschlossen, neu geöffnet (ohne Initialisierung) und $msg
erneut gesendet. Sollte nun eine Antwort eintreffen, so wird das Event CONNECTED
generiert und die empfangene Antwort zurückgegeben. Sollte dennoch keine Antwort eintreffen, so wird das Event DISCONNECTED
getriggert.
Parameter:
Parameter | Bedeutung |
---|---|
$hash
mandatory |
Die Hash-Referenz der Definition, für deren Verbindung Daten gesendet und anschließen gelesen werden sollen. |
$msg
mandatory |
Die zu sendenden Daten auf die eine Antwort erwartet wird. Die Daten werden ohne Konvertierung direkt gesendet (entspricht $type gleich 0 bei DevIo_SimpleWrite).
|
$timeout
mandatory |
Die maximale Wartezeit in Sekunden bis zum Eintreffen einer Antwort. |
Rückgabewert:
Rückgabe | Bedeutung |
---|---|
$buf |
Die empfangene Antwort. Sollte es zu einem Fehler gekommen sein (Senden von $msg fehlgeschlagen, keine Antwort innerhalb von $timeout Sekunden erhalten, etc.), so wird undef zurückgegeben.
|
DevIo_CloseDev()
DevIo_CloseDev($hash); DevIo_CloseDev($hash, $isFork);
Die Funktion DevIo_CloseDev() schließt eine evtl. geöffnete Verbindung für die Definition $hash
.
Optional kann man mit dem Flag $isFork
angeben, dass man sich aktuell in einem Fork vom Hauptprozess befindet. Dadurch werden beim Schließen einer seriellen Verbindung die Kommunikationsparameter nicht zurückgesetzt. Dies verhindert einen Ausfall der nachwievor bestehenden Verbindung im Elternprozess.
Parameter:
Parameter | Bedeutung |
---|---|
$hash
mandatory |
Die Hash-Referenz der Definition, deren Verbindung geschlossen werden soll. |
$isFork
optional |
Ein Flag (0 oder 1), welches angibt, dass DevIo_CloseDev() innerhalb eines geforkten Kindprozess ausgeführt wird. Dadurch werden die Kommunikationsparameter im Falle einer seriellen Verbindung (Baudrate, Datenbits, Parität, etc.) nicht zurückgesetzt. Dieses Flag wird primär von dem Modul Blocking.pm verwendet um Verbindungen in einem geforkten Kindprozess zu schließen, ohne die Verbindung im Hauptprozess zu beeinträchtigen.
Standardwert: 0 (Verbindung wird im Hauptprozess geschlossen) |
Generierte Events
Die Funktionen von DevIo generieren verschiedene Events für die entsprechende Definition (parametrisiert durch $hash
). Diese können bspw. in der Notify-Funktion des entsprechenden Moduls verarbeitet werden.
Hier eine Übersicht der generierten Events:
Event | Bedeutung |
---|---|
CONNECTED |
Die Verbindung wurde nach einem Verbindungsabbruch erfolgreich wieder aufgebaut. Dieses Event wird nur nach einem erfolgreichen Reconnect von einer zuvor verlorenen Verbindung generiert. Es wird nicht nach der erfolgreichen Initialverbindung generiert.
Hinweis: |
DISCONNECTED |
Die zuvor erfolgreich aufgebaute Verbindung ist zusammengebrochen. Es wird ein neuer Verbindungsversuch nach $hash->{nextOpenDelay} Sekunden erfolgen (siehe Wichtige Internals zur Konfiguration).
Hinweis: |
FAILED |
Dieses Event wird nur bei der Nutzung der Funktion DevIo_Expect() generiert, sofern es keine Antwort auf die zuvor gesendeten Daten gibt. Dies bedeutet konkret, dass Gerät hat nicht auf die eigene Anfrage geantwortet und die Verbindung wird daher neu aufgebaut. Details dazu, siehe DevIo_Expect(). |
Hinweis bei der Datenverarbeitung (Buffering)
DevIo beschränkt sich ausschließlich auf die Verbindungsverwaltung. Sobald die Verbindung etabliert wurde, übernimmt FHEM (fhem.pl) die Verarbeitung von eingehenden Daten und informiert das entsprechende Modul durch Aufruf der Read-Funktion. Dabei hat weder DevIo, noch FHEM selber Kenntnis von der Struktur der Daten. Sowohl DevIo, als auch FHEM selber können nicht erkennen, ob die empfangenen Daten weder vollständig, noch valide in ihrem Aufbau sind. Dies alles obliegt dem Modul, welches die Verbindung initiiert hat. Man sollte daher immer davon ausgehen, dass beim Lesen von Daten (in X_Read()) diese unvollständig sein können, oder sogar mehrere Datagramme enthalten sein können.
Dazu stellt DevIo dem Modulentwickler das Internal $hash->{PARTIAL}
in der jeweiligen Definition zur Verfügung um dort Daten zwischenzuspeichern, bis ein vollständiges Datagramm daraus verarbeitet werden kann. Alle eingelesenen Daten werden dazu in der Read-Funktion immer an $hash->{PARTIAL}
hinten angehangen. Sobald ein vollständiges Datagramm in $hash->{PARTIAL}
erkannt wurde, wird dieses herausgenommen und verarbeitet, solange bis kein vollständiges Datagramm in $hash->{PARTIAL}
erkennbar ist. Hierbei ist jedoch entscheidend, woran man ein abgeschlossenes Datagramm erkennen kann. Dies kann je nach Protokoll sehr unterschiedlich sein (bspw. Zeilenumbruch \r\n
oder ein Semikolon ;
, etc.).
DevIo initialisiert $hash->{PARTIAL}
nach dem erfolgreichen Verbindungsaufbau mit einem Leerstring (""
). Sobald eine Verbindung mit DevIo_CloseDev() geschlossen wird, wird $hash->{PARTIAL}
gelöscht.
Hier ein Beispiel, wie man ein solches Buffering verwendet für ein Protokoll, welches Datagramme durch einen Zeilenumbruch \n
(Newline) abtrennt:
sub MY_MODULE_Read($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
my $data = DevIo_SimpleRead($hash);
return if(!defined($data)); # connection lost
my $buffer = $hash->{PARTIAL};
Log3 $name, 5, "MY_MODULE ($name) - received $data (buffer contains: $buffer)";
# concat received data to $buffer
$buffer .= $data;
# as long as the buffer contains newlines (complete datagramm)
while($buffer =~ m/\n/)
{
my $msg;
# extract the complete message ($msg), everything else is assigned to $buffer
($msg, $buffer) = split("\n", $buffer, 2);
# remove trailing whitespaces
chomp $msg;
# parse the extracted message
MY_MODULE_ParseMessage($hash, $msg);
}
# update $hash->{PARTIAL} with the current buffer content
$hash->{PARTIAL} = $buffer;
}
Beispielimplementierung in einem Modul
serielle Verbindung
package main;
use strict;
use warnings;
use DevIo; # load DevIo.pm if not already loaded
# called upon loading the module MY_MODULE
sub MY_MODULE_Initialize($)
{
my ($hash) = @_;
$hash->{DefFn} = "MY_MODULE_Define";
$hash->{UndefFn} = "MY_MODULE_Undef";
$hash->{SetFn} = "MY_MODULE_Set";
$hash->{ReadFn} = "MY_MODULE_Read";
$hash->{ReadyFn} = "MY_MODULE_Ready";
}
# called when a new definition is created (by hand or from configuration read on FHEM startup)
sub MY_MODULE_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t]+", $def);
my $name = $a[0];
# $a[1] is always equals the module name "MY_MODULE"
# first argument is a serial device (e.g. "/dev/ttyUSB0@9600")
my $dev = $a[2];
return "no device given" unless($dev);
# close connection if maybe open (on definition modify)
DevIo_CloseDev($hash) if(DevIo_IsOpen($hash));
# add a default baud rate (9600), if not given by user
$dev .= '@9600' if(not $dev =~ m/\@\d+$/);
# set the device to open
$hash->{DeviceName} = $dev;
# open connection with custom init function
my $ret = DevIo_OpenDev($hash, 0, "MY_MODULE_Init");
return undef;
}
# called when definition is undefined
# (config reload, shutdown or delete of definition)
sub MY_MODULE_Undef($$)
{
my ($hash, $name) = @_;
# close the connection
DevIo_CloseDev($hash);
return undef;
}
# called repeatedly if device disappeared
sub MY_MODULE_Ready($)
{
my ($hash) = @_;
# try to reopen the connection in case the connection is lost
return DevIo_OpenDev($hash, 1, "MY_MODULE_Init");
}
# called when data was received
sub MY_MODULE_Read($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
# read the available data
my $buf = DevIo_SimpleRead($hash);
# stop processing if no data is available (device disconnected)
return if(!defined($buf));
Log3 $name, 5, "MY_MODULE ($name) - received: $buf";
#
# do something with $buf, e.g. generate readings, send answers via DevIo_SimpleWrite(), ...
#
}
# called if set command is executed
sub MY_MODULE_Set($$@)
{
my ($hash, $name, $cmd) = @_;
my $usage = "unknown argument $cmd, choose one of statusRequest:noArg on:noArg off:noArg";
if($cmd eq "statusRequest")
{
DevIo_SimpleWrite($hash, "get_status\r\n", 2);
}
elsif($cmd eq "on")
{
DevIo_SimpleWrite($hash, "on\r\n", 2);
}
elsif($cmd eq "off")
{
DevIo_SimpleWrite($hash, "off\r\n", 2);
}
else
{
return $usage;
}
}
# will be executed upon successful connection establishment (see DevIo_OpenDev())
sub MY_MODULE_Init($)
{
my ($hash) = @_;
# send a status request to the device
DevIo_SimpleWrite($hash, "get_status\r\n", 2);
return undef;
}
1;
TCP/IP-Verbindung
package main;
use strict;
use warnings;
use DevIo; # load DevIo.pm if not already loaded
# called upon loading the module MY_MODULE
sub MY_MODULE_Initialize($)
{
my ($hash) = @_;
$hash->{DefFn} = "MY_MODULE_Define";
$hash->{UndefFn} = "MY_MODULE_Undef";
$hash->{SetFn} = "MY_MODULE_Set";
$hash->{ReadFn} = "MY_MODULE_Read";
$hash->{ReadyFn} = "MY_MODULE_Ready";
}
# called when a new definition is created (by hand or from configuration read on FHEM startup)
sub MY_MODULE_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t]+", $def);
my $name = $a[0];
# $a[1] is always equals the module name "MY_MODULE"
# first argument is the hostname or IP address of the device (e.g. "192.168.1.120")
my $dev = $a[2];
return "no device given" unless($dev);
# close connection if maybe open (on definition modify)
DevIo_CloseDev($hash) if(DevIo_IsOpen($hash));
# add a default port (1012), if not explicitly given by user
$dev .= ':1012' if(not $dev =~ m/:\d+$/);
# set the IP/Port for DevIo
$hash->{DeviceName} = $dev;
# open connection with custom init and error callback function (non-blocking connection establishment)
DevIo_OpenDev($hash, 0, "MY_MODULE_Init", "MY_MODULE_Callback");
return undef;
}
# called when definition is undefined
# (config reload, shutdown or delete of definition)
sub MY_MODULE_Undef($$)
{
my ($hash, $name) = @_;
# close the connection
DevIo_CloseDev($hash);
return undef;
}
# called repeatedly if device disappeared
sub MY_MODULE_Ready($)
{
my ($hash) = @_;
# try to reopen the connection in case the connection is lost
return DevIo_OpenDev($hash, 1, "MY_MODULE_Init", "MY_MODULE_Callback");
}
# called when data was received
sub MY_MODULE_Read($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
# read the available data
my $buf = DevIo_SimpleRead($hash);
# stop processing if no data is available (device disconnected)
return if(!defined($buf));
Log3 $name, 5, "MY_MODULE ($name) - received: $buf";
#
# do something with $buf, e.g. generate readings, send answers via DevIo_SimpleWrite(), ...
#
}
# called if set command is executed
sub MY_MODULE_Set($$@)
{
my ($hash, $name, $cmd) = @_;
my $usage = "unknown argument $cmd, choose one of statusRequest:noArg on:noArg off:noArg";
if($cmd eq "statusRequest")
{
DevIo_SimpleWrite($hash, "get_status\r\n", 2);
}
elsif($cmd eq "on")
{
DevIo_SimpleWrite($hash, "on\r\n", 2);
}
elsif($cmd eq "off")
{
DevIo_SimpleWrite($hash, "off\r\n", 2);
}
else
{
return $usage;
}
}
# will be executed upon successful connection establishment (see DevIo_OpenDev())
sub MY_MODULE_Init($)
{
my ($hash) = @_;
# send a status request to the device
DevIo_SimpleWrite($hash, "get_status\r\n", 2);
return undef;
}
# will be executed if connection establishment fails (see DevIo_OpenDev())
sub MY_MODULE_Callback($)
{
my ($hash, $error) = @_;
my $name = $hash->{NAME};
# create a log emtry with the error message
Log3 $name, 5, "MY_MODULE ($name) - error while connecting: $error";
return undef;
}
1;
Referenzen
- ↑ 1,0 1,1 Development Module Introduction - Wichtige globale Variablen aus fhem.pl