Hallo Rudi,
DevIo wird bereits von mehr als 30 Modulen für den Zugriff auf IO-Devices (über DevIo_OpenDev) genutzt. Bei einem solchen IO-Device handelt es sich meist um eine serielle Schnittstelle bzw. USB-Schnittstelle oder eine Netzwerkverbindung. Während Netzwerkverbindung per se flexibel sind, sind die anderen Schnittstellen bisher an die lokale Hardware gebunden. Will man nun weg von der lokalen Hardware, könnte man entsprechende Sonderlösungen in das jeweilige Modul einbauen.
Wünschenswert ist jedoch ein Schnittstellenabstraktion außerhalb der Anwendungsmodule, und die existiert mit DevIo ja schon. Es fehlt "nur" die Möglichkeit, statt eines der bisher unterstützten betriebssystemnahen IO-Device-Typen ein FHEM-Modul als IO-Device anzugeben. Durch eine Erweiterung der Syntax von DevIo für den Devicenamen (z.B. mit Präfix "FHEM:...", analog "UNIX:...") könnte man diese neue Form eines IO-Devices auswählen. Hinter dem Präfix folgen dann weitere Parameter, u.a. der Modulname, der als IO-Device angesprochen werden soll. Ein solches IO-Device-Modul müsste die für ein IO-Device erforderlichen Funktionen exportieren, z.B. als IOOpenFn, IOWriteFn und IOCloseFn, während es selbst die ReadFn des Anwendungsmoduls bedient. Wenn das IO-Device-Modul neue Daten aufliefert, werden sie im Hash des Anwendungsmoduls geparkt (z.B. als {IODevRxBuffer}), bis das Anwendungsmodul eine Variante von DevIo_SimpleRead aufruft. Damit bleiben die Programmierschnittstellen von DevIo und den Anwendungsmodulen unverändert. Eine konsequente Nutzung dieses Ansatzes bietet die Möglichkeit, dass Telegrammdekoder und die nachfolgenden Verarbeitungsketten nur einmal programmiert werden müssten, während der Weg zur Datenquelle bei Bedarf geändert werden kann. Dies könnte vor allem bei neuen Modulen berücksichtigt werden.
Damit das Ganze nicht so abstrakt klingt, hier ein Anwendungsbeispiel: Das Anwendungsmodul HEATRONIC nutzt eine serielle Schnittstelle als Datenquelle über DevIo. Ab Version 2.5 hat Firmata Support für serielle Schnittstellen und bietet damit die Möglichkeit eines COM-Servers. Um nun die HEATRONIC über die serielle Schnittstelle am Ardunino nutzen zu können, ist es nicht sinnvoll, dazu das Modul HEATRONIC auf Firmata anzupassen, sondern es sollte reichen, dort für DEF z.B. "FHEM:DEVIO:<FRM device name>:<serial port>@<baud rate>" anzugeben. Auch jedes andere Anwendungsmodul, das DevIo_OpenDev benutzt, könnte sich so Zugang zu einer seriellen Schnittstelle über Firmata verschaffen, ohne geändert werden zu müssen.
Den einzigen Verhaltensunterschied zwischen betriebssystemnahen IO-Devices und IO-Device-Modulen scheint nach meinen Experimenten beim Neustart von FHEM aufzutreten, da die betriebssystemnahen IO-Devices mit der Initialisierung des Anwendungsmodul z.T. sofort zur Verfügung stehen (z.B. bei seriellen Schnittstellen), während ein IO-Device-Modul u.U. etwas Zeit zur Initialisierung benötigt, z.B. um eine Netzwerkverbindung herzustellen.
Was hältst du von diesem Vorschlag?
Grüße,
Jens
Ich verstehe noch nicht den Unterschied zu den vorhandenen physikalischen/logischen Modulen (ala CUL/FS20).
Hallo Rudi,
vielleicht hilft uns dieses Beispiel weiter:
Jetzt:
USB-CUL -> lokale USB-Schnittstelle -> CUL-Modul (in DevIo, out Dispatch) -> FS20-Modul (in Dispatch)
mit "define myCUL CUL /dev/ttyACM0"
Neue Option:
USB-CUL -> USB-TTL-Wandler -> Arduino mit Firmata -> FRM-Modul (in DevIo, out DevIo-Neu) -> CUL-Modul (in DevIo, out Dispatch) -> FS20-Modul (in Dispatch)
mit "define myCUL CUL FHEM:DEVIO:myFirmata:1@9600"
Im Kern geht mein Vorschlag davon aus, dass man sich als IO-Device-Modul (hier das noch zu modifizierende FRM-Modul) mit dem modifizierten DevIo gegenüber anderen Modulen als Ersatz für ein physikalischen Device (wie hier z.B. /dev/ttyACM0) ausgeben kann. Es geht also darum, ggf. die Verbindung zwischen dem CUL-Modul und dem physikalischen CUL-Device ändern zu können, indem man über ein IO-Device-Modul den physikalischen Layer für den Datentransport ändert (hier Firmata statt lokales USB).
Funktionell bleibt dieses Beispiel erst einmal noch Theorie, da ich nicht weiß, wie zeitkritisch beim CUL die Kommunikation abläuft.
Alternativ könnte man statt der vorgeschlagenen DevIo-Erweiterung auch einen Socket über localhost aufbauen (hier müsste das FRM-Modul einen TCP Server implementieren), denn DevIo kann ja bereits einen TCP Client nutzten. Wahrscheinlich wäre der IO-Delay zwischen den Modulen dadurch geringfügig höher als beim Direktaufruf mit CallFn.
Grüße,
Jens
Trotz deines Beispiels verstehe ich immer noch nicht so recht den Einsatzzweck, aber was solls, FHEM ist auch zum spielen da (bitte nicht weitersagen).
Ich sehe noch ungeloeste Probleme bei deinem Vorschlag:
- wer & wann ruft das ReadFn des oberen Moduls auf? Bisher wird das durch das globale select erledigt.
- wie loest man das "CUL_ReadAnswer" Problem (da wird ganz haesslich blockierend vom FD gelesen).
- nicht alle Module verwenden DevIo_SimpleWrite, z.Bsp. 00_CUL.pm und 00_ZWCUL.pm
Ich wuerde diese Probleme durch einen altmodischen pipe() loesen.
Zitat... wer & wann ruft das ReadFn des oberen Moduls auf?
Das globale select würde im letzten Beispiel zunächst die ReadFn des FRM-Modul auslösen, wenn der Arduino Daten von seiner seriellen Schnittstelle schickt. Der Firmata-Protokollhandler würde das Firmata-Telegramm einlesen und das FRM-Modul würde die ReadFn des CUL-Moduls direkt aufrufen und die seriellen Daten aus dem Firmata-Telegramm weitergeben. Der Anfang ist also unverändert, nur die Verarbeitungskette ist wegen der Protokollumsetzung etwas länger.
ZitatIch wuerde diese Probleme durch einen altmodischen pipe() loesen.
War auch einer meiner ersten Überlegungen. Aber Pipe und Socket haben einen entscheidenden Nachteil: Die Schnittstellenparameter (z.B. serielle Portnummer, Baudrate, etc.) müssten bei jedem Verbindungsaufbau als erstes übertragen werden, damit die verlagerte physikalische Schnittstelle richtig konfiguriert werden kann. Das ist nicht unmöglich, nur aufwendiger. Damit es das CUL-Modul nicht merkt, müsste man hierfür im DevIo_OpenDev direkt nach dem Öffnen eine entsprechendes Setup-Telegramm in die Pipe bzw. den Socket schicken. Außerdem sollte man auch IO-Device-Module berücksichtigen, die mehrere IO-Devices anbieten können. Ein Teensy LC hat z.B. 4 serielle Schnittstellen, von denen 3 über Firmata genutzt werden können. Will man das ggf. ausnutzen, bräuchte man 3 Pipes bzw. Sockets zu einem FRM-Modul. Wenn man CallFn verwendet, übergibt man die Schnittstellenparameter einfach als Aufrufparameter oder im Hash.
Die beiden anderen Aspekte mit CUL_ReadAnswer bzw. ohne DevIo_SimpleWrite werde ich mir noch ansehen.
muss ja eine coole Party gewesen sein... 8)
Wer braucht sowas wirklich? Welche real existierenden, funktionseinschränkenden Probleme kann man durch die vorgeschlagene Änderung lösen?
Hallo betateilchen,
ja, hat Spaß gemacht, vor allem weil es am Ende funktioniert hat ;)
Wofür mans aus meiner Sicht z.B. gebrauchen kann, steht im 1. Post: Firmata als COM-Server für vorhandene FHEM-Module, ohne diese ändern zu müssen. Kommerzielle COM-Server sind nicht nur relativ teuer, sie haben meist auch kein TTL-Interface, was für uns Bastler hin und wieder die bessere Andockstelle ist.
Die Tendenz, insbesondere klassische serielle Schnittstellen loszuwerden, hat mit USB ja schon vor längerer Zeit begonnen - aber auch USB hängt an einem kurzem Kabel. Erst mit LAN oder einem der Funkstandards hat man die erforderliche Flexibilität. Solange nicht alles darauf umgestellt ist, braucht man Brückenlösungen und COM-Server sind für vorhandene serielle Schnittstellen die 1. Wahl.
Grüße,
Jens
Fuer diesen Zweck wuerde ich nicht mein eigenes Protokoll bauen, sondern auf TCP/IP setzen, z.Bsp auf einem RPi mit socat/ser2net. Wem das zu doof ist kann bestimmt tcp/ip auf dem Arduino zum laufen bringen.
Für solche Spezialbauten mit Arduino, etc. gibt es extra die Module ECMD und ECMDDevice. Die sind sehr flexibel und für solche Sachen gedacht, wo man z.B. am Arduino mehrere Sachen an den schaltbaren Aus- und Eingängen hat. Die kann man damit logisch voneinander trennen.
Ansonsten haue das Ding an einen Raspberry oder ähnliches und mach ser2net drauf, wie von Rudi vorgeschlagen. Funktioniert bei mir mit dem panStamp-Shield auf dem Raspberry super. FHEM greift dann direkt auf den Socket zu und wird die seriellen Kommandos los. Geht astrein.
Aus meiner Sicht ist dein Szenario zu speziell, als das ich so etwas in FHEM einbauen würde.
Grüße
Markus
Hallo Rudi, hallo Markus,
danke für eure Rückmeldungen.
Vielleicht noch mal kurz zu meiner Motivation: Momentan habe ich das alles am RPi und es funktioniert in Bezug auf die serielle Schnittstelle prima. Aber mein RPi steht im Keller und mein USB-CUL kann von da aus nicht alles erreichen. Da ich keinen CUL-Repeater verwenden möchte, muss der RPi mit CUL umziehen - aber die seriellen Kabel kann er dabei nicht mitnehmen. FHEM kann Firmata und Firmata hat IO, I2C und serielle Schnittstellen im Protokoll. Lediglich die Implementierung der Firmata-Telegramme für serielles IO fehlt noch im FRM-Modul. Daher war das für mich die naheliegende Lösung, vor allem weil ich gleichzeitig noch I2C und digitales IO machen möchte. Wenn ich dafür einen Arduino selbst programmiere, müsste ich Firmata noch mal erfinden und das macht keinen Sinn. Nehme ich 2 Arduinos, einen mit Firmata und einen für die serielle Schnittstelle, brauche ich einen Switch und das ist auch nicht elegant. Und einen ganzen RPi (z.B. mit FHEM2FHEM) für eine serielle Schnittstelle, einmal I2C und 3 digitale Ausgänge abzustellen, halte ich für oversized.
Ein eigenes Protokoll oder spezielle Firmware für den Arduino sind also nicht nötig, lediglich eine Datenbrücke über DevIo.
Ich glaube nicht, dass man z.B. das HEATRONIC-Modul per Konfiguration überreden kann, ECMD als Gateway zu akzeptieren, denn das HEATRONIC-Modul ist kein ECMDDevice mit IODev-Support, sondern verwendet DevIo zum Zugriff auf die serielle Schnittstelle. Genau an dieser Stelle setzt mein Vorschlag an. Er macht die Daten aus dem einen Modul in einem vorhandenen Modul als Device-Ersatz verfügbar.
Grüße,
Jens
ich halte Die Idee grundsätzlich mal schon für Sinnvoll - das ist architekturell ja ähnlich wie die I2C-geschichte, bei der der Hardwarezugriff ja auch über das IODev wegabstrahiert ist.
Allerdings halte ich nix davon in der Definition des Clients irgendwelche Details zur Konfiguration des IODevs mitzugeben:
Zitat von: jensb am 27 Dezember 2015, 21:41:28
"define myCUL CUL FHEM:DEVIO:myFirmata:1@9600"
Also in dem Beispiel sollte das '1@9600' entfallen, das gehört doch in die Konfiguration des IODevs selber.
Gruß,
Norbert
Hallo Norbert,
damit könnte ich auch gut mit leben. Dann brauchen wir aber in FRM für jeden seriellen Port eine entsprechenden Konfigurationsmöglichkeit.
Grüße,
Jens
ja genau, da machen wir ein FRM_SER modul in dem dann Pin und Baudrate konfiguriert werden. Das wird dann im Client z.B. per IODev-Attribut referenziert. Es macht ja keinen Sinn, wenn man die IODev-funktionalität auf virtuelle Devices ausdehnt und damit die Hardware wegabstrahiert, dann trotzdem etwas hardwarespezifisches im Define des Clients stehen zu haben.
Hallo Norbert,
ein FRM_SER-Modul für Pin-Zuordnung und Baudkonfiguration ist eine gute Idee.
Wenn man das aber im Client als IODev-Attribut verwenden will, muss man, anders als bei meinem Vorschlag, die bestehenden potentiellen Clients ändern. Mein Referenzbeispiel 98_HEATRONIC.pm kennt nur OS-Devices, auf die mit DevIo zugegriffen wird. Ähnlich ist das auch bei den anderen Modulen, die zwar DevIo nutzen, aber nicht das IODev-Konzept. Ein entsprechender Umbau der Client-Module auf parallelen Support für IODev kann bei einigen Modulen durchaus aufwendig sein.
Grüße,
Jens
Zitat von: jensb am 10 Januar 2016, 17:00:44Wenn man das aber im Client als IODev-Attribut verwenden will, muss man, anders als bei meinem Vorschlag, die bestehenden potentiellen Clients ändern.
Da hast Du natürlich recht, den namen des IODevs analog zum physikalischen Device im Define zu übergeben ist natürlich abwärtskompatibel. Wirklich funktionieren tut das aber nur, wenn das jeweilige client-device in Define nach einem Aufruf von DevIOOpenDev nichts relevantes mehr tut, da es sich ja nicht darauf verlassen kann, dass das virtuelle serielle Device überhaupt schon existiert oder tatsächlich verbunden ist. Ich nehme mal stark an, dass da die eine oder andere Nacharbeit nötig sein wird, bis alle Devices kompatibel sind (das kann ja 'ad hoc' geschehen, wenn es jemand braucht und es - noch - nicht geht).
Das ist einer der Punkte, die mir beim Testen aufgefallen ist. Module, die mit OS-Devices arbeiten, sind meist Gateways und werden daher meist relativ früh initialisiert. Es dauert aber einige Sekunden, bis ein Arduino sich nach einem FHEM-Neustart verbunden hat. In der Übergangszeit kann man das Device als offen an das Client-Modul melden, aber das Client-Modul könnte nicht erfolgreich senden. Puffert man das dann oder gibt mein beim Write einen IO-Fehler zurück? Da gibt es leider noch einige Abgründe. Trotzdem würde vielleicht das eine oder andere Client-Modul "spontan" funktionieren.
Wenn man statt dessen die IODev-Lösung anbietet, bleibt es im Ermessen der Modulentwickler, ob eine Unterstützung erfolgt - die dann aber auch so etwas berücksichtigen könnte.
Grüße,
Jens
ok, ich hol das Thema mal wieder hoch.
Jens hat mir seine Änderungen über Github geschickt. Ich pick jetzt mal nur die DevIo.pm raus:
DevIo.pm (https://raw.githubusercontent.com/ntruchsess/fhem-mirror/dev/fhem/FHEM/DevIo.pm)
und hier der Commit selbst (https://github.com/ntruchsess/fhem-mirror/commit/1c27df9c6544d9a495e1a12779562054a9d7d701)
@rudolfkoenig: vieleicht kannst Du da grade mal einen Blick drauf werfen, ob man das so übernehmen kann?
- Norbert
Habs eingecheckt.
Verstehe es zwar immer noch nicht, will aber die Laune zum spielen nicht bremsen.
Und ich gehe davon aus, dass es keine Nebenwirkungen hat.
Hallo,
Zitat
Ich sehe noch ungeloeste Probleme bei deinem Vorschlag:
- wer & wann ruft das ReadFn des oberen Moduls auf? Bisher wird das durch das globale select erledigt.
- wie loest man das "CUL_ReadAnswer" Problem (da wird ganz haesslich blockierend vom FD gelesen).
- nicht alle Module verwenden DevIo_SimpleWrite, z.Bsp. 00_CUL.pm und 00_ZWCUL.pm
Ich wuerde diese Probleme durch einen altmodischen pipe() loesen.
Verwendet ein Modul nur nicht sequenzielle Schreib- und Lesezugriffe, geht das vorgeschlagene Konzept auf (Beispiel serielle HEATRONIC hinter Ethernet-FRM).
Wenn ein Modul aber direktes IO verwendet oder blockierende write/read Sequenzen, funktioniert es nicht immer wie mit einem physikalischem Device. Ein Beispiel hierfür ist z.B. serielles FRM hinter Ethernet FRM, da neue Daten auf der über TCP abstrahierten seriellen Schnittstelle von extern erst verarbeitet werden, wenn FHEM den FD des TCP Socketes auf ready-for-read prüft, was aber nicht erfolgen kann, wenn ein Modul gerade blockend arbeitet. Eine Pipe würde daran leider auch nichts ändern, da auch hier erst der Read vom TCP Socket erfolgen muss, bevor man die Daten in die Pipe schreiben kann. Nur wenn der TCP Receive tatsächlich parallel erfolgen würde, könnte man das vermeiden. Hier fehlt mir noch eine Idee, wie man das ohne Multithreading/Forking erreichen kann.
ZitatUnd ich gehe davon aus, dass es keine Nebenwirkungen hat.
Da die Erweiterung um "IODev" in DevIO.pm immer am Ende der vorhandenen if-Ketten erfolgt, hat sie keine Auswirkungen auf die Performance oder die Logik besehender Installationen.
Grüße,
Jens