Arduino / Firmata (FRM)

Begonnen von ntruchsess, 24 Januar 2013, 12:07:28

Vorheriges Thema - Nächstes Thema

ntruchsess

Hallo Klaus,

das FRM_I2C-modul kann ja nichts außer Registerinhalte über I2C auslesen. Die eigentliche Kommunikation geht da über die Perl-firmata-library und der I2C-bus selber hängt dann am Arduino. Zwischen der Perl-firmata und dem FRM_I2C-modul sitzt noch das eigentliche FRM-modul das die Verbindung zur Perl-firmata verwaltet. Der einzige Anwendungsfall für I2C den ich selber nutzen konnte war ein über I2C angebundenes LCD-display (siehe Modul FRM_LCD).

Nützlich wäre es natürlich für unterschiedliche Arten von I2C-bus-anbindung ein einheitliches Interface in FHEM zu haben, das von Modulen wie z.b. mein Modul für I2C-basierte LCDs als IODev benutzt wird und den Hardwarenahen Zugriff einheitlich kapselt (Analog zu OWX das ja auch zwischen den 1-Wire-devicespezifischen Modulen und den verschiedenen Busmastern vermittelt). Wie das Interface konkret für I2C ausssehen könnte, darüber hbe ich mir bisher allerdings noch nicht so recht Gedanken gemacht - wenn Du da was sinnvolles erarbeitest, dann würde ich mein FRM_I2C-modul gerne daran anpassen. FRM-like wäre es Referenzen auf die Funktionen in der Initialize-methode über einen symbolischen Namen im device-hash abzulegen (so wie das z.B. mit ReadFn etc gemacht wird). Perl-like wäre es die Hardwarespezifischen Module in eigenen Packages unterzubringen und die Methoden tatsächlich gleich zu benennen (und den Hardwarespezifischen Treiber als Objekt zu instanziieren). Guck die mal an, wie das im asynchronen OWX gelöst ist. Siehe dazu:
00_OWX.pm
11_OWX_SER.pm
11_OWX_FRM.pm
11_OWX_CCC.pm
11_OWX_Executor.pm
Hier implementiert 00_OWX die Methoden, die die einzelnen Device-module (OWTHERM, OWSWITCH...) aufrufen und delegiert die Aufrufe an die in den Busmasterspezifischen Module weiter.

Gruß,

Norbert
while (!asleep()) {sheep++};

klausw

Hallo Norbert,

Die OWX ist derzeit zu komplex für meine Programmierkenntnisse. Ich habe z.B. nicht verstanden, wie 21_OWTHERM die Werte zurückgeliefert bekommt. Aber das nur am Rande. Vielleicht geht auch ein anderer Ansatz:
Derzeit habe ich 2 I2C Hardwarelösungen zum testen.

  • Raspbery Pi
  • ein über Ethernet angebundener PIC-Controller, auf dessen I2C über RAWTCP durchgegriffen werden kann
Und ein logisches Modul für den PCF8574 Portextender.

In denen ist die WriteFn implementiert, auf die ich über logische Module mit IOWrite() zugreife.
In umgekehrter Richtung kann ich mit  Dispatch() an das logische Modul die Anworten senden, welche dort in der  ParseFn verarbeitet werden.
Die Grundlagen dafür habe ich aus der 00_CUL.pm und der 10_FS20.pm übernommen.
Den Vorteil in dieser Lösung sehe ich, das zwar für jede I2C Hardware ein Modul geschrieben werden müsste, aber die logischen Module dafür nicht angefasst werden müssen. Ausserdem lässt sich leicht auch unterschiedliche Schnittstellengegebenheiten eingehen:
Beim Raspberry z.B. kann ich die I2Cread Kommandos direkt beantworten, während ich es bei der Lösung über Netzwerk über einen Sendepuffer und die ReadFn mache.
Hat diese Variante einen Nachteil? Du hast es ja doch ein bisschen anders gelöst.
Grüße
Klaus
RasPi B v2 mit FHEM 18B20 über 1Wire, LED PWM Treiber über I2C, Luchtdruck-, Feuchtesensor und ein paar Schalter/LED\'s zum testen
Module: RPI_GPIO, RPII2C, I2C_EEPROM, I2C_MCP23008, I2C_MCP23017, I2C_MCP342x, I2C_PCA9532, I2C_PCF8574, I2C_SHT21, I2C_BME280

ntruchsess

was sind denn die Aufrufparameter der WriteFn? I2C-addresse, Übertragungsrichtung, optionale Daten und Übertragungsgeschwindigkeit?
while (!asleep()) {sheep++};

klausw

An die WriteFn wird mit beispielsweise IOWrite übergeben:

IOWrite($hash, "i2cwrite", ( defined( $hash->{ID} )? $hash->{ID} : "00" ) . " " . $hash->{I2C_ADDR} . " " . sprintf("%.2X ",$msg));

IOWrite($hash, "<i2cwrite|i2cread>", <id> ) . " " . <I2C Adresse> [. " " . <Daten für I2C wenn i2csend>]);

Die ID hatte ich eingefügt, falls man z.B. einen Portextender in einzelene Devices aufteilen möchte...sonst würde die Zuordnung über Dispatch() in die andere Ringtung nicht laufen. Sie kann aber "00" bleiben wenn nur ein Device pro I2C Adresse genutzt wird.

Empfangene Daten kommen über die ParseFn:
sub I2C_PCF8574_Parse($$) {
my ($hash, $msg) = @_;
my($sid, $addr, @msg) = split(/ /,$msg);
...

da ist wieder ID, I2CAdresse, und die Daten

Übertragungsgeschwindigkeit würde ich, wenn nötig, im physikalischen Modul für die Schnittstelle fest einstellen. Habe ich derzeit nicht vorgesehen. Ist für das LCD ein Sendeintervall notwendig?

Als ich mit dem PIC-Controller angefangen habe musste ich mir keine Gedanken machen, ob ich Byte oder Word Werte lesen möchte. Das lief
automatisch. BeiM Raspberry ist das anders. Das ist für das LCD sicher nicht so interessant, da nur Byteweise gesendet wird.

Wie gesagt, alles ein erster Wurf. Für Vorschläge bin ich offen.
RasPi B v2 mit FHEM 18B20 über 1Wire, LED PWM Treiber über I2C, Luchtdruck-, Feuchtesensor und ein paar Schalter/LED\'s zum testen
Module: RPI_GPIO, RPII2C, I2C_EEPROM, I2C_MCP23008, I2C_MCP23017, I2C_MCP342x, I2C_PCA9532, I2C_PCF8574, I2C_SHT21, I2C_BME280

ntruchsess

#19

                                          Hier ist wohl eine Klammer zu viel?
                                          V
IOWrite($hash, "<i2cwrite|i2cread>", <id> ) . " " . <I2C Adresse> [. " " . <Daten für I2C wenn i2csend>]);


Im Prinzip würde ich sagen, dass das prinzipiell passt, wobei ich die parameter nicht als einen einzigen String, sondern als einzelne Parameter übergeben würde. Der Aufruf der WriteFn erlaubt das. Eventuell in einen Hash der Art { direction => i2cwrite, id => <..>, i2caddress => <...>, data => <...> } gepackt - wenn man optionale Parameter hat, können die im Hash einfach weggelassen werden, bei positionalen Parametern muss man sie explizit als undef mit angeben (und in einem zu parsenden String müsste man einen Platzhalter für ungesetzte Parameter vereinbaren).

Dispatch und ParseFn würde ich nicht benutzen, das ist einfach zu sehr auf den Anwendungsfall zugeschnitten, dass quasi ungefragt empfangene Messages von Dispatch verteilt werden sollen und dieses erst herausfinden muss, an welches Device die jeweilige Message gesendet werden soll. Bei der I2C-Kommunikation ist die I2C-addresse ja schon bekannt (und nicht Bestandteil der gelesenen Daten. Das sollte man benutzen, in dem man über die Clientdevices iteriert und schaut, bei welchem die I2C-addresse passt. Man kann ja trotzdem die in der Initialize-methode angegebene ParseFn aufrufen oder (da die Aufrufsemantic ja nicht dieselbe ist) z.B. eine 'I2CReceiveFn' oder so definieren.

So was wie Sendeintervalle würde nicht im physikalischen Modul abwickeln.
while (!asleep()) {sheep++};

klausw

Die Übergabe durch Parameter ist natürlich viel übersichtlicher. Das werde ich abändern.

Mit Dispatch und ParseFn war ich auch nicht so glücklich, ich habe die I2C Adresse halt immer mit zurückgesendet, um das passende Modul zu finden.
Wie kann ich denn im physikal. Modulteil nach dem passenden Client suchen? Würde das mit devspec2array gehen?
Es könnten ja die verschiedensten Module sein.
Und wie bekomme ich die Botschaft dann an das passende Client  'I2CReceiveFn' gesendet?

Zitat von: ntruchsess am 04 Februar 2014, 17:09:39
So was wie Sendeintervalle würde nicht im physikalischen Modul abwickeln.
Ok, was ich nicht verstehe ist, wozu die Sendeintervalle benötigt werden.
Ich schicke im Moment alles so schnell wie möglich raus (also direkt nach einer Antwort vom I2C Bus)
Grundsätzlich kann man Intervalle in die Sendeschleife im phys. Modul mit einbauen.
Wenn ein Sendeintervall über IOWrite mit übergeben wird, speichere ich den mit im Sendebuffer ab und setze wenn eine Zeit definiert ist einfach einen Timer, der entsprechend wartet.
RasPi B v2 mit FHEM 18B20 über 1Wire, LED PWM Treiber über I2C, Luchtdruck-, Feuchtesensor und ein paar Schalter/LED\'s zum testen
Module: RPI_GPIO, RPII2C, I2C_EEPROM, I2C_MCP23008, I2C_MCP23017, I2C_MCP342x, I2C_PCA9532, I2C_PCF8574, I2C_SHT21, I2C_BME280

ntruchsess

Wie findet das physikalische Modul den zur I2C-response passenden Client?

Man legt die I2C-Addresse wärend define im Client-hash ab. Anschließend assoziiert man den Client-hash per AssignIODev mit seinem physikalischen Modul. Hierbei wird der der Client-hash im '.clientArray' des physikalischen Moduls abgelegt. Wenn man die I2C-response an den Client zurückschicken will, iteriert man (so wie in der Dispatch-funktion) über alle Clients aus '.clientArray' des physikalischen Devices bis man den Client-hash findet, der die passende I2C-addresse enthält.

die 'I2CReceiveFn' ruft man mit der CallFn-methode (aus fhem.pl) auf. Aufrufschema findest Du gleich mehrfach in fhem.pl.

der I2CReceiveFn sollte auch einen Parameter 'success' mitgeben, damit das Modul erfährt, ob ein Schreibvorgang erfolgreich war.

ein Sende- oder Leseinterval ist natürlich nur für I2C-module sinnvoll, aus denen man einen veränderlichen Wert auslesen kann - z.B. einen A/D-wandler oder eine Echtzeituhr. Weil das naturgemäß spezifisch für das jeweilige I2C-Device ist, muss das im Client-modul definiert sein.

FRM_I2C ist übrigens ein generisches I2C-Client-modul, das eigentlich nur dazu taugt in einem regelmäßigen Intervall ein I2C-Device auszulesen. Das physikalische Modul dazu ist FRM selbst. FRM_LCD setzt direkt auf FRM auf und hat sonst keine Verbindung mit FRM_I2C.

... Wir sollten für das I2C-thema einen eigenen Thread aufmachen, das führt von Arduino/Firmata jetzt ganz schön weit weg ;-)

Gruß,

Norbert
while (!asleep()) {sheep++};

klausw

da hast du recht

hier ist der Neue:
http://forum.fhem.de/index.php/topic,19797.0.html

da wartet schon die NAchricht ;)
RasPi B v2 mit FHEM 18B20 über 1Wire, LED PWM Treiber über I2C, Luchtdruck-, Feuchtesensor und ein paar Schalter/LED\'s zum testen
Module: RPI_GPIO, RPII2C, I2C_EEPROM, I2C_MCP23008, I2C_MCP23017, I2C_MCP342x, I2C_PCA9532, I2C_PCF8574, I2C_SHT21, I2C_BME280

Dr. Boris Neubert

Hallo Norbert,

nachdem mein Raspberry Pi samt 1-Wire-Bus-Installation beim Blitzschlag in Elektronikschrott verwandelt wurde, suche ich nun eine neue Lösung für das Interfacing von FHEM mit meinem S0-fähigen Stromzähler.

Die alte Lösung nutzte den DS2423-Dual-Counter. Der ist aber abgekündigt und nur noch teuer erhältlich.

Ich habe hier noch einen Arduino-Nano-Klon aus China rumliegen. Der könnte dann auch gleich noch ein paar andere Schaltaufgaben übernehmen, z.B. die Lüfter im Rack schalten. Entweder baue ich mir eine eigene Spezialfirmware dafür oder ich nutze Firmata mit Deinen Modulen. Zu letzteren habe ich eine Frage:

Ich möchte die Zählimpulse puffern, so dass ich nicht nach jedem Neustart von FHEM den Zählerstand händisch neu kalibrieren muss. Das geht prinzipiell, wenn ich den eingebauten Counter im ATmega328p nutze. Gehe ich recht in der Annahme, dass Firmata das Auslesen des Counters NICHT unterstützt? Meine Google-Suche dazu war ergebnislos.

Viele Grüße
Boris



Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

ntruchsess

Hallo Boris,

die Counter des Atmega328 nutzt die Firmata bisher noch nicht. Wäre aber an sich nicht schwer das einzubauen.

Ist eher eine Frage des drumherum (Brownout-detection, Persistierung im eeprom, Konfiguration der Firmata aus eeprom bei Reset (ohne Verbindung zu FHEM, sauberes Wiederaufsetzen beim re-connect ohne 'Reset und Neukonfiguration') was fehlt, damit das tatsächlich signifikant besser wäre als FHEM-seitiges Zählen dessen, was der verbundene Arduino 'live' meldet. Die Firmata ist halt primär auf 'Verbunden mit einem PC' hin ausgerichtet. Wobei eigentlich alle anderen Firmata-features von gewissen Offline-fähigkeiten bzw. konfigurierbarem Safe-fallback profitieren würden. Vieliecht hättest Du ja Lust dich da an der Entwicklung (mit code oder auch nur Konzeptionell - sprich Protokoll-design) zu beteiligen?

Gruß,

Norbert
while (!asleep()) {sheep++};

Dr. Boris Neubert

Danke, Norbert, für die Antwort.

Um ein Gefühl dafür zu bekommen, was eigentlich gemacht werden müsste, habe ich einmal eine Custom-Firmware für den Arduino-Nano-Klon geschrieben, welche bis zu 6 Counter (schalten bei fallender Flanke) und 3 Switches bedienen kann, und welche ich über eine kleine Kommandosprache steuere. Wegen einiger durch die Arduino-Library bedingten Einschränkungen habe ich die Counter nicht über die internen Counter/Timer gelöst sondern über Pin Change Interrupts.

In Kombination mit der Entprellung der Zähleingänge habe ich da nun doch einen ganzen Tag lang dran gesessen. Eine Integration in Firmata würde noch eine Schicht Komplexität darüber legen, ohne mir extra Nutzen zu bringen. Bei Interesse stelle ich den Kode aber gerne als Anschauungsobjekt zur Verfügung. Ich habe ihn so geschrieben, dass die Kommandos leicht über ECMD/ECMDDevice zu geben sind.

Morgen nehme ich das Teil mal als kombiniertes Interface für den S0-Zähler und die Lüftersteuerung im Rack in Betrieb.

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!