NEWS
Wie mehrere Werte nach Änderung synchron verrechnen
-
@sputnik24 sagte: Meine momentane Lösung:
Diese Lösung würde mir auch als erste einfallen.
Eine Möglichkeit wäre, auf jeden Datenpunkt zu triggern, sich den Wert und den Zeitstempel in je einer Variablen zu merken und in einer gemeinsamen Funktion zu prüfen, ob die Differenz aller Zeitstempel im Bereich zwischen +/- 2 s liegt; falls ja, dann berechnen. Es bieten sich Arrays an (für IDs, Zeitstempel, Werte), etwa so:const ids = [ 'id1', 'id2', '...' ]; const tss = []; const vals = []; on({id: ids}, function(dp) { let idx = ids.indexOf(dp.id); tss[idx] = dp.state.ts; vals[idx] = dp.state.val; for(let i = 0; i < tss.length; i++) { if(Math.abs(dp.state.ts - tss[i]) > 2000) return; } // Berechnungen });
-
@paul53 Ok, dann ist meine Lösung zumindest nicht grundsätzlich falsch.
Ich würde es irgendwie begrüßen, wenn getState() mit der Rückgabe wartet, falls ein Schreibvorgang für den Datenpunkt noch in der Queue ist. Man findet irgendwie sehr wenig bis gar nichts dazu im Netz. Entweder bin ich einer der wenigen mit dem Problem, was kein Problem ist, oder vielen ist es nicht bewusst?
Das mit der Differenz der Zeitstempel klingt interessant, aber auch komplex. V.a. wird es nicht für alle Datenpunkte funktionieren, da z.B. der Energiezähler für Import und Export sich nur dann ändert, wenn zum letzten Mal Strom bezogen oder eingespeist wurde. Dennoch ist der Wert ja gültig und synchron. Hier müsste ich Ausnahmen einbauen, auch nicht eleganter als meine momentane Lösung.
-
@sputnik24 sagte: Energiezähler für Import und Export sich nur dann ändert,
Erfolgt nicht alle 10 s ein Polling? Dann sollte auch der Zeitstempel von Datenpunkten, deren Wert sich nicht ändert, aktualisiert werden. Der gewählte Trigger reagiert auf Aktualisierung.
@sputnik24 sagte in Wie mehrere Werte nach Änderung synchron verrechnen:
wenn getState() mit der Rückgabe wartet, falls ein Schreibvorgang für den Datenpunkt noch in der Queue ist.
Das synchrone getState() holt sich den Wert aus dem Puffer der Javascript-Instanz und ist schneller als setState(). Bei meinem Vorschlag wird kein getState() benötigt, denn alle Werte befinden sich im Array vals.
-
@paul53 Das Polling erfolgt alle 10 s, aber der Zeitstempel ändert sich nur, wenn sich auch der Wert "ändert". Ich weiß allerdings nicht, auf welcher Ebene das festgestellt wird, also ob sich der Wert wirklich numerisch ändern muss oder ob im modbus Protokoll ein Zeitstempel mitgegeben wird. Siehe den Import-Zähler als Beispiel, der aufgrund PV und Batteriespeicher lange nicht aktualisiert wurde, da kein Strombezug:
Zu einem Array, stimmt, das wäre wiederum recht elegant.
Aber Fazit für mich erstmal: Es ist nicht ganz falsch, was ich mache, und es gibt keine Bordmittellösung (eben ein getState(), welches wartet, falls ein Schreibvorgang in der Queue ist).
-
@sputnik24 sagte: der Zeitstempel ändert sich nur, wenn sich auch der Wert "ändert".
Dann erstelle ein Issue auf Github. Der Zeitstempel sollte beim Polling mit jedem Zyklus aktualisiert werden.
@sputnik24 sagte in Wie mehrere Werte nach Änderung synchron verrechnen:
getState(), welches wartet, falls ein Schreibvorgang in der Queue ist).
Das asynchrone getState() (mit Callback-Funktion) holt sich den Zustand des Datenpunktes aus dem js-controller. Ob es wartet, falls eine Schreiboperation im Gange ist, kann ich nicht beurteilen.
-
@sputnik24 Ich denke, Du bist einer der wenigen, aber nicht der Einzige. (;-)
Genau Dein Problem habe ich auch beim WR-Auslesen via ModBus und habe auch noch keine elegante und 100% zuverlässige Lösung gefunden/umgesetzt.
Momentan mache ich es noch mit einer (sehr) kleinen Pause (ms-Bereich, da meine ModBus-Abfrage-Durchläufe jede Sekunde erfolgen (also nicht "nur" alle 10s) und für einen ganzen Durchlauf nur 250-400ms benötigt werden) und weil das nicht zuverlässig ist, blende ich unplausible Werte (z. B. Verbauch negativ (das kann sich bei berechnetem Verbrauch aus nicht synchronen Werten für Erzeugung, Bezug und Einspeisung leider ergeben)) im VIS nicht ein, sondern warte dann auf den nächsten plausiblen Wert, wohlwissend, daß der auch falsch sein könnte.Den Ansatz von @paul53 hatte ich bisher noch nicht weiter verfolgt, da er ja doch nicht ganz trivial ist und ich den "Aufwand" erst treiben wollte, wenn ich mir sicher bin, daß die bei einer ModBus-Abfrage gelieferten Daten synchron sind und die Asynchronität nicht bereits "im WR entsteht", also "bereits mitgeliefert wird".
Könnt Ihr Letzteres eindeutig bestätigen/ausschließen?
Bei der Gelegenheit, falls Ihr SMA-WR und/oder -Homemanager (HM) einsetzt:
Ich habe alles an einem zentralen Switch und lasse jedem Gerät von einem DHCP-Server (FritzBox7590) IP-Adressen zuweisen. Das funktioniert alles grundsätzlich prima. Allerdings hängt sich der ModBus fast jedesmal auf, wenn der Switch oder die FritzBox neu gestartet wird. Dann muß man den WR ersteinmal DC- und AC-seitig abschalten, einem Moment warten und ihn dann wieder einschalten. Danach ist dann der ModBus in der Regel "wieder da".
Der Homemanger ist bei mir im ioBroker via sma-em-Adapter eingebunden. der geht bei Switch- und/oder FritzBox-Restart auch auf gelb. Fehlermeldung im Log:
(6854) UDP Socket error: Error: bind EADDRINUSE 0.0.0.0:9522
Ihn wieder auf grün zu bekommen hat bisher nur via (oft mehrfachen) Restart der FritzBox und des Raspi (auf dem ich ioBroker laufen habe) funktioniert. Die erforderliche Reihenfolge konnte ich bisher noch nicht eindeutig ermitteln.Kann das jemand erklären oder weiß, wie man das verhindert?
Ich als Ahnungsloser würde die Fehlermeldung so interpretieren, daß der Adapter/die Instanz der Meinung ist, daß die Adresse 0.0.0.0:9522 bereits benutzt wird und daher die Verbindung verweigert. Ich frage mich dann aber, warum er überhaut 0.0.0.0 probiert und nicht die tatsächlich konfigurierte Adresse.
Ein Erklärungsversuch wäre, daß der HM nach einem Restart von Switch oder Router erst nach einer gewissen Zeit via DHCP seine IP-Adresse bekommt und bis dahin 0.0.0.0 ist? Das würde aber auch bedeuten, daß der sma-em-Adapter nicht aktiv versucht, eine Verbindung nur zur konfigurierten IP-Adresse aufzunehmen, um die UDP-Multicastpakete vom HM auszuwerten, sondern "nimmt, was er kriegt" und das könnte zunächst dann tatsächlich 0.0.0.0 sein!? -
@andersmacher Ja das stimmt. Dennoch wundere ich mich. Die meisten Adapter funktionieren doch nach dem Prinzip, Daten von Extern zu holen und in Datenpunkte zu schreiben, die dann untereinander nie synchron sind. Vermutlich, weil man bei den meisten Adaptern die Daten nicht in hoher Frequenz ausliest oder miteinander verrechnen will.
Was die Synchronität der Daten aus dem modbus Register angeht, stimme ich dir voll zu. Mein Momentanverbrauch rutscht auch ab und zu ins Negative (fang ich ab) und selbst den positiven Werten kannst du nicht trauen. Kann gravierend ist das Thema bei den Skalierungsfaktoren. Wenn hier Rohwert und SF nicht sync sind, gute Nacht. Da war/ist sogar noch ein riesen Bug im modbus Adapter, hatte dazu ein issue erstellt: https://github.com/ioBroker/ioBroker.modbus/issues/127
Ich denke, vielen wird das Problem gar nicht bewusst sein und bei den meisten Daten fällt es auch nicht sofort auf. Ich überlege, ein issue im javascript Adapter zur erstellen, um das mit den Entwicklern zu diskutieren. Eine getState() Funktion, die wartet, bis ein Schreibvorgang für den DP beendet ist, fänd ich immer noch am elegantesten. Dann muss ich mich als Endnutzer nicht darum kümmern.
-
@sputnik24 Ich habe mir ´mal Dein issue durchgelesen. Du steckst in der Thematik viel tiefer drin, als ich, aber mein Gedanke mit weitergehender Programmierung zu warten, bis sichergestellt ist, daß die Daten synchron geliefert werden, war ja wohl doch eine glückliche Intuition.
Ich nutze, wie oben geschrieben, SMA-Geräte, da ist mir noch nie bewußt geworden, daß ich mich um Skalierungsfaktoren kümmern müßte.
- Ist das eine Eigenheit von Fronius oder habe ich da ´was verpennt?
- Meine ausgelesenen Werte scheinen mir jeder für sich genommen (dann gibt es die Synch-Problematik ja nicht), ohne daß ich da bisher irgenwelche Skalierungen beachtet habe, bisher alle palusibel zu sein.
Skaliert der Fronius die in den Registern abgelegten / bereitgestellten Daten von selber dauernd anders? - Hängt das mit Auflösung und Wertebereich zusammen oder warum macht er das? Ich hätte gedacht, daß ein Skalierungsfaktor z. B. dazu dient, externe Verhältnisse zu berücksichtigen, z. B. wenn Strom- oder Spannungswandler benutzt werden. Dann würde man diese Faktoren aber eigentlich nur einmal manuell setzen / eingeben und ab dann wären sie konstant!?
Mit gelben sma-em- oder ModBus-Instanzen oder solchen Fehlermeldungen (UDP Socket error: Error: bind EADDRINUSE 0.0.0.0:9522) im Log hat kein anderer, der hier mitliest, Probleme?
-
@sputnik24 Ich kann Dir nur sagen, wie ich es mit NodeRed lösen würde - aber vielleicht hilft ja die Logik, damit ihr das in Javascript oder Blockly direkt umsetzen könnt.
Ich würde alle Werte in einer Objektvariable sammeln und jede Aktualisierung eines Wertes triggert einen Timer von mir aus 250ms, der sich aber mit jedem neuen Wert/Aktualisierung wieder verlängert. Erst wenn der Timer keinerlei Aktualisierung mehr feststellt, ist das Objekt aktuell bzw. vollständig aktualisiert und wird weiterverarbeitet.
Damit wartet man keine feste Zeitspanne - sondern geht einfach davon aus, dass wenn keine Aktualisierung mehr erfolgt, dass dann alle zu einer Aktualisierung gehörigen Werte beschrieben sind.
Wie man das in JS umsetzt, weiß ich nicht - aber vielleicht wäre das ein brauchbarer Weg. Alles andere mit von vorneherein festgelegten Zeitspannen würde mir auch nicht gefallen.
Die maximale Verzögerung würde dann nach Aktualisierung des letzten Wertes + 250ms betragen. Mit 250ms sollte eine maximale Latenz zwischen 2 Aktualisierungen abgedeckt haben. Diesen Wert kann man ggf. soweit runtersetzen, dass eben die "Vollständigkeit" des Objektes nicht zu früh diagnostiziert wird - wenn ich mich hoffentlich verständlich ausgedrückt habe.
Mit anderen Worten:
Sprich Du reagierst nicht mit EINEM on() auf einen der Datenpunkt, sondern mit ALLEN on()s aller zum Aktualisierungszyklus zugehörigen Datenpunkte und beschreibst mit jedem EIN Objekt und triggerst mit allen on()s den gleichen Timer. Nach Ablauf dieses Timers wird dieses Objekt dann weiter verarbeitet.Oder noch anders ausgedrückt:
Du holst nie die Werte, sondern jeder zum Aktualisierungszyklus gehörige Wert muss triggern. Ändert sich ein Wert nicht, dann wird der entsprechende Wert in dem Objekt nicht aktualisiert, aber dann ebenfalls mitgeschickt. -
@mickym
Ich nutze für Modbus auch Node Red. Da nehme ich zum zwischenspeichern Node Red flow- interne Variablen. Die sind synchron. Die Ergebnisse der Berechnungen damit kommen dann in den ioBroker. -
@mickym Servus. Danke für den Input, die Idee gefällt mir sehr gut. Ich habe es auf meiner Spielwiese mal für wenige Register testweise in Javascript umgesetzt und deine Logik funktioniert. Hier mal Script aus der Spielwiese, was noch nichts berechnet, sondern nur mal logs ausspuckt:
var register = []; var timer; var listenTo = [ 'modbus.0.holdingRegisters.1.40083_W', 'modbus.0.holdingRegisters.1.40100_DCW', 'modbus.0.holdingRegisters.1.40314_3_DCW', 'modbus.0.holdingRegisters.200.40087_W']; //init array with current values listenTo.forEach(function(item,index,array) { register[item] = getState(item).val; }); on({id: listenTo}, function(data) { clearTimeout(timer); log(data.id + ': ' + data.state.val); register[data.id] = data.state.val; timer = setTimeout(printRegister, 100); }); function printRegister() { log('All values updated - let us calculate'); listenTo.forEach(function(item,index,array) { log(item + '-> ' + register[item]); }); }
Die printRegister() Funktion wird erst aufgerufen, wenn der letzte Wert geschrieben wurde. 50 ms sind aber schon zu wenig, hier läuft der Timer zwischendrin ab.
Deine Lösung ist sicherlich eleganter, da wir definitiv mit den echten Werten rechnen, egal wie lang es braucht, bis der Datenpunkt geschrieben ist. Das Problem der Race Condition bleibt, im obigen Beispiel gehen wir davon aus, dass nie mehr als 100 ms zwischen zwei on-events vergehen - oder müssen uns händisch an die Realität annähern.
-
@sputnik24 Genau. Freut mich dass es klappt. Ich bin ja deswegen von 250ms zwischen 2 on events ausgegangen, da ich das schon mehrfach als magisch e Grenze gesehen habe. Letztlich finde ich den Verzug insgesamt zugunsten der Akuratess eher zu verschmerzen. Schließlich heißt es das ja nicht, dass es jedesmal 250ms sind. Sondern nur 250ms nach dem letzten Event. Aktualisieren sich die Werte schneller um so eher beginnt ja auch der Timer abzulaufen.
Die Zeitspanne multipliziert sich also nicht mit der Anzahl der Datenpunkte. Das Produkt stellt lediglich den maximalen Delay dar, ist aber eben nicht konstant.