mcm4iob / ioBroker.pid

Configurable PID Controller for ioBroker
MIT License
2 stars 4 forks source link

[Feature]: Ideen / Diskussion #6

Closed fu-zhou closed 1 year ago

fu-zhou commented 1 year ago

Hallo, echt stark, dass du da dran gehst! Mit dem PID von Philmod habe ich auch schon rumgespielt, da fehlt mir aber die Stellwert-Begrenzung, es kann nur der I-Anteil limitiert werden (Anti-Wind-Up).

Nachdem ich jetzt mit dem npm PI-Controller Erfahrung gesammelt habe und meine berufliche Heimat die Prozessleittechnik ist, würde ich hier gerne ein paar Ideen einfließen lassen - für die Punkte unten wäre jeweils ein Objekt hilfreich, das auch dynamisch aus einem anderen Skript oder vis verändert werden kann:

  1. Parametrierbares Totband: Der Betrag der Regelabweichung w-x muss mindestens so groß sein, dass der Regler die Stellgröße neu berechnet. Z.B.: Raumsollwert = 20°, Totzone: 0.5° => sind 19.5° erreicht, bleibt der Regler stehen, die Stellgröße für's Heizen hält den letzten Wert. Überschreitet dann die Raumtemperatur z.B. durch Sonneneinstrahlung die 20.5°, wird die Stellgröße vom Regler neu berechnet. Beim PI-Controller habe ich das mit Javascript aktuell so gelöst:
    if (Math.abs(w - x) < 0.2 { // Regelabweichung mindestens 0.2°, um neuen Stellwert zu berechnen = Totband
        var y = pi.Control(0); // Stellwert nicht verändern
    }
  2. Bool-Eingang, um die Stellgröße temporär unverändert zu lassen/ den Regler zu pausieren, z.B. bei einer Sprungantwort. Das ist z.B. ein Thema beim PV-Überschussladen. Beim Umschalten der Wallbox (goE-Charger) von 1 auf 3 Phasen springt die Ladeleistung von 3.7 auf 4.2 kW. Der P-Anteil arbeitet sofort gegen den Sprung und es wird auf 1 Phase zurückgeschaltet. Beim PI-Controller habe ich das mit Javascript aktuell so gelöst:
    if getState('mqtt.0.go-eCharger.modelStatus').val == 23) { // bei Phasenumschaltung Regler pausiern
        var y = pi.Control(0); // Stellwert nicht verändern
    }
  3. Stellgröße begrenzen. Z.B.: abhängig von der Außentemperatur soll eine Ventilstellung auf 30, 50, 70% begrenzt werden. Selbst wenn es im Raum zu kalt bleibt, wird die Stellgröße auf max. diesen Wert geschoben. im PI-Controller gibt es dafür pi.setOutputMax(100); // Begrenzung auf 100

Ist das okay, wenn ich Anforderungen, Ideen hier als "Issue" einbringe?

Danke für deine Mühe!

mcm1957 commented 1 year ago

Ja Anmerkungen / Ideen sind herzlich willkommen.

ad a) werd ich mir ansehen. Ev. erst in V0.0.2. Allerdings ist mir der Sinn hier nur bedingt klar - ein Totband würde ja den I Anteil zumindest temporär blockiern. Mit i != 0 sollte sich der Stellwert (CV) auch bei constantem Eingang (PV) ja ändern. Andrerseits einbauen kann man es ja - wer es in welchem setting dann aktiviert ist ja nicht Sache des Adapters.

ad b) ich hab einen State ("role:switch") vorgesehen mit dem der Regler angehalten wird, d.h. keine zyklische Neuberechnungen ausführt. Siehe auch in der (derzeitigen) Config das Flag AutoStart mit dem man einstellen kann, ob der Regler sofort losläuft oder via State zu starten ist.

ad c) Ich hab ein Min und Max Value für den Stellwert (CV) vorgesehen bei dem der Regler "anschlägt". Bin mir nur noch nicht sicher ob ich den I Anteil auch mit diesem Wert begrenzen soll oder "weiterlaufen" lassen soll. Bin eher für ein Begrenzen. (Input gerne gesehen - mein Studium der Regelungstechnik ist schon 40 Jahre her :-) )

fu-zhou commented 1 year ago

zu a) nochmal: das Totband könnte man auch in einem (Blockly-) Skript bauen und damit den Bool-Eingang zum pausieren des Reglers beschalten.

Die Anforderung des Totbands kommt aus der Tatsache, dass manche Regelkreise nicht ausgeregelt werden können, weil das Stellglied nicht fein genug arbeitet (z.B. mechanisch) oder die Trägheit des Regelkreises zu groß ist ("Hallenheizung"). Bevor dann zwischen 2 Zuständen hin- und hergeschaltet wird, nimmt man eine bleibende Regelabweichung in Kauf, um Verschleiß und Energieverbrauch zu reduzieren. In meinem Fall mit der Wallbox passiert Folgendes: Der Regler ermittelt die erlaubte Leistung der Wallbox (Sollwert Netzbezug = 0, Stellglied = Wallbox), die resultierende Ladestromstärke übergebe ich an die Wallbox. Die Wallbox kann aber nur ganze Ampere, d.h. wenn mit 7 A geladen wird und ich Strom ins Netz einspeise (Netzbezug < 0), schiebt der Regler die Ladeleistung hoch und irgendwann sind 8 A erreicht und die Wallbox macht den Leistungssprung (1-phasig) von 1.6 auf 1.85 kW. Dann ist der Netzbezug > 0 (es kommt gerade nur 1.9 kW von der PV-Anlage und das Haus hat ja noch eine Grundlast), ich möchte aber nur von PV Laden, nicht aus dem Netz und der Regler regelt wieder runter, bis die Wallbox dann wieder bei 1.6 kW (7A) liegt und ich wieder ins Netz einspeise und dann geht's wieder von vorne los. In diesem Fall möchte ich der Ladeelektronik im Auto und der Wallbox ersparen, ständig hin- und herzuschalten und ich baue das Totband ein.

Vielleicht hat das noch einmal geholfen...

fu-zhou commented 1 year ago

zu c): der I-Anteil muss auch anhalten (s. Anti-Wind-Up https://github.com/iobroker-community-adapters/ioBroker.pid/issues/6#issuecomment-1481972669)

fu-zhou commented 1 year ago

Und ganz klar: Ich teste auch gerne, der Regler ist schon auf meiner ioBroker Testumgebung installiert!

fu-zhou commented 1 year ago

Vielleicht noch ein Hinweis: Es gibt eine freie SPS-Bibliothek "OSCAT". Hier ist auch ein PID-Regler dabei, der eigentlich alles kann, was man so braucht und ein bisschen mehr ;-) Hier der Link zu Dokumentation der gesamten Bibliothek (ist ein PDF), der PID Regler ist auf S. 395 - 397 beschrieben (23.8. CTRL_PID) http://www.oscat.de/de/component/jdownloads/summary/2-oscat-basic/7-oscat-basic333-de.html

Wind-Up ist hier auf S. 387/388 gut beschrieben.

Im Idealfall legt der ioBroker PID-Adapter die Objekte an und verwendet sie, wie der CTRL_PID: (Teilweise habe ich die jetzt schon im Konfig-Dialog der Instanz in ioBroker gesehen)

Inputs
ACT : REAL (Aktualwert)
SET : REAL (Sollwert)
SUP : REAL (Rauschunterdrückung/ Totband)
OFS : REAL (Offset für den Ausgang)
M_I : REAL (Eingangswert für manuellen Betrieb)
MAN : BOOL (Umschalten auf Handbetrieb, MANUAL = TRUE)
RST : BOOL (Asynchroner Reset-Eingang)
KP : REAL (Verstärkung des Reglers)
TN : REAL (Nachstellzeit des Reglers)
TV : REAL (Vorhaltezeit des Reglers)
LL : REAL (untere Ausgangsbegrenzung)
LH : REAL (obere Ausgangsbegrenzung)

Outputs
Y : REAL (Stellwert)
DIFF : Real (Regelabweichung)
LIM : BOOL (TRUE, wenn der Ausgang ein Limit erreicht hat)
mcm1957 commented 1 year ago

Danke für diene fundierte Hilfe und die Links.

Bezüglich Parameter hab ich noch Fragen bzwwürde ich das so einbauen. Ich notier mal die iob Typen (bezüglich Namen denk ich noch mal drüber nach):

Inputs
### als iob States, Typ: number, Role: value, read/write ###
ACT : REAL (Aktualwert) 
SET : REAL (Sollwert)
SUP : REAL (Rauschunterdrückung/ Totband)
OFS : REAL (Offset für den Ausgang)
M_I : REAL (Eingangswert für manuellen Betrieb)

### als iob States, Typ: Boolean, Role: Switch, read/write ###
HOLD: BOOL (Anhalten des Reglers) 
MAN : BOOL (Umschalten auf Handbetrieb)

### als iob States, Typ: Boolean, Role: Button, write only ###
RST : BOOL (Asynchroner Reset-Eingang)

### als iob config Paramaters, Typ: Number, optional ###
LL : REAL (untere Ausgangsbegrenzung)                       # iob config Parameter, number, optional
LH : REAL (obere Ausgangsbegrenzung)                       # iob config Parameter, number, optional

### siehe Kommentar unten ###
KP : REAL (Verstärkung des Reglers)
TN : REAL (Nachstellzeit des Reglers)
TV : REAL (Vorhaltezeit des Reglers)

Outputs
### als iob States, Typ: Number, Role: Value, read only ###
Y : REAL (Stellwert)                                                          # iob State, Number, Value, RO
DIFF : Real (Regelabweichung)                                        # iob State, Number, Value, RO

### als iob States, Typ: Boolean, Role: Indicator, read only ###
LIM : BOOL (TRUE, wenn der Ausgang ein Limit erreicht hat) # iob State, Boolean Indicator, RO

Bezüglich der Regelparamater bin ich ein wenig verunsichert. Meiner Ken ntnis nach hat ein PID Regler 3 Stellgrößen für die Regelcharacteristik (bzw. 4 wenn man die Zykluszeit dazu nimmt): k_p - Proportionalanteil k_i - Integralanteil k_d - Differentialanteil

Der Ausgangswert des Reglers berechnet sich dann relativ einfach durch k_p*error + k_i * sumError) + k_d * diffError mit

error = Soll - Ist
sumError = lastSumErr + error * cycletime
diffError = (error - lastError)/cycletime

Im Prinzip ist dies im Code von node-pid-controller auch so implementiert. Ich habe geplant diesen Code zu verwenden bzw. mit den notwendigen Anpassungen betreffend hold & limit in den Adapter zu migrieren.

In deiner Auflistung sind jedoch TN und TV angegeben. Sehe ich das richtig, dass dies nur eine andere Schreibweise bzw. anderer Paramatertyp ist und man das Regelverhalten bezüglich Zeitkonstante in Verstärkungsanteiul jederzeit umrechnen kann?

Oder anders: k_p, k_i, k_i wären als Paramater sicher auch OK. Oder?

fu-zhou commented 1 year ago

Die Objekte finde ich gut und stimme zu, dass du dir die Namen nochmal ioBroker-kompatibel überlegen kannst. Die 3 Einstellparameter des Regler können unterschiedlich dargestellt werden. In der OSCAT Doku steht dazu auf S.397:

Die Regelparameter werden in der Form KP, TN und TV angegeben, falls die Parameter als KP, KI und KD vorliegen können
sie entsprechend der folgenden Formel umgerechnet werden:
TN = KP/KI und TV = KD/KP

k_p, k_i, k_d: so wie im npm PID-controller verwendet, würde ich sagen. Und die können aus meiner Sicht auch im Konfig-Dialog angegeben werden, so wie die Zykluszeit, also nicht als (dynamisches) Objekt. Regelstrecken ändern sich in der Regel nicht (so sehr), so dass diese Parameter dynamisch anpassbar sein müssen. Und mal ehrlich: der Regler-Adapter muss ja für jeden ioBroker-Nutzer versteh- und verwendbar sein und nicht nur für Spinner wie mich.

Auf der anderen Seite ist das Ermitteln der Regelparameter natürlich einfacher, wenn die in den Objekten beschreibbar sind (=Rumprobieren) , sonst muss man ja immer wieder in die Instanz-Konfig rein, aber das kann ich in der Praxis mal testen, wenn es so weit ist.

mcm1957 commented 1 year ago

@fu-zhou Wenn du Zeit / Lust hast, schau dir mal den aktuellen GITHUB Stand an. Er sit alles andere als fertig (sozusagen pre-pre-alpha) aber die Richtung sollte erkennbar sein.

Um hier issues nicht übermäßig frü Diskussionen zu strapazieren, hab ich Discussions freigeschaltet. Ich rege an dass wir dort disjutieren bis das Ganze soweit reif ist, dass es als 0.0.x ins Testerforum kann.

https://github.com/iobroker-community-adapters/ioBroker.pid/discussions/9

DANKE für feedback

fu-zhou commented 1 year ago

Gehe morgen Abend mal dran und steige dann auf die Diskussion um. Bin schon sehr gespannt!

mcm1957 commented 1 year ago

Ich habe mal eine alpha zum allgemeinen Test angeboten: https://forum.iobroker.net/topic/64250/test-neuer-adapter-pid-pid-regler-v0-0-1-alpha-x

GitHub Discussion hab ich damit wieder geschlossen

fu-zhou commented 1 year ago

Jetzt hätte ich dir gerne noch auf die Fragen in der Github Discussion geantwortet. An die Erste kann ich mich noch erinnern: Eine zyklisch Berechnung der Stellsignals ist der richtige Weg, so macht das auch die Prozessleittechnik. Eine Änderung des Soll- oder Istwerts braucht/ sollte nicht zu einer "Zwischenberechnung" führen. Den Rest habe ich jetzt nicht mehr auf dem Schirm...

Zum detailierten Test komme ich erst ab morgen Abend, bin im Moment unterwegs, klinke mich dann im ioBroker Forum ein.

mcm1957 commented 1 year ago

Ich hatte dort geschrieben:

Ich habe überlegt und werde die Art wann eine Neuberechnung stattfindet nochmals ändern.
Derzeit wird zyklisch neu berechnet und zusätzlichimmer dann wenn ein neuer IST Wert gesetzt wird. Das hat aber m.E. den Nachteiul dass bei einer sehr raschen Wikung des Stellwerts auf den IST Wert ununterbochen neu berechnet wird. Und das erscheitn inakzeptabel.

Ich plane daher nun Folgendes:

Funktion der Hysterese:
Ist der neue IST Wert um weniger als die Hysterese zum alten IST Wert unterschiedlich, so wird der neue IST Wert ignoriert und NICHT verspeichert.

a) Ist "run" inaktiv, dann wird nichts neu berechnet, d.h. der Ausgangswert bleibt konstant

b) Ist eine Zykluszeit angegeben und "run" aktiv, dann wird alls x ms neu berechnet, unabhängig davon wann und ob ein neuer IST Wert gesetzt wird/wurde

c) Ist KEINE Zykluszeit angegeben und run aktiv wird jeweils dann neu berechnet wenn ein IST Wert (wirklich) gesetzt wird, d.h. die Hysteresebedingung erfüllt ist.

d) Ev. sehe ich noch einen "calculate once" button vor.

Was mir Gedanken macht ist der I Anteil da bei c) und d) die Berechnungen wohl auch sehr lange auseinander leigen können. Und damit der I Anteil, genauer sumErr doch schnell sehr groß werden könnten.

Hast du irgendwelche Kommentare dazu mit deiner Regeltechnikerfahrung?
fu-zhou commented 1 year ago

Ich denke, da sind wir im Forum jetzt schon ein ganze Stück weiter... Macht echt Spaß! Hast du irgendwelche Kommentare dazu mit deiner Regeltechnikerfahrung? Ja - LL und LH werden durchaus dynamisch verwendet und daher ist Kp neben Tn und Tv das Mittel der Wahl ;-)

mcm1957 commented 1 year ago

Aktuelle Diskussion siehe Forum. Fehler / Features ggF als neue Issues