NEWS
Adapter Architektur von ioBroker?
-
Hallo,
es geht hier um eine kleine Verständnisfrage…
Hintergrund:
-
Wie gesagt ich bereite gerade ein "How to" für die Adapter Programmierung vor.
-
Deswegen bin am recherchieren.
-
Ich würde nicht nur gerne aufzeigen wie man einen neuen Adapter schreibt, sondern auch warum dies so implementiert wurde.
Fragen:
-
Wie seit Ihr (Bluefox/Hobbyquaker?) auf die Idee mit den Adapter gekommen?
-
Kann man sagen das es generelle Adapter/Plugins für NodeJS Apps sind?
-
Ist es vergleichbar mit "Flatiron"? http://flatironjs.org
-
Wie erzeugt ioBroker eine Adapter Instanze?
Erstmal danke…
Steve
-
-
Hallo,
es geht hier um eine kleine Verständnisfrage…
Hintergrund:
-
Wie gesagt ich bereite gerade ein "How to" für die Adapter Programmierung vor.
-
Deswegen bin am recherchieren.
-
Ich würde nicht nur gerne aufzeigen wie man einen neuen Adapter schreibt, sondern auch warum dies so implementiert wurde.
Fragen:
-
Wie seit Ihr (Bluefox/Hobbyquaker?) auf die Idee mit den Adapter gekommen?
-
Kann man sagen das es generelle Adapter/Plugins für NodeJS Apps sind?
-
Ist es vergleichbar mit "Flatiron"? http://flatironjs.org
-
Wie erzeugt ioBroker eine Adapter Instanze?
Erstmal danke…
Steve `
- Wie seit Ihr (Bluefox/Hobbyquaker?) auf die Idee mit den Adapter gekommen? `
nach dem in ccu.io über 40 Adapter waren und die waren fest integriert in einem Paket war das unheimlich schwer das ganze zu warten.
Bei jeder Änderung in einem Adapter sollte man eine ccu.io Version machen. Und das konnten (und können) nur die Admins(BF, HQ) machen.
Die Idee wahr ein Kontroller zu machen, der die Adapter steuert und beobachtet. Und bei der Änderung im Adapter nur Adapter upgedatet werden sollte.
Mit so einem Konzept kann dann jeder einen Adapter schreiben und den pflegen.
- Kann man sagen das es generelle Adapter/Plugins für NodeJS Apps sind? `
Um parallel mit Apple/Android zu machen … Generell, Ja.
- Ist es vergleichbar mit "Flatiron"? http://flatironjs.org `
Auf dem ersten Blick, nein.
- Wie erzeugt ioBroker eine Adapter Instanze? `
Das ist nur ein Eintrag in der Konfiguration.
Aber das ist nicht in zwei Zeilen zu beschreiben.
-
-
Was man noch zu den Adapter instanzen sagen kann ist. Das es eigenständige Processe sind. Die vom Controller überwacht werden und ggf. neugestartet werden.
So bringt ein Fehler im Adapter nicht kommplet iobroker zum stillstand.
-
Hi Steve,
gibt es das Howto schon?
Würde mich sehr interessieren.
Gruß
Michael
Gesendet von iPhone mit Tapatalk
-
> gibt es das Howto schon?
Nur teilweise: -
sorry, war doppelt
-
Danke für die Infos!
Das ist doch schon einmal was
Ich fürchte trotzdem… das wird dauern :roll:
Was bisher geschah:
- node.js extra direkt unter Mac OS X installiert
(produktiv unter Debian auf einer VM)
-
ioBroker unter Mac OS X installiert
-
WebStorm, 30 Tage Testlizenz unter Mac OS X installiert
-
github Zugang
-
Installation, setup and first steps with an ioBroker Development Environment
gelesen und Schritt für Schritt durchgegangen.
Da, wo es richtig interessant werden würde, hört es auf
- um einen Überblick in der Filestruktur zu bekommen:
ping
hmm
example
installiert und angesehen.
Als nächstes schau ich mal, ob ich aus der Beschreibung:
Guide for adapter developers (javascript/NodeJS)
schlau werde.
Dann die Beschreibung des ccu.io fritzbox-Adapters:
https://github.com/hobbyquaker/ccu.io/b … ritzBox.js
Spannend. Die erste Berührung mit Javascript ist jetzt gerade mal wenige Wochen alt und beschränkt sich auf Miniscripts in ioBroker und den Node-red Flows hier.
Vielleicht sollte ich mir erst einmal ein Javascript Buch holen
Die Einbindung anderer Bibliotheken ist schon das erste böhmische Dorf für mich.
Dann Pfade, Werte übergeben, usw.
Ich kämpfe mich mal durch :mrgreen:
wird schon
Zwei Stichworte, was für einen Anfänger die richtige Vorgehensweise (nächste Schritte) wären...
-
Die Einbindung anderer Bibliotheken ist schon das erste böhmische Dorf für mich.
Dann Pfade, Werte übergeben, usw. `
Um ein Modul (z.b. xml2js) an zu schließen, muss man folgendes machen:Nehmen wir an, dass /iobroker/node_modules/iobroker.DEIN_ADAPTER schon existiert und auch package.json dadrin auch.
cd /iobroker/node_modules/iobroker.DEIN_ADAPTER echo Damit installieren wir xml2js und sagen, dass es für immer in packet.json rein geschrieben werden muss (--save) npm install xml2js --save
Danach in main.js (oder DEIN_ADAPTER.js) schreiben
var xml2js = require('xml2js');
Das war es. Jetzt kannst du xml2js benutzen. Z.b. so:
var xml2js = require('xml2js'); var parseString = xml2js.parseString; var xml = "<root>Hello xml2js!</root>" parseString(xml, function (err, result) { console.dir(result); });
> Werte übergeben, usw.
In deinem Fall solltest du doch keine Werte vom System bekommen. D.h du muss nur in das System schreiben.Das ist auch auch einfach. Wenn du die Werte zusammen gebaut hast, z.B. "fritzbox.0.calling", dann schreibst du einfach
adapter.setState('calling', true, true);
Erste "true", das ist das Wert (Anruf läuft). Zweite "true" sagt, dass es um Zustand (und nicht um Befehl) geht.
> Dann Pfade, usw.
Welche Pfade? "Usw" bitte genauer spezifizieren -
.
Danke für die Infos!
EDIT:
Fragen gelöscht… tlw. geklärt
Adapter Grundgerüst und Debug läuft nun mit Webstorm auf dem Mac.
Hatte mich zu sehr auf die ioBroker Adminoberfläche versteift.
Nun geht es darum, eine gescheite Struktur von main.js zu finden.
Ich bekomme zwar schon einmal Daten von der Fritzbox. So wird es aber nicht Sinn der Sache sein
function connectToFritzbox() { var socketBox = net.connect({port: 1012, host: '172.16.130.254'}, function() { // IP muss noch von der html admin Seite kommen adapter.log.info("adapter fritzBox connected to fritz: "); // + ip (todo) socketBox.on('data', function(data) { //hat gefühlt nichts in der Funktion und in main() zu suchen adapter.log.info("Daten empfangen:" + data); }); }); } function main() { adapter.log.debug('main: Hallo, ich bin es. Der Fritzbox-Adapter'); connectToFritzbox(); }
-
Ich habe gerade mich zusammen gerissen die Fragen zu beantworten… Nun sind die weg.
Ich versuche trotzdem was zu schreiben:
-
"net" ist Bestandteil von node.js und man muss es NICHT mit "npm install" installieren. Es ist immer da.
-
Um admin/index.html in "admin" zu bekommen muss man "iobroker upload fritzbox" schreiben.
Andere Fragen habe ich vergessen. 8-)
> Ich bekomme zwar schon einmal Daten von der Fritzbox. So wird es aber nicht Sinn der Sache sein
Wie ich schon gesagt habe, man kann viel bei ccu.io/fritzbox abschauen und auch node-red ist fast Javascript.Und bitte konkrete Fragen stellen
-
-
.
Danke für die Antworten, auch für die nicht mehr gestellten :mrgreen:
Die Webseite lädt schon einmal
Mit net hatte ich gar nicht so auf dem Schirm. Jetzt ist es klarer (auch in Verbindung mit der node.js Doku).
Die anderen Fragen drehten sich um die package.json und die io-package.json. Das hat keine Prio. Darum kümmere ich mich, wenn schon was brauchbares läuft.
Dann:
@Bluefox:In deinem Fall solltest du doch keine Werte vom System bekommen. D.h du muss nur in das System schreiben.
Das ist auch auch einfach. Wenn du die Werte zusammen gebaut hast, z.B. "fritzbox.0.calling", dann schreibst du einfach
adapter.setState('calling', true, true);
Erste "true", das ist das Wert (Anruf läuft). Zweite "true" sagt, dass es um Zustand (und nicht um Befehl) geht. `
Muss ich das Objekt/den Datenpunkt erst noch anlegen, bevor ich ihn beschreiben kann?
D.h. ein adapter.createState() oder so?
So sehe ich die Werte unter Ereignisse und Zustände.
Unter Objekte taucht der Wert nicht auf (das Objekt wird nicht erzeugt).
Soweit kämpfe ich mich durch. Man kann sich ja einiges abschauen (andere Adapter, Fritzbox-Adapter der ccu.io).
Ab und an ein Schubser… und es geht schneller
Erst einmal bin ich jetzt froh, dass Webstorm läuft, die Webseite lädt, Debugging funktioniert und Daten aus der Fritzbox im Log ausgegeben werden.
Nun aber meine größte Hürde.
Javascript in Node-red schön und gut. So ein Flow ist schön zu verfolgen. Im Adapter sieht es ganz anders aus. Hier fehlt mir jegliche Erfahrung.
Wie baut man denn so etwas auf (gerne auch Pseudocode)? Die Funktion main() ist in den ganzen Adaptern ja erfrischend klein.
Was gehört in main()?
Wo reagiere ich auf die Daten aus der Fritzbox?
Wann brauche ich adapter.on('message', function (obj) {?
Und wann adapter.on('stateChange', function (id, state) {?
Brauche ich das überhaupt?
Mein Dilemma wird man an diesem Stück Code sehen:
function connectToFritzbox() { var socketBox = net.connect({port: 1012, host: '172.16.130.254'}, function() { // IP muss noch von der html admin Seite kommen adapter.log.info("adapter fritzBox connected to fritz: "); // + ip (todo) socketBox.on('data', function(data) { //hat gefühlt nichts in der Funktion und in main() zu suchen adapter.log.info("Daten empfangen:" + data); }); }); } function main() { adapter.log.debug('main: Hallo, ich bin es. Der Fritzbox-Adapter'); connectToFritzbox(); }
Die Funktion connectToFritzbox() halte ich so für Blödsinn (damit meine ich nicht, dass sie noch nichts zurückgibt). Denn Connect und das "Warten auf Daten" sollte man doch wahrscheinlich trennen. Auch der Aufruf aus main() heraus.
In Node-red gab es nur ein paar lokale Funktionen und kein main(). Es wurde nur an wenigen Stellen auf etwas reagiert. Ansonsten gab es einen schönen linearen Verlauf. Alles kein Thema. Brauche nur den Einstieg. Ansonsten kämpfe ich mich da schon durch.
-
` > Muss ich das Objekt/den Datenpunkt erst noch anlegen, bevor ich ihn beschreiben kann?
D.h. ein adapter.createState() oder so?
So sehe ich die Werte unter Ereignisse und Zustände.
Unter Objekte taucht der Wert nicht auf (das Objekt wird nicht erzeugt). `
Wie du richtig bemerkt hast, kannst du schon das Wert schreiben, nun wird es dafür kein Objekt erzeugt.Weil alle deine Datenpunkte (DP) nicht dynamisch sind (es wird nicht meht und nicht weniger Datenpunkte und die Namen sind konstant), dann kannst du alle Datenpunkte schon in io-package.json definieren und die werden automatisch (bei INSTALLL) angelegt.
Schaue, z.b https://github.com/ioBroker/ioBroker.yr … ckage.json
... "objects": [ ], "instanceObjects": [ { "_id": "forecast.day0", "type": "channel", "common": { "name": "actual weather or forecast for today", "role": "weather.forecast" }, "native": {} }, { "_id": "forecast.day0.date", "type": "state", "common": { "name": "Actual date", "type": "string", "role": "date", "read": true, "write": false, "desc": "Actual date as string" }, "native": {} } ... }
Beim INSTALL werden dann automatisch yr.X.forecast.day0 und yr.X.forecast.day0.date angelegt. Du musst genauso machen.
Die DP kann man auch als CHANNELS zusammengruppieren oder flach lassen. Hängt davon ab, wie viel und welche Variablen du hast. Ich kann jetzt schon folgende Kanäle sehen: actualcall (oder call), history, control…
Und dann kansst du so aufbauen:
fritzbox.X.call.incoming (oder
fritzbox.X.call.duration ...
fritzbox.X.history.HTMLtable
fritzbox.X.history.JSONtable
...
> Wie baut man denn so etwas auf (gerne auch Pseudocode)? Die Funktion main() ist in den ganzen Adaptern ja erfrischend klein.
"main" das ist nur ein Namen, der sagt, hier startet das Programm. Es muss aber nicht unbedingt "main" heißen.Einstiegs-punkt ist
adapter.on("ready", function () {....});
> Was gehört in main()?
Wie gesagt, nichts. Du kannst aus "ready" direct "connectToFritzbox" aufrufen.> Wo reagiere ich auf die Daten aus der Fritzbox?
Hier:socketBox.on('data', function (data) { //Analyse the data });
Wie du es machst, ist dir überlassen. Du kannst direkt da analysieren wie in https://github.com/hobbyquaker/ccu.io/b … ritzBox.js (Zeile 307) oder als Funktion ...
` > Wann brauche ich adapter.on('message', function (obj) {?
Und wann adapter.on('stateChange', function (id, state) {?
Brauche ich das überhaupt? `
Reicht es momentan, wenn ich sage: Nein. Du brauchst die nicht.` > Mein Dilemma wird man an diesem Stück Code sehen:
function connectToFritzbox() { var socketBox = net.connect({port: 1012, host: '172.16.130.254'}, function() { // IP muss noch von der html admin Seite kommen adapter.log.info("adapter fritzBox connected to fritz: "); // + ip (todo) socketBox.on('data', function(data) { //hat gefühlt nichts in der Funktion und in main() zu suchen adapter.log.info("Daten empfangen:" + data); }); }); } function main() { adapter.log.debug('main: Hallo, ich bin es. Der Fritzbox-Adapter'); connectToFritzbox(); }
Die Funktion connectToFritzbox() halte ich so für Blödsinn (damit meine ich nicht, dass sie noch nichts zurückgibt). Denn Connect und das "Warten auf Daten" sollte man doch wahrscheinlich trennen. Auch der Aufruf aus main() heraus. `
"main" ist nur ein Zeichen, dass hier Anfang ist. Nichts weiteres.im ccu.io/fritzbox werden noch die Ereignisse: close, end und error behandelt und einfach Reconnection gestartet.
> In Node-red gab es nur ein paar lokale Funktionen und kein main().
Vergiss "main" :lol:> Es wurde nur an wenigen Stellen auf etwas reagiert. Ansonsten gab es einen schönen linearen Verlauf. Alles kein Thema. Brauche nur den Einstieg. Ansonsten kämpfe ich mich da schon durch. ;-)
Hier ist auch alles linear. Und hier wird dein Startpunkt on("data") sein. Da startet dein node-red flow.
-
wow… das nenne ich mal eine ausführliche Antwort.
sorry Bluefox, wenn ich an der Stelle etwas nachhake. Ich möchte nur vermeiden, dass ich am Anfang an der falschen Stelle anfange. So komme ich gut zurecht. Deine Antworten waren extrem hilfreich.
Die Punkte sind für mich nun klar und ich gehe mal davon aus, dass ich das hinbekomme:
-
Datenpunkte anlegen, z.B. über io-package.json
-
main() ja, habe ich begriffen
-
socketBox.on() ist klar. da empfange ich auch schon Daten und habe angefangen das zu zerpflücken
(da habe ich aber noch eine Frage)
- close, end und error. Damit meinst Du wahrscheinlich die tcp Verbindung. Werde ich auch mit klar kommen.
(geht ja auch noch nachträglich)
Jetzt trotzdem noch einmal grundlegend:
a)
In dem Beispielcode wird in einer Funktion die Verbindung hergestellt und dann auf Daten gewartet.
Ist das prinzipiell OK? D.h. bei Daten wird ja quasi mitten in die Funktion gesprungen...
Funktion
--tcp Verbindung herstellen
--warte auf Daten <---- on()
--verarbeite Daten
Funtkion Ende
b)
Alternativ muss ich das on() aus der Funktion rausnehmen und SocketBox muss global definiert werden.
oder c)
ohne Funktion die tcp Verbindung herstellen und dann Daten empfangen und verarbeiten
Was ist schicker, bzw. der richtige Weg? a), b) oder c)
Oder ist das gänzlich egal?
Wenn ich raten müsste, dann ist es kein Problem, mitten in eine Funktion zu springen. Wäre ja in adapter.on("ready", function () {....}); genauso.
Und eine, hoffentlich erst einmal, letzte Frage zu Objekten/Variablen.
Ich habe einmal die Datenpunkte in den Objekten. Die sind restartsicher.
Dann habe ich die Variablen im Programm, die nicht restartsicher sind.
Ich brauche wahrscheinlich einige Infos restartsicher, die ich aber nicht als Datenpunkte benötige. Ich muss noch überlegen, ob das wirklich nötig ist. Gibt es da einen Weg oder besteht nur die Wahl aus den Datenpunkten oder Variablen zur Laufzeit?
Ich gehe mal davon aus, dass ich dann erst einmal genug Infos habe, um zu starten und erst später wieder die eine oder andere Frage haben werde
-
-
OK, ich werfe gerade einen Blick auf den Code…
...und glaube, die Frage, ob man innerhalb einer Funktion auf ein Ereignis reagieren kann/soll war blöd...
-
Jetzt trotzdem noch einmal grundlegend:
a)
In dem Beispielcode wird in einer Funktion die Verbindung hergestellt und dann auf Daten gewartet.
Ist das prinzipiell OK? D.h. bei Daten wird ja quasi mitten in die Funktion gesprungen…
Funktion
--tcp Verbindung herstellen
--warte auf Daten <---- on()
--verarbeite Daten
Funtkion Ende `
b)
Alternativ muss ich das on() aus der Funktion rausnehmen und SocketBox muss global definiert werden.
oder c)
ohne Funktion die tcp Verbindung herstellen und dann Daten empfangen und verarbeiten
Was ist schicker, bzw. der richtige Weg? a), b) oder c)
Oder ist das gänzlich egal?
Du hast es begriffen.
Es gibt folgendes, falls die Funktion groß ist, dann ist besser das als Funktion zu gestallten.
function parseData(data) { //200 Zeilen } socketBox.on('data', parseData);
Falls Funktion an mehreren stellen verwendet wird, dann auch als named Funktion:
var connecting = false; function restartConnection() { if (socketBox) { socketBox.end(); socketBox = null; } if (!connecting){ connecting = setTimeout(function () { connectToFritzBox(); }, 10000); } } socketBox.on('error', restartConnection); socketBox.on('close', restartConnection); socketBox.on('end', restartConnection);
Wenn ich raten müsste, dann ist es kein Problem, mitten in eine Funktion zu springen. Wäre ja in adapter.on("ready", function () {….}); genauso. `
"Viele Wege führen nach Rom"Ich habe einmal die Datenpunkte in den Objekten. Die sind restartsicher.
Dann habe ich die Variablen im Programm, die nicht restartsicher sind.
Ich brauche wahrscheinlich einige Infos restartsicher, die ich aber nicht als Datenpunkte benötige. Ich muss noch überlegen, ob das wirklich nötig ist. Gibt es da einen Weg oder besteht nur die Wahl aus den Datenpunkten oder Variablen zur Laufzeit? `
Eigentlich ja. Nur states und objects. Du kannst noch natürlich einfach in einer Datei speichern… Aber dann wird es später nicht mit redundantem Ausbau funktionieren.Kannst du Beispiel nennen?
-
Ein Beispiel sind die Callids mit Ihren langsam aufbauenden Zuständen.
Wenn ich das beim Überfliegen des ccu.io fritzbox-Adapters richtig gesehen habe, geht der davon aus, dass es immer max einen Anruf parallel gibt. Ich bin der Meinung, dass ich mit den Callids besser auch parallel Anrufe verarbeiten kann.
Wenn man im bestehenden Gespräch den Adapter neu startet, dann sollen die aktuellen Zustände erhalten bleiben.
mmh… das mit den Zuständen wird wahrscheinlich nicht funktionieren, da dann eine identische ID unter Umständen schon ein anderer Anruf ist. Ich denk das noch einmal durch, ob das persistent sein muss oder nicht.
Die Antwort ist (?):
-
persistent über Objekte/Datenpunkte in ioBroker
-
flüchtig mit Variablen im Script
Damit kann ich arbeiten.
Danke für die Info mit den Funktionen!
-
-
> Wenn man im bestehenden Gespräch den Adapter neu startet, dann sollen die aktuellen Zustände erhalten bleiben.
Ich denke das kann man Opfern. -
ja, kann man
Muss doch noch einmal stören. :mrgreen:
Wie kann ich denn ein Objekt/Datenpunkt aus ioBroker lesen?
Ich möchte einen DP aus Node-red lesen, und bei Änderung reagieren.
Hintergrund. Für das Testen möchte ich den fertigen Callgenerator aus dem Node-red Flow nutzen. Dieser simuliert dann die Calls und schreibt die simulierte Fritzboxmeldung in ein Node-red Datenpunkt. Diesen möchte ich dann alternativ mit der parseData() Funktion verwenden.
-
Grummel…
wahrscheinlich mit:
var callgenMessage = adapter.getState("node-red.0.fritzbox.meldungAusDerFritzBox");
Wundere mich, warum ich immer undefined zurückbekomme…
In der Entwicklungsumgebung für ioBroker habe ich kein Node-red installiert, kein Flow, keine DP
Allerdings bekomme ich Node-red auch nicht auf dem Mac installiert. Überlege mir was anderes.
EDIT: mit einem tcp output in Node-red hat es funktioniert.
-
Grummel…
wahrscheinlich mit:
var callgenMessage = adapter.getState("node-red.0.fritzbox.meldungAusDerFritzBox");
Wundere mich, warum ich immer undefined zurückbekomme…
In der Entwicklungsumgebung für ioBroker habe ich kein Node-red installiert, kein Flow, keine DP
Allerdings bekomme ich Node-red auch nicht auf dem Mac installiert. Überlege mir was anderes. `
Richtig ist:
adapter.getState("node-red.0.fritzbox.meldungAusDerFritzBox", function (err, val) { // Asynchron!! var callgenMessage = val.val; });
Aber vermutlich du willst darauf reagieren , wenn das Wert gesetzt ist?
Dann:
- erst einmalig "Subscriben", z.B in "ready":
adapter.subscribeForeignStates("node-red.0.fritzbox.*);
Dann auf die Änderungen reagieren:
adapter.on("stateChange", function (id, state) { if (state && id == "node-red.0.fritzbox.meldungAusDerFritzBox") { // New value of adapter.log.info(state .val); } });