Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Deutsch
    3. Skripten / Logik
    4. JavaScript
    5. Wie mehrere Werte nach Änderung synchron verrechnen

    NEWS

    • Neuer Blog: Fotos und Eindrücke aus Solingen

    • ioBroker@Smart Living Forum Solingen, 14.06. - Agenda added

    • ioBroker goes Matter ... Matter Adapter in Stable

    Wie mehrere Werte nach Änderung synchron verrechnen

    This topic has been deleted. Only users with topic management privileges can see it.
    • S
      Sputnik24 last edited by

      Hallo zusammen,

      ich lese mit dem modbus Adapter alle 10 s Werte aus meinem Wechselrichter der PV-Anlage aus. Der modbus Adapter schreibt die neuen Werte nacheinander in die Datenpunkte, zwischen dem ersten und den letzten Wert liegen ca. 150 ms, je nach Systemauslastung natürlich. Diese Rohwerte verrechne ich dann in einem Javascript zu weiteren Werten, die mich sowohl in der Visualisierung als auch der Statistik (loggen in influxdb und Darstellung mit Grafana) interessieren.

      Problem:
      Ich reagiere mit on() auf einen der Datenpunkte, hole mir die Werte der anderen mit getState() und berechne daraus die Zielwerte. Leider liefert getState() für die anderen Werte aber noch den alten Wert, nicht den aktualisierten. Selbst wenn ich mit on() auf den zuletzt geschriebenen Datenpunkt reagiere, sind die vorherigen Werte noch nicht alle geschrieben. Kurz: Die Datenpunkte sind nicht synchron hinsichtlich des Auslesevorgangs und damit sind die berechneten Werte falsch.

      Meine momentane Lösung:

      on({id: 'modbus.0.holdingRegisters.200.40087_W'}, function(data) {
          setTimeout(function() {
              //alle Berechnungen mit getState() aus modbus.0.*
          }, 500);
      });
      

      Ich warte also nach Änderung des Wertes, in dem Fall 500 ms, in anderen Fällen sogar 1 und 2 s, bis ich die Daten mit getState() hole und verarbeite. Funktioniert auch soweit. Aber mir gefällt die Lösung nicht und ich habe meine Zweifel, dass das die richtige Vorgehensweise ist.

      • Ich bin kein Fan davon, race conditions mit Timern zu umgehen. Man hat auch hier keine Garantie, dass die Daten synchron sind, außer man setzt den Timer sehr hoch. Dann wären Rohwerte und berechnete Werte aber nicht mehr zeitsynchron in der grafischen Darstellung (sind sie ja schon jetzt nicht, die 500 ms fallen aber nicht auf).
      • In der Visualisierung führt das zumindest dazu, dass angezeigte Rohwerte und berechnete Werte (z.B. Ertragsbalken in %) sich zeitversetzt auf den neuen Wert ändern, was zumindest unschön aussieht.

      Lange Rede: Ist die Vorgehensweise oben so überhaupt korrekt oder gibt es eine elegantere und robustere Lösung?

      Danke euch
      Daniel

      paul53 1 Reply Last reply Reply Quote 0
      • paul53
        paul53 @Sputnik24 last edited by paul53

        @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
        });
        
        S 1 Reply Last reply Reply Quote 1
        • S
          Sputnik24 @paul53 last edited by

          @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.

          paul53 1 Reply Last reply Reply Quote 0
          • paul53
            paul53 @Sputnik24 last edited by paul53

            @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.

            S 1 Reply Last reply Reply Quote 1
            • S
              Sputnik24 @paul53 last edited by

              @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:
              Unbenannt.png

              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).

              paul53 1 Reply Last reply Reply Quote 0
              • paul53
                paul53 @Sputnik24 last edited by paul53

                @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.

                1 Reply Last reply Reply Quote 1
                • A
                  Andersmacher last edited by

                  @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!?

                  S 1 Reply Last reply Reply Quote 0
                  • S
                    Sputnik24 @Andersmacher last edited by

                    @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 created this issue in ioBroker/ioBroker.modbus

                    closed New Scale Factor not used immeditaley, old one used instead #127

                    A mickym 2 Replies Last reply Reply Quote 0
                    • A
                      Andersmacher @Sputnik24 last edited by

                      @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.

                      1. Ist das eine Eigenheit von Fronius oder habe ich da ´was verpennt?
                      2. 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?
                      3. 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?

                      1 Reply Last reply Reply Quote 0
                      • mickym
                        mickym Most Active @Sputnik24 last edited by mickym

                        @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.

                        P S 2 Replies Last reply Reply Quote 0
                        • P
                          peterfido @mickym last edited by

                          @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.

                          1 Reply Last reply Reply Quote 0
                          • S
                            Sputnik24 @mickym last edited by

                            @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.

                            mickym 1 Reply Last reply Reply Quote 0
                            • mickym
                              mickym Most Active @Sputnik24 last edited by mickym

                              @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.

                              1 Reply Last reply Reply Quote 0
                              • First post
                                Last post

                              Support us

                              ioBroker
                              Community Adapters
                              Donate

                              501
                              Online

                              31.9k
                              Users

                              80.1k
                              Topics

                              1.3m
                              Posts

                              5
                              13
                              545
                              Loading More Posts
                              • Oldest to Newest
                              • Newest to Oldest
                              • Most Votes
                              Reply
                              • Reply as topic
                              Log in to reply
                              Community
                              Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                              The ioBroker Community 2014-2023
                              logo