Weiter zum Inhalt

Künstliche Intelligenz

6 Themen 132 Beiträge

Alles rund um Künstliche Intelligenz im Smart Home: Integration von LLMs, ChatGPT, lokalen KI-Modellen (Ollama, LM Studio), Sprachassistenten, Bilderkennung und Automatisierungen mit ioBroker.

NEWS

  • KI in ioBroker – Anwendungen, Erfahrungen und Ideen - Info

    Angeheftet Gesperrt
    1
    0 Stimmen
    1 Beiträge
    87 Aufrufe
    Niemand hat geantwortet
  • KI in ioBroker – Anwendungen, Erfahrungen und Ideen

    Verschoben
    53
    0 Stimmen
    53 Beiträge
    3k Aufrufe
    crunchipC
    @Stefan341 Cursor am PC installiert, MCP-Adapter konfigurieren „WEB-Adapter erweitern“: meine web.0-Instanz wählen → MCP hängt am Webserver, URL z. B. http://<IP-des-ioBroker>:8082/mcp Die MCP-Server-URL steht in der Adapter-Konfiguration (Kopieren-Button) – die brauchst du in Cursor Rechte nur einschalten, was du brauchst (z. B. States lesen/schreiben; Objekt-/Skript-Änderungen standardmäßig aus) Bei mir im LAN ohne Auth (auth: false) – nur sinnvoll im vertrauenswürdigen Netz; von außen HTTPS + OAuth oder ioBroker Remote (https://mcp.iobroker.in/mcp) nutzen (steht in der Adapter-README) 3. Cursor einrichten Cursor → Settings → MCP (oder Datei %USERPROFILE%.cursor\mcp.json) Server eintragen, z. B.: { "mcpServers": { "iobroker": { "url": "http://<IP-des-ioBroker>:8082/mcp" } } } Cursor neu laden / MCP-Verbindung prüfen (grüner Status)
  • Projekt: ioBroker RAG

    2
    2 Stimmen
    2 Beiträge
    165 Aufrufe
    BluefoxB
    @bloop Danke für die Vorstellung und vor allem für die Messungen. Dass gemma4 ohne Kontext bei 8 von 12 Fragen Funktionen erfindet, deckt sich mit dem, was ich bei kleinen lokalen Modellen sehe. Hast du schon iobroker.ai-server + assistant + pi-agent ausprobiert? ai-server startet Ollama im Docker oder nutzt einen bestehenden Ollama-Server und stellt eine OpenAI-kompatible API bereit. Mit deiner 4090 wäre das Backend "External Ollama server" das richtige, NVIDIA im Docker-Container wird noch nicht unterstützt. Der assistant kann ein lokales LLM und pi-agent als Provider nutzen. Mich würde interessieren, ob dein RAG dort als zusätzliche Quelle über MCP Sinn ergibt und wie die Antworten damit im Vergleich aussehen. Zu deiner Frage nach weiteren Quellen: Ja, die Adapter-READMEs halte ich für den wichtigsten nächsten Schritt. Die meisten Forumsfragen betreffen einen bestimmten Adapter und nicht die Skript-API oder io-package.json. Wahrscheinlich kommen deine 12 Fehlgriffe von 65 genau daher. Bei den Adaptern aus dem Latest-Repo sind die Lizenzen sehr unterschiedlich, das sollte in sources.json sauber mitlaufen. Interessant wären auch docs/ und die Changelogs, wo es sie gibt. Zwei Fragen dazu: Wie lange braucht ein Rebuild, wenn du alle Adapter aus dem Latest-Repo aufnimmst? Ein inkrementelles Update (nur Repos mit neuem Commit) wäre dann vermutlich sinnvoller als ein wöchentlicher Komplett-Rebuild. Hast du einmal gemessen, wie groß der Unterschied bei einem großen Cloud-Modell mit und ohne RAG ist? Das wäre für viele hier interessanter, weil die meisten keine 4090 haben.
  • Lust zu unterstützen? Sicherheit für KI-Agenten in ioBroker

    1
    1 Stimmen
    1 Beiträge
    107 Aufrufe
    Niemand hat geantwortet
  • Zeigt her eure KI Projekte

    Verschoben
    55
    2
    6 Stimmen
    55 Beiträge
    7k Aufrufe
    U
    ein kleines KI Projekt zum Thema Wärmepumpe Teil 1: Wärmepumpe live im log-p-h-Diagramm (R290, Wolf CHA) Ich wollte den Kältekreis meiner Wärmepumpe als log-p-h-Diagramm mit Livewerten visualisieren. Nebenbei hat sich ein Monitoring für die Anlage ergeben, das Veränderungen am Kältekreis erkennt. Den theoretischen Hintergrund für das Thema habe ich, aber am Skript würde ich wochenlang sitzen und vermutlich aufgeben. Daher die KI zu Hilfe genommen und nur den fachlichen Input geliefert. Ausgangspunkt war die Frage, ob man aus den Daten, die die Wärmepumpe ohnehin liefert, mehr herausholen kann als „VL, RL, COP“? [image: image.jpeg] Das log-p-h-Diagramm ist das Standard-Diagramm in der Kältetechnik: x-Achse Enthalpie, y-Achse Druck (logarithmisch). Darin: Nassdampfkuppel (Siede- und Taulinie) von R290 als Hintergrund Isothermen 0–100 °C im überhitzten Bereich der aktuelle Kreisprozess mit den vier Eckpunkten: Verdichtereintritt (Sauggas) Verdichteraustritt (Heißgas) Verflüssigeraustritt nach dem Expansionsventil gestrichelt die isentrope Verdichtung als Vergleich eine Spur der letzten Zyklen, damit man sieht, wie der Prozess beim Modulieren oder Umschalten auf Warmwasser „wandert“ Ein zweites Widget rechts zeigt die daraus berechneten Kennwerte: Verdampfungs- und Kondensationstemperatur, Sauggas- und Heißgasüberhitzung, Unterkühlung, isentroper Verdichterwirkungsgrad, Kältemittel-Massenstrom und drei COP-Varianten (Kreisprozess, hydraulisch, laut Wolf). Aus Gründen der Lesbarkeit sind die Isentropen nicht dargestellt die Isothermen nur im Bereich des überhitzten Dampfs gezeichnet Welche Daten braucht man? die Wolf CHA liefert über MQTT (ism7mqtt) die thermischen Daten, elektrische kommen von einem Shelly 3EM. Wert Wozu Saug- und Heißgasdruck (bar absolut!) Verdampfungs-/Kondensationstemperatur, y-Position der Punkte Saug- und Heißgastemperatur Enthalpie Punkt 1 und 2 Rücklauftemperatur Schätzung Punkt 3 (kein Flüssigkeitsfühler vorhanden) Vorlauf, Durchfluss Heizkreis hydraulische Heizleistung elektrische Leistung (Shelly 3EM) COP Umschaltventil HZ/WW Betriebsart Wer eine andere Wärmepumpe hat: wichtig sind die zwei Drücke und zwei Temperaturen am Verdichter. Sind die vorhanden, lässt sich das Prinzip übertragen. Prüft, ob die Drücke absolut oder relativ (Überdruck) sind. Mit Relativdruck hätte der Verdichter „nasses“ Gas angesaugt, was nicht sein kann/darf. Wie funktioniert es? Die Stoffdaten von R290 (Kuppel, Enthalpie und Entropie im überhitzten Bereich) sind einmalig mit CoolProp (Python) berechnet und als Tabelle direkt im Skript abgelegt. Zur Laufzeit wird nur noch bilinear interpoliert, die Abweichung zu CoolProp liegt unter 0,1 kJ/kg. Es wird also keine Stoffwertbibliothek in ioBroker gebraucht und keine Cloud. Skript WP_LogPH.js triggert auf die Eingangs-DPs, rechnet die vier Punkte und alle Kennwerte, schreibt ein JSON für die Widgets und die Kennwerte als einzelne, loggbare DPs. Die DPs legt das Skript selbst an. Widget LogPH_R290V8.html: zeichnet nur das JSON, rechnet nichts (siehe Teil 2). Widget LogPH_Kennwerte.html: zeigt die Kennwerte als Tabelle (siehe Teil 2). Skript: Spoiler // @desc Kältekreis der Wolf CHA (R290) live berechnen und als JSON für das log-p-h-Widget bereitstellen // @version 1.3.0 // @changelog 1.0.0 | 2026-09-24 | Erstversion: 4 Kreisprozesspunkte, Kennwerte, JSON für Widget LogPH_R290V1.html // @changelog 1.1.0 | 2026-09-24 | Isothermen (0-100 °C, überhitzter Bereich) in Stofftabelle und JSON für Widget ab LogPH_R290V4.html // @changelog 1.2.0 | 2026-09-24 | Einschwingerkennung: Anlauf/Moduswechsel und η_is-Plausibilität; Kennwert-DPs nur im stationären Betrieb, neuer DP stabil // @changelog 1.3.0 | 2026-09-24 | Hydraulische Heizleistung aus Durchfluss x Spreizung, Verhältnis Q_Wolf/Q_hydr., COP hydraulisch; Massenstrom aus Q_hydr. // @desc-long Liest Saug-/Heißgasdruck und -temperatur (MQTT), Heizungs-RL/VL, Durchfluss, Heizleistung Wolf und elektrische Leistung (Shelly 3EM). // @desc-long Berechnet über eine vorab mit CoolProp erzeugte R290-Stofftabelle (bilineare Interpolation über log p und Überhitzung) die Enthalpien der Punkte 1-4 sowie die isentrope Referenz h2s. // @desc-long Punkt 3 (Flüssigkeit nach Verflüssiger) wird aus RL + 1,5 K geschätzt, da kein Flüssigkeitsfühler vorhanden ist. // @desc-long Die hydraulische Heizleistung (Durchfluss × Spreizung) dient als unabhängige Kontrolle der Wolf-Heizleistung und als Basis für den Kältemittelmassenstrom. // @desc-long Kennwert-DPs werden nur im stationären Betrieb geschrieben (Verdichter läuft, Anlaufzeit 5 min vorbei, η_is zwischen 0,35 und 0,95), damit die SQL-Historie nicht durch Stillstand oder Einschwingvorgänge verfälscht wird. // @desc-long Hintergrund: Drucksensoren reagieren sofort, Rohranlegefühler hinken nach. Direkt nach Start oder HZ/WW-Umschaltung passen p und T nicht zusammen (z. B. η_is > 1). // ===================== Konfiguration ===================== var ID_P_LO = 'alias.0.1000_Heizung.WP.Verdichter.SauggasDruck'; var ID_T_LO = 'alias.0.1000_Heizung.WP.Verdichter.SauggasTemperatur'; var ID_P_HI = 'alias.0.1000_Heizung.WP.Verdichter.HeissgasDruck'; var ID_T_HI = 'alias.0.1000_Heizung.WP.Verdichter.HeissgasTemperatur'; var ID_Q_HEIZ = 'alias.0.1000_Heizung.WP.Verdichter.Heizleistung'; // kW var ID_P_EL = 'alias.0.5000_EnergieVerbrauch.WP.Leistungsaufnahme'; // kW (Shelly 3EM, gesamte WP) var ID_FLOW = 'alias.0.1000_Heizung.WP.DurchflussHK'; // l/min var ID_RL = 'alias.0.1000_Heizung.WP.RLTemperatur'; var ID_VL = 'alias.0.1000_Heizung.WP.VLTemperatur'; var ID_VENTIL = 'alias.0.1000_Heizung.WP.Umschaltventil'; // "1" = Warmwasser var BASE = '0_userdata.0.1000_Heizung.WP.LogPH.'; var P_EL_FAKTOR = 1; // Leistungsaufnahme -> kW (Alias liefert bereits kW) var RHO_KG_L = 0.99; // Dichte Heizwasser bei ~40 °C [kg/l] var CP_KJ_KGK = 4.18; // Wärmekapazität [kJ/(kg*K)]; bei Glykol je nach Anteil ca. 3,7-3,9 var MIN_SPREIZ_K = 1.0; // darunter ist Q_hydr. zu unsicher (Fühlertoleranz) var RL_OFFSET_K = 1.5; // Schätzung: Flüssigkeit nach Verflüssiger = RL + Offset var MIN_DP_BAR = 2.0; // Mindest-Druckdifferenz für "Verdichter läuft" var MIN_Q_KW = 0.3; // Mindest-Heizleistung für "Verdichter läuft" var MAX_ALTER_MS = 6 * 60 * 1000; // ältere MQTT-Werte gelten als veraltet var DEBOUNCE_MS = 1500; // mehrere MQTT-Werte kommen fast gleichzeitig -> einmal rechnen var ANLAUF_MS = 5 * 60 * 1000; // nach Verdichterstart / Wechsel HZ<->WW gilt der Kreis als instationär var ETA_MIN = 0.35; // Plausibilitätsgrenzen isentroper Wirkungsgrad: außerhalb passen var ETA_MAX = 0.95; // Druck- und Temperaturwerte nicht zueinander (träge Rohranlegefühler) // ===================== Stofftabelle R290 ===================== // Erzeugt mit CoolProp 6.x. p: Drücke [bar abs], log-verteilt. // sat: [p, Tsat °C, h' kJ/kg, h'' kJ/kg]; dT: Überhitzungsstufen [K] // h / s: [p][dT] Enthalpie [kJ/kg] / Entropie [kJ/kgK] des überhitzten Dampfs // dome: Siede- + Taulinie als [h, p] für das Widget // iso: Isothermen im überhitzten Bereich, je {T °C, pts [[h, p], ...]} ab Taulinie abwärts // (100 °C liegt über Tkrit = 96,7 °C und beginnt deshalb am oberen Diagrammrand) var TAB = { fluid: "R290", p: [1.5,1.645,1.804,1.979,2.17,2.38,2.611,2.863,3.14,3.444,3.777,4.142,4.543,4.983,5.465,5.993,6.573,7.209,7.907,8.672,9.51,10.431,11.44,12.546,13.76,15.091,16.551,18.153,19.909,21.835,23.947,26.264,28.805,31.592,34.648,38.0], dT: [0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100], sat: [ [1.5,-32.818,121.48,537.04], [1.645,-30.508,126.8,539.78], [1.804,-28.149,132.26,542.57], [1.979,-25.73,137.89,545.42], [2.17,-23.27,143.65,548.31], [2.38,-20.748,149.59,551.26], [2.611,-18.163,155.72,554.27], [2.863,-15.533,162.0,557.31], [3.14,-12.837,168.48,560.41], [3.444,-10.077,175.16,563.57], [3.777,-7.257,182.04,566.76], [4.142,-4.372,189.14,570.01], [4.543,-1.414,196.47,573.3], [4.983,1.616,204.05,576.64], [5.465,4.714,211.87,580.02], [5.993,7.883,219.96,583.42], [6.573,11.135,228.33,586.87], [7.209,14.466,237.0,590.34], [7.907,17.88,246.0,593.82], [8.672,21.377,255.32,597.31], [9.51,24.956,264.99,600.8], [10.431,28.632,275.05,604.27], [11.44,32.395,285.51,607.71], [12.546,36.252,296.39,611.09], [13.76,40.208,307.75,614.38], [15.091,44.262,319.61,617.56], [16.551,48.418,332.01,620.58], [18.153,52.68,345.01,623.39], [19.909,57.044,358.67,625.9], [21.835,61.515,373.06,628.02], [23.947,66.093,388.29,629.61], [26.264,70.779,404.5,630.45], [28.805,75.57,421.88,630.24], [31.592,80.464,440.78,628.48], [34.648,85.453,461.82,624.22], [38.0,90.524,486.51,615.18] ], h: [ [537.04,544.55,552.14,559.79,567.53,575.35,583.25,591.25,599.33,607.51,615.79,624.16,632.63,641.21,649.88,658.66,667.55,676.54,685.64,694.84,704.15], [539.78,547.37,555.02,562.74,570.55,578.43,586.41,594.46,602.61,610.86,619.2,627.63,636.17,644.81,653.54,662.39,671.33,680.39,689.54,698.81,708.18], [542.57,550.23,557.96,565.75,573.63,581.58,589.62,597.75,605.97,614.28,622.68,631.19,639.79,648.49,657.29,666.2,675.21,684.32,693.55,702.87,712.31], [545.42,553.16,560.96,568.84,576.78,584.81,592.92,601.12,609.41,617.79,626.26,634.83,643.5,652.27,661.14,670.11,679.19,688.37,697.66,707.05,716.55], [548.31,556.13,564.01,571.96,579.99,588.09,596.28,604.55,612.91,621.36,629.91,638.55,647.28,656.12,665.06,674.1,683.25,692.49,701.85,711.3,720.87], [551.26,559.16,567.13,575.16,583.27,591.45,599.71,608.06,616.5,625.02,633.64,642.35,651.17,660.07,669.08,678.2,687.41,696.73,706.15,715.67,725.31], [554.27,562.26,570.32,578.43,586.62,594.89,603.23,611.66,620.17,628.78,637.47,646.26,655.15,664.13,673.22,682.4,691.68,701.07,710.57,720.16,729.87], [557.31,565.4,573.54,581.75,590.03,598.38,606.81,615.32,623.91,632.6,641.37,650.24,659.2,668.26,677.42,686.68,696.04,705.5,715.07,724.74,734.51], [560.41,568.6,576.84,585.14,593.51,601.95,610.47,619.06,627.74,636.51,645.37,654.32,663.36,672.5,681.74,691.08,700.51,710.05,719.69,729.44,739.28], [563.57,571.86,580.2,588.6,597.07,605.6,614.21,622.89,631.66,640.52,649.46,658.49,667.62,676.84,686.16,695.58,705.1,714.71,724.43,734.25,744.18], [566.76,575.17,583.62,592.12,600.69,609.32,618.02,626.8,635.66,644.61,653.64,662.76,671.97,681.28,690.68,700.18,709.78,719.48,729.28,739.18,749.18], [570.01,578.53,587.09,595.7,604.37,613.11,621.91,630.79,639.75,648.79,657.91,667.12,676.43,685.82,695.31,704.9,714.58,724.37,734.25,744.23,754.31], [573.3,581.95,590.63,599.36,608.14,616.98,625.89,634.87,643.93,653.07,662.29,671.6,680.99,690.48,700.06,709.74,719.51,729.38,739.35,749.41,759.58], [576.64,585.42,594.23,603.08,611.98,620.93,629.95,639.04,648.2,657.45,666.77,676.17,685.67,695.25,704.93,714.69,724.56,734.52,744.57,754.73,764.98], [580.02,588.94,597.88,606.86,615.88,624.96,634.09,643.29,652.57,661.91,671.34,680.85,690.44,700.13,709.9,719.76,729.72,739.77,749.92,760.17,770.51], [583.42,592.5,601.58,610.7,619.85,629.05,638.31,647.63,657.01,666.47,676.01,685.63,695.33,705.11,714.98,724.95,735.0,745.15,755.4,765.74,776.17], [586.87,596.1,605.34,614.6,623.89,633.23,642.61,652.06,661.56,671.14,680.79,690.52,700.33,710.22,720.2,730.27,740.42,750.67,761.01,771.45,781.98], [590.34,599.74,609.15,618.56,628.0,637.48,647.0,656.57,666.21,675.91,685.68,695.52,705.44,715.45,725.54,735.71,745.97,756.33,766.77,777.31,787.94], [593.82,603.42,613.0,622.58,632.18,641.8,651.47,661.18,670.94,680.77,690.67,700.64,710.68,720.8,731.0,741.29,751.66,762.12,772.67,783.31,794.05], [597.31,607.12,616.89,626.65,636.41,646.19,656.01,665.87,675.77,685.74,695.77,705.86,716.03,726.27,736.59,746.99,757.48,768.05,778.71,789.46,800.3], [600.8,610.83,620.81,630.75,640.7,650.65,660.62,670.63,680.69,690.8,700.96,711.19,721.49,731.86,742.3,752.82,763.43,774.12,784.89,795.75,806.7], [604.27,614.55,624.75,634.91,645.04,655.17,665.32,675.49,685.71,695.96,706.27,716.64,727.08,737.58,748.15,758.8,769.53,780.34,791.23,802.21,813.27], [607.71,618.27,628.71,639.09,649.43,659.76,670.08,680.43,690.81,701.22,711.68,722.2,732.78,743.42,754.13,764.91,775.77,786.7,797.72,808.82,820.0], [611.09,621.95,632.67,643.29,653.85,664.39,674.91,685.44,695.99,706.57,717.2,727.87,738.59,749.38,760.23,771.15,782.14,793.21,804.35,815.58,826.88], [614.38,625.59,636.61,647.5,658.31,669.06,679.8,690.52,701.26,712.02,722.81,733.65,744.53,755.47,766.47,777.53,788.67,799.87,811.15,822.5,833.94], [617.56,629.17,640.52,651.71,662.78,673.78,684.73,695.67,706.6,717.55,728.52,739.53,750.58,761.68,772.84,784.05,795.33,806.68,818.09,829.59,841.15], [620.58,632.65,644.38,655.89,667.25,678.51,689.71,700.87,712.02,723.17,734.33,745.52,756.75,768.02,779.33,790.71,802.14,813.63,825.2,836.83,848.54], [623.39,635.98,648.15,660.03,671.71,683.26,694.72,706.13,717.5,728.86,740.23,751.61,763.02,774.47,785.96,797.5,809.09,820.74,832.46,844.24,856.09], [625.9,639.13,651.8,664.09,676.13,687.99,699.74,711.41,723.03,734.62,746.2,757.79,769.4,781.03,792.7,804.42,816.18,827.99,839.87,851.81,863.81], [628.02,642.03,655.28,668.05,680.49,692.7,704.76,716.72,728.6,740.44,752.25,764.06,775.87,787.7,799.56,811.46,823.4,835.39,847.43,859.53,871.69], [629.61,644.6,658.55,671.86,684.75,697.36,709.76,722.03,734.19,746.29,758.35,770.39,782.43,794.47,806.53,818.62,830.75,842.92,855.13,867.4,879.73], [630.45,646.74,661.54,675.49,688.89,701.93,714.72,727.32,739.79,752.18,764.5,776.79,789.06,801.33,813.6,825.9,838.22,850.58,862.97,875.42,887.92], [630.24,648.31,664.16,678.86,692.86,706.39,719.59,732.57,745.38,758.07,770.68,783.24,795.75,808.26,820.76,833.27,845.79,858.35,870.94,883.57,896.25], [628.48,649.14,666.33,681.93,696.6,710.68,724.36,737.75,750.92,763.95,776.86,789.7,802.49,815.24,827.98,840.72,853.46,866.23,879.02,891.85,904.71], [624.22,649.0,667.91,684.59,700.06,714.77,728.97,742.81,756.39,769.77,783.02,796.16,809.23,822.25,835.24,848.22,861.19,874.18,887.18,900.21,913.28], [615.18,647.58,668.77,686.76,703.15,718.58,733.36,747.7,761.72,775.49,789.09,802.56,815.93,829.24,842.5,855.73,868.94,882.16,895.39,908.63,921.9] ], s: [ [2.4254,2.4564,2.487,2.5173,2.5473,2.577,2.6065,2.6358,2.6649,2.6938,2.7226,2.7512,2.7796,2.8079,2.8361,2.8642,2.8922,2.92,2.9478,2.9754,3.003], [2.4203,2.4512,2.4818,2.5121,2.5421,2.5718,2.6013,2.6306,2.6597,2.6886,2.7174,2.7459,2.7744,2.8027,2.8309,2.8589,2.8869,2.9147,2.9425,2.9701,2.9977], [2.4153,2.4462,2.4768,2.5071,2.5371,2.5668,2.5963,2.6256,2.6547,2.6836,2.7123,2.7409,2.7694,2.7977,2.8258,2.8539,2.8818,2.9096,2.9374,2.965,2.9926], [2.4104,2.4414,2.472,2.5023,2.5323,2.562,2.5915,2.6208,2.6499,2.6788,2.7075,2.7361,2.7645,2.7928,2.821,2.849,2.877,2.9048,2.9325,2.9601,2.9877], [2.4057,2.4367,2.4673,2.4976,2.5276,2.5574,2.5869,2.6162,2.6453,2.6742,2.7029,2.7315,2.7599,2.7882,2.8164,2.8444,2.8724,2.9002,2.9279,2.9555,2.9831], [2.4012,2.4322,2.4628,2.4932,2.5232,2.553,2.5825,2.6118,2.6409,2.6698,2.6985,2.7271,2.7555,2.7838,2.812,2.84,2.868,2.8958,2.9235,2.9511,2.9787], [2.3968,2.4279,2.4585,2.4889,2.5189,2.5487,2.5783,2.6076,2.6367,2.6656,2.6944,2.7229,2.7514,2.7797,2.8078,2.8359,2.8638,2.8916,2.9193,2.947,2.9745], [2.3926,2.4237,2.4544,2.4848,2.5149,2.5447,2.5743,2.6036,2.6327,2.6617,2.6904,2.719,2.7475,2.7758,2.8039,2.832,2.8599,2.8877,2.9155,2.9431,2.9706], [2.3886,2.4197,2.4505,2.4809,2.511,2.5409,2.5705,2.5998,2.629,2.6579,2.6867,2.7153,2.7438,2.7721,2.8003,2.8283,2.8563,2.8841,2.9118,2.9394,2.9669], [2.3847,2.4159,2.4467,2.4772,2.5074,2.5373,2.5669,2.5963,2.6255,2.6544,2.6832,2.7119,2.7403,2.7687,2.7968,2.8249,2.8528,2.8807,2.9084,2.936,2.9635], [2.3809,2.4123,2.4432,2.4737,2.5039,2.5339,2.5635,2.593,2.6222,2.6512,2.68,2.7086,2.7371,2.7655,2.7937,2.8217,2.8497,2.8775,2.9053,2.9329,2.9604], [2.3774,2.4088,2.4398,2.4704,2.5007,2.5307,2.5604,2.5899,2.6191,2.6481,2.677,2.7057,2.7342,2.7625,2.7908,2.8189,2.8468,2.8747,2.9024,2.93,2.9576], [2.3739,2.4055,2.4366,2.4673,2.4976,2.5277,2.5575,2.587,2.6163,2.6453,2.6742,2.7029,2.7315,2.7599,2.7881,2.8162,2.8442,2.8721,2.8998,2.9274,2.955], [2.3707,2.4023,2.4335,2.4643,2.4948,2.5249,2.5547,2.5843,2.6137,2.6428,2.6717,2.7005,2.729,2.7575,2.7857,2.8138,2.8418,2.8697,2.8975,2.9251,2.9527], [2.3675,2.3993,2.4307,2.4616,2.4921,2.5223,2.5523,2.5819,2.6113,2.6405,2.6695,2.6982,2.7269,2.7553,2.7836,2.8118,2.8398,2.8677,2.8954,2.9231,2.9507], [2.3645,2.3965,2.428,2.459,2.4897,2.52,2.55,2.5797,2.6092,2.6384,2.6675,2.6963,2.7249,2.7534,2.7818,2.8099,2.838,2.8659,2.8937,2.9214,2.949], [2.3616,2.3938,2.4254,2.4566,2.4874,2.5179,2.548,2.5778,2.6073,2.6366,2.6657,2.6946,2.7233,2.7518,2.7802,2.8084,2.8365,2.8644,2.8922,2.9199,2.9475], [2.3588,2.3912,2.4231,2.4544,2.4854,2.5159,2.5461,2.5761,2.6057,2.6351,2.6642,2.6932,2.7219,2.7505,2.7789,2.8072,2.8353,2.8632,2.8911,2.9188,2.9464], [2.356,2.3887,2.4208,2.4524,2.4835,2.5142,2.5445,2.5746,2.6043,2.6338,2.663,2.692,2.7208,2.7494,2.7779,2.8062,2.8343,2.8623,2.8902,2.918,2.9456], [2.3534,2.3864,2.4187,2.4505,2.4818,2.5127,2.5432,2.5733,2.6031,2.6327,2.662,2.6911,2.72,2.7487,2.7772,2.8055,2.8337,2.8618,2.8897,2.9174,2.9451], [2.3508,2.3841,2.4168,2.4488,2.4803,2.5113,2.542,2.5723,2.6022,2.6319,2.6613,2.6905,2.7194,2.7482,2.7768,2.8052,2.8334,2.8615,2.8894,2.9172,2.9449], [2.3481,2.3819,2.4149,2.4472,2.4789,2.5102,2.541,2.5714,2.6015,2.6313,2.6608,2.6901,2.7191,2.748,2.7766,2.8051,2.8334,2.8615,2.8895,2.9173,2.945], [2.3454,2.3797,2.4131,2.4457,2.4777,2.5092,2.5402,2.5708,2.6011,2.631,2.6606,2.69,2.7191,2.7481,2.7768,2.8053,2.8336,2.8618,2.8898,2.9177,2.9455], [2.3427,2.3775,2.4113,2.4443,2.4767,2.5084,2.5396,2.5704,2.6008,2.6309,2.6607,2.6902,2.7194,2.7484,2.7772,2.8058,2.8342,2.8624,2.8905,2.9184,2.9462], [2.3398,2.3753,2.4096,2.443,2.4757,2.5077,2.5392,2.5702,2.6008,2.6311,2.661,2.6906,2.7199,2.749,2.7779,2.8066,2.8351,2.8634,2.8915,2.9195,2.9473], [2.3366,2.3729,2.4078,2.4417,2.4748,2.5072,2.5389,2.5702,2.601,2.6314,2.6615,2.6912,2.7207,2.7499,2.7789,2.8077,2.8362,2.8646,2.8928,2.9208,2.9487], [2.3331,2.3704,2.406,2.4405,2.474,2.5067,2.5388,2.5703,2.6014,2.632,2.6622,2.6921,2.7218,2.7511,2.7802,2.809,2.8377,2.8661,2.8944,2.9225,2.9504], [2.3292,2.3675,2.404,2.4391,2.4732,2.5063,2.5388,2.5706,2.6019,2.6327,2.6632,2.6933,2.723,2.7525,2.7817,2.8107,2.8394,2.868,2.8963,2.9245,2.9525], [2.3245,2.3643,2.4018,2.4377,2.4723,2.506,2.5388,2.571,2.6026,2.6337,2.6643,2.6946,2.7246,2.7542,2.7835,2.8126,2.8415,2.8701,2.8985,2.9268,2.9548], [2.319,2.3606,2.3993,2.436,2.4714,2.5056,2.5389,2.5714,2.6034,2.6347,2.6657,2.6962,2.7263,2.7561,2.7856,2.8148,2.8438,2.8725,2.901,2.9293,2.9575], [2.3122,2.3561,2.3963,2.4342,2.4703,2.5051,2.539,2.572,2.6042,2.6359,2.6671,2.6979,2.7282,2.7582,2.7878,2.8172,2.8463,2.8751,2.9038,2.9322,2.9604], [2.3036,2.3506,2.3928,2.4319,2.469,2.5046,2.539,2.5725,2.6052,2.6372,2.6687,2.6997,2.7303,2.7605,2.7903,2.8198,2.8491,2.878,2.9068,2.9353,2.9636], [2.2925,2.3439,2.3884,2.4291,2.4674,2.5038,2.5389,2.5729,2.6061,2.6386,2.6704,2.7017,2.7325,2.7629,2.7929,2.8226,2.852,2.8811,2.91,2.9386,2.967], [2.2775,2.3355,2.3831,2.4257,2.4653,2.5027,2.5386,2.5733,2.607,2.6399,2.6721,2.7037,2.7348,2.7654,2.7957,2.8256,2.8551,2.8844,2.9134,2.9422,2.9707], [2.2563,2.3249,2.3766,2.4215,2.4627,2.5013,2.5381,2.5734,2.6077,2.6411,2.6737,2.7057,2.7371,2.768,2.7985,2.8286,2.8584,2.8878,2.9169,2.9458,2.9745], [2.2228,2.3114,2.3685,2.4163,2.4593,2.4993,2.5371,2.5733,2.6082,2.6421,2.6752,2.7076,2.7393,2.7705,2.8013,2.8316,2.8616,2.8912,2.9205,2.9495,2.9783] ], dome: [[121.48,1.5],[126.8,1.645],[132.26,1.804],[137.89,1.979],[143.65,2.17],[149.59,2.38],[155.72,2.611],[162.0,2.863],[168.48,3.14],[175.16,3.444],[182.04,3.777],[189.14,4.142],[196.47,4.543],[204.05,4.983],[211.87,5.465],[219.96,5.993],[228.33,6.573],[237.0,7.209],[246.0,7.907],[255.32,8.672],[264.99,9.51],[275.05,10.431],[285.51,11.44],[296.39,12.546],[307.75,13.76],[319.61,15.091],[332.01,16.551],[345.01,18.153],[358.67,19.909],[373.06,21.835],[388.29,23.947],[404.5,26.264],[421.88,28.805],[440.78,31.592],[461.82,34.648],[486.51,38.0],[499.0,39.5],[511.7,40.8],[524.9,41.8],[536.1,42.3],[564.8,42.51],[576.1,42.3],[588.0,41.8],[599.6,40.8],[608.4,39.5],[615.18,38.0],[624.22,34.648],[628.48,31.592],[630.24,28.805],[630.45,26.264],[629.61,23.947],[628.02,21.835],[625.9,19.909],[623.39,18.153],[620.58,16.551],[617.56,15.091],[614.38,13.76],[611.09,12.546],[607.71,11.44],[604.27,10.431],[600.8,9.51],[597.31,8.672],[593.82,7.907],[590.34,7.209],[586.87,6.573],[583.42,5.993],[580.02,5.465],[576.64,4.983],[573.3,4.543],[570.01,4.142],[566.76,3.777],[563.57,3.444],[560.41,3.14],[557.31,2.863],[554.27,2.611],[551.26,2.38],[548.31,2.17],[545.42,1.979],[542.57,1.804],[539.78,1.645],[537.04,1.5]], iso: [ {"T":0,"pts":[[574.9,4.744],[576.3,4.403],[577.7,4.087],[578.9,3.793],[580.0,3.521],[581.0,3.268],[582.0,3.033],[582.8,2.815],[583.6,2.613],[584.3,2.425],[585.0,2.251],[585.6,2.089],[586.1,1.939],[586.7,1.8]]}, {"T":20,"pts":[[595.9,8.364],[599.9,7.432],[603.1,6.603],[605.9,5.867],[608.3,5.214],[610.3,4.632],[612.1,4.116],[613.6,3.657],[615.0,3.25],[616.2,2.888],[617.2,2.566],[618.1,2.28],[618.9,2.026],[619.6,1.8]]}, {"T":40,"pts":[[614.2,13.693],[622.6,11.714],[629.0,10.021],[634.1,8.573],[638.1,7.334],[641.4,6.274],[644.1,5.368],[646.4,4.592],[648.2,3.928],[649.8,3.361],[651.2,2.875],[652.3,2.459],[653.2,2.104],[654.0,1.8]]}, {"T":60,"pts":[[627.4,21.165],[644.3,17.51],[655.4,14.486],[663.5,11.985],[669.6,9.915],[674.3,8.203],[678.0,6.786],[681.0,5.614],[683.4,4.645],[685.4,3.842],[686.9,3.179],[688.2,2.63],[689.3,2.176],[690.1,1.8]]}, {"T":80,"pts":[[628.7,31.316],[664.2,25.139],[682.3,20.18],[694.2,16.199],[702.8,13.004],[709.1,10.439],[713.9,8.38],[717.6,6.727],[720.5,5.4],[722.7,4.335],[724.5,3.48],[725.9,2.793],[727.0,2.242],[727.9,1.8]]}, {"T":100,"pts":[[515.5,50.0],[662.6,38.718],[700.9,29.982],[721.3,23.217],[734.5,17.979],[743.7,13.922],[750.3,10.781],[755.2,8.348],[758.9,6.465],[761.6,5.006],[763.7,3.876],[765.3,3.002],[766.5,2.324],[767.5,1.8]]} ] }; // ===================== Ausgabe-DPs ===================== // @dp LogPH.json | JSON für das Widget (nicht loggen) // @dp LogPH.aktiv | Verdichter läuft // @dp LogPH.stabil | Kreis eingeschwungen und plausibel -> nur dann werden die Kennwert-DPs geschrieben // @dp LogPH.T0 / Tc | Verdampfungs- / Kondensationstemperatur °C // @dp LogPH.Ueberhitzung / Unterkuehlung / HeissgasUeberhitzung | K // @dp LogPH.EtaIs / COPKreis / COPMess / Massenstrom | - / - / - / g/s // @dp LogPH.QHydraulisch / QVerhaeltnis / COPHydraulisch | kW / - / - var OUT = { json: { name: 'log-p-h JSON für Widget', type: 'string', role: 'json', def: '' }, aktiv: { name: 'Verdichter läuft', type: 'boolean', role: 'indicator', def: false }, stabil: { name: 'Kältekreis stationär (Kennwerte gültig)', type: 'boolean', role: 'indicator', def: false }, T0: { name: 'Verdampfungstemperatur', type: 'number', role: 'value.temperature', unit: '°C', def: 0 }, Tc: { name: 'Kondensationstemperatur', type: 'number', role: 'value.temperature', unit: '°C', def: 0 }, Ueberhitzung: { name: 'Sauggasüberhitzung', type: 'number', role: 'value', unit: 'K', def: 0 }, Unterkuehlung: { name: 'Unterkühlung (geschätzt aus RL)', type: 'number', role: 'value', unit: 'K', def: 0 }, HeissgasUeberhitzung: { name: 'Heißgasüberhitzung', type: 'number', role: 'value', unit: 'K', def: 0 }, EtaIs: { name: 'Isentroper Verdichterwirkungsgrad', type: 'number', role: 'value', def: 0 }, COPKreis: { name: 'COP Kreisprozess', type: 'number', role: 'value', def: 0 }, COPMess: { name: 'COP Wolf (Heizleistung Wolf / Shelly)', type: 'number', role: 'value', def: 0 }, QHydraulisch: { name: 'Heizleistung hydraulisch (Durchfluss x Spreizung)', type: 'number', role: 'value.power', unit: 'kW', def: 0 }, QVerhaeltnis: { name: 'Verhältnis Heizleistung Wolf / hydraulisch', type: 'number', role: 'value', def: 0 }, COPHydraulisch: { name: 'COP hydraulisch (Q hydr. / Shelly)', type: 'number', role: 'value', def: 0 }, Massenstrom: { name: 'Kältemittelmassenstrom', type: 'number', role: 'value', unit: 'g/s', def: 0 } }; // ===================== Stoffwert-Funktionen ===================== function lerp(a, b, f) { return a + (b - a) * f; } // Index + Anteil im log-p-Raster suchen (Randwerte werden geklemmt) function findP(p) { var P = TAB.p, lp = Math.log(p), i; if (p <= P[0]) return { i: 0, f: 0 }; if (p >= P[P.length - 1]) return { i: P.length - 2, f: 1 }; for (i = 0; i < P.length - 1; i++) { if (p <= P[i + 1]) return { i: i, f: (lp - Math.log(P[i])) / (Math.log(P[i + 1]) - Math.log(P[i])) }; } } // Sättigungszustand bei Druck p [bar abs] -> {T [°C], hL, hV [kJ/kg]} function satAtP(p) { var k = findP(p), a = TAB.sat[k.i], b = TAB.sat[k.i + 1]; return { T: lerp(a[1], b[1], k.f), hL: lerp(a[2], b[2], k.f), hV: lerp(a[3], b[3], k.f) }; } // Sättigungsdruck zu Temperatur T [°C] (Umkehrung von satAtP) function pSatAtT(T) { var S = TAB.sat, i; if (T <= S[0][1]) return S[0][0]; for (i = 0; i < S.length - 1; i++) { if (T <= S[i + 1][1]) { var f = (T - S[i][1]) / (S[i + 1][1] - S[i][1]); return Math.exp(lerp(Math.log(S[i][0]), Math.log(S[i + 1][0]), f)); } } return S[S.length - 1][0]; } // Wert aus Überhitzungs-Tabelle (h oder s) bei p und Überhitzung dT, bilinear function tabSH(grid, p, dT) { var k = findP(p), D = TAB.dT, step = D[1] - D[0]; if (dT < 0) dT = 0; if (dT > D[D.length - 1]) dT = D[D.length - 1]; var j = Math.min(Math.floor(dT / step), D.length - 2), g = (dT - D[j]) / step; var v0 = lerp(grid[k.i][j], grid[k.i][j + 1], g); var v1 = lerp(grid[k.i + 1][j], grid[k.i + 1][j + 1], g); return lerp(v0, v1, k.f); } // Enthalpie überhitzter Dampf bei p [bar] und T [°C] function hVapor(p, T) { return tabSH(TAB.h, p, T - satAtP(p).T); } function sVapor(p, T) { return tabSH(TAB.s, p, T - satAtP(p).T); } // Isentrope Endenthalpie: bei Druck p die Überhitzung suchen, bei der s = s1 function hIsentrop(p, s1) { var D = TAB.dT, j, sa, sb; for (j = 0; j < D.length - 1; j++) { sa = tabSH(TAB.s, p, D[j]); sb = tabSH(TAB.s, p, D[j + 1]); if (s1 <= sb || j === D.length - 2) { var g = Math.max(0, (s1 - sa) / (sb - sa)); return tabSH(TAB.h, p, D[j] + g * (D[j + 1] - D[j])); } } } // Enthalpie unterkühlte Flüssigkeit bei T [°C] (Näherung: Siedeenthalpie bei T) function hLiquid(T) { return satAtP(pSatAtT(T)).hL; } // ===================== Hilfsfunktionen ===================== function r(v, n) { var f = Math.pow(10, n); return Math.round(v * f) / f; } function num(id) { var st = getState(id); if (!st || st.val === null || st.val === undefined || isNaN(parseFloat(st.val))) return null; return { v: parseFloat(st.val), ts: st.ts }; } // @func berechne | Liest alle Eingänge, rechnet den Kreisprozess und schreibt JSON + Kennwerte function berechne() { var pLo = num(ID_P_LO), tLo = num(ID_T_LO), pHi = num(ID_P_HI), tHi = num(ID_T_HI); var qH = num(ID_Q_HEIZ), pEl = num(ID_P_EL), rl = num(ID_RL), vl = num(ID_VL), flow = num(ID_FLOW); var ventil = getState(ID_VENTIL); var modus = (ventil && String(ventil.val) === '1') ? 'WW' : 'HZ'; var jetzt = Date.now(); var out = { ts: jetzt, fluid: TAB.fluid, modus: modus, aktiv: false, dome: TAB.dome, iso: TAB.iso }; if (!pLo || !tLo || !pHi || !tHi) { out.grund = 'Eingangswerte fehlen'; return schreibe(out); } if (jetzt - pHi.ts > MAX_ALTER_MS) { out.grund = 'MQTT-Werte veraltet'; return schreibe(out); } var satLo = satAtP(pLo.v), satHi = satAtP(pHi.v); var laeuft = (pHi.v - pLo.v) >= MIN_DP_BAR && tHi.v > satHi.T && (!qH || qH.v >= MIN_Q_KW); if (!laeuft) { laeuftSeit = null; out.grund = 'Verdichter aus'; return schreibe(out); } // Einschwingzeit neu starten bei Verdichterstart und bei Wechsel Heizen <-> Warmwasser if (laeuftSeit === null || modus !== letzterModus) laeuftSeit = jetzt; letzterModus = modus; // Punkt 1: Verdichtereintritt (überhitzter Dampf, gemessen) var h1 = hVapor(pLo.v, tLo.v); var s1 = sVapor(pLo.v, tLo.v); // Punkt 2: Verdichteraustritt (Heißgas, gemessen) + isentrope Referenz var h2 = hVapor(pHi.v, tHi.v); var h2s = hIsentrop(pHi.v, s1); // Punkt 3: Flüssigkeit nach Verflüssiger (geschätzt aus RL, nie wärmer als Tc) var t3 = rl ? Math.min(rl.v + RL_OFFSET_K, satHi.T) : satHi.T; var h3 = hLiquid(t3); // Punkt 4: nach Expansionsventil (isenthalp) var h4 = h3; var qK = h2 - h3; // spez. Verflüssigerleistung kJ/kg var wV = h2 - h1; // spez. Verdichterarbeit kJ/kg var copKreis = wV > 1 ? qK / wV : null; var eta = wV > 1 ? (h2s - h1) / wV : null; var pElKW = pEl ? pEl.v * P_EL_FAKTOR : null; var copMess = (qH && pElKW && pElKW > 0.1) ? qH.v / pElKW : null; // Hydraulische Heizleistung: unabhängige Kontrolle der Wolf-Heizleistung var spreiz = (vl && rl) ? vl.v - rl.v : null; var qHyd = (flow && spreiz !== null && spreiz >= MIN_SPREIZ_K && flow.v > 0) ? flow.v / 60 * RHO_KG_L * CP_KJ_KGK * spreiz : null; // kW var qVerh = (qHyd && qH && qHyd > 0.5) ? qH.v / qHyd : null; var copHyd = (qHyd && pElKW && pElKW > 0.1) ? qHyd / pElKW : null; // Massenstrom bevorzugt aus der hydraulischen Leistung, sonst aus der Wolf-Heizleistung var qBasis = qHyd || (qH ? qH.v : null); var mdot = (qBasis && qK > 1) ? qBasis / qK * 1000 : null; // g/s // Stationär? Erst nach Ablauf der Anlaufzeit und nur, wenn η_is physikalisch plausibel ist var phase = 'stabil', hinweis = ''; if (jetzt - laeuftSeit < ANLAUF_MS) { phase = 'anlauf'; hinweis = 'Instationär: Anlauf/Umschaltung seit ' + Math.round((jetzt - laeuftSeit) / 1000) + ' s'; } else if (eta !== null && (eta < ETA_MIN || eta > ETA_MAX)) { phase = 'unplausibel'; hinweis = 'Unplausibel: η_is ' + r(eta, 2) + ' (Fühler hinken den Drücken nach)'; } out.aktiv = true; out.phase = phase; out.hinweis = hinweis; out.pts = { p1: [r(h1, 1), pLo.v], p2: [r(h2, 1), pHi.v], p2v: [r(satHi.hV, 1), pHi.v], // Taulinie Hochdruck p3l: [r(satHi.hL, 1), pHi.v], // Siedelinie Hochdruck p3: [r(h3, 1), pHi.v], p4: [r(h4, 1), pLo.v], p1v: [r(satLo.hV, 1), pLo.v], // Taulinie Niederdruck p2s: [r(h2s, 1), pHi.v] // isentrope Referenz }; out.temp = { t1: tLo.v, t2: tHi.v, t3: r(t3, 1), t4: r(satLo.T, 1), T0: r(satLo.T, 1), Tc: r(satHi.T, 1), RL: rl ? rl.v : null, VL: vl ? vl.v : null }; out.kpi = { ueberhitzung: r(tLo.v - satLo.T, 1), unterkuehlung: r(satHi.T - t3, 1), heissgasUeberhitzung: r(tHi.v - satHi.T, 1), etaIs: eta !== null ? r(eta, 2) : null, copKreis: copKreis !== null ? r(copKreis, 2) : null, copMess: copMess !== null ? r(copMess, 2) : null, massenstrom: mdot !== null ? r(mdot, 1) : null, qHeiz: qH ? qH.v : null, qHyd: qHyd !== null ? r(qHyd, 2) : null, qVerh: qVerh !== null ? r(qVerh, 2) : null, copHyd: copHyd !== null ? r(copHyd, 2) : null, durchfluss: flow ? flow.v : null, spreizung: spreiz !== null ? r(spreiz, 1) : null, pEl: pElKW !== null ? r(pElKW, 2) : null }; schreibe(out); } // @func schreibe | JSON-DP immer, Kennwert-DPs nur im stationären Betrieb function schreibe(o) { var stabil = o.aktiv && o.phase === 'stabil'; setState(BASE + 'json', JSON.stringify(o), true); setState(BASE + 'aktiv', o.aktiv, true); setState(BASE + 'stabil', stabil, true); if (!stabil) return; setState(BASE + 'T0', o.temp.T0, true); setState(BASE + 'Tc', o.temp.Tc, true); setState(BASE + 'Ueberhitzung', o.kpi.ueberhitzung, true); setState(BASE + 'Unterkuehlung', o.kpi.unterkuehlung, true); setState(BASE + 'HeissgasUeberhitzung', o.kpi.heissgasUeberhitzung, true); if (o.kpi.etaIs !== null) setState(BASE + 'EtaIs', o.kpi.etaIs, true); if (o.kpi.copKreis !== null) setState(BASE + 'COPKreis', o.kpi.copKreis, true); if (o.kpi.copMess !== null) setState(BASE + 'COPMess', o.kpi.copMess, true); if (o.kpi.massenstrom !== null) setState(BASE + 'Massenstrom', o.kpi.massenstrom, true); if (o.kpi.qHyd !== null) setState(BASE + 'QHydraulisch', o.kpi.qHyd, true); if (o.kpi.qVerh !== null) setState(BASE + 'QVerhaeltnis', o.kpi.qVerh, true); if (o.kpi.copHyd !== null) setState(BASE + 'COPHydraulisch', o.kpi.copHyd, true); } // ===================== Start ===================== var timer = null; var laeuftSeit = null; // Zeitpunkt Verdichterstart bzw. letzter Moduswechsel var letzterModus = null; function anstossen() { if (timer) clearTimeout(timer); timer = setTimeout(function () { timer = null; berechne(); }, DEBOUNCE_MS); } function start() { var ids = [ID_P_LO, ID_T_LO, ID_P_HI, ID_T_HI, ID_Q_HEIZ, ID_P_EL, ID_RL, ID_VL, ID_FLOW, ID_VENTIL]; on({ id: ids, change: 'any' }, anstossen); schedule('* * * * *', anstossen); // jede Minute, damit "Anlauf" auch ohne neue MQTT-Werte endet berechne(); } (function anlegen() { var keys = Object.keys(OUT), offen = keys.length; keys.forEach(function (k) { var c = OUT[k]; var common = { name: c.name, type: c.type, role: c.role, read: true, write: false }; if (c.unit) common.unit = c.unit; createState(BASE + k, c.def, false, common, function () { offen--; if (offen === 0) start(); }); }); })(); Chart und Kennwert Tabelle: LogPH_R290V8.html LogPH_Kennwerte.html Anmerkung: Einschwingvorgänge: Drucksensoren reagieren sofort, Rohranlegefühler hinken eine Weile hinterher. Direkt nach dem Start oder nach dem Umschalten auf Warmwasser kommen physikalisch unmögliche Werte heraus (bei mir ein Verdichterwirkungsgrad von 3,05 😄). Das Skript erkennt deshalb eine Anlaufphase von 5 Minuten und prüft die Plausibilität über den isentropen Wirkungsgrad. Nur stationäre Werte werden geloggt. die ausgegebene Heizleistung der Wolf ist zu hoch. Der gemessene COP lag über dem COP des Kreisprozesses. Mit der hydraulischen Heizleistung (Durchfluss × Spreizung VL/RL) passt die Reihenfolge wieder. Wolf liegt bei mir im Mittel rund 25 % zu hoch. Teil 2: Voraussetzungen für die ECharts-Widgets Alle Widgets sind eigenständige HTML-Dateien mit lokalem ECharts, die sich per Socket.IO direkt an ioBroker hängen. Sie laufen damit in Vis-2 genauso wie im Browser. Was wird benötigt? ECharts lokal als echarts.min.js (Version 5.x), von echarts.apache.org herunterladen. Kein Internet, alles bleibt lokal. Socket.IO von ioBroker: wird aus /lib/js/socket.io.js geladen, das liefert der Web-Adapter mit. Nichts zu installieren. JavaScript-Adapter für das Skript. für das Monitoring aus Teil 3 zusätzlich der SQL-Adapter (bei mir PostgreSQL/TimescaleDB). Das log-p-h-Diagramm selbst braucht keine Datenbank. Dateien ablegen Über den Dateimanager des Admin im Bereich von vis-2.0, z. B.: vis-2.0/ └── charts/ ├── echarts.min.js └── WP/ ├── LogPH_R290V8.html ├── LogPH_Kennwerte.html ├── SPC_WP_V3.html └── SPC_WP_Hilfe.html Die Widgets laden ECharts mit ../echarts.min.js, also eine Ebene über dem eigenen Ordner. Wer eine andere Struktur hat, passt den Pfad im <script>-Tag des HTML an. In Vis-2 einbinden Als iFrame-Widget mit der URL der Datei, z. B. /vis-2.0/charts/WP/LogPH_R290V8.html?box=0. Die Widgets haben URL-Parameter, damit man sie an den verfügbaren Platz anpassen kann: Widget Parameter Wirkung LogPH_R290V8.html box=0 Kennwerte-Box im Diagramm ausblenden (dann LogPH_Kennwerte.html daneben) LogPH_Kennwerte.html spalten=1 / 2, kopf=0 Spaltenzahl, Kopfzeile aus SPC_WP_V3.html karten=eev,sh, titel=0, fuss=0, zoom=0 Karten auf mehrere Widgets verteilen SPC_WP_Hilfe.html karte=eev usw. Hilfeseite direkt beim passenden Reiter öffnen Dark/Light wird automatisch über prefers-color-scheme umgeschaltet, der Hintergrund ist transparent. Anpassen an die eigene Anlage Im Kopf von WP_LogPH.js stehen die DP-IDs als Konstanten. Die tauscht ihr gegen eure eigenen aus. Für ein anderes Kältemittel muss die Stofftabelle neu erzeugt werden. Das Ganze ist auf meine Anlage zugeschnitten, mit Anpassung der DP's im Skriptkopf sollte es aber auch bei anderen laufen. Teil 3: Monitoring mit Regelkarten (SPC) für den Kältekreis Das log-p-h-Diagramm ist eine Momentaufnahme. Eine andere Frage ist aber: Verändert sich die Anlage über die Zeit? Dazu habe ich aus historischen Daten Modelle für die relevanten Kennwerte rechnen lassen und diese in Regelkarten als "Abweichung" dargestellt. Kurze Erklärung ist weiter unten. [image: image.jpeg] das Skript dazu: Spoiler // @desc WP SPC: tägliche EWMA-Regelkarten für den Kältekreis der Wolf CHA plus EEV-Stundenalarm // @version 1.0.0 // @changelog 1.0.0 | 2026-09-24 | Erstversion; Referenzphase 2025-10-01 bis 2026-04-30 (nach Reparatur/Firmware) // @desc-long Läuft täglich um 00:20 und beim Skriptstart, in drei Schritten. // @desc-long 1) Nachführen: Vollständige Tage seit dem letzten Lauf werden stundenweise aus den Rohdaten (ts_number/ts_string) ausgewertet und per Upsert in wp_kennwerte_h geschrieben. Filter und Stoffwerte sind identisch zu WP_LogPH_Backfill.js, höchstens 60 Tage je Lauf. // @desc-long Stundenfilter: alle Pflicht-DPs vorhanden, Δp ≥ 2 bar über die ganze Stunde, EEV Kühlen = 0 (kein Abtauen/Kühlen), Umschaltventil innerhalb der Stunde unverändert. // @desc-long 2) Regelkarten: Alle Stunden ab SPC_START werden geladen und gegen die Referenzmodelle (lineare Regression) zu Residuen verrechnet. Daraus entstehen Tagesmittel (nur Heizbetrieb, plausibel, mindestens 3 Stunden), dann EWMA, dann Eingriffsgrenzen μ ± 3·sEWMA. // @desc-long 3) Stundenalarm: Das EEV-Residuum einzelner Stunden (alle Betriebsarten, auch unplausible) über 60 Schritte ist der Sofortindikator. Beim Leck 2025 stand er zwei Tage vor dem Totalausfall bei +139. // @desc-long Warum EWMA mit empirischer Streuung: Die Tagesresiduen sind autokorreliert (EEV AC1 = 0,6), die theoretische EWMA-Streuung würde zu enge Grenzen liefern. sEWMA ist die Streuung der EWMA-Statistik in der Referenzphase. // @desc-long Warum eine eigene Tabelle: wp_kennwerte_h hängt an keinem Continuous Aggregate, es besteht also kein Invalidierungsrisiko. // @desc-long Ein Tagesalarm zählt nur, wenn der letzte ausgewertete Tag höchstens 3 Tage alt ist, denn im Sommer steht die EWMA oft wochenlang. Stundenalarme zählen für den Status 36 h lang. // @ipo-input Rohdaten Kältekreis | Rohdaten Umgebung & Leistung | Betriebsart | Stundenkennwerte // @ipo-input-sub ts_number: p/T Saug- und Heißgas, RL/VL, EEV Heizen | Zuluft, Abluft, AT, Heizleistung, P_el, Durchfluss | Umschaltventil, EEV Kühlen | wp_kennwerte_h ab SPC_START // @ipo-proc Nachführen | Stundenkennwerte | Residuen | Tagesmittel + EWMA | Alarme // @ipo-proc-sub bis 60 Tage je Lauf | R290-Stofftabelle, gleiche Filter wie Backfill | Ist − Referenzmodell je KPI | nur HZ, plausibel, ≥ 3 h, λ = 0,2 | EWMA > μ ± 3·sEWMA, EEV-Stunde > 60 // @ipo-output wp_kennwerte_h | Widget-JSON | Alarm-DPs | EWMA-DPs // @ipo-output-sub Upsert je Stunde, version spc-1.0.0 | Grenzen, Tagesreihe, Stundenalarme | Alarm / AlarmText / LetzterTag | EWMA_eev / _sh / _gv / _eta / _hg // @dp SPC.json | JSON | Widget-Daten: Grenzen, Tagesreihe mit EWMA, Stundenalarme, Alarmstatus (nicht loggen) // @dp SPC.Alarm | Boolean | EWMA außerhalb der Grenzen (frischer Tag) oder EEV-Stundenalarm in den letzten 36 h // @dp SPC.AlarmText | String | Alarmtexte, mit ; getrennt // @dp SPC.LetzterTag | String | Letzter ausgewerteter Tag JJJJ-MM-TT // @dp SPC.BisTs | Number | intern: Rohdaten ausgewertet bis (ms) – Startpunkt für das nächste Nachführen // @dp SPC.EWMA_eev | Number | EWMA Residuum EEV-Öffnung (Schritte) // @dp SPC.EWMA_sh | Number | EWMA Residuum Sauggasüberhitzung (K) // @dp SPC.EWMA_gv | Number | EWMA Residuum Grädigkeit Verdampfer (K) // @dp SPC.EWMA_eta | Number | EWMA Residuum η isentrop // @dp SPC.EWMA_hg | Number | EWMA Residuum Heißgasüberhitzung (K) // @schema grenzen.<kpi> | name/einheit/nk/mu/ucl/lcl | Mittellinie und Eingriffsgrenzen je KPI // @schema tage[] | d/n/<kpi>/z_<kpi>/a_<kpi> | Tag, Anzahl Stunden, Tagesresiduum, EWMA, Alarmflag // @schema <kpi> | eev/sh/gv/eta/hg | EEV-Öffnung / Überhitzung / Grädigkeit Verdampfer / η_is / Heißgasüberhitzung // @schema stdAlarme[] | ts/r/modus | EEV-Stundenalarme der letzten 120 Tage (r = Residuum in Schritten) // @schema alarm | true/false | Gesamtstatus, Text in alarmText // @schema lambda | 0,2 | EWMA-Gewicht // @schema L | 3 | Eingriffsgrenze in sEWMA // @schema minStd | 3 | Mindest-Heizstunden je Tag // @schema eevStdGrenze | 60 | Stundenalarm-Grenze EEV in Schritten // @func lauf | Ablauf Nachführen → Regelkarten, gegen Doppelaufruf gesperrt // @func nachfuehren | Alle vollständigen Tage seit BisTs (bzw. seit max(ts) der Tabelle) auswerten // @func tagAusRohdaten | Einen Tag stundenweise aggregieren (min/max/avg/last), filtern und rechnen // @func rechneStunde | Kennwerte einer Stunde (identisch zu WP_LogPH_Backfill.js); null = nicht rechenbar // @func schreibeStunden | Upsert in wp_kennwerte_h (ON CONFLICT ts) // @func ids | datapoints-IDs der Eingangs-DPs auflösen (einmalig) // @func startzustand | Letzter Wert vor Beginn für Umschaltventil und EEV Kühlen (Fortschreibung) // @func ventilRohString | Umschaltventil stundenweise aus ts_string (min/max/last, fortgeschrieben) // @func regelkarten | Residuen, Tagesmittel, EWMA, Alarme → DPs + JSON // @func modellwert | c0 + Σ c_i·x_i; null, wenn eine Einflussgröße fehlt // @func query | sendTo-Wrapper für die SQL-Instanz; cb(rows, fehler) // @calc Residuum = y_ist − (c0 + Σ c_i · x_i) (Modelle aus Referenzphase 2025-10-01 … 2026-04-30) // @calc π = p_hi / p_lo // @calc x_t = Σ Residuen_Tag / n (nur HZ, plausibel, n ≥ 3) // @calc z_t = λ · x_t + (1 − λ) · z_(t−1), z_0 = μ, λ = 0,2 // @calc UCL / LCL = μ ± 3 · sEWMA // @calc Stundenalarm: |eev_hz − (c0 + c1·t0 + c2·tc + c3·q_wolf + c4·ww)| > 60 Schritte // @calc Grädigkeit Verdampfer = Zuluft − T0 ; Grädigkeit Kondensator = Tc − VL // @asset SPC_WP_V3.html | vis-2.0/charts/WolfCHA/ | Heizung | cardDlgWaermepumpe // ===================== Konfiguration ===================== var SQL_INSTANZ = 'sql.3'; var TAB_KW = 'wp_kennwerte_h'; var SPC_BASE = '0_userdata.0.1000_Heizung.WP.SPC.'; var SPC_START = '2025-10-01'; // Beginn Regelkarten (= Beginn Referenzphase nach Reparatur) var LAMBDA = 0.2; // EWMA-Gewicht var L_GRENZE = 3; // Eingriffsgrenze in sEWMA var MIN_STD_TAG = 3; // Mindestanzahl gültiger Heizstunden je Tag var EEV_STD_GRENZE = 60; // Stundenalarm: |Residuum EEV| in Schritten (~7 sigma) var MAX_NACHFUEHR_TAGE = 60; // Nachführen höchstens so viele Tage je Lauf var HISTORIE_STD_ALARM_TAGE = 120; // Stundenalarme im JSON so weit zurück var RECHEN_VERSION = 'spc-1.0.0'; // Filter/Konstanten identisch zu WP_LogPH.js / Backfill var MIN_DP_BAR = 2.0, RL_OFFSET_K = 1.5, RHO_KG_L = 0.99, CP_KJ_KGK = 4.18, MIN_SPREIZ_K = 1.0, ETA_MIN = 0.35, ETA_MAX = 0.95; var DP = { pLo: 'alias.0.1000_Heizung.WP.Verdichter.SauggasDruck', tLo: 'alias.0.1000_Heizung.WP.Verdichter.SauggasTemperatur', pHi: 'alias.0.1000_Heizung.WP.Verdichter.HeissgasDruck', tHi: 'alias.0.1000_Heizung.WP.Verdichter.HeissgasTemperatur', rl: 'alias.0.1000_Heizung.WP.RLTemperatur', vl: 'alias.0.1000_Heizung.WP.VLTemperatur', zuluft: 'alias.0.1000_Heizung.WP.Verdichter.ZuluftTemperatur', abluft: 'alias.0.1000_Heizung.WP.Verdichter.AbluftTemperatur', at: 'alias.0.1000_Heizung.Klima.AussenTemperatur', qWolf: 'alias.0.1000_Heizung.WP.Verdichter.Heizleistung', pEl: 'alias.0.5000_EnergieVerbrauch.WP.Leistungsaufnahme', flow: 'alias.0.1000_Heizung.WP.DurchflussHK', eevK: 'alias.0.1000_Heizung.WP.Verdichter.EEVKuehlen', eevHz: 'alias.0.1000_Heizung.WP.Verdichter.EEVHeizen', ventil: 'alias.0.1000_Heizung.WP.Umschaltventil' }; var PFLICHT = ['pLo', 'tLo', 'pHi', 'tHi', 'rl', 'vl']; // ===================== Referenzmodelle (Referenzphase 2025-10-01 .. 2026-04-30, Heizbetrieb, plausibel) ===================== // y = c0 + c1*x1 + c2*x2 + ...; Einflussgrößen in 'x' (Spaltennamen aus wp_kennwerte_h, 'pi' = p_hi/p_lo) // mu / sEWMA: Mittelwert der Tagesresiduen und Streuung der EWMA-Statistik in der Referenzphase var KPI = { eev: { y: 'eev_hz', x: ['t0', 'tc', 'q_wolf'], c: [66.04745, 0.72077, -0.75554, 30.59632], mu: 0.00274, sEWMA: 4.31165, name: 'EEV-Öffnung', einheit: 'Schritte', nk: 1 }, sh: { y: 'ueberhitzung', x: [], c: [4.87450], mu: 0.03611, sEWMA: 0.24039, name: 'Sauggasüberhitzung', einheit: 'K', nk: 2 }, gv: { y: 'graed_verd', x: ['q_wolf', 'zuluft'], c: [-0.60701, 0.91086, 0.01665], mu: 0.05405, sEWMA: 0.24280, name: 'Grädigkeit Verdampfer', einheit: 'K', nk: 2 }, eta: { y: 'eta_is', x: ['pi', 'ueberhitzung'], c: [0.77102, -0.03826, 0.01077], mu: 0.00035, sEWMA: 0.00369, name: 'η isentrop', einheit: '', nk: 4 }, hg: { y: 'heissgas_ueberh', x: ['pi', 'ueberhitzung'], c: [-2.64778, 6.32724, 0.56270], mu: -0.00625, sEWMA: 0.30715, name: 'Heißgasüberhitzung', einheit: 'K', nk: 2 } }; // Stundenalarm EEV: Modell inkl. Betriebsart (ww = 1 bei Warmwasser), gleiche Referenzphase, alle plausiblen Stunden var EEV_STD = { x: ['t0', 'tc', 'q_wolf', 'ww'], c: [65.9429, 0.7772, -0.7591, 30.665, 0.7977] }; // ===================== Stofftabelle R290 (identisch zu WP_LogPH.js) ===================== var TAB = { fluid: "R290", p: [1.5,1.645,1.804,1.979,2.17,2.38,2.611,2.863,3.14,3.444,3.777,4.142,4.543,4.983,5.465,5.993,6.573,7.209,7.907,8.672,9.51,10.431,11.44,12.546,13.76,15.091,16.551,18.153,19.909,21.835,23.947,26.264,28.805,31.592,34.648,38.0], dT: [0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100], sat: [ [1.5,-32.818,121.48,537.04], [1.645,-30.508,126.8,539.78], [1.804,-28.149,132.26,542.57], [1.979,-25.73,137.89,545.42], [2.17,-23.27,143.65,548.31], [2.38,-20.748,149.59,551.26], [2.611,-18.163,155.72,554.27], [2.863,-15.533,162.0,557.31], [3.14,-12.837,168.48,560.41], [3.444,-10.077,175.16,563.57], [3.777,-7.257,182.04,566.76], [4.142,-4.372,189.14,570.01], [4.543,-1.414,196.47,573.3], [4.983,1.616,204.05,576.64], [5.465,4.714,211.87,580.02], [5.993,7.883,219.96,583.42], [6.573,11.135,228.33,586.87], [7.209,14.466,237.0,590.34], [7.907,17.88,246.0,593.82], [8.672,21.377,255.32,597.31], [9.51,24.956,264.99,600.8], [10.431,28.632,275.05,604.27], [11.44,32.395,285.51,607.71], [12.546,36.252,296.39,611.09], [13.76,40.208,307.75,614.38], [15.091,44.262,319.61,617.56], [16.551,48.418,332.01,620.58], [18.153,52.68,345.01,623.39], [19.909,57.044,358.67,625.9], [21.835,61.515,373.06,628.02], [23.947,66.093,388.29,629.61], [26.264,70.779,404.5,630.45], [28.805,75.57,421.88,630.24], [31.592,80.464,440.78,628.48], [34.648,85.453,461.82,624.22], [38.0,90.524,486.51,615.18] ], h: [ [537.04,544.55,552.14,559.79,567.53,575.35,583.25,591.25,599.33,607.51,615.79,624.16,632.63,641.21,649.88,658.66,667.55,676.54,685.64,694.84,704.15], [539.78,547.37,555.02,562.74,570.55,578.43,586.41,594.46,602.61,610.86,619.2,627.63,636.17,644.81,653.54,662.39,671.33,680.39,689.54,698.81,708.18], [542.57,550.23,557.96,565.75,573.63,581.58,589.62,597.75,605.97,614.28,622.68,631.19,639.79,648.49,657.29,666.2,675.21,684.32,693.55,702.87,712.31], [545.42,553.16,560.96,568.84,576.78,584.81,592.92,601.12,609.41,617.79,626.26,634.83,643.5,652.27,661.14,670.11,679.19,688.37,697.66,707.05,716.55], [548.31,556.13,564.01,571.96,579.99,588.09,596.28,604.55,612.91,621.36,629.91,638.55,647.28,656.12,665.06,674.1,683.25,692.49,701.85,711.3,720.87], [551.26,559.16,567.13,575.16,583.27,591.45,599.71,608.06,616.5,625.02,633.64,642.35,651.17,660.07,669.08,678.2,687.41,696.73,706.15,715.67,725.31], [554.27,562.26,570.32,578.43,586.62,594.89,603.23,611.66,620.17,628.78,637.47,646.26,655.15,664.13,673.22,682.4,691.68,701.07,710.57,720.16,729.87], [557.31,565.4,573.54,581.75,590.03,598.38,606.81,615.32,623.91,632.6,641.37,650.24,659.2,668.26,677.42,686.68,696.04,705.5,715.07,724.74,734.51], [560.41,568.6,576.84,585.14,593.51,601.95,610.47,619.06,627.74,636.51,645.37,654.32,663.36,672.5,681.74,691.08,700.51,710.05,719.69,729.44,739.28], [563.57,571.86,580.2,588.6,597.07,605.6,614.21,622.89,631.66,640.52,649.46,658.49,667.62,676.84,686.16,695.58,705.1,714.71,724.43,734.25,744.18], [566.76,575.17,583.62,592.12,600.69,609.32,618.02,626.8,635.66,644.61,653.64,662.76,671.97,681.28,690.68,700.18,709.78,719.48,729.28,739.18,749.18], [570.01,578.53,587.09,595.7,604.37,613.11,621.91,630.79,639.75,648.79,657.91,667.12,676.43,685.82,695.31,704.9,714.58,724.37,734.25,744.23,754.31], [573.3,581.95,590.63,599.36,608.14,616.98,625.89,634.87,643.93,653.07,662.29,671.6,680.99,690.48,700.06,709.74,719.51,729.38,739.35,749.41,759.58], [576.64,585.42,594.23,603.08,611.98,620.93,629.95,639.04,648.2,657.45,666.77,676.17,685.67,695.25,704.93,714.69,724.56,734.52,744.57,754.73,764.98], [580.02,588.94,597.88,606.86,615.88,624.96,634.09,643.29,652.57,661.91,671.34,680.85,690.44,700.13,709.9,719.76,729.72,739.77,749.92,760.17,770.51], [583.42,592.5,601.58,610.7,619.85,629.05,638.31,647.63,657.01,666.47,676.01,685.63,695.33,705.11,714.98,724.95,735.0,745.15,755.4,765.74,776.17], [586.87,596.1,605.34,614.6,623.89,633.23,642.61,652.06,661.56,671.14,680.79,690.52,700.33,710.22,720.2,730.27,740.42,750.67,761.01,771.45,781.98], [590.34,599.74,609.15,618.56,628.0,637.48,647.0,656.57,666.21,675.91,685.68,695.52,705.44,715.45,725.54,735.71,745.97,756.33,766.77,777.31,787.94], [593.82,603.42,613.0,622.58,632.18,641.8,651.47,661.18,670.94,680.77,690.67,700.64,710.68,720.8,731.0,741.29,751.66,762.12,772.67,783.31,794.05], [597.31,607.12,616.89,626.65,636.41,646.19,656.01,665.87,675.77,685.74,695.77,705.86,716.03,726.27,736.59,746.99,757.48,768.05,778.71,789.46,800.3], [600.8,610.83,620.81,630.75,640.7,650.65,660.62,670.63,680.69,690.8,700.96,711.19,721.49,731.86,742.3,752.82,763.43,774.12,784.89,795.75,806.7], [604.27,614.55,624.75,634.91,645.04,655.17,665.32,675.49,685.71,695.96,706.27,716.64,727.08,737.58,748.15,758.8,769.53,780.34,791.23,802.21,813.27], [607.71,618.27,628.71,639.09,649.43,659.76,670.08,680.43,690.81,701.22,711.68,722.2,732.78,743.42,754.13,764.91,775.77,786.7,797.72,808.82,820.0], [611.09,621.95,632.67,643.29,653.85,664.39,674.91,685.44,695.99,706.57,717.2,727.87,738.59,749.38,760.23,771.15,782.14,793.21,804.35,815.58,826.88], [614.38,625.59,636.61,647.5,658.31,669.06,679.8,690.52,701.26,712.02,722.81,733.65,744.53,755.47,766.47,777.53,788.67,799.87,811.15,822.5,833.94], [617.56,629.17,640.52,651.71,662.78,673.78,684.73,695.67,706.6,717.55,728.52,739.53,750.58,761.68,772.84,784.05,795.33,806.68,818.09,829.59,841.15], [620.58,632.65,644.38,655.89,667.25,678.51,689.71,700.87,712.02,723.17,734.33,745.52,756.75,768.02,779.33,790.71,802.14,813.63,825.2,836.83,848.54], [623.39,635.98,648.15,660.03,671.71,683.26,694.72,706.13,717.5,728.86,740.23,751.61,763.02,774.47,785.96,797.5,809.09,820.74,832.46,844.24,856.09], [625.9,639.13,651.8,664.09,676.13,687.99,699.74,711.41,723.03,734.62,746.2,757.79,769.4,781.03,792.7,804.42,816.18,827.99,839.87,851.81,863.81], [628.02,642.03,655.28,668.05,680.49,692.7,704.76,716.72,728.6,740.44,752.25,764.06,775.87,787.7,799.56,811.46,823.4,835.39,847.43,859.53,871.69], [629.61,644.6,658.55,671.86,684.75,697.36,709.76,722.03,734.19,746.29,758.35,770.39,782.43,794.47,806.53,818.62,830.75,842.92,855.13,867.4,879.73], [630.45,646.74,661.54,675.49,688.89,701.93,714.72,727.32,739.79,752.18,764.5,776.79,789.06,801.33,813.6,825.9,838.22,850.58,862.97,875.42,887.92], [630.24,648.31,664.16,678.86,692.86,706.39,719.59,732.57,745.38,758.07,770.68,783.24,795.75,808.26,820.76,833.27,845.79,858.35,870.94,883.57,896.25], [628.48,649.14,666.33,681.93,696.6,710.68,724.36,737.75,750.92,763.95,776.86,789.7,802.49,815.24,827.98,840.72,853.46,866.23,879.02,891.85,904.71], [624.22,649.0,667.91,684.59,700.06,714.77,728.97,742.81,756.39,769.77,783.02,796.16,809.23,822.25,835.24,848.22,861.19,874.18,887.18,900.21,913.28], [615.18,647.58,668.77,686.76,703.15,718.58,733.36,747.7,761.72,775.49,789.09,802.56,815.93,829.24,842.5,855.73,868.94,882.16,895.39,908.63,921.9] ], s: [ [2.4254,2.4564,2.487,2.5173,2.5473,2.577,2.6065,2.6358,2.6649,2.6938,2.7226,2.7512,2.7796,2.8079,2.8361,2.8642,2.8922,2.92,2.9478,2.9754,3.003], [2.4203,2.4512,2.4818,2.5121,2.5421,2.5718,2.6013,2.6306,2.6597,2.6886,2.7174,2.7459,2.7744,2.8027,2.8309,2.8589,2.8869,2.9147,2.9425,2.9701,2.9977], [2.4153,2.4462,2.4768,2.5071,2.5371,2.5668,2.5963,2.6256,2.6547,2.6836,2.7123,2.7409,2.7694,2.7977,2.8258,2.8539,2.8818,2.9096,2.9374,2.965,2.9926], [2.4104,2.4414,2.472,2.5023,2.5323,2.562,2.5915,2.6208,2.6499,2.6788,2.7075,2.7361,2.7645,2.7928,2.821,2.849,2.877,2.9048,2.9325,2.9601,2.9877], [2.4057,2.4367,2.4673,2.4976,2.5276,2.5574,2.5869,2.6162,2.6453,2.6742,2.7029,2.7315,2.7599,2.7882,2.8164,2.8444,2.8724,2.9002,2.9279,2.9555,2.9831], [2.4012,2.4322,2.4628,2.4932,2.5232,2.553,2.5825,2.6118,2.6409,2.6698,2.6985,2.7271,2.7555,2.7838,2.812,2.84,2.868,2.8958,2.9235,2.9511,2.9787], [2.3968,2.4279,2.4585,2.4889,2.5189,2.5487,2.5783,2.6076,2.6367,2.6656,2.6944,2.7229,2.7514,2.7797,2.8078,2.8359,2.8638,2.8916,2.9193,2.947,2.9745], [2.3926,2.4237,2.4544,2.4848,2.5149,2.5447,2.5743,2.6036,2.6327,2.6617,2.6904,2.719,2.7475,2.7758,2.8039,2.832,2.8599,2.8877,2.9155,2.9431,2.9706], [2.3886,2.4197,2.4505,2.4809,2.511,2.5409,2.5705,2.5998,2.629,2.6579,2.6867,2.7153,2.7438,2.7721,2.8003,2.8283,2.8563,2.8841,2.9118,2.9394,2.9669], [2.3847,2.4159,2.4467,2.4772,2.5074,2.5373,2.5669,2.5963,2.6255,2.6544,2.6832,2.7119,2.7403,2.7687,2.7968,2.8249,2.8528,2.8807,2.9084,2.936,2.9635], [2.3809,2.4123,2.4432,2.4737,2.5039,2.5339,2.5635,2.593,2.6222,2.6512,2.68,2.7086,2.7371,2.7655,2.7937,2.8217,2.8497,2.8775,2.9053,2.9329,2.9604], [2.3774,2.4088,2.4398,2.4704,2.5007,2.5307,2.5604,2.5899,2.6191,2.6481,2.677,2.7057,2.7342,2.7625,2.7908,2.8189,2.8468,2.8747,2.9024,2.93,2.9576], [2.3739,2.4055,2.4366,2.4673,2.4976,2.5277,2.5575,2.587,2.6163,2.6453,2.6742,2.7029,2.7315,2.7599,2.7881,2.8162,2.8442,2.8721,2.8998,2.9274,2.955], [2.3707,2.4023,2.4335,2.4643,2.4948,2.5249,2.5547,2.5843,2.6137,2.6428,2.6717,2.7005,2.729,2.7575,2.7857,2.8138,2.8418,2.8697,2.8975,2.9251,2.9527], [2.3675,2.3993,2.4307,2.4616,2.4921,2.5223,2.5523,2.5819,2.6113,2.6405,2.6695,2.6982,2.7269,2.7553,2.7836,2.8118,2.8398,2.8677,2.8954,2.9231,2.9507], [2.3645,2.3965,2.428,2.459,2.4897,2.52,2.55,2.5797,2.6092,2.6384,2.6675,2.6963,2.7249,2.7534,2.7818,2.8099,2.838,2.8659,2.8937,2.9214,2.949], [2.3616,2.3938,2.4254,2.4566,2.4874,2.5179,2.548,2.5778,2.6073,2.6366,2.6657,2.6946,2.7233,2.7518,2.7802,2.8084,2.8365,2.8644,2.8922,2.9199,2.9475], [2.3588,2.3912,2.4231,2.4544,2.4854,2.5159,2.5461,2.5761,2.6057,2.6351,2.6642,2.6932,2.7219,2.7505,2.7789,2.8072,2.8353,2.8632,2.8911,2.9188,2.9464], [2.356,2.3887,2.4208,2.4524,2.4835,2.5142,2.5445,2.5746,2.6043,2.6338,2.663,2.692,2.7208,2.7494,2.7779,2.8062,2.8343,2.8623,2.8902,2.918,2.9456], [2.3534,2.3864,2.4187,2.4505,2.4818,2.5127,2.5432,2.5733,2.6031,2.6327,2.662,2.6911,2.72,2.7487,2.7772,2.8055,2.8337,2.8618,2.8897,2.9174,2.9451], [2.3508,2.3841,2.4168,2.4488,2.4803,2.5113,2.542,2.5723,2.6022,2.6319,2.6613,2.6905,2.7194,2.7482,2.7768,2.8052,2.8334,2.8615,2.8894,2.9172,2.9449], [2.3481,2.3819,2.4149,2.4472,2.4789,2.5102,2.541,2.5714,2.6015,2.6313,2.6608,2.6901,2.7191,2.748,2.7766,2.8051,2.8334,2.8615,2.8895,2.9173,2.945], [2.3454,2.3797,2.4131,2.4457,2.4777,2.5092,2.5402,2.5708,2.6011,2.631,2.6606,2.69,2.7191,2.7481,2.7768,2.8053,2.8336,2.8618,2.8898,2.9177,2.9455], [2.3427,2.3775,2.4113,2.4443,2.4767,2.5084,2.5396,2.5704,2.6008,2.6309,2.6607,2.6902,2.7194,2.7484,2.7772,2.8058,2.8342,2.8624,2.8905,2.9184,2.9462], [2.3398,2.3753,2.4096,2.443,2.4757,2.5077,2.5392,2.5702,2.6008,2.6311,2.661,2.6906,2.7199,2.749,2.7779,2.8066,2.8351,2.8634,2.8915,2.9195,2.9473], [2.3366,2.3729,2.4078,2.4417,2.4748,2.5072,2.5389,2.5702,2.601,2.6314,2.6615,2.6912,2.7207,2.7499,2.7789,2.8077,2.8362,2.8646,2.8928,2.9208,2.9487], [2.3331,2.3704,2.406,2.4405,2.474,2.5067,2.5388,2.5703,2.6014,2.632,2.6622,2.6921,2.7218,2.7511,2.7802,2.809,2.8377,2.8661,2.8944,2.9225,2.9504], [2.3292,2.3675,2.404,2.4391,2.4732,2.5063,2.5388,2.5706,2.6019,2.6327,2.6632,2.6933,2.723,2.7525,2.7817,2.8107,2.8394,2.868,2.8963,2.9245,2.9525], [2.3245,2.3643,2.4018,2.4377,2.4723,2.506,2.5388,2.571,2.6026,2.6337,2.6643,2.6946,2.7246,2.7542,2.7835,2.8126,2.8415,2.8701,2.8985,2.9268,2.9548], [2.319,2.3606,2.3993,2.436,2.4714,2.5056,2.5389,2.5714,2.6034,2.6347,2.6657,2.6962,2.7263,2.7561,2.7856,2.8148,2.8438,2.8725,2.901,2.9293,2.9575], [2.3122,2.3561,2.3963,2.4342,2.4703,2.5051,2.539,2.572,2.6042,2.6359,2.6671,2.6979,2.7282,2.7582,2.7878,2.8172,2.8463,2.8751,2.9038,2.9322,2.9604], [2.3036,2.3506,2.3928,2.4319,2.469,2.5046,2.539,2.5725,2.6052,2.6372,2.6687,2.6997,2.7303,2.7605,2.7903,2.8198,2.8491,2.878,2.9068,2.9353,2.9636], [2.2925,2.3439,2.3884,2.4291,2.4674,2.5038,2.5389,2.5729,2.6061,2.6386,2.6704,2.7017,2.7325,2.7629,2.7929,2.8226,2.852,2.8811,2.91,2.9386,2.967], [2.2775,2.3355,2.3831,2.4257,2.4653,2.5027,2.5386,2.5733,2.607,2.6399,2.6721,2.7037,2.7348,2.7654,2.7957,2.8256,2.8551,2.8844,2.9134,2.9422,2.9707], [2.2563,2.3249,2.3766,2.4215,2.4627,2.5013,2.5381,2.5734,2.6077,2.6411,2.6737,2.7057,2.7371,2.768,2.7985,2.8286,2.8584,2.8878,2.9169,2.9458,2.9745], [2.2228,2.3114,2.3685,2.4163,2.4593,2.4993,2.5371,2.5733,2.6082,2.6421,2.6752,2.7076,2.7393,2.7705,2.8013,2.8316,2.8616,2.8912,2.9205,2.9495,2.9783] ] }; // ===================== Stoffwert-Funktionen (identisch zu WP_LogPH.js) ===================== function lerp(a, b, f) { return a + (b - a) * f; } // Index + Anteil im log-p-Raster suchen (Randwerte werden geklemmt) function findP(p) { var P = TAB.p, lp = Math.log(p), i; if (p <= P[0]) return { i: 0, f: 0 }; if (p >= P[P.length - 1]) return { i: P.length - 2, f: 1 }; for (i = 0; i < P.length - 1; i++) { if (p <= P[i + 1]) return { i: i, f: (lp - Math.log(P[i])) / (Math.log(P[i + 1]) - Math.log(P[i])) }; } } // Sättigungszustand bei Druck p [bar abs] -> {T [°C], hL, hV [kJ/kg]} function satAtP(p) { var k = findP(p), a = TAB.sat[k.i], b = TAB.sat[k.i + 1]; return { T: lerp(a[1], b[1], k.f), hL: lerp(a[2], b[2], k.f), hV: lerp(a[3], b[3], k.f) }; } // Sättigungsdruck zu Temperatur T [°C] (Umkehrung von satAtP) function pSatAtT(T) { var S = TAB.sat, i; if (T <= S[0][1]) return S[0][0]; for (i = 0; i < S.length - 1; i++) { if (T <= S[i + 1][1]) { var f = (T - S[i][1]) / (S[i + 1][1] - S[i][1]); return Math.exp(lerp(Math.log(S[i][0]), Math.log(S[i + 1][0]), f)); } } return S[S.length - 1][0]; } // Wert aus Überhitzungs-Tabelle (h oder s) bei p und Überhitzung dT, bilinear function tabSH(grid, p, dT) { var k = findP(p), D = TAB.dT, step = D[1] - D[0]; if (dT < 0) dT = 0; if (dT > D[D.length - 1]) dT = D[D.length - 1]; var j = Math.min(Math.floor(dT / step), D.length - 2), g = (dT - D[j]) / step; var v0 = lerp(grid[k.i][j], grid[k.i][j + 1], g); var v1 = lerp(grid[k.i + 1][j], grid[k.i + 1][j + 1], g); return lerp(v0, v1, k.f); } // Enthalpie überhitzter Dampf bei p [bar] und T [°C] function hVapor(p, T) { return tabSH(TAB.h, p, T - satAtP(p).T); } function sVapor(p, T) { return tabSH(TAB.s, p, T - satAtP(p).T); } // Isentrope Endenthalpie: bei Druck p die Überhitzung suchen, bei der s = s1 function hIsentrop(p, s1) { var D = TAB.dT, j, sa, sb; for (j = 0; j < D.length - 1; j++) { sa = tabSH(TAB.s, p, D[j]); sb = tabSH(TAB.s, p, D[j + 1]); if (s1 <= sb || j === D.length - 2) { var g = Math.max(0, (s1 - sa) / (sb - sa)); return tabSH(TAB.h, p, D[j] + g * (D[j + 1] - D[j])); } } } // Enthalpie unterkühlte Flüssigkeit bei T [°C] (Näherung: Siedeenthalpie bei T) function hLiquid(T) { return satAtP(pSatAtT(T)).hL; } // ===================== Hilfsfunktionen ===================== function r(v, n) { var f = Math.pow(10, n); return Math.round(v * f) / f; } function esc(s) { return String(s).replace(/'/g, "''"); } function sqlNum(v, n) { return (v === null || v === undefined || isNaN(v)) ? 'NULL' : String(r(v, n)); } function tagText(ms) { var d = new Date(ms); return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2); } function tagStart(ms) { var d = new Date(ms); return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); } function num(v) { return (v === null || v === undefined || v === '') ? null : Number(v); } // @func query | sendTo-Wrapper; cb(rows, fehler) function query(sql, cb) { sendTo(SQL_INSTANZ, 'query', sql, function (res) { if (res && res.error) { log('SPC SQL-Fehler: ' + res.error + ' | ' + sql.substr(0, 300), 'warn'); return cb([], String(res.error)); } cb((res && res.result) || []); }); } // ===================== Zustand ===================== var idVon = {}, schluesselVon = {}, ventilTyp = 0; var letzteVentil = null, letzteEevK = null; var laeuft = false; // ===================== Teil 1: Nachführen aus Rohdaten ===================== // @func ids | datapoints-IDs der Eingangs-DPs auflösen (einmalig) function ids(weiter) { if (Object.keys(idVon).length) return weiter(); var namen = Object.keys(DP).map(function (k) { return "'" + esc(DP[k]) + "'"; }).join(','); query('SELECT id, name, type FROM datapoints WHERE name IN (' + namen + ')', function (rows) { rows.forEach(function (row) { Object.keys(DP).forEach(function (k) { if (DP[k] === row.name) { idVon[k] = row.id; schluesselVon[row.id] = k; if (k === 'ventil') ventilTyp = row.type; } }); }); weiter(); }); } // @func startzustand | letzter Wert vor Beginn für die fortgeschriebenen DPs (Umschaltventil, EEV Kühlen) function startzustand(ab, weiter) { var vTab = ventilTyp === 1 ? 'ts_string' : 'ts_number'; var q1 = idVon.ventil !== undefined ? 'SELECT val FROM ' + vTab + ' WHERE id = ' + idVon.ventil + ' AND ts < ' + ab + ' ORDER BY ts DESC LIMIT 1' : null; var q2 = idVon.eevK !== undefined ? 'SELECT val FROM ts_number WHERE id = ' + idVon.eevK + ' AND ts < ' + ab + ' ORDER BY ts DESC LIMIT 1' : null; var weiter2 = function () { if (!q2) return weiter(); query(q2, function (rows) { if (rows[0]) letzteEevK = Number(rows[0].val); weiter(); }); }; if (!q1) return weiter2(); query(q1, function (rows) { if (rows[0]) letzteVentil = Number(rows[0].val); weiter2(); }); } // @func ventilRohString | Umschaltventil stundenweise aus ts_string (min/max/last je Stunde, fortgeschrieben) function ventilRohString(a, b, cb) { if (idVon.ventil === undefined || ventilTyp !== 1) return cb({}); query('SELECT ts, val FROM ts_string WHERE id = ' + idVon.ventil + ' AND ts >= ' + a + ' AND ts < ' + b + ' ORDER BY ts', function (rows) { var stunden = {}, akt = letzteVentil, j = 0; for (var h = a; h < b; h += 3600000) { var mn = akt, mx = akt; while (j < rows.length && Number(rows[j].ts) < h + 3600000) { akt = Number(rows[j].val); j++; mn = mn === null ? akt : Math.min(mn, akt); mx = mx === null ? akt : Math.max(mx, akt); } if (mn !== null) stunden[h] = { min: mn, max: mx, last: akt }; } cb(stunden); }); } // @func rechneStunde | Kennwerte einer Stunde (identisch zu WP_LogPH_Backfill.js); null = nicht rechenbar function rechneStunde(w, modus) { var pLo = w.pLo.avg, pHi = w.pHi.avg, tLo = w.tLo.avg, tHi = w.tHi.avg; var satLo = satAtP(pLo), satHi = satAtP(pHi); if (tHi <= satHi.T || tLo < satLo.T - 1) return null; var h1 = hVapor(pLo, tLo), s1 = sVapor(pLo, tLo); var h2 = hVapor(pHi, tHi), h2s = hIsentrop(pHi, s1); var t3 = Math.min(w.rl.avg + RL_OFFSET_K, satHi.T); var h3 = hLiquid(t3); var qK = h2 - h3, wV = h2 - h1; if (wV <= 1 || qK <= 1) return null; var eta = (h2s - h1) / wV, cop = qK / wV; var val = function (k) { return w[k] ? w[k].avg : null; }; var pEl = val('pEl'); if (pEl !== null && pEl > 50) pEl = pEl / 1000; var qWolf = val('qWolf'), flow = val('flow'), spreiz = w.vl.avg - w.rl.avg; var qHyd = (flow !== null && flow > 0 && spreiz >= MIN_SPREIZ_K) ? flow / 60 * RHO_KG_L * CP_KJ_KGK * spreiz : null; var qBasis = qHyd || qWolf; return { modus: modus, at: val('at'), zuluft: val('zuluft'), abluft: val('abluft'), p_lo: pLo, p_hi: pHi, t_saug: tLo, t_heiss: tHi, vl: w.vl.avg, rl: w.rl.avg, t0: satLo.T, tc: satHi.T, ueberhitzung: tLo - satLo.T, unterkuehlung: satHi.T - t3, heissgas_ueberh: tHi - satHi.T, eta_is: eta, cop_kreis: cop, graed_verd: val('zuluft') !== null ? val('zuluft') - satLo.T : null, graed_kond: satHi.T - w.vl.avg, q_wolf: qWolf, q_hyd: qHyd, q_verh: (qHyd && qWolf && qHyd > 0.5) ? qWolf / qHyd : null, p_el: pEl, cop_wolf: (qWolf && pEl && pEl > 0.1) ? qWolf / pEl : null, cop_hyd: (qHyd && pEl && pEl > 0.1) ? qHyd / pEl : null, durchfluss: flow, mdot: qBasis ? qBasis / qK * 1000 : null, eev_hz: val('eevHz'), plausibel: eta >= ETA_MIN && eta <= ETA_MAX }; } var SPALTEN = ['ts', 'modus', 'at', 'zuluft', 'abluft', 'p_lo', 'p_hi', 't_saug', 't_heiss', 'vl', 'rl', 't0', 'tc', 'ueberhitzung', 'unterkuehlung', 'heissgas_ueberh', 'eta_is', 'cop_kreis', 'graed_verd', 'graed_kond', 'q_wolf', 'q_hyd', 'q_verh', 'p_el', 'cop_wolf', 'cop_hyd', 'durchfluss', 'mdot', 'eev_hz', 'plausibel', 'version']; // @func schreibeStunden | upsert in wp_kennwerte_h function schreibeStunden(zeilen, cb) { if (!zeilen.length) return cb(); var werte = zeilen.map(function (z) { return '(' + SPALTEN.map(function (s) { if (s === 'ts') return String(z.ts); if (s === 'modus') return z.modus ? "'" + z.modus + "'" : 'NULL'; if (s === 'plausibel') return z.plausibel ? 'true' : 'false'; if (s === 'version') return "'" + RECHEN_VERSION + "'"; return sqlNum(z[s], 3); }).join(',') + ')'; }); var upd = SPALTEN.filter(function (s) { return s !== 'ts'; }).map(function (s) { return s + ' = EXCLUDED.' + s; }).join(', '); query('INSERT INTO ' + TAB_KW + ' (' + SPALTEN.join(',') + ') VALUES ' + werte.join(',') + ' ON CONFLICT (ts) DO UPDATE SET ' + upd, function () { cb(); }); } // @func tagAusRohdaten | einen Tag [a, b) stundenweise aus den Rohdaten auswerten function tagAusRohdaten(a, b, cb) { var liste = Object.keys(idVon).filter(function (k) { return !(k === 'ventil' && ventilTyp === 1); }) .map(function (k) { return idVon[k]; }).join(','); var sql = 'SELECT id, (ts - ts % 3600000) AS b, min(val) AS mn, max(val) AS mx, avg(val) AS av, ' + '(array_agg(val ORDER BY ts DESC))[1] AS la FROM ts_number WHERE id IN (' + liste + ') ' + 'AND ts >= ' + a + ' AND ts < ' + b + ' GROUP BY id, (ts - ts % 3600000)'; query(sql, function (rows) { var stunden = {}; rows.forEach(function (row) { var k = schluesselVon[row.id], h = Number(row.b); if (!k) return; (stunden[h] = stunden[h] || {})[k] = { min: Number(row.mn), max: Number(row.mx), avg: Number(row.av), last: Number(row.la) }; }); ventilRohString(a, b, function (roh) { var erg = []; for (var h = a; h < b; h += 3600000) { var w = stunden[h] || {}; var v = w.ventil || roh[h]; if (v) letzteVentil = v.last; else if (letzteVentil !== null) v = { min: letzteVentil, max: letzteVentil }; var e = w.eevK; if (e) letzteEevK = e.last; else if (letzteEevK !== null) e = { max: letzteEevK }; if (!PFLICHT.every(function (k) { return w[k]; })) continue; if (w.pHi.min - w.pLo.max < MIN_DP_BAR) continue; if (e && e.max > 0) continue; if (v && v.min !== v.max) continue; var z = rechneStunde(w, v ? (v.min === 1 ? 'WW' : 'HZ') : null); if (!z) continue; z.ts = h; erg.push(z); } schreibeStunden(erg, function () { cb(erg.length); }); }); }); } // @func nachfuehren | alle vollständigen Tage seit dem letzten Lauf function nachfuehren(weiter) { ids(function () { if (PFLICHT.some(function (k) { return idVon[k] === undefined; })) { log('SPC: Pflicht-DPs fehlen in datapoints, Nachführen übersprungen', 'warn'); return weiter(); } var bis = getState(SPC_BASE + 'BisTs'); var heute = tagStart(Date.now()); var startMit = function (ab) { ab = tagStart(ab); var ende = Math.min(heute, ab + MAX_NACHFUEHR_TAGE * 86400000); if (ab >= ende) return weiter(); startzustand(ab, function () { var t = ab, summe = 0; (function naechster() { if (t >= ende) { setState(SPC_BASE + 'BisTs', ende, true); log('SPC: nachgeführt ' + tagText(ab) + ' bis ' + tagText(ende - 1) + ', ' + summe + ' gültige Stunden'); return weiter(); } var t1 = new Date(new Date(t).getFullYear(), new Date(t).getMonth(), new Date(t).getDate() + 1).getTime(); tagAusRohdaten(t, t1, function (n) { summe += n; t = t1; setTimeout(naechster, 50); }); })(); }); }; if (bis && bis.val) return startMit(Number(bis.val)); // erster Lauf: ab dem Tag nach der letzten Stunde in der Tabelle query('SELECT max(ts) AS m FROM ' + TAB_KW, function (rows) { var m = rows[0] && rows[0].m ? Number(rows[0].m) : heute - 86400000; startMit(tagStart(m) + 86400000); }); }); } // ===================== Teil 2: Regelkarten ===================== // @func modellwert | c0 + Summe c_i * x_i; null wenn eine Einflussgröße fehlt function modellwert(c, x, row) { var v = c[0]; for (var i = 0; i < x.length; i++) { var xi = row[x[i]]; if (xi === null || xi === undefined || isNaN(xi)) return null; v += c[i + 1] * xi; } return v; } // @func regelkarten | Residuen, Tagesmittel, EWMA, Alarme -> DPs + JSON function regelkarten(weiter) { var ab = new Date(SPC_START).getTime(); query('SELECT ts, modus, t0, tc, q_wolf, zuluft, eev_hz, ueberhitzung, graed_verd, eta_is, heissgas_ueberh, p_hi, p_lo, plausibel FROM ' + TAB_KW + ' WHERE ts >= ' + ab + ' ORDER BY ts', function (rows) { var tage = {}, stdAlarme = [], grenzeStd = Date.now() - HISTORIE_STD_ALARM_TAGE * 86400000; rows.forEach(function (row) { var z = {}; Object.keys(row).forEach(function (k) { z[k] = (k === 'modus' || k === 'plausibel') ? row[k] : num(row[k]); }); z.pi = (z.p_hi && z.p_lo) ? z.p_hi / z.p_lo : null; z.ww = z.modus === 'WW' ? 1 : 0; var ts = Number(row.ts); // Stundenalarm EEV: alle Betriebsarten, auch unplausible Stunden var mEev = modellwert(EEV_STD.c, EEV_STD.x, z); if (mEev !== null && z.eev_hz !== null && Math.abs(z.eev_hz - mEev) > EEV_STD_GRENZE && ts >= grenzeStd) { stdAlarme.push({ ts: ts, r: r(z.eev_hz - mEev, 0), modus: z.modus }); } // Tagesmittel: nur Heizbetrieb, plausibel if (z.modus !== 'HZ' || !(row.plausibel === true || row.plausibel === 't' || row.plausibel === 1)) return; var tag = tagText(ts), t = tage[tag] = tage[tag] || { n: 0, s: {}, c: {} }; t.n++; Object.keys(KPI).forEach(function (k) { var m = modellwert(KPI[k].c, KPI[k].x, z), y = z[KPI[k].y]; if (m === null || y === null) return; t.s[k] = (t.s[k] || 0) + (y - m); t.c[k] = (t.c[k] || 0) + 1; }); }); // EWMA über die Tage (Tage mit zu wenig Stunden werden übersprungen, EWMA bleibt stehen) var ewma = {}, reihe = []; Object.keys(KPI).forEach(function (k) { ewma[k] = KPI[k].mu; }); Object.keys(tage).sort().forEach(function (tag) { var t = tage[tag]; if (t.n < MIN_STD_TAG) return; var e = { d: tag, n: t.n }; Object.keys(KPI).forEach(function (k) { if (!t.c[k]) return; var x = t.s[k] / t.c[k]; ewma[k] = LAMBDA * x + (1 - LAMBDA) * ewma[k]; e[k] = r(x, KPI[k].nk + 1); e['z_' + k] = r(ewma[k], KPI[k].nk + 1); if (Math.abs(ewma[k] - KPI[k].mu) > L_GRENZE * KPI[k].sEWMA) e['a_' + k] = 1; }); reihe.push(e); }); // Status letzter Tag; ein Tagesalarm zählt nur, wenn der Tag frisch ist (im Sommer steht die EWMA oft wochenlang) var letzter = reihe.length ? reihe[reihe.length - 1] : null, texte = []; var frisch = letzter && (Date.now() - new Date(letzter.d).getTime() < 3 * 86400000); if (letzter && frisch) Object.keys(KPI).forEach(function (k) { if (letzter['a_' + k]) texte.push(KPI[k].name + ' EWMA ' + letzter['z_' + k] + ' ' + KPI[k].einheit + ' (' + letzter.d + ')'); }); var stdLetzte24 = stdAlarme.filter(function (a) { return a.ts >= Date.now() - 36 * 3600000; }); stdLetzte24.forEach(function (a) { texte.push('EEV-Stundenalarm ' + (a.r > 0 ? '+' : '') + a.r + ' Schritte (' + a.modus + ')'); }); var grenzen = {}; Object.keys(KPI).forEach(function (k) { var p = KPI[k]; grenzen[k] = { name: p.name, einheit: p.einheit, nk: p.nk, mu: p.mu, ucl: r(p.mu + L_GRENZE * p.sEWMA, p.nk + 1), lcl: r(p.mu - L_GRENZE * p.sEWMA, p.nk + 1) }; }); var json = { ts: Date.now(), start: SPC_START, lambda: LAMBDA, L: L_GRENZE, minStd: MIN_STD_TAG, eevStdGrenze: EEV_STD_GRENZE, grenzen: grenzen, tage: reihe, stdAlarme: stdAlarme, letzterTag: letzter ? letzter.d : null, alarm: texte.length > 0, alarmText: texte.join('; ') }; setState(SPC_BASE + 'json', JSON.stringify(json), true); setState(SPC_BASE + 'Alarm', texte.length > 0, true); setState(SPC_BASE + 'AlarmText', texte.join('; '), true); if (letzter) { setState(SPC_BASE + 'LetzterTag', letzter.d, true); Object.keys(KPI).forEach(function (k) { if (letzter['z_' + k] !== undefined) setState(SPC_BASE + 'EWMA_' + k, letzter['z_' + k], true); }); } log('SPC: ' + reihe.length + ' Tage ausgewertet, letzter ' + (letzter ? letzter.d : '-') + ', ' + stdAlarme.length + ' EEV-Stundenalarme (' + HISTORIE_STD_ALARM_TAGE + ' Tage)' + (texte.length ? ' | ALARM: ' + texte.join('; ') : '')); weiter(); }); } // ===================== Ablauf ===================== function lauf() { if (laeuft) return; laeuft = true; nachfuehren(function () { regelkarten(function () { laeuft = false; }); }); } (function anlegen() { var dps = { json: { name: 'SPC JSON für Widget (nicht loggen)', type: 'string', role: 'json', def: '' }, Alarm: { name: 'SPC Alarm (EWMA außerhalb oder EEV-Stundenalarm)', type: 'boolean', role: 'indicator.alarm', def: false }, AlarmText: { name: 'SPC Alarmtext', type: 'string', role: 'text', def: '' }, LetzterTag:{ name: 'SPC letzter ausgewerteter Tag', type: 'string', role: 'text', def: '' }, BisTs: { name: 'SPC intern: Rohdaten ausgewertet bis (ms)', type: 'number', role: 'value', def: 0 } }; Object.keys(KPI).forEach(function (k) { dps['EWMA_' + k] = { name: 'SPC EWMA Residuum ' + KPI[k].name, type: 'number', role: 'value', unit: KPI[k].einheit, def: 0 }; }); var keys = Object.keys(dps), offen = keys.length; keys.forEach(function (k) { var c = dps[k], common = { name: c.name, type: c.type, role: c.role, read: true, write: false }; if (c.unit) common.unit = c.unit; createState(SPC_BASE + k, c.def, false, common, function () { if (--offen === 0) { schedule('20 0 * * *', lauf); lauf(); } }); }); })(); die HTML Widgets (Einbinden in Vis analog zu Teil 2): SPC_WP_Hilfe.html SPC_WP_V3.html kurz die Idee Die Rohwerte einer Wärmepumpe hängen stark vom Wetter ab, eine Regelkarte direkt auf die Überhitzung zeigt also vor allem den Winter. Deshalb wird für jeden Kennwert zuerst ein Modell erstellt: Welchen Wert hat die Anlage normalerweise bei genau diesen Bedingungen (Leistung, Drücke, Außenluft)? In die Regelkarte kommt nur die Abweichung vom Modell. Eine gesunde Anlage liegt dann im Januar wie im April um die Nulllinie. die fünf Karten Karte Was sie zeigt Typische Ursache bei Abweichung EEV-Öffnung Wie weit das Expansionsventil offen ist, im Vergleich zum Normalwert Kältemittelverlust (Frühwarner!), Filtertrockner, Fühler Sauggasüberhitzung Ob das Ventil seinen Sollwert noch schafft Kältemittelmangel, Ventil defekt Grädigkeit Verdampfer Abstand Außenluft ↔ Verdampfungstemperatur Lamellen verschmutzt, Vereisung, Ventilator η isentrop Verdichterwirkungsgrad Verschleiß am Verdichter Heißgasüberhitzung Temperatur am Verdichterausgang Kältemittelmangel, Verdichter, Durchfluss Erst die Kombination der auffälligen Karten verrät die Ursache. Eine Musterübersicht und die Erklärung je Karte stecken in der Hilfeseite, die man über einen Info-Button direkt aus der Vis öffnen kann. So liest man die Karten Graue Punkte: mittlere Abweichung eines Tages (nur Heizbetrieb, Tage mit mindestens 3 Laufstunden) Blaue Linie: EWMA (λ = 0,2), ein exponentiell geglätteter Verlauf. Ein einzelner Ausreißer-Tag bewegt sie kaum, eine anhaltende Verschiebung zieht sie mit. Genau richtig für schleichende Veränderungen. OEG/UEG: Eingriffsgrenzen (±3σ aus einer Referenzphase) Rote senkrechte Linien (EEV-Karte): Stundenalarm, wenn das Ventil in einer einzelnen Stunde extrem ungewöhnlich steht die Erklärungen dazu aus ...Hilfe.html [image: image.jpeg] Hat es einen Nutzen? Unsere Anlage hatte im Mai 2025 ein Leck am Kältemittelsammler (durchgerostet) und hatte innerhalb weniger Tage kein Kältemittel mehr. Mit dem Modell rückwirkend auf die Daten angewendet: 20.05.2025: Das EEV stand rund 140 Schritte über normal (etwa 15 σ). Sauggasüberhitzung, Verdichterwirkungsgrad und alles andere waren noch unauffällig: Das Ventil hat den Verlust noch ausgeregelt. 22.05.2025: Der Kreis war leer. Überhitzung über 50 K, Verdampfung bei −31 °C, Heißgas 65 K statt 20 K. Die Regelkarte hätte zwei Tage vor dem Totalausfall Alarm geschlagen. Und die Frage „verschleißt mein Verdichter?“ ließ sich auch beantworten: Der Wirkungsgrad war von 2021 bis 2026 in allen Wintern stabil. Das scheinbare Absinken in den Rohdaten war reines Wetter. Technik Datenbasis: Die Kältekreis-Werte lagen bei mir seit Ende 2021 in der Datenbank. Ein Skript hat daraus rückwirkend Stunden-Kennwerte berechnet. Gewertet werden nur Stunden, in denen der Verdichter durchgehend lief, ohne Abtauung und ohne Wechsel HZ/WW. So sind rund 8.000 gültige Stunden zusammengekommen. Laufender Betrieb: Das Skript WP_SPC.js läuft jede Nacht. Es führt die neuen Tage aus den Rohdaten nach, berechnet Residuen und EWMA und setzt einen Alarm-DP mit Klartext. Den kann man direkt an Telegram & Co. hängen. Referenzphase: Nach der Reparatur gab es eine neue Firmware, also ist das Verhalten vorher und nachher nicht vergleichbar. Das aktuelle Modell bzw. die aktuellen Grenzen stammen aus dem Winter 2025/26. Hinweis für TimescaleDB-Nutzer: die berechneten historischen Kennwerte werden bewusst in eine eigene Tabelle und nicht nach ts_number geschrieben. Schreibt man rückwirkend Werte in eine Tabelle, an der Continuous Aggregates hängen, werden deren Buckets invalide. Beim nächsten Refresh werden sie aus den dann lückenhaften Rohdaten neu berechnet, und die Aggregate aller anderen DPs in dem Zeitraum sind futsch. Das habe ich auf die harte Tour gelernt (dank Proxmox Umgebung mit Backup/snapshot kein Thema). Grenzen im Sommer gibt es kaum Heizstunden, die Karten pausieren dann. Das ist normal. die Modelle sind auf meine Anlage angelernt. Auf einer anderen WP muss man sie aus den eigenen Daten neu erzeugen, und dafür braucht man mindestens einen Winter. das Ganze ersetzt keine Wartung. Es zeigt, dass sich etwas verändert und wo man suchen sollte. hier wird Postgres SQL mit Timescale als DB genutzt. Die SQL Abfragen müssen angepasst werden. Influx User müssen das in Eigenregie machen, ich habe dazu keine Umgebung verfügbar. Viel Text zu einem nicht ganz trivialen Thema. Falls jemand das auf seine eigene Wärmepumpe überträgt, würde mich das Ergebnis interessieren.
  • Darf die KI die Haustür aufschließen?

    Verschoben
    20
    1 Stimmen
    20 Beiträge
    878 Aufrufe
    mcm1957M
    @markusmazurczak said: Wie gepflegt ist das in der Praxis? Sind Räume und Funktionen bei euch meistens vollständig zugeordnet, oder hängt vieles ohne Zuordnung im Objektbaum? Bei mir läuft das rein technisch orientiert. ich hab keine Räume oder Functions sondern spreche direkt dei einzelnen States (bzw. deren Alias) an.

406

Online

33.1k

Benutzer

84.2k

Themen

1.4m

Beiträge