This commit is contained in:
@@ -24,6 +24,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
private const VERBRAUCHER_MODUL_IDS = [
|
||||
'{B7C54AF4-AD7D-4FE4-B75D-203693906251}',
|
||||
'{15879A4E-D0C2-4495-83DE-46E1E462591E}',
|
||||
'{A31C9274-54F7-4BD2-9804-7AF23E86C4D1}',
|
||||
'{0D94913C-0F31-4C29-A685-6EB4AE58E55D}',
|
||||
'{32F65988-BE55-4FD6-9FFF-CF1C4C8628F8}',
|
||||
'{C92D5EEF-9632-47A5-9659-4B02BF40FBE9}',
|
||||
|
||||
@@ -16,7 +16,7 @@ weiteren Module sind als Diskussionsentwürfe dokumentiert.
|
||||
- Wassererwaermer (implementiert)
|
||||
- Pufferspeicher
|
||||
- Verbraucher 1-Stufig (implementiert)
|
||||
- Waermepumpe
|
||||
- Waermepumpe (implementiert)
|
||||
- Ladestation Stand-Alone (implementiert)
|
||||
- Ladestation 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
|
||||
|
||||
> Manager, Warmwassererwaermer, Batterie, Verbraucher 1-Stufig, Ladestation Stand-Alone,
|
||||
> Easee Gateway und Ladestation Gateway sind als installierbare IP-Symcon-Module
|
||||
> umgesetzt. Die weiteren Ordner enthalten Besprechungsgrundlagen.
|
||||
> Manager, Warmwassererwaermer, Batterie, Verbraucher 1-Stufig, Waermepumpe,
|
||||
> Ladestation Stand-Alone, Easee Gateway und Ladestation Gateway sind als
|
||||
> installierbare IP-Symcon-Module umgesetzt. Die weiteren Ordner enthalten Besprechungsgrundlagen.
|
||||
|
||||
Alle steuerbaren Verbraucher verwenden die gemeinsamen Datenpunkte aus der
|
||||
[EMS-Schnittstelle](../Schnittstelle.md). In den Modul-READMEs stehen deshalb
|
||||
|
||||
@@ -1,47 +1,89 @@
|
||||
# Wärmepumpe
|
||||
|
||||
> Status: Diskussionsentwurf.
|
||||
> Status: Implementiert.
|
||||
|
||||
Die Kontaktwirkung ist als Sperrkontakte oder SG Ready konfigurierbar. Die
|
||||
elektrische Leistung kann fehlen, berechnet oder gemessen werden.
|
||||
Das Modul bindet eine Wärmepumpe über zwei schaltbare Boolean-Ausgänge in den
|
||||
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. |
|
||||
| `SperreAktiv` | Boolean / Anzeige | Aktuelle Sperranforderung. |
|
||||
| `ErhoehungAktiv` | Boolean / Anzeige | Aktuelle Erhöhungsanforderung. |
|
||||
| `SGReadyZustand` | Integer / Anzeige | Bei SG Ready Zustand 0–3. |
|
||||
| `Laufzeit` | Integer / Anzeige | Kumulierte bestätigte Laufzeit in Sekunden. |
|
||||
| `Waermepumpenzustand` | Integer | `1` normal/aus, `2` normal/laufend, `3` erhöht, `4` gesperrt |
|
||||
| `WaermepumpeLaeuft` | Boolean | Bestätigter Betriebsstatus |
|
||||
| `SperreAktiv` | Boolean | Sperrkontakt ist aktiv |
|
||||
| `ErhoehungAktiv` | Boolean | Erhöhungskontakt ist aktiv |
|
||||
| `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 |
|
||||
| --- | --- | --- |
|
||||
| `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. |
|
||||
## Adaption aus Enelix 1
|
||||
|
||||
Zeit- und wetterabhängiges Sperren beziehungsweise Erhöhen aus dem Altmodul
|
||||
entfällt.
|
||||
| Bereich | Behandlung in Enelix 2 |
|
||||
| --- | --- |
|
||||
| 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
|
||||
`SGReadyZustand` als Status, `Laufzeit_s` als Istwert und
|
||||
`Kommunikationsfehler` als Störung.
|
||||
Der Manager erkennt das Modul über die Modul-ID
|
||||
`{A31C9274-54F7-4BD2-9804-7AF23E86C4D1}`. Es verwendet das bestehende
|
||||
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
|
||||
Regelwirkung, bis Anschluss und Verriegelung gemeinsam definiert sind.
|
||||
Die beiden Ausgänge müssen unterschiedliche, schaltbare Boolean-Variablen sein.
|
||||
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
|
||||
{
|
||||
/** @return list<array{string}> */
|
||||
public function modulReadmes(): array
|
||||
public function testWaermepumpeDokumentiertDenImplementiertenStand(): void
|
||||
{
|
||||
return [
|
||||
['Waermepumpe'],
|
||||
];
|
||||
}
|
||||
|
||||
/** @dataProvider modulReadmes */
|
||||
public function testJederModulentwurfHatEinReadme(string $modul): void
|
||||
{
|
||||
$pfad = __DIR__ . '/../docs/module/' . $modul . '/README.md';
|
||||
$pfad = __DIR__ . '/../docs/module/Waermepumpe/README.md';
|
||||
self::assertFileExists($pfad);
|
||||
|
||||
$inhalt = file_get_contents($pfad);
|
||||
self::assertNotFalse($inhalt);
|
||||
self::assertStringContainsString('Status: Diskussionsentwurf', $inhalt);
|
||||
self::assertStringContainsString('Properties', $inhalt);
|
||||
self::assertStringContainsString('Status: Implementiert', $inhalt);
|
||||
self::assertStringContainsString('Konfiguration', $inhalt);
|
||||
self::assertStringContainsString('Adaption aus Enelix 1', $inhalt);
|
||||
}
|
||||
|
||||
public function testBatterieDokumentiertDenImplementiertenStand(): void
|
||||
|
||||
@@ -74,6 +74,16 @@ return [
|
||||
'libs/Nachrichtenvertrag.php',
|
||||
],
|
||||
],
|
||||
'Waermepumpe' => [
|
||||
'file' => __DIR__ . '/modules/Waermepumpe.php',
|
||||
'paths' => [
|
||||
'Waermepumpe/*',
|
||||
'libs/WaermepumpenRegler.php',
|
||||
'libs/VerbraucherBasisTrait.php',
|
||||
'libs/VerbraucherSchnittstelle.php',
|
||||
'libs/Nachrichtenvertrag.php',
|
||||
],
|
||||
],
|
||||
'Warmwassererwaermer' => [
|
||||
'file' => __DIR__ . '/modules/Warmwassererwaermer.php',
|
||||
'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',
|
||||
'Pufferspeicher',
|
||||
'VerbraucherEinStufig',
|
||||
'Waermepumpe',
|
||||
'Warmwassererwaermer',
|
||||
],
|
||||
$this->resolveAffectedModules(['libs/Nachrichtenvertrag.php'])
|
||||
@@ -90,6 +91,7 @@ final class SymconTestContractTest extends TestCase
|
||||
'Manager',
|
||||
'Pufferspeicher',
|
||||
'VerbraucherEinStufig',
|
||||
'Waermepumpe',
|
||||
'Warmwassererwaermer',
|
||||
],
|
||||
$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