NEWS
SetState() nur bei Änderung von Werten?!?
-
Hallo Zusammen,
ich arbeite gerade an einem neuen Adapter der in regelmäßigen Abständen tausende Werte irgendwo abholt diese dann für ioBroker aufbereitet und nach anlegen der State die Werte dann entsprechend mittels setState(…., {ack=true, val=XXXX}); an ioBroker meldet. Das funktioniert soweit auch sehr gut.
Nun stelle ich mir jedoch gerade die Frage ob es neben der setState() Funktion noch eine ähnliche Funktion gibt die den state allerdings nur dann auf einen neuen wert setzt wenn dieser nicht bereits diesen wert hat. Mir war nämlich aufgefallen das im ObjectView meiner State diese immer regelmäßig im Abholinterval meines Adapters grün auflashen was für bedeutet das die state auch bei gleichen werten immer neu gesetzt werden und das empfinde ich als unnötigen Overhead da ich ja wie gesagt mehrere tausend states bei jedem abholinterval (wohl jede 1-5 minuten) mittels setState() an ioBroker melde.
Gibt es also irgendeine Möglichkeit/Funktion einen setState() nur dann auszuführen wenn der Wert des States nicht der selbe ist denn man gerade melden will? Oder muss ich das selbstständig hier mittels eines Konstruktes aus "if(getState() != val) setState()" ? Denn auch das erscheint mir unnötiger Overhead auf der Adapter-Seite zu sein, oder mache ich mir da unnötige Gedanken?
-
tausende Werte? Klingt interessant - bei homematic knallen sicher schon die Sektkorken
Verrätst du auch was das für Werte sind?
Jedenfalls könntest du die Funktionalität im Adapter selber abbilden, falls in ioBroker die Werte dann nur mehr gelesen werden…
-
Hallo,
Ich würde diesbezüglich mal Apollon77 anschreiben. Er hat so etwas in des SQL Adapter eingebaut. Er kann dir sicher sagen wie er das gelöst hat.
Lg
Günther
-
mittels eines Konstruktes aus "if(getState() != val) setState()" ? Denn auch das erscheint mir unnötiger Overhead auf der Adapter-Seite zu sein, `
Den geringsten Overhead hat man sicherlich, wenn man für jeden Wert eine (globale) Skriptvariale hat, mit der verglichen wird, bevor sie aktualisiert und der Datenpunkt geändert wird.varN = getState(id).val; ... if(varN != val) { varN = val; setState(id, val, true); }
-
Einzige sinnvolle Variante ist die aktuellen Werte im Adapter-Prozess nochmal im Speicher zu haben. Dann kannst Du mit denen vergleichen und entscheiden wann Du neu in setState schreibst. Das kostet an sich nicht sooo viel Speicher.
Am Ende ist halt die Frage was das für Werte sind und welche Dinge die Nutzer damit anstellen. Vllt ist es ja interessant auch "nicht geänderte Werte" getriggert zu bekommen. Das geht dann nicht mehr wenn Du Sie nur bei Änderungen schreibst. Aber bei "Pull-Szenarien" (also regelmäßiges abholen) ist das wohl eher nicht so relevant.
-
mittels eines Konstruktes aus "if(getState() != val) setState()" ? Denn auch das erscheint mir unnötiger Overhead auf der Adapter-Seite zu sein,
Den geringsten Overhead hat man sicherlich, wenn man für jeden Wert eine (globale) Skriptvariale hat, mit der verglichen wird, bevor sie aktualisiert und der Datenpunkt geändert wird.
Oh, ich denke ich hab mich zu ungenau ausgedrückt. Es geht hier natürlich um die Entwicklung eines eigenen Adapters, dort gibt es nicht setState() sondern eben "adapter.setState()" genauso wie "adapter.getState()" und das verhält sich leider auch etwas anders, denn es ist immer asynchron sodass sowas z.B. nicht zu funktionieren scheint:
for(var i=0; i < 100; i++) { var newval = i; var stateName = "test." + i; adapter.getState(stateName, function(err, state) { if(state.val != newval) adapter.setState(stateName, {ack: true, val: newval); }); }
Das Problem hierbei ist, wie gesagt, das adapter.getState() asynchron ist und somit "newval" und "stateName" innerhalb der anyonmen Funktion die an adapter.getState() übergeben wird zur Ausführungszeit leider die falschen werte hat. Zumindest ist das hier gerade der Fall.
-
Am Ende ist halt die Frage was das für Werte sind und welche Dinge die Nutzer damit anstellen. Vllt ist es ja interessant auch "nicht geänderte Werte" getriggert zu bekommen. Das geht dann nicht mehr wenn Du Sie nur bei Änderungen schreibst. Aber bei "Pull-Szenarien" (also regelmäßiges abholen) ist das wohl eher nicht so relevant. `
Ich denke auch das das nicht wirklich relevant ist für solche Pull-Szenarien. Hier würde es auch eher von Vorteil sein das nicht jedes adapter.setState() das an ioBroker abgesetzt wird darin endet dass dieser mit dem setzen der Werte und dem publisher an alle Interessenten beschäftigt ist. Das macht IMHO nur unnötigen Aufwand. Meiner Meinung nach wäre es sicherlich sinnvoll zusätzlich zu adapter.setState() eine Art adapter.setStateNotChanged() Funktion ode reinen zusätzlichen Parameter bei setState() einzuführen der erlaubt zu definieren ob ioBroker vor setzen des Wertes prüfen soll ob der Wert nicht ohnehin schon auf diesen Wert steht.
-
mittels eines Konstruktes aus "if(getState() != val) setState()" ? Denn auch das erscheint mir unnötiger Overhead auf der Adapter-Seite zu sein,
Den geringsten Overhead hat man sicherlich, wenn man für jeden Wert eine (globale) Skriptvariale hat, mit der verglichen wird, bevor sie aktualisiert und der Datenpunkt geändert wird.
Oh, ich denke ich hab mich zu ungenau ausgedrückt. Es geht hier natürlich um die Entwicklung eines eigenen Adapters, dort gibt es nicht setState() sondern eben "adapter.setState()" genauso wie "adapter.getState()" und das verhält sich leider auch etwas anders, denn es ist immer asynchron sodass sowas z.B. nicht zu funktionieren scheint:
for(var i=0; i < 100; i++) { var newval = i; var stateName = "test." + i; adapter.getState(stateName, function(err, state) { if(state.val != newval) adapter.setState(stateName, {ack: true, val: newval); }); }
Das Problem hierbei ist, wie gesagt, das adapter.getState() asynchron ist und somit "newval" und "stateName" innerhalb der anyonmen Funktion die an adapter.getState() übergeben wird zur Ausführungszeit leider die falschen werte hat. Zumindest ist das hier gerade der Fall. `
Das macht man einbisschen anders:function setStates(_states, callback) { if (!_states || !_states.length) { if (typeof callback === 'function') callback(); return; } var newState = _states.shift(); adapter.getState(newState.name, function (err, oldState) { if (newState.val != oldState.val) { adapter.setState(newState.name, {ack: true, val: newState.val}, function () { setTimeout(setStates, 0, _states, callback); }); } else { setTimeout(setStates, 0, _states, callback); } }); } var states = []; for (var i = 0; i < 100; i++) { var newval = i; var stateName = 'test.' + i; states.push({name: stateName, val: newval}; } setStates(states, function () { console.log('finished'); });
Falls die Werte nicht zu gross sind (ein Wert unter 1kb) ich empfehle dir alle 1000 Werte in RAM im Adapter zu halten.
-
Meiner Meinung nach wäre es sicherlich sinnvoll zusätzlich zu adapter.setState() eine Art adapter.setStateNotChanged() Funktion ode reinen zusätzlichen Parameter bei setState() einzuführen der erlaubt zu definieren ob ioBroker vor setzen des Wertes prüfen soll ob der Wert nicht ohnehin schon auf diesen Wert steht. `
Meines WIssens macht die "setState" Logik das schon. Wenn der Wert nicht geändert ist wird der "lc" (=lastchanged") Timestamp nicht geändert sondern nur "ts" (Timestamp"). Also die Erkennungslogik ist da schon drin.
Und bei den Triggern kann man "ne" als "not equal" angeben, sodass man nur bei Änderungen notifiziert wird …
-
Meines WIssens macht die "setState" Logik das schon. Wenn der Wert nicht geändert ist wird der "lc" (=lastchanged") Timestamp nicht geändert sondern nur "ts" (Timestamp"). Also die Erkennungslogik ist da schon drin.
Und bei den Triggern kann man "ne" als "not equal" angeben, sodass man nur bei Änderungen notifiziert wird … `
Nun, dann ist aber doch immer noch der Overhead da das der normale "ts"-Timestamp angepasst werden muss von ioBroker. Des Weiteren sehe ich zumindest im admin adapter das alle state zur selben zeit immer grün aufleuchten, d.h. also eine änderung/anpassung anzeigen immer genau dann wenn mein adapter wieder Intervall-basiert die Werte mit setState() einfach blind setzt.
-
Korrekt. Auch wenn sich nur "ts" ändern ist das eine Änderung und wird an alle Subscriber notifiziert die das wissen wollen … Und ich denke Admin will alles wissen
So oder so: Ja bei Pull-Szenarien macht es Sinn nicht jede "Nicht-Änderung" per setState zu senden sondern nur Änderungen.
Also lokal die Werte Speichern (geht übrigens auch für mehr als 1000 Datensätze noch ... wird halt irgendwann zur Speicherverbrauchsfrage.
Ingo F
-
Korrekt. Auch wenn sich nur "ts" ändern ist das eine Änderung und wird an alle Subscriber notifiziert die das wissen wollen … Und ich denke Admin will alles wissen `
Das stimmt wohl, aber IMHO sollte Admin zumindest keine Änderung der States anzeigen (mit grünem Aufflackern) wenn sich nichts am Wert geändert hat. Vielleicht könnte man das ja im admin adapter mal anpassen/ändern?!?
Auch wäre es sicherlich sinnvoll wenn man dem Subscribe Befehl eine Option/Möglichkeit hinzufügen könnte nur auf Wertänderungen zu reagieren und nicht auf allgemeine Änderungen (z.b. anpassen der 'ts'), dann würde sich auch hier der overhead für alle subscriber sicherlich weiter reduzieren denn mit jedem weiteren Adapter wird das system ja dann nicht unnötig jeden adapter bei jeder Anpassung an einen state benachrichtigen sondern nur eben die die wirklich auch auf reine ts Änderungen reagieren wollen (was sicherlich die wenigstens wirklich wollen).
So oder so: Ja bei Pull-Szenarien macht es Sinn nicht jede "Nicht-Änderung" per setState zu senden sondern nur Änderungen. `
Genau deshalb argumentiere ich ja für diese Szenarien den adapter.setState() befehl dahingehend zu erweitern das man ihm mitteilen kann das er nur den State anpassen soll wenn der Wert des State wirklich unterschiedlich ist. Das könnte man ja IMHO ähnlich wie mit dem "ack=true/false" machen in dem man hier sowas wie folgendes implementiert:
adapter.setState(state, {val: 1, ack=true, forceChange=false});
In dem falle würde dann bei "forceChange=false" der setState Befehl eben selbst überprüfen ob sich "val" wirklich ändert und dann eben nur im falle einer Änderung wirklich weitermachen. Und forceChange würde natürlich per default auf true stehen zwecks rückwärts-kompatibilität. Denn wenn diese Möglichkeit geschaffen würde, dann müssten eben genau die Adapter die reine Pull-Szenarien bedienen nicht immer selbst solche recht komplexen Dinge wie Bluefox sie gezeigt hat selbst implementieren.
Denkst du das macht Sinn? Wenn ja könnte ich mir das mal anschauen und ggf. euch einen PullRequest dazu schicken wenn ich die Stelle finde und entsprechend angepasst bekomme?!?!
Also lokal die Werte Speichern (geht übrigens auch für mehr als 1000 Datensätze noch … wird halt irgendwann zur Speicherverbrauchsfrage. `
Naja, ich muss ja nicht wirklich mir die Daten merken sondern kann Sie eben per getState() wie von Bluefox gezeigt vorher abholen und selbst vergleichen, zumindest hab ich das hier so vor zu tun. Chicer wäre natürlich eben besagte "forceChange=false" Anpassungen an setState()
-
Auch wäre es sicherlich sinnvoll wenn man dem Subscribe Befehl eine Option/Möglichkeit hinzufügen könnte nur auf Wertänderungen zu reagieren und nicht auf allgemeine Änderungen (z.b. anpassen der 'ts') `
Der JavaScript-Adapter macht das schon so: https://github.com/ioBroker/ioBroker.ja ... some-state
Zu der setState-Idee müsste Bluefox was sagen
Ingo F
-
Auch wäre es sicherlich sinnvoll wenn man dem Subscribe Befehl eine Option/Möglichkeit hinzufügen könnte nur auf Wertänderungen zu reagieren und nicht auf allgemeine Änderungen (z.b. anpassen der 'ts') `
Der JavaScript-Adapter macht das schon so: https://github.com/ioBroker/ioBroker.ja ... some-state `
Na dann sollte doch IMHO der adapter.subscribeStates() Methode die gleiche Funktionalität spendiert werden
Zu der setState-Idee müsste Bluefox was sagen `
Na dann hoffe ich mal er liest hier aufmerksam mit. Gerne kann ich auch (um die Verständigung zu verbessern) das ganze nochmal in Englisch verfassen wenn das helfen sollte.
-
Denke Deutsch passt
-
Hier noch ein Argument dafür eine setState() option zu schaffen bei der nur bei Änderungen des val wertes wirklich das setState durchkommt:
2017-01-20 14:01:49.187 - info: admin.0 Unsubscribe from all states, except system's, because over 3 seconds the number of events is over 60 (in last second 0) 2017-01-20 14:02:20.636 - info: admin.0 Subscribe on all states again
Dies passiert bei mir jetzt bei jedem Abholen der Datenwerte (jede X Minuten). Hab zwar nun die Methode die Bluefox hier beschrieben hat implementiert und das klappt auch gut, trotzdem denke ich eine Erweiterung der setState() method könnte nicht schaden.
-
Es geht hier natürlich um die Entwicklung eines eigenen Adapters, dort gibt es nicht setState() sondern eben "adapter.setState()" genauso wie "adapter.getState()" und das verhält sich leider auch etwas anders, denn es ist immer asynchron sodass sowas z.B. nicht zu funktionieren scheint:
for(var i=0; i < 100; i++) { var newval = i; var stateName = "test." + i; adapter.getState(stateName, function(err, state) { if(state.val != newval) adapter.setState(stateName, {ack: true, val: newval); }); }
Das Problem hierbei ist, wie gesagt, das adapter.getState() asynchron ist und somit "newval" und "stateName" innerhalb der anyonmen Funktion die an adapter.getState() übergeben wird zur Ausführungszeit leider die falschen werte hat. Zumindest ist das hier gerade der Fall. `
Bin ein bisschen spät mit der Antwort, aber vielleicht hilft es ja noch zum Verständnis. Bluefox hat dir eine recht elegante Version geschrieben, die sequentiell abläuft. Man kann das Problem aber auch lösen, indem man innerhalb der Schleife eine Funktion definiert, die die Schleifenvariable kapselt -> Stichwort Closure.
for(var i=0; i < 100; i++) { // Hier beginnt die umhüllende Funktion (function(i) { // Der Wert von i ändert sich innerhalb dieser Funktion nicht mehr, er ist gekapselt var newval = i; var stateName = "test." + i; adapter.getState(stateName, function(err, state) { if(state.val != newval) adapter.setState(stateName, {ack: true, val: newval); }); // und hier wird die Funktion aufgerufen und der aktuelle Wert der Schleifenvariable übergeben })(i); }
Der Code ist ungetestet, sollte aber laufen. Ich hoffe, ich habe mich jetzt nicht vertippt.