This commit is contained in:
@@ -24,6 +24,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
private const VERBRAUCHER_MODUL_IDS = [
|
private const VERBRAUCHER_MODUL_IDS = [
|
||||||
'{B7C54AF4-AD7D-4FE4-B75D-203693906251}',
|
'{B7C54AF4-AD7D-4FE4-B75D-203693906251}',
|
||||||
'{15879A4E-D0C2-4495-83DE-46E1E462591E}',
|
'{15879A4E-D0C2-4495-83DE-46E1E462591E}',
|
||||||
|
'{A31C9274-54F7-4BD2-9804-7AF23E86C4D1}',
|
||||||
'{0D94913C-0F31-4C29-A685-6EB4AE58E55D}',
|
'{0D94913C-0F31-4C29-A685-6EB4AE58E55D}',
|
||||||
'{32F65988-BE55-4FD6-9FFF-CF1C4C8628F8}',
|
'{32F65988-BE55-4FD6-9FFF-CF1C4C8628F8}',
|
||||||
'{C92D5EEF-9632-47A5-9659-4B02BF40FBE9}',
|
'{C92D5EEF-9632-47A5-9659-4B02BF40FBE9}',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ weiteren Module sind als Diskussionsentwürfe dokumentiert.
|
|||||||
- Wassererwaermer (implementiert)
|
- Wassererwaermer (implementiert)
|
||||||
- Pufferspeicher
|
- Pufferspeicher
|
||||||
- Verbraucher 1-Stufig (implementiert)
|
- Verbraucher 1-Stufig (implementiert)
|
||||||
- Waermepumpe
|
- Waermepumpe (implementiert)
|
||||||
- Ladestation Stand-Alone (implementiert)
|
- Ladestation Stand-Alone (implementiert)
|
||||||
- Ladestation Gateway
|
- Ladestation Gateway
|
||||||
- Easee Gateway
|
- Easee Gateway
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
{
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "ExpansionPanel",
|
||||||
|
"caption": "Manager und Zeitverhalten",
|
||||||
|
"expanded": true,
|
||||||
|
"items": [
|
||||||
|
{"type": "NumberSpinner", "name": "PrioritaetPV", "caption": "Prioritaet PV", "minimum": 0},
|
||||||
|
{"type": "NumberSpinner", "name": "PrioritaetPeak", "caption": "Prioritaet Peak", "minimum": 0},
|
||||||
|
{"type": "NumberSpinner", "name": "Meldeintervall", "caption": "Meldeintervall", "suffix": " s", "minimum": 1},
|
||||||
|
{"type": "NumberSpinner", "name": "VorgabeTimeout", "caption": "Vorgabe-Timeout", "suffix": " s", "minimum": 1}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ExpansionPanel",
|
||||||
|
"caption": "Waermepumpe und Kontakte",
|
||||||
|
"expanded": true,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "Select",
|
||||||
|
"name": "Kontaktart",
|
||||||
|
"caption": "Kontaktart",
|
||||||
|
"options": [
|
||||||
|
{"caption": "Sperre und Erhoehung", "value": 0},
|
||||||
|
{"caption": "SG Ready", "value": 1}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SelectVariable",
|
||||||
|
"name": "Kontakt1VariableID",
|
||||||
|
"caption": "Kontakt 1 (Sperre / SG Ready 1)",
|
||||||
|
"validVariableTypes": [0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "Kontakt1Invertiert",
|
||||||
|
"caption": "Kontakt 1 invertieren"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SelectVariable",
|
||||||
|
"name": "Kontakt2VariableID",
|
||||||
|
"caption": "Kontakt 2 (Erhoehung / SG Ready 2)",
|
||||||
|
"validVariableTypes": [0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "Kontakt2Invertiert",
|
||||||
|
"caption": "Kontakt 2 invertieren"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "Nennleistung",
|
||||||
|
"caption": "Nennleistung",
|
||||||
|
"suffix": " W",
|
||||||
|
"minimum": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ExpansionPanel",
|
||||||
|
"caption": "Betriebsrueckmeldung",
|
||||||
|
"expanded": true,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "Select",
|
||||||
|
"name": "Rueckmeldungsart",
|
||||||
|
"caption": "Rueckmeldungsart",
|
||||||
|
"options": [
|
||||||
|
{"caption": "Gemessene Leistung", "value": 0},
|
||||||
|
{"caption": "Betriebsstatus", "value": 1}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SelectVariable",
|
||||||
|
"name": "IstleistungVariableID",
|
||||||
|
"caption": "Leistung oder Betriebsstatus",
|
||||||
|
"validVariableTypes": [0, 1, 2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "Laufschwelle",
|
||||||
|
"caption": "Laufschwelle bei Leistungsmessung",
|
||||||
|
"suffix": " W",
|
||||||
|
"minimum": 0,
|
||||||
|
"digits": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ExpansionPanel",
|
||||||
|
"caption": "Schutzzeiten",
|
||||||
|
"expanded": true,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "Anlaufwartezeit",
|
||||||
|
"caption": "Anlaufwartezeit",
|
||||||
|
"suffix": " s",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "Wiederholsperre",
|
||||||
|
"caption": "Wiederholsperre nach erfolglosem Anlauf",
|
||||||
|
"suffix": " s",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "Mindestlaufzeit",
|
||||||
|
"caption": "Mindestlaufzeit",
|
||||||
|
"suffix": " s",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "Mindestsperrzeit",
|
||||||
|
"caption": "Mindestsperrzeit",
|
||||||
|
"suffix": " s",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "MaximaleSperrzeit",
|
||||||
|
"caption": "Maximale Sperrzeit am Stueck",
|
||||||
|
"suffix": " s",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "MaximaleSperrzeit24h",
|
||||||
|
"caption": "Maximale Sperrzeit in 24 Stunden",
|
||||||
|
"suffix": " s",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 86400
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ExpansionPanel",
|
||||||
|
"caption": "Darstellung und Diagnose",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "EinstellungenInVisu",
|
||||||
|
"caption": "Lokale Einstellungen in der Visualisierung anzeigen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "DiagnosevariablenAnzeigen",
|
||||||
|
"caption": "Diagnosevariablen anzeigen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "LoggingEin",
|
||||||
|
"caption": "Diagnoseprotokoll aktivieren"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": [
|
||||||
|
{"code": 102, "icon": "active", "caption": "Aktiv"},
|
||||||
|
{"code": 201, "icon": "error", "caption": "Konfiguration ungueltig"},
|
||||||
|
{"code": 202, "icon": "error", "caption": "Schalt- oder Rueckmeldefehler"}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"id": "{A31C9274-54F7-4BD2-9804-7AF23E86C4D1}",
|
||||||
|
"name": "VerbraucherWaermepumpe",
|
||||||
|
"type": 3,
|
||||||
|
"vendor": "Enelix",
|
||||||
|
"aliases": [
|
||||||
|
"Waermepumpe"
|
||||||
|
],
|
||||||
|
"parentRequirements": [],
|
||||||
|
"childRequirements": [],
|
||||||
|
"implemented": [],
|
||||||
|
"prefix": "ENELIX",
|
||||||
|
"url": "https://git.belevo.ch/ENELIX/Enelix-EMS/src/branch/develop/Waermepumpe"
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
# EMS-Module und Modulentwürfe
|
# EMS-Module und Modulentwürfe
|
||||||
|
|
||||||
> Manager, Warmwassererwaermer, Batterie, Verbraucher 1-Stufig, Ladestation Stand-Alone,
|
> Manager, Warmwassererwaermer, Batterie, Verbraucher 1-Stufig, Waermepumpe,
|
||||||
> Easee Gateway und Ladestation Gateway sind als installierbare IP-Symcon-Module
|
> Ladestation Stand-Alone, Easee Gateway und Ladestation Gateway sind als
|
||||||
> umgesetzt. Die weiteren Ordner enthalten Besprechungsgrundlagen.
|
> installierbare IP-Symcon-Module umgesetzt. Die weiteren Ordner enthalten Besprechungsgrundlagen.
|
||||||
|
|
||||||
Alle steuerbaren Verbraucher verwenden die gemeinsamen Datenpunkte aus der
|
Alle steuerbaren Verbraucher verwenden die gemeinsamen Datenpunkte aus der
|
||||||
[EMS-Schnittstelle](../Schnittstelle.md). In den Modul-READMEs stehen deshalb
|
[EMS-Schnittstelle](../Schnittstelle.md). In den Modul-READMEs stehen deshalb
|
||||||
|
|||||||
@@ -1,47 +1,89 @@
|
|||||||
# Wärmepumpe
|
# Wärmepumpe
|
||||||
|
|
||||||
> Status: Diskussionsentwurf.
|
> Status: Implementiert.
|
||||||
|
|
||||||
Die Kontaktwirkung ist als Sperrkontakte oder SG Ready konfigurierbar. Die
|
Das Modul bindet eine Wärmepumpe über zwei schaltbare Boolean-Ausgänge in den
|
||||||
elektrische Leistung kann fehlen, berechnet oder gemessen werden.
|
Enelix-Manager ein. Die Ausgänge können als getrennte Kontakte für Sperre und
|
||||||
|
Erhöhung oder als SG-Ready-Eingänge verwendet werden. Die Regelung arbeitet mit
|
||||||
|
dem Nachrichtenvertrag 4.0 und meldet für PV und Peak ein
|
||||||
|
zustandsabhängiges Leistungsangebot.
|
||||||
|
|
||||||
## Zusätzliche Variablen
|
## Verhalten
|
||||||
|
|
||||||
| Ident | Typ / Zugriff | Beschreibung |
|
- Im PV-Betrieb kann eine freigegebene Wärmepumpe aus dem Normalzustand in die
|
||||||
|
Erhöhung geschaltet werden.
|
||||||
|
- Im Peak-Betrieb kann eine laufende Wärmepumpe gesperrt werden, sofern
|
||||||
|
Mindestlaufzeit und Sperrgrenzen dies erlauben.
|
||||||
|
- Schaltvorgänge erfolgen als Break-before-Make: beide Kontakte werden zuerst
|
||||||
|
deaktiviert, danach wird höchstens ein Kontakt aktiviert.
|
||||||
|
- Nach einem erfolglosen PV-Anlauf wird die Erhöhung aufgehoben und bis zum
|
||||||
|
Ablauf der Wiederholsperre nicht erneut angeboten.
|
||||||
|
- Die maximale Sperrzeit am Stück und innerhalb von 24 Stunden erzwingt eine
|
||||||
|
Erholungsphase im Normalbetrieb.
|
||||||
|
- Ohne gültige Manager-Vorgabe fällt die Steuerung in den sicheren
|
||||||
|
Normalzustand zurück.
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
| Property | Standard | Beschreibung |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
| `Kontaktart` | `0` | `0`: Sperre/Erhöhung, `1`: SG Ready |
|
||||||
|
| `Kontakt1VariableID` | `0` | Schaltbarer Ausgang für Sperre beziehungsweise SG Ready 1 |
|
||||||
|
| `Kontakt2VariableID` | `0` | Schaltbarer Ausgang für Erhöhung beziehungsweise SG Ready 2 |
|
||||||
|
| `Kontakt1Invertiert` | `false` | Logik des ersten Ausgangs invertieren |
|
||||||
|
| `Kontakt2Invertiert` | `false` | Logik des zweiten Ausgangs invertieren |
|
||||||
|
| `Nennleistung` | `0 W` | Elektrische Nennleistung der Wärmepumpe |
|
||||||
|
| `Rueckmeldungsart` | `0` | `0`: gemessene Leistung, `1`: Boolean-Betriebsstatus |
|
||||||
|
| `IstleistungVariableID` | `0` | Leistungsmessung oder Betriebsrückmeldung |
|
||||||
|
| `Laufschwelle` | `100 W` | Ab dieser Leistung gilt die Wärmepumpe als laufend |
|
||||||
|
| `Anlaufwartezeit` | `30 s` | Wartezeit auf bestätigten Anlauf |
|
||||||
|
| `Wiederholsperre` | `300 s` | Pause nach einem erfolglosen Anlauf |
|
||||||
|
| `Mindestlaufzeit` | `1200 s` | Mindestlaufzeit nach bestätigtem Start |
|
||||||
|
| `Mindestsperrzeit` | `1200 s` | Mindestdauer einer begonnenen Sperre |
|
||||||
|
| `MaximaleSperrzeit` | `7200 s` | Maximale zusammenhängende Sperre; `0` deaktiviert |
|
||||||
|
| `MaximaleSperrzeit24h` | `21600 s` | Maximale Sperrsumme in 24 Stunden; `0` deaktiviert |
|
||||||
|
|
||||||
|
Hinzu kommen die gemeinsamen Verbraucher-Properties für Prioritäten,
|
||||||
|
Meldeintervall, Vorgabe-Timeout, Visualisierung und Logging.
|
||||||
|
|
||||||
|
## Variablen
|
||||||
|
|
||||||
|
| Ident | Typ | Beschreibung |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `Waermepumpenzustand` | Integer / Anzeige | `0` unbekannt, `1` aus, `2` normal, `3` erhöht, `4` gesperrt. |
|
| `Waermepumpenzustand` | Integer | `1` normal/aus, `2` normal/laufend, `3` erhöht, `4` gesperrt |
|
||||||
| `SperreAktiv` | Boolean / Anzeige | Aktuelle Sperranforderung. |
|
| `WaermepumpeLaeuft` | Boolean | Bestätigter Betriebsstatus |
|
||||||
| `ErhoehungAktiv` | Boolean / Anzeige | Aktuelle Erhöhungsanforderung. |
|
| `SperreAktiv` | Boolean | Sperrkontakt ist aktiv |
|
||||||
| `SGReadyZustand` | Integer / Anzeige | Bei SG Ready Zustand 0–3. |
|
| `ErhoehungAktiv` | Boolean | Erhöhungskontakt ist aktiv |
|
||||||
| `Laufzeit` | Integer / Anzeige | Kumulierte bestätigte Laufzeit in Sekunden. |
|
| `SGReadyZustand` | Integer | Bitwert der beiden logischen Kontakte |
|
||||||
|
| `Laufzeit` | Integer | Kumulierte bestätigte Laufzeit in Sekunden |
|
||||||
|
|
||||||
## Zusätzliche Properties
|
Optionale Diagnosevariablen zeigen unter anderem Ist- und Sollleistung,
|
||||||
|
Verfügbarkeit, Anlaufstatus, Wiederholsperre, Sperrerholung, verbleibende
|
||||||
|
Mindestzeiten und die Sperrsumme der letzten 24 Stunden.
|
||||||
|
|
||||||
| Ident | Typ | Standard / Beschreibung |
|
## Adaption aus Enelix 1
|
||||||
| --- | --- | --- |
|
|
||||||
| `Kontaktwirkung` | Auswahl | `Sperrkontakte`; alternativ `SG Ready`. |
|
|
||||||
| `Kontakt1VariableID` | Integer | `0`; Sperre oder SG-Ready-Eingang 1. |
|
|
||||||
| `Kontakt2VariableID` | Integer | `0`; Erhöhung oder SG-Ready-Eingang 2. |
|
|
||||||
| `Kontakt1Invertiert` | Boolean | `false`. |
|
|
||||||
| `Kontakt2Invertiert` | Boolean | `false`. |
|
|
||||||
| `Leistungsermittlung` | Auswahl | `Nicht vorhanden`, `Berechnet` oder `Gemessen`; entspricht Quelle 0/1/2. |
|
|
||||||
| `LeistungsmessungVariableID` | Integer | `0`; nur bei gemessener Leistung. |
|
|
||||||
| `Zustandskennlinie` | String/JSON | `[]`; bei berechneter Leistung je Zustand Planleistung und Kontaktbelegung. |
|
|
||||||
| `BetriebsrueckmeldungVariableID` | Integer | `0`; optionale echte Betriebsrückmeldung. |
|
|
||||||
| `Mindestlaufzeit` | Integer | `0*` s; anlagengerecht festlegen. |
|
|
||||||
| `Mindeststillstand` | Integer | `0*` s; anlagengerecht festlegen. |
|
|
||||||
| `MaxSperrzeit` | Integer | `0` s; `0` deaktiviert die EMS-Sperrfunktion. |
|
|
||||||
|
|
||||||
Zeit- und wetterabhängiges Sperren beziehungsweise Erhöhen aus dem Altmodul
|
| Bereich | Behandlung in Enelix 2 |
|
||||||
entfällt.
|
| --- | --- |
|
||||||
|
| Grundzustände Normal, Sperre und Erhöhung | angepasst übernommen |
|
||||||
|
| Zwei physische Kontakte | angepasst und mit Invertierung sowie Rücklesekontrolle umgesetzt |
|
||||||
|
| Leistungsangebot an den Manager | neu nach Nachrichtenvertrag 4.0 implementiert |
|
||||||
|
| Mindestlauf- und Sperrzeiten | neu als ereignisbasierte Schutzlogik implementiert |
|
||||||
|
| Wetter-, Wolken- und Sonnenaufgangslogik | nicht uebernommen |
|
||||||
|
| Warmwasser-Schwellwert im Wärmepumpenmodul | nicht uebernommen |
|
||||||
|
| Fester zyklischer Fünf-Sekunden-Regler | nicht uebernommen; Ereignisse und Einmal-Timer |
|
||||||
|
|
||||||
## Zustand
|
## Lizenzierung und Manager
|
||||||
|
|
||||||
`Waermepumpenzustand`, `SperreAktiv`, `ErhoehungAktiv` beziehungsweise
|
Der Manager erkennt das Modul über die Modul-ID
|
||||||
`SGReadyZustand` als Status, `Laufzeit_s` als Istwert und
|
`{A31C9274-54F7-4BD2-9804-7AF23E86C4D1}`. Es verwendet das bestehende
|
||||||
`Kommunikationsfehler` als Störung.
|
Lizenzkontingent `consumer_single`, das im Portal als einstufiger Verbraucher
|
||||||
|
mit Wärmepumpe als Beispiel angeboten wird.
|
||||||
|
|
||||||
## Offene Punkte
|
## Sicherheitshinweise
|
||||||
|
|
||||||
- Der Formularbutton „Wassererwärmer über Wärmepumpe betreiben“ bleibt ohne
|
Die beiden Ausgänge müssen unterschiedliche, schaltbare Boolean-Variablen sein.
|
||||||
Regelwirkung, bis Anschluss und Verriegelung gemeinsam definiert sind.
|
Die Rückmeldung ist verpflichtend: entweder eine numerische Leistungsmessung
|
||||||
|
oder ein Boolean-Betriebsstatus. Bei ungültiger Konfiguration, fehlgeschlagenem
|
||||||
|
Schalten oder widersprüchlicher Rücklesung setzt das Modul seinen Status auf
|
||||||
|
Fehler und meldet sich dem Manager als nicht verfügbar.
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Belevo\EnelixEMS;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
final class WaermepumpenRegler
|
||||||
|
{
|
||||||
|
public const KONTAKTART_SPERRE_ERHOEHUNG = 0;
|
||||||
|
public const KONTAKTART_SG_READY = 1;
|
||||||
|
public const RUECKMELDUNG_LEISTUNG = 0;
|
||||||
|
public const RUECKMELDUNG_BETRIEB = 1;
|
||||||
|
public const STEUERZUSTAND_NORMAL = 0;
|
||||||
|
public const STEUERZUSTAND_SPERRE = 1;
|
||||||
|
public const STEUERZUSTAND_ERHOEHUNG = 2;
|
||||||
|
|
||||||
|
/** @return array{Kontakt1: bool, Kontakt2: bool} */
|
||||||
|
public static function kontaktwerte(int $kontaktart, int $steuerzustand): array
|
||||||
|
{
|
||||||
|
self::pruefeKontaktart($kontaktart);
|
||||||
|
self::pruefeSteuerzustand($steuerzustand);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Kontakt1' => $steuerzustand === self::STEUERZUSTAND_SPERRE,
|
||||||
|
'Kontakt2' => $steuerzustand === self::STEUERZUSTAND_ERHOEHUNG,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param bool|int|float $rueckmeldung */
|
||||||
|
public static function laeuft($rueckmeldung, int $rueckmeldungsart, float $laufschwelle): bool
|
||||||
|
{
|
||||||
|
self::pruefeRueckmeldungsart($rueckmeldungsart);
|
||||||
|
if ($laufschwelle < 0) {
|
||||||
|
throw new InvalidArgumentException('Die Laufschwelle darf nicht negativ sein.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rueckmeldungsart === self::RUECKMELDUNG_BETRIEB) {
|
||||||
|
return (bool) $rueckmeldung;
|
||||||
|
}
|
||||||
|
|
||||||
|
return abs((float) $rueckmeldung) >= $laufschwelle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param bool|int|float $rueckmeldung */
|
||||||
|
public static function istleistung(
|
||||||
|
$rueckmeldung,
|
||||||
|
int $rueckmeldungsart,
|
||||||
|
int $nennleistung
|
||||||
|
): float {
|
||||||
|
self::pruefeRueckmeldungsart($rueckmeldungsart);
|
||||||
|
self::pruefeNennleistung($nennleistung);
|
||||||
|
|
||||||
|
if ($rueckmeldungsart === self::RUECKMELDUNG_BETRIEB) {
|
||||||
|
return (bool) $rueckmeldung ? (float) $nennleistung : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return max(0.0, (float) $rueckmeldung);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return list<int> */
|
||||||
|
public static function leistungsangebot(
|
||||||
|
int $nennleistung,
|
||||||
|
string $betriebsart,
|
||||||
|
bool $laeuft,
|
||||||
|
bool $verfuegbar,
|
||||||
|
bool $mindestlaufzeitAktiv,
|
||||||
|
bool $mindestsperrzeitAktiv,
|
||||||
|
bool $sperrenZulaessig,
|
||||||
|
bool $startenZulaessig,
|
||||||
|
bool $sperrerholung
|
||||||
|
): array {
|
||||||
|
self::pruefeNennleistung($nennleistung);
|
||||||
|
if (!in_array($betriebsart, [
|
||||||
|
Nachrichtenvertrag::BETRIEBSART_PV,
|
||||||
|
Nachrichtenvertrag::BETRIEBSART_PEAK,
|
||||||
|
], true)) {
|
||||||
|
throw new InvalidArgumentException('Die Betriebsart ist ungueltig.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$verfuegbar) {
|
||||||
|
return [$laeuft ? $nennleistung : 0];
|
||||||
|
}
|
||||||
|
if ($sperrerholung || $mindestlaufzeitAktiv) {
|
||||||
|
return [$laeuft ? $nennleistung : 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($betriebsart === Nachrichtenvertrag::BETRIEBSART_PV) {
|
||||||
|
return ($laeuft || (!$mindestsperrzeitAktiv && $startenZulaessig))
|
||||||
|
? [0, $nennleistung]
|
||||||
|
: [0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($laeuft && $sperrenZulaessig)
|
||||||
|
? [0, $nennleistung]
|
||||||
|
: [$laeuft ? $nennleistung : 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function restzeit(int $beginn, int $dauer, int $jetzt): int
|
||||||
|
{
|
||||||
|
if ($beginn < 0 || $dauer < 0 || $jetzt < 0) {
|
||||||
|
throw new InvalidArgumentException('Zeitwerte duerfen nicht negativ sein.');
|
||||||
|
}
|
||||||
|
if ($beginn === 0 || $dauer === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return max(0, $beginn + $dauer - $jetzt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<array{Von: int, Bis: int}> $historie
|
||||||
|
* @return list<array{Von: int, Bis: int}>
|
||||||
|
*/
|
||||||
|
public static function bereinigeSperrhistorie(array $historie, int $jetzt): array
|
||||||
|
{
|
||||||
|
if ($jetzt < 0) {
|
||||||
|
throw new InvalidArgumentException('Der Zeitpunkt darf nicht negativ sein.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$grenze = $jetzt - 86400;
|
||||||
|
$ergebnis = [];
|
||||||
|
foreach ($historie as $intervall) {
|
||||||
|
$von = (int) ($intervall['Von'] ?? -1);
|
||||||
|
$bis = (int) ($intervall['Bis'] ?? -1);
|
||||||
|
if ($von < 0 || $bis < $von || $bis <= $grenze) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ergebnis[] = ['Von' => max($von, $grenze), 'Bis' => min($bis, $jetzt)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ergebnis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param list<array{Von: int, Bis: int}> $historie */
|
||||||
|
public static function sperrsekunden24h(
|
||||||
|
array $historie,
|
||||||
|
int $jetzt,
|
||||||
|
?int $aktiverBeginn = null
|
||||||
|
): int {
|
||||||
|
$sekunden = 0;
|
||||||
|
foreach (self::bereinigeSperrhistorie($historie, $jetzt) as $intervall) {
|
||||||
|
$sekunden += $intervall['Bis'] - $intervall['Von'];
|
||||||
|
}
|
||||||
|
if ($aktiverBeginn !== null && $aktiverBeginn > 0) {
|
||||||
|
$sekunden += max(0, $jetzt - max($aktiverBeginn, $jetzt - 86400));
|
||||||
|
}
|
||||||
|
|
||||||
|
return min(86400, $sekunden);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function pruefeKontaktart(int $kontaktart): void
|
||||||
|
{
|
||||||
|
if (!in_array($kontaktart, [
|
||||||
|
self::KONTAKTART_SPERRE_ERHOEHUNG,
|
||||||
|
self::KONTAKTART_SG_READY,
|
||||||
|
], true)) {
|
||||||
|
throw new InvalidArgumentException('Die Kontaktart ist ungueltig.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function pruefeRueckmeldungsart(int $rueckmeldungsart): void
|
||||||
|
{
|
||||||
|
if (!in_array($rueckmeldungsart, [
|
||||||
|
self::RUECKMELDUNG_LEISTUNG,
|
||||||
|
self::RUECKMELDUNG_BETRIEB,
|
||||||
|
], true)) {
|
||||||
|
throw new InvalidArgumentException('Die Rueckmeldungsart ist ungueltig.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function pruefeSteuerzustand(int $steuerzustand): void
|
||||||
|
{
|
||||||
|
if (!in_array($steuerzustand, [
|
||||||
|
self::STEUERZUSTAND_NORMAL,
|
||||||
|
self::STEUERZUSTAND_SPERRE,
|
||||||
|
self::STEUERZUSTAND_ERHOEHUNG,
|
||||||
|
], true)) {
|
||||||
|
throw new InvalidArgumentException('Der Steuerzustand ist ungueltig.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function pruefeNennleistung(int $nennleistung): void
|
||||||
|
{
|
||||||
|
if ($nennleistung <= 0) {
|
||||||
|
throw new InvalidArgumentException('Die Nennleistung muss groesser als 0 sein.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,24 +8,16 @@ use PHPUnit\Framework\TestCase;
|
|||||||
|
|
||||||
final class DokumentationsstrukturTest extends TestCase
|
final class DokumentationsstrukturTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @return list<array{string}> */
|
public function testWaermepumpeDokumentiertDenImplementiertenStand(): void
|
||||||
public function modulReadmes(): array
|
|
||||||
{
|
{
|
||||||
return [
|
$pfad = __DIR__ . '/../docs/module/Waermepumpe/README.md';
|
||||||
['Waermepumpe'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @dataProvider modulReadmes */
|
|
||||||
public function testJederModulentwurfHatEinReadme(string $modul): void
|
|
||||||
{
|
|
||||||
$pfad = __DIR__ . '/../docs/module/' . $modul . '/README.md';
|
|
||||||
self::assertFileExists($pfad);
|
self::assertFileExists($pfad);
|
||||||
|
|
||||||
$inhalt = file_get_contents($pfad);
|
$inhalt = file_get_contents($pfad);
|
||||||
self::assertNotFalse($inhalt);
|
self::assertNotFalse($inhalt);
|
||||||
self::assertStringContainsString('Status: Diskussionsentwurf', $inhalt);
|
self::assertStringContainsString('Status: Implementiert', $inhalt);
|
||||||
self::assertStringContainsString('Properties', $inhalt);
|
self::assertStringContainsString('Konfiguration', $inhalt);
|
||||||
|
self::assertStringContainsString('Adaption aus Enelix 1', $inhalt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBatterieDokumentiertDenImplementiertenStand(): void
|
public function testBatterieDokumentiertDenImplementiertenStand(): void
|
||||||
|
|||||||
@@ -74,6 +74,16 @@ return [
|
|||||||
'libs/Nachrichtenvertrag.php',
|
'libs/Nachrichtenvertrag.php',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'Waermepumpe' => [
|
||||||
|
'file' => __DIR__ . '/modules/Waermepumpe.php',
|
||||||
|
'paths' => [
|
||||||
|
'Waermepumpe/*',
|
||||||
|
'libs/WaermepumpenRegler.php',
|
||||||
|
'libs/VerbraucherBasisTrait.php',
|
||||||
|
'libs/VerbraucherSchnittstelle.php',
|
||||||
|
'libs/Nachrichtenvertrag.php',
|
||||||
|
],
|
||||||
|
],
|
||||||
'Warmwassererwaermer' => [
|
'Warmwassererwaermer' => [
|
||||||
'file' => __DIR__ . '/modules/Warmwassererwaermer.php',
|
'file' => __DIR__ . '/modules/Warmwassererwaermer.php',
|
||||||
'paths' => [
|
'paths' => [
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Belevo\EnelixEMS\SymconTest\TestContext;
|
||||||
|
|
||||||
|
return static function (TestContext $test): void {
|
||||||
|
$kontakt1 = $test->createActionVariable('WP Kontakt 1');
|
||||||
|
$kontakt2 = $test->createActionVariable('WP Kontakt 2');
|
||||||
|
$rueckmeldung = $test->createVariable(0, 'WP läuft', false);
|
||||||
|
$waermepumpe = $test->createInstance(
|
||||||
|
'{A31C9274-54F7-4BD2-9804-7AF23E86C4D1}',
|
||||||
|
'Wärmepumpe',
|
||||||
|
[
|
||||||
|
'Kontaktart' => 0,
|
||||||
|
'Kontakt1VariableID' => $kontakt1,
|
||||||
|
'Kontakt2VariableID' => $kontakt2,
|
||||||
|
'Nennleistung' => 5000,
|
||||||
|
'Rueckmeldungsart' => 1,
|
||||||
|
'IstleistungVariableID' => $rueckmeldung,
|
||||||
|
'Anlaufwartezeit' => 1,
|
||||||
|
'Wiederholsperre' => 3,
|
||||||
|
'Mindestlaufzeit' => 1,
|
||||||
|
'Mindestsperrzeit' => 0,
|
||||||
|
'MaximaleSperrzeit' => 2,
|
||||||
|
'MaximaleSperrzeit24h' => 60,
|
||||||
|
'VorgabeTimeout' => 30,
|
||||||
|
'DiagnosevariablenAnzeigen' => true,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$manager = $test->createInstance(
|
||||||
|
'{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}',
|
||||||
|
'WP Hilfsmanager',
|
||||||
|
[
|
||||||
|
'AutomatischeSuche' => false,
|
||||||
|
'VerbraucherZuordnung' => json_encode([
|
||||||
|
['InstanzID' => $waermepumpe, 'Aktiv' => true],
|
||||||
|
], JSON_THROW_ON_ERROR),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$senden = static function (?int $leistung, string $betriebsart) use (
|
||||||
|
$manager,
|
||||||
|
$waermepumpe
|
||||||
|
): void {
|
||||||
|
IPS_RequestAction(
|
||||||
|
$waermepumpe,
|
||||||
|
'ManagerdatenEmpfangen',
|
||||||
|
json_encode([
|
||||||
|
'Kopf' => [
|
||||||
|
'Version' => '4.0',
|
||||||
|
'AbsenderID' => $manager,
|
||||||
|
'EmpfaengerID' => $waermepumpe,
|
||||||
|
'Zeitpunkt' => time(),
|
||||||
|
],
|
||||||
|
'Betriebsart' => $betriebsart,
|
||||||
|
'Sollleistung_W' => $leistung,
|
||||||
|
], JSON_THROW_ON_ERROR)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$test->runCase('Instanz und Diagnosevariablen werden angelegt', static function (
|
||||||
|
TestContext $test
|
||||||
|
) use ($waermepumpe): void {
|
||||||
|
$test->assertInstanceStatus($waermepumpe);
|
||||||
|
foreach ([
|
||||||
|
'Aktiv',
|
||||||
|
'Waermepumpenzustand',
|
||||||
|
'WaermepumpeLaeuft',
|
||||||
|
'SperreAktiv',
|
||||||
|
'ErhoehungAktiv',
|
||||||
|
'SGReadyZustand',
|
||||||
|
'Istleistung',
|
||||||
|
'Sollleistung',
|
||||||
|
'AnlaufAusstehend',
|
||||||
|
'Sperrzeit24h',
|
||||||
|
] as $ident) {
|
||||||
|
$test->objectByIdent($ident, $waermepumpe);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$test->runCase('PV fordert Erhoehung an und bestaetigt den Anlauf', static function (
|
||||||
|
TestContext $test
|
||||||
|
) use ($waermepumpe, $kontakt1, $kontakt2, $rueckmeldung, $senden): void {
|
||||||
|
IPS_RequestAction($waermepumpe, 'Aktiv', true);
|
||||||
|
$senden(null, 'PV');
|
||||||
|
$senden(5000, 'PV');
|
||||||
|
$test->waitUntil(static fn (): bool => GetValue($kontakt2) === true);
|
||||||
|
$test->assertSame(false, GetValue($kontakt1), 'Kontakt 1 muss im PV-Anlauf aus sein.');
|
||||||
|
$test->assertSame(true, GetValue(
|
||||||
|
$test->objectByIdent('AnlaufAusstehend', $waermepumpe)
|
||||||
|
), 'Der PV-Anlauf muss bis zur Rueckmeldung ausstehend sein.');
|
||||||
|
|
||||||
|
$test->waitUntil(static fn (): bool => GetValue($kontakt2) === false, 2500);
|
||||||
|
$test->waitUntil(static fn (): bool => GetValue(
|
||||||
|
$test->objectByIdent('WiederholsperreAktiv', $waermepumpe)
|
||||||
|
) === true);
|
||||||
|
$test->assertSame(true, GetValue(
|
||||||
|
$test->objectByIdent('WiederholsperreAktiv', $waermepumpe)
|
||||||
|
), 'Nach dem fehlgeschlagenen Anlauf muss die Wiederholsperre aktiv sein.');
|
||||||
|
$test->assertSame(false, GetValue(
|
||||||
|
$test->objectByIdent('AenderungMoeglich', $waermepumpe)
|
||||||
|
), 'Waehrend der Wiederholsperre darf keine Aenderung angeboten werden.');
|
||||||
|
$test->waitUntil(static fn (): bool => GetValue(
|
||||||
|
$test->objectByIdent('WiederholsperreAktiv', $waermepumpe)
|
||||||
|
) === false, 4500);
|
||||||
|
|
||||||
|
$senden(5000, 'PV');
|
||||||
|
$test->waitUntil(static fn (): bool => GetValue($kontakt2) === true);
|
||||||
|
SetValue($rueckmeldung, true);
|
||||||
|
$test->waitUntil(static fn (): bool => GetValue(
|
||||||
|
$test->objectByIdent('WaermepumpeLaeuft', $waermepumpe)
|
||||||
|
) === true);
|
||||||
|
$test->assertSame(5000, (int) GetValue(
|
||||||
|
$test->objectByIdent('Istleistung', $waermepumpe)
|
||||||
|
));
|
||||||
|
IPS_Sleep(1100);
|
||||||
|
});
|
||||||
|
|
||||||
|
$test->runCase('Peak sperrt und maximale Sperrzeit erzwingt Normalbetrieb', static function (
|
||||||
|
TestContext $test
|
||||||
|
) use ($waermepumpe, $kontakt1, $kontakt2, $senden): void {
|
||||||
|
$senden(null, 'Peak');
|
||||||
|
$senden(0, 'Peak');
|
||||||
|
$test->waitUntil(static fn (): bool => GetValue($kontakt1) === true);
|
||||||
|
$test->assertSame(false, GetValue($kontakt2));
|
||||||
|
$test->assertSame(true, GetValue(
|
||||||
|
$test->objectByIdent('SperreAktiv', $waermepumpe)
|
||||||
|
));
|
||||||
|
|
||||||
|
$test->waitUntil(static fn (): bool => GetValue($kontakt1) === false, 2500);
|
||||||
|
$test->assertSame(true, GetValue(
|
||||||
|
$test->objectByIdent('SperrerholungAktiv', $waermepumpe)
|
||||||
|
));
|
||||||
|
$test->assertSame(false, GetValue(
|
||||||
|
$test->objectByIdent('SperreAktiv', $waermepumpe)
|
||||||
|
));
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -65,6 +65,7 @@ final class SymconTestContractTest extends TestCase
|
|||||||
'Manager',
|
'Manager',
|
||||||
'Pufferspeicher',
|
'Pufferspeicher',
|
||||||
'VerbraucherEinStufig',
|
'VerbraucherEinStufig',
|
||||||
|
'Waermepumpe',
|
||||||
'Warmwassererwaermer',
|
'Warmwassererwaermer',
|
||||||
],
|
],
|
||||||
$this->resolveAffectedModules(['libs/Nachrichtenvertrag.php'])
|
$this->resolveAffectedModules(['libs/Nachrichtenvertrag.php'])
|
||||||
@@ -90,6 +91,7 @@ final class SymconTestContractTest extends TestCase
|
|||||||
'Manager',
|
'Manager',
|
||||||
'Pufferspeicher',
|
'Pufferspeicher',
|
||||||
'VerbraucherEinStufig',
|
'VerbraucherEinStufig',
|
||||||
|
'Waermepumpe',
|
||||||
'Warmwassererwaermer',
|
'Warmwassererwaermer',
|
||||||
],
|
],
|
||||||
$this->resolveAffectedModules(['tests/Symcon/TestContext.php'])
|
$this->resolveAffectedModules(['tests/Symcon/TestContext.php'])
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Belevo\EnelixEMS\Tests;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class WaermepumpeModulstrukturTest extends TestCase
|
||||||
|
{
|
||||||
|
private const MODULPFAD = __DIR__ . '/../Waermepumpe/';
|
||||||
|
private const MODULID = '{A31C9274-54F7-4BD2-9804-7AF23E86C4D1}';
|
||||||
|
|
||||||
|
public function testModulIstVollstaendigUndMetadatenSindGueltig(): void
|
||||||
|
{
|
||||||
|
foreach (['module.json', 'form.json', 'module.php'] as $datei) {
|
||||||
|
self::assertFileExists(self::MODULPFAD . $datei);
|
||||||
|
}
|
||||||
|
$metadaten = json_decode(
|
||||||
|
(string) file_get_contents(self::MODULPFAD . 'module.json'),
|
||||||
|
true,
|
||||||
|
512,
|
||||||
|
JSON_THROW_ON_ERROR
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame('VerbraucherWaermepumpe', $metadaten['name']);
|
||||||
|
self::assertSame('Enelix', $metadaten['vendor']);
|
||||||
|
self::assertSame(['Waermepumpe'], $metadaten['aliases']);
|
||||||
|
self::assertSame(self::MODULID, $metadaten['id']);
|
||||||
|
self::assertSame(3, $metadaten['type']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllePropertiesSindRegistriertUndImFormularEnthalten(): void
|
||||||
|
{
|
||||||
|
$basis = (string) file_get_contents(__DIR__ . '/../libs/VerbraucherBasisTrait.php');
|
||||||
|
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||||
|
preg_match_all(
|
||||||
|
'/RegisterProperty(?:Integer|Float|Boolean|String)\(\'([^\']+)\'/',
|
||||||
|
$basis . $modul,
|
||||||
|
$treffer
|
||||||
|
);
|
||||||
|
$formular = (string) file_get_contents(self::MODULPFAD . 'form.json');
|
||||||
|
|
||||||
|
foreach (array_values(array_unique($treffer[1])) as $property) {
|
||||||
|
self::assertStringContainsString('"name": "' . $property . '"', $formular, $property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVertrag40UndBetriebsartabhaengigesAngebotWerdenVerwendet(): void
|
||||||
|
{
|
||||||
|
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||||
|
|
||||||
|
self::assertStringContainsString('implements VerbraucherSchnittstelle', $modul);
|
||||||
|
self::assertStringContainsString('ManagerdatenEmpfangen(array $daten)', $modul);
|
||||||
|
self::assertStringContainsString('Nachrichtenvertrag::pruefeManagerdaten', $modul);
|
||||||
|
self::assertStringContainsString('Nachrichtenvertrag::pruefeVerbraucherdaten', $modul);
|
||||||
|
self::assertStringContainsString("['Betriebsart']", $modul);
|
||||||
|
self::assertStringNotContainsString("RegisterPropertyInteger('ManagerID'", $modul);
|
||||||
|
self::assertStringNotContainsString("RegisterVariableString('PowerSteps'", $modul);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRegelungIstEreignisbasiertUndBegrenztSperrzeiten(): void
|
||||||
|
{
|
||||||
|
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||||
|
|
||||||
|
self::assertStringContainsString('public function MessageSink', $modul);
|
||||||
|
self::assertStringContainsString('RegisterMessage($neueID, self::VM_UPDATE)', $modul);
|
||||||
|
foreach ([
|
||||||
|
'Meldezyklus',
|
||||||
|
'MeldungVerzoegert',
|
||||||
|
'Zustandspruefung',
|
||||||
|
'VorgabeTimeout',
|
||||||
|
] as $timer) {
|
||||||
|
self::assertStringContainsString("'" . $timer . "'", $modul);
|
||||||
|
}
|
||||||
|
foreach ([
|
||||||
|
'Mindestlaufzeit',
|
||||||
|
'Mindestsperrzeit',
|
||||||
|
'MaximaleSperrzeit',
|
||||||
|
'MaximaleSperrzeit24h',
|
||||||
|
'Anlaufwartezeit',
|
||||||
|
'Wiederholsperre',
|
||||||
|
] as $property) {
|
||||||
|
self::assertStringContainsString("'" . $property . "'", $modul);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testKontakteWerdenBreakBeforeMakeGeschaltet(): void
|
||||||
|
{
|
||||||
|
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||||
|
$aus = strpos($modul, '$this->setzeKontakt(1, false)');
|
||||||
|
$ein = strpos($modul, '$this->setzeKontakt(1, true)');
|
||||||
|
|
||||||
|
self::assertIsInt($aus);
|
||||||
|
self::assertIsInt($ein);
|
||||||
|
self::assertLessThan($ein, $aus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFachUndDiagnosevariablenSindVorhanden(): void
|
||||||
|
{
|
||||||
|
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||||
|
foreach ([
|
||||||
|
'Waermepumpenzustand',
|
||||||
|
'WaermepumpeLaeuft',
|
||||||
|
'SperreAktiv',
|
||||||
|
'ErhoehungAktiv',
|
||||||
|
'SGReadyZustand',
|
||||||
|
'Laufzeit',
|
||||||
|
'AnlaufAusstehend',
|
||||||
|
'WiederholsperreAktiv',
|
||||||
|
'SperrerholungAktiv',
|
||||||
|
'Sperrzeit24h',
|
||||||
|
] as $ident) {
|
||||||
|
self::assertStringContainsString("'" . $ident . "'", $modul);
|
||||||
|
}
|
||||||
|
self::assertStringContainsString("ReadPropertyBoolean('DiagnosevariablenAnzeigen')", $modul);
|
||||||
|
self::assertStringContainsString('UnregisterVariable($ident)', $modul);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testManagerKenntDieModulIDGenauEinmal(): void
|
||||||
|
{
|
||||||
|
$manager = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
|
||||||
|
|
||||||
|
self::assertSame(1, substr_count($manager, self::MODULID));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDokumentationBeschreibtImplementiertenStand(): void
|
||||||
|
{
|
||||||
|
$dokumentation = (string) file_get_contents(
|
||||||
|
__DIR__ . '/../docs/module/Waermepumpe/README.md'
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertStringContainsString('Status: Implementiert', $dokumentation);
|
||||||
|
self::assertStringContainsString('nicht uebernommen', $dokumentation);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Belevo\EnelixEMS\Tests;
|
||||||
|
|
||||||
|
use Belevo\EnelixEMS\Nachrichtenvertrag;
|
||||||
|
use Belevo\EnelixEMS\WaermepumpenRegler;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class WaermepumpenReglerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testKontaktwerteBildenNormalSperreUndErhoehungAb(): void
|
||||||
|
{
|
||||||
|
self::assertSame(
|
||||||
|
['Kontakt1' => false, 'Kontakt2' => false],
|
||||||
|
WaermepumpenRegler::kontaktwerte(0, WaermepumpenRegler::STEUERZUSTAND_NORMAL)
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
['Kontakt1' => true, 'Kontakt2' => false],
|
||||||
|
WaermepumpenRegler::kontaktwerte(1, WaermepumpenRegler::STEUERZUSTAND_SPERRE)
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
['Kontakt1' => false, 'Kontakt2' => true],
|
||||||
|
WaermepumpenRegler::kontaktwerte(1, WaermepumpenRegler::STEUERZUSTAND_ERHOEHUNG)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLeistungUndBetriebsstatusBestimmenLaufUndIstleistung(): void
|
||||||
|
{
|
||||||
|
self::assertTrue(WaermepumpenRegler::laeuft(650.0, 0, 100.0));
|
||||||
|
self::assertFalse(WaermepumpenRegler::laeuft(50.0, 0, 100.0));
|
||||||
|
self::assertTrue(WaermepumpenRegler::laeuft(true, 1, 100.0));
|
||||||
|
self::assertSame(650.0, WaermepumpenRegler::istleistung(650.0, 0, 5000));
|
||||||
|
self::assertSame(5000.0, WaermepumpenRegler::istleistung(true, 1, 5000));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPvBietetStartUndErhoehungNurBeiFreigabeAn(): void
|
||||||
|
{
|
||||||
|
self::assertSame(
|
||||||
|
[0, 5000],
|
||||||
|
WaermepumpenRegler::leistungsangebot(
|
||||||
|
5000,
|
||||||
|
Nachrichtenvertrag::BETRIEBSART_PV,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
[0],
|
||||||
|
WaermepumpenRegler::leistungsangebot(
|
||||||
|
5000,
|
||||||
|
Nachrichtenvertrag::BETRIEBSART_PV,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPeakBietetSperreNurBeiLaufenderUndFreigegebenerPumpeAn(): void
|
||||||
|
{
|
||||||
|
self::assertSame(
|
||||||
|
[0, 5000],
|
||||||
|
WaermepumpenRegler::leistungsangebot(
|
||||||
|
5000,
|
||||||
|
Nachrichtenvertrag::BETRIEBSART_PEAK,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
[5000],
|
||||||
|
WaermepumpenRegler::leistungsangebot(
|
||||||
|
5000,
|
||||||
|
Nachrichtenvertrag::BETRIEBSART_PEAK,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
[0],
|
||||||
|
WaermepumpenRegler::leistungsangebot(
|
||||||
|
5000,
|
||||||
|
Nachrichtenvertrag::BETRIEBSART_PEAK,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSperrerholungHaeltDenBestaetigtenBetrieb(): void
|
||||||
|
{
|
||||||
|
self::assertSame(
|
||||||
|
[5000],
|
||||||
|
WaermepumpenRegler::leistungsangebot(
|
||||||
|
5000,
|
||||||
|
Nachrichtenvertrag::BETRIEBSART_PEAK,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRestzeitWirdNieNegativ(): void
|
||||||
|
{
|
||||||
|
self::assertSame(20, WaermepumpenRegler::restzeit(100, 30, 110));
|
||||||
|
self::assertSame(0, WaermepumpenRegler::restzeit(100, 30, 140));
|
||||||
|
self::assertSame(0, WaermepumpenRegler::restzeit(0, 30, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSperrzeit24hSchneidetAlteIntervalleAb(): void
|
||||||
|
{
|
||||||
|
$jetzt = 200000;
|
||||||
|
$historie = [
|
||||||
|
['Von' => $jetzt - 90000, 'Bis' => $jetzt - 80000],
|
||||||
|
['Von' => $jetzt - 1000, 'Bis' => $jetzt - 400],
|
||||||
|
];
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
7400,
|
||||||
|
WaermepumpenRegler::sperrsekunden24h($historie, $jetzt, $jetzt - 400)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUngueltigeKontaktartWirdAbgewiesen(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
|
||||||
|
WaermepumpenRegler::kontaktwerte(99, WaermepumpenRegler::STEUERZUSTAND_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user