This commit is contained in:
@@ -21,6 +21,9 @@ jobs:
|
|||||||
- name: Abhaengigkeiten installieren
|
- name: Abhaengigkeiten installieren
|
||||||
run: composer install --no-interaction --no-progress --prefer-dist
|
run: composer install --no-interaction --no-progress --prefer-dist
|
||||||
|
|
||||||
- name: Pruefungen ausfuehren
|
- name: Verbraucher 1-Stufig separat pruefen
|
||||||
|
run: composer check:verbraucher-einstufig
|
||||||
|
|
||||||
|
- name: Gesamttest ausfuehren
|
||||||
run: composer check
|
run: composer check
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,13 @@ composer install
|
|||||||
composer check
|
composer check
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Der Gesamttest schliesst alle Modulsuiten ein. Der einstufige Verbraucher kann
|
||||||
|
zusaetzlich unabhaengig ausgefuehrt werden:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer check:verbraucher-einstufig
|
||||||
|
```
|
||||||
|
|
||||||
Neue oder geaenderte Funktionen werden zusammen mit ihren PHPUnit-Tests eingecheckt. Die CI-Pruefung muss vor der Uebernahme in einen Freigabebranch erfolgreich sein.
|
Neue oder geaenderte Funktionen werden zusammen mit ihren PHPUnit-Tests eingecheckt. Die CI-Pruefung muss vor der Uebernahme in einen Freigabebranch erfolgreich sein.
|
||||||
|
|
||||||
| Git-Branch | IP-Symcon-Kanal |
|
| Git-Branch | IP-Symcon-Kanal |
|
||||||
|
|||||||
@@ -33,8 +33,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "NumberSpinner",
|
"type": "NumberSpinner",
|
||||||
"name": "Umschaltabstand",
|
"name": "Mindesteinschaltdauer",
|
||||||
"caption": "Zeit bis zum naechsten Lastwechsel",
|
"caption": "Mindest-Einschaltdauer",
|
||||||
|
"suffix": " s",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "Mindestausschaltdauer",
|
||||||
|
"caption": "Mindest-Ausschaltdauer",
|
||||||
"suffix": " s",
|
"suffix": " s",
|
||||||
"minimum": 0
|
"minimum": 0
|
||||||
}
|
}
|
||||||
|
|||||||
+152
-50
@@ -33,6 +33,8 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
'Stoerung',
|
'Stoerung',
|
||||||
'Stoertext',
|
'Stoertext',
|
||||||
'Rueckmeldefehler',
|
'Rueckmeldefehler',
|
||||||
|
'SchaltbefehlAusstehend',
|
||||||
|
'RestMindestzeit',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function Create(): void
|
public function Create(): void
|
||||||
@@ -45,7 +47,8 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
$this->RegisterPropertyBoolean('SchaltkontaktInvertiert', false);
|
$this->RegisterPropertyBoolean('SchaltkontaktInvertiert', false);
|
||||||
$this->RegisterPropertyInteger('RueckmeldungVariableID', 0);
|
$this->RegisterPropertyInteger('RueckmeldungVariableID', 0);
|
||||||
$this->RegisterPropertyInteger('Mindestlaufzeit', 0);
|
$this->RegisterPropertyInteger('Mindestlaufzeit', 0);
|
||||||
$this->RegisterPropertyInteger('Umschaltabstand', 5);
|
$this->RegisterPropertyInteger('Mindesteinschaltdauer', 5);
|
||||||
|
$this->RegisterPropertyInteger('Mindestausschaltdauer', 5);
|
||||||
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
|
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
|
||||||
|
|
||||||
$this->RegisterVariableBoolean('Schaltzustand', 'Schaltzustand', '~Switch', 100);
|
$this->RegisterVariableBoolean('Schaltzustand', 'Schaltzustand', '~Switch', 100);
|
||||||
@@ -55,7 +58,8 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
$this->RegisterAttributeInteger('RegistrierteRueckmeldung', 0);
|
$this->RegisterAttributeInteger('RegistrierteRueckmeldung', 0);
|
||||||
$this->RegisterAttributeInteger('LetzteVorgabeZeit', 0);
|
$this->RegisterAttributeInteger('LetzteVorgabeZeit', 0);
|
||||||
$this->RegisterAttributeInteger('LetzteManagerID', 0);
|
$this->RegisterAttributeInteger('LetzteManagerID', 0);
|
||||||
$this->RegisterAttributeInteger('GesperrtBis', 0);
|
$this->RegisterAttributeInteger('BestaetigtSeit', 0);
|
||||||
|
$this->RegisterAttributeInteger('FreigabeAb', 0);
|
||||||
$this->RegisterAttributeInteger('LaufzeitStandZeit', 0);
|
$this->RegisterAttributeInteger('LaufzeitStandZeit', 0);
|
||||||
$this->RegisterAttributeString('LaufzeitTag', '');
|
$this->RegisterAttributeString('LaufzeitTag', '');
|
||||||
$this->RegisterAttributeString('Leistungsangebot', '[0]');
|
$this->RegisterAttributeString('Leistungsangebot', '[0]');
|
||||||
@@ -75,6 +79,8 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
$this->RegisterAttributeBoolean('ZustandSchaltzustand', false);
|
$this->RegisterAttributeBoolean('ZustandSchaltzustand', false);
|
||||||
$this->RegisterAttributeInteger('ZustandTageslaufzeit', 0);
|
$this->RegisterAttributeInteger('ZustandTageslaufzeit', 0);
|
||||||
$this->RegisterAttributeBoolean('ZustandRueckmeldefehler', false);
|
$this->RegisterAttributeBoolean('ZustandRueckmeldefehler', false);
|
||||||
|
$this->RegisterAttributeBoolean('ZustandSchaltbefehlAusstehend', false);
|
||||||
|
$this->RegisterAttributeInteger('ZustandRestMindestzeit', 0);
|
||||||
|
|
||||||
$this->RegisterTimer(
|
$this->RegisterTimer(
|
||||||
'Meldezyklus',
|
'Meldezyklus',
|
||||||
@@ -86,10 +92,12 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
0,
|
0,
|
||||||
"IPS_RequestAction(\$_IPS['TARGET'], 'Melden', true);"
|
"IPS_RequestAction(\$_IPS['TARGET'], 'Melden', true);"
|
||||||
);
|
);
|
||||||
|
// Bestehende Instanzen behalten den alten Timer deaktiviert.
|
||||||
|
$this->RegisterTimer('Umschaltfreigabe', 0, '');
|
||||||
$this->RegisterTimer(
|
$this->RegisterTimer(
|
||||||
'Umschaltfreigabe',
|
'MindestzeitAbgelaufen',
|
||||||
0,
|
0,
|
||||||
"IPS_RequestAction(\$_IPS['TARGET'], 'Umschaltfreigabe', 0);"
|
"IPS_RequestAction(\$_IPS['TARGET'], 'MindestzeitAbgelaufen', 0);"
|
||||||
);
|
);
|
||||||
$this->RegisterTimer(
|
$this->RegisterTimer(
|
||||||
'VorgabeTimeout',
|
'VorgabeTimeout',
|
||||||
@@ -162,7 +170,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
$this->sendeVerbraucherdaten();
|
$this->sendeVerbraucherdaten();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'Umschaltfreigabe':
|
case 'MindestzeitAbgelaufen':
|
||||||
case 'VorgabeTimeout':
|
case 'VorgabeTimeout':
|
||||||
case 'Tagesplanung':
|
case 'Tagesplanung':
|
||||||
$this->SetTimerInterval((string) $ident, 0);
|
$this->SetTimerInterval((string) $ident, 0);
|
||||||
@@ -223,14 +231,8 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
$schaltzustand = $this->leseSchaltzustand();
|
$schaltzustand = $this->leseSchaltzustand();
|
||||||
|
|
||||||
if ($schaltzustand !== $vorherigerSchaltzustand) {
|
if ($schaltzustand !== $vorherigerSchaltzustand) {
|
||||||
$this->setzeSchaltzustand($schaltzustand);
|
$this->bestaetigeZustandswechsel($schaltzustand, $jetzt);
|
||||||
if ($this->ReadAttributeBoolean('Initialisiert')
|
|
||||||
&& !$this->ReadAttributeBoolean('SchaltpruefungAusstehend')
|
|
||||||
) {
|
|
||||||
$this->starteUmschaltsperre($jetzt);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$this->pruefeAusstehendenSchaltvorgang($schaltzustand, $jetzt);
|
$this->pruefeAusstehendenSchaltvorgang($schaltzustand, $jetzt);
|
||||||
$this->pruefeVorgabeTimeout($jetzt);
|
$this->pruefeVorgabeTimeout($jetzt);
|
||||||
if ((bool) $this->leseZustand('SollwertGueltig')
|
if ((bool) $this->leseZustand('SollwertGueltig')
|
||||||
@@ -244,12 +246,23 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
}
|
}
|
||||||
|
|
||||||
$aktiv = (bool) $this->GetValue('Aktiv');
|
$aktiv = (bool) $this->GetValue('Aktiv');
|
||||||
|
$wartetAufRueckmeldung = $this->ReadAttributeBoolean('SchaltpruefungAusstehend');
|
||||||
|
$rueckmeldefehler = $this->ReadPropertyInteger('RueckmeldungVariableID') > 0
|
||||||
|
&& !$wartetAufRueckmeldung
|
||||||
|
&& $schaltzustand !== $this->leseAktorSchaltzustand();
|
||||||
|
$this->setzeZustand('Rueckmeldefehler', $rueckmeldefehler);
|
||||||
|
|
||||||
$fehlerfrei = $this->ReadAttributeString('Schaltfehler') === ''
|
$fehlerfrei = $this->ReadAttributeString('Schaltfehler') === ''
|
||||||
&& !(bool) $this->leseZustand('Rueckmeldefehler');
|
&& !$rueckmeldefehler;
|
||||||
$verfuegbar = $aktiv && $fehlerfrei;
|
$verfuegbar = $aktiv && $fehlerfrei;
|
||||||
$mindestlaufzeitErzwungen = $verfuegbar
|
$mindestlaufzeitErzwungen = $verfuegbar
|
||||||
&& $this->mussMindestlaufzeitErzwingen($jetzt);
|
&& $this->mussMindestlaufzeitErzwingen($jetzt);
|
||||||
$gesperrt = $jetzt < $this->ReadAttributeInteger('GesperrtBis');
|
$restMindestzeit = $wartetAufRueckmeldung
|
||||||
|
? 0
|
||||||
|
: EinStufigRegler::restMindestzeit(
|
||||||
|
$this->ReadAttributeInteger('FreigabeAb'),
|
||||||
|
$jetzt
|
||||||
|
);
|
||||||
|
|
||||||
$ziel = 0;
|
$ziel = 0;
|
||||||
if ($verfuegbar) {
|
if ($verfuegbar) {
|
||||||
@@ -261,20 +274,37 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
}
|
}
|
||||||
|
|
||||||
$istleistung = $this->leistungFuerSchaltzustand($schaltzustand);
|
$istleistung = $this->leistungFuerSchaltzustand($schaltzustand);
|
||||||
if ($ziel !== $istleistung && (!$aktiv || (!$gesperrt && $fehlerfrei))) {
|
$zielzustand = $ziel > 0;
|
||||||
$this->schalte($ziel > 0, $jetzt);
|
$befehlNoetig = $ziel !== $istleistung
|
||||||
|
|| ($wartetAufRueckmeldung
|
||||||
|
&& $zielzustand !== $this->ReadAttributeBoolean('Schaltziel'));
|
||||||
|
// Die lokale Deaktivierung bleibt der einzige bewusste Mindestzeit-Override.
|
||||||
|
$sicheresAusschalten = !$zielzustand && !$aktiv;
|
||||||
|
if ($befehlNoetig
|
||||||
|
&& ($sicheresAusschalten
|
||||||
|
|| ($restMindestzeit === 0 && !$wartetAufRueckmeldung && $fehlerfrei))
|
||||||
|
) {
|
||||||
|
$this->schalte($zielzustand, $jetzt);
|
||||||
$schaltzustand = $this->leseSchaltzustand();
|
$schaltzustand = $this->leseSchaltzustand();
|
||||||
$this->setzeSchaltzustand($schaltzustand);
|
$this->pruefeAusstehendenSchaltvorgang($schaltzustand, $jetzt);
|
||||||
$istleistung = $this->leistungFuerSchaltzustand($schaltzustand);
|
$istleistung = $this->leistungFuerSchaltzustand($schaltzustand);
|
||||||
$gesperrt = $jetzt < $this->ReadAttributeInteger('GesperrtBis');
|
$wartetAufRueckmeldung = $this->ReadAttributeBoolean('SchaltpruefungAusstehend');
|
||||||
|
$restMindestzeit = $wartetAufRueckmeldung
|
||||||
|
? 0
|
||||||
|
: EinStufigRegler::restMindestzeit(
|
||||||
|
$this->ReadAttributeInteger('FreigabeAb'),
|
||||||
|
$jetzt
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->setzeZustand('Sollleistung', $ziel);
|
$this->setzeZustand('Sollleistung', $ziel);
|
||||||
$this->setzeZustand('Verfuegbar', $verfuegbar);
|
$this->setzeZustand('Verfuegbar', $verfuegbar);
|
||||||
|
$this->setzeZustand('SchaltbefehlAusstehend', $wartetAufRueckmeldung);
|
||||||
|
$this->setzeZustand('RestMindestzeit', $restMindestzeit);
|
||||||
$aenderungMoeglich = $verfuegbar
|
$aenderungMoeglich = $verfuegbar
|
||||||
&& !$mindestlaufzeitErzwungen
|
&& !$mindestlaufzeitErzwungen
|
||||||
&& !$gesperrt
|
&& $restMindestzeit === 0
|
||||||
&& !$this->ReadAttributeBoolean('SchaltpruefungAusstehend');
|
&& !$wartetAufRueckmeldung;
|
||||||
$this->setzeZustand('AenderungMoeglich', $aenderungMoeglich);
|
$this->setzeZustand('AenderungMoeglich', $aenderungMoeglich);
|
||||||
|
|
||||||
$angebot = EinStufigRegler::leistungsangebot(
|
$angebot = EinStufigRegler::leistungsangebot(
|
||||||
@@ -314,58 +344,77 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
|
|
||||||
$this->WriteAttributeBoolean('Schaltziel', $einschalten);
|
$this->WriteAttributeBoolean('Schaltziel', $einschalten);
|
||||||
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', true);
|
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', true);
|
||||||
|
$this->setzeZustand('SchaltbefehlAusstehend', true);
|
||||||
$this->WriteAttributeBoolean('SchaltvorgangLaeuft', true);
|
$this->WriteAttributeBoolean('SchaltvorgangLaeuft', true);
|
||||||
try {
|
try {
|
||||||
RequestAction($kontaktID, $aktorwert);
|
RequestAction($kontaktID, $aktorwert);
|
||||||
} catch (Throwable $fehler) {
|
} catch (Throwable $fehler) {
|
||||||
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', false);
|
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', false);
|
||||||
|
$this->setzeZustand('SchaltbefehlAusstehend', false);
|
||||||
throw new RuntimeException('Schaltkontakt konnte nicht gesetzt werden: ' . $fehler->getMessage());
|
throw new RuntimeException('Schaltkontakt konnte nicht gesetzt werden: ' . $fehler->getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
$this->WriteAttributeBoolean('SchaltvorgangLaeuft', false);
|
$this->WriteAttributeBoolean('SchaltvorgangLaeuft', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->starteUmschaltsperre($jetzt);
|
|
||||||
$istzustand = $this->leseSchaltzustand();
|
$istzustand = $this->leseSchaltzustand();
|
||||||
if ($istzustand === $einschalten) {
|
if ($istzustand === $einschalten) {
|
||||||
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', false);
|
$this->bestaetigeZustandswechsel($istzustand, $jetzt);
|
||||||
$this->setzeZustand('Rueckmeldefehler', false);
|
|
||||||
} elseif ($this->ReadPropertyInteger('RueckmeldungVariableID') <= 0) {
|
} elseif ($this->ReadPropertyInteger('RueckmeldungVariableID') <= 0) {
|
||||||
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', false);
|
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', false);
|
||||||
|
$this->setzeZustand('SchaltbefehlAusstehend', false);
|
||||||
throw new RuntimeException('Schaltkontakt hat den angeforderten Zustand nicht uebernommen.');
|
throw new RuntimeException('Schaltkontakt hat den angeforderten Zustand nicht uebernommen.');
|
||||||
} elseif ($this->ReadPropertyInteger('Umschaltabstand') === 0) {
|
} else {
|
||||||
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', false);
|
// Die physische Rueckmeldung bestimmt Zustand und Beginn der Mindestzeit.
|
||||||
$this->setzeZustand('Rueckmeldefehler', true);
|
$this->setzeZustand('Rueckmeldefehler', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->WriteAttributeString('Schaltfehler', '');
|
$this->WriteAttributeString('Schaltfehler', '');
|
||||||
$this->protokolliere('Lastwechsel', ['Eingeschaltet' => $einschalten]);
|
$this->protokolliere('Schaltbefehl', ['Eingeschaltet' => $einschalten]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function pruefeAusstehendenSchaltvorgang(bool $schaltzustand, int $jetzt): void
|
private function pruefeAusstehendenSchaltvorgang(bool $schaltzustand, int $jetzt): void
|
||||||
{
|
{
|
||||||
if (!$this->ReadAttributeBoolean('SchaltpruefungAusstehend')) {
|
if ($this->ReadAttributeBoolean('SchaltpruefungAusstehend')
|
||||||
if ((bool) $this->leseZustand('Rueckmeldefehler')
|
|
||||||
&& $schaltzustand === $this->ReadAttributeBoolean('Schaltziel')
|
&& $schaltzustand === $this->ReadAttributeBoolean('Schaltziel')
|
||||||
) {
|
) {
|
||||||
$this->setzeZustand('Rueckmeldefehler', false);
|
$this->bestaetigeZustandswechsel($schaltzustand, $jetzt);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($schaltzustand === $this->ReadAttributeBoolean('Schaltziel')) {
|
|
||||||
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', false);
|
|
||||||
$this->setzeZustand('Rueckmeldefehler', false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($jetzt >= $this->ReadAttributeInteger('GesperrtBis')) {
|
|
||||||
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', false);
|
|
||||||
$this->setzeZustand('Rueckmeldefehler', true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function starteUmschaltsperre(int $jetzt): void
|
$this->setzeZustand(
|
||||||
|
'SchaltbefehlAusstehend',
|
||||||
|
$this->ReadAttributeBoolean('SchaltpruefungAusstehend')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function bestaetigeZustandswechsel(bool $schaltzustand, int $jetzt): void
|
||||||
{
|
{
|
||||||
$gesperrtBis = $jetzt + $this->ReadPropertyInteger('Umschaltabstand');
|
$vorherigerZustand = (bool) $this->leseZustand('Schaltzustand');
|
||||||
$this->WriteAttributeInteger('GesperrtBis', $gesperrtBis);
|
$zustandGeaendert = $schaltzustand !== $vorherigerZustand
|
||||||
|
|| $this->ReadAttributeInteger('BestaetigtSeit') <= 0;
|
||||||
|
|
||||||
|
$this->setzeSchaltzustand($schaltzustand);
|
||||||
|
if ($zustandGeaendert) {
|
||||||
|
$this->WriteAttributeInteger('BestaetigtSeit', $jetzt);
|
||||||
|
$this->WriteAttributeInteger(
|
||||||
|
'FreigabeAb',
|
||||||
|
EinStufigRegler::freigabezeitpunkt(
|
||||||
|
$schaltzustand,
|
||||||
|
$jetzt,
|
||||||
|
$this->ReadPropertyInteger('Mindesteinschaltdauer'),
|
||||||
|
$this->ReadPropertyInteger('Mindestausschaltdauer')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($schaltzustand === $this->ReadAttributeBoolean('Schaltziel')) {
|
||||||
|
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', false);
|
||||||
|
}
|
||||||
|
$this->setzeZustand(
|
||||||
|
'SchaltbefehlAusstehend',
|
||||||
|
$this->ReadAttributeBoolean('SchaltpruefungAusstehend')
|
||||||
|
);
|
||||||
|
$this->setzeZustand('Rueckmeldefehler', false);
|
||||||
|
$this->protokolliere('Schaltzustand bestaetigt', ['Eingeschaltet' => $schaltzustand]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function aktualisiereTageslaufzeit(int $jetzt): void
|
private function aktualisiereTageslaufzeit(int $jetzt): void
|
||||||
@@ -410,10 +459,14 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
|
|
||||||
private function planeTimer(int $jetzt): void
|
private function planeTimer(int $jetzt): void
|
||||||
{
|
{
|
||||||
$gesperrtBis = $this->ReadAttributeInteger('GesperrtBis');
|
$this->SetTimerInterval('Umschaltfreigabe', 0);
|
||||||
|
$freigabeAb = $this->ReadAttributeInteger('FreigabeAb');
|
||||||
|
$wartetAufRueckmeldung = $this->ReadAttributeBoolean('SchaltpruefungAusstehend');
|
||||||
$this->SetTimerInterval(
|
$this->SetTimerInterval(
|
||||||
'Umschaltfreigabe',
|
'MindestzeitAbgelaufen',
|
||||||
$gesperrtBis > $jetzt ? max(1, ($gesperrtBis - $jetzt) * 1000) : 0
|
!$wartetAufRueckmeldung && $freigabeAb > $jetzt
|
||||||
|
? max(1, ($freigabeAb - $jetzt) * 1000)
|
||||||
|
: 0
|
||||||
);
|
);
|
||||||
$this->planeVorgabeTimeout($jetzt);
|
$this->planeVorgabeTimeout($jetzt);
|
||||||
|
|
||||||
@@ -457,7 +510,19 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
$this->setzeZustand('Rueckmeldefehler', false);
|
$this->setzeZustand('Rueckmeldefehler', false);
|
||||||
$this->WriteAttributeBoolean('Initialisiert', true);
|
$this->WriteAttributeBoolean('Initialisiert', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->ReadAttributeInteger('BestaetigtSeit') <= 0) {
|
||||||
|
// Migration bestehender Instanzen ohne bestaetigten Zeitbezug.
|
||||||
|
$this->WriteAttributeBoolean('Schaltziel', $schaltzustand);
|
||||||
|
$this->WriteAttributeBoolean('SchaltpruefungAusstehend', false);
|
||||||
|
$this->bestaetigeZustandswechsel($schaltzustand, $jetzt);
|
||||||
|
} else {
|
||||||
$this->setzeSchaltzustand($schaltzustand);
|
$this->setzeSchaltzustand($schaltzustand);
|
||||||
|
$this->setzeZustand(
|
||||||
|
'SchaltbefehlAusstehend',
|
||||||
|
$this->ReadAttributeBoolean('SchaltpruefungAusstehend')
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setzeSchaltzustand(bool $eingeschaltet): void
|
private function setzeSchaltzustand(bool $eingeschaltet): void
|
||||||
@@ -474,6 +539,11 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
return (bool) GetValue($rueckmeldungID);
|
return (bool) GetValue($rueckmeldungID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->leseAktorSchaltzustand();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function leseAktorSchaltzustand(): bool
|
||||||
|
{
|
||||||
$kontaktwert = (bool) GetValue($this->ReadPropertyInteger('SchaltkontaktVariableID'));
|
$kontaktwert = (bool) GetValue($this->ReadPropertyInteger('SchaltkontaktVariableID'));
|
||||||
|
|
||||||
return $kontaktwert !== $this->ReadPropertyBoolean('SchaltkontaktInvertiert');
|
return $kontaktwert !== $this->ReadPropertyBoolean('SchaltkontaktInvertiert');
|
||||||
@@ -551,6 +621,24 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
'Wert' => (bool) $this->leseZustand('Schaltzustand'),
|
'Wert' => (bool) $this->leseZustand('Schaltzustand'),
|
||||||
'Einheit' => '',
|
'Einheit' => '',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'Kennung' => 'SchaltbefehlAusstehend',
|
||||||
|
'Art' => 'Status',
|
||||||
|
'Wert' => (bool) $this->leseZustand('SchaltbefehlAusstehend'),
|
||||||
|
'Einheit' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Kennung' => 'Schaltbereit',
|
||||||
|
'Art' => 'Status',
|
||||||
|
'Wert' => (bool) $this->leseZustand('AenderungMoeglich'),
|
||||||
|
'Einheit' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Kennung' => 'RestMindestzeit_s',
|
||||||
|
'Art' => 'Istwert',
|
||||||
|
'Wert' => (int) $this->leseZustand('RestMindestzeit'),
|
||||||
|
'Einheit' => 's',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'Kennung' => 'Tageslaufzeit_s',
|
'Kennung' => 'Tageslaufzeit_s',
|
||||||
'Art' => 'Istwert',
|
'Art' => 'Istwert',
|
||||||
@@ -636,8 +724,10 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
) {
|
) {
|
||||||
throw new InvalidArgumentException('Mindestlaufzeit muss zwischen 0 und 86400 Sekunden liegen.');
|
throw new InvalidArgumentException('Mindestlaufzeit muss zwischen 0 und 86400 Sekunden liegen.');
|
||||||
}
|
}
|
||||||
if ($this->ReadPropertyInteger('Umschaltabstand') < 0) {
|
foreach (['Mindesteinschaltdauer', 'Mindestausschaltdauer'] as $property) {
|
||||||
throw new InvalidArgumentException('Umschaltabstand darf nicht negativ sein.');
|
if ($this->ReadPropertyInteger($property) < 0) {
|
||||||
|
throw new InvalidArgumentException($property . ' darf nicht negativ sein.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$kontaktID = $this->ReadPropertyInteger('SchaltkontaktVariableID');
|
$kontaktID = $this->ReadPropertyInteger('SchaltkontaktVariableID');
|
||||||
@@ -713,6 +803,13 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
if ($this->ReadPropertyBoolean('DiagnosevariablenAnzeigen')) {
|
if ($this->ReadPropertyBoolean('DiagnosevariablenAnzeigen')) {
|
||||||
$this->registriereVerbraucherDiagnose();
|
$this->registriereVerbraucherDiagnose();
|
||||||
$this->RegisterVariableBoolean('Rueckmeldefehler', 'Rueckmeldefehler', '~Alert', 200);
|
$this->RegisterVariableBoolean('Rueckmeldefehler', 'Rueckmeldefehler', '~Alert', 200);
|
||||||
|
$this->RegisterVariableBoolean(
|
||||||
|
'SchaltbefehlAusstehend',
|
||||||
|
'Schaltbefehl ausstehend',
|
||||||
|
'~Switch',
|
||||||
|
210
|
||||||
|
);
|
||||||
|
$this->RegisterVariableInteger('RestMindestzeit', 'Restliche Mindestzeit', '', 220);
|
||||||
foreach (self::DIAGNOSE_VARIABLEN as $ident) {
|
foreach (self::DIAGNOSE_VARIABLEN as $ident) {
|
||||||
$wert = $ident === 'Leistungsquelle'
|
$wert = $ident === 'Leistungsquelle'
|
||||||
? Nachrichtenvertrag::LEISTUNGSQUELLE_BERECHNET
|
? Nachrichtenvertrag::LEISTUNGSQUELLE_BERECHNET
|
||||||
@@ -739,10 +836,12 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
'Stoerung' => 'ZustandStoerung',
|
'Stoerung' => 'ZustandStoerung',
|
||||||
'Schaltzustand' => 'ZustandSchaltzustand',
|
'Schaltzustand' => 'ZustandSchaltzustand',
|
||||||
'Rueckmeldefehler' => 'ZustandRueckmeldefehler',
|
'Rueckmeldefehler' => 'ZustandRueckmeldefehler',
|
||||||
|
'SchaltbefehlAusstehend' => 'ZustandSchaltbefehlAusstehend',
|
||||||
];
|
];
|
||||||
$integerAttribute = [
|
$integerAttribute = [
|
||||||
'Sollleistung' => 'ZustandSollleistung',
|
'Sollleistung' => 'ZustandSollleistung',
|
||||||
'Tageslaufzeit' => 'ZustandTageslaufzeit',
|
'Tageslaufzeit' => 'ZustandTageslaufzeit',
|
||||||
|
'RestMindestzeit' => 'ZustandRestMindestzeit',
|
||||||
];
|
];
|
||||||
$floatAttribute = [
|
$floatAttribute = [
|
||||||
'Istleistung' => 'ZustandIstleistung',
|
'Istleistung' => 'ZustandIstleistung',
|
||||||
@@ -783,10 +882,12 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
'Stoerung' => 'ZustandStoerung',
|
'Stoerung' => 'ZustandStoerung',
|
||||||
'Schaltzustand' => 'ZustandSchaltzustand',
|
'Schaltzustand' => 'ZustandSchaltzustand',
|
||||||
'Rueckmeldefehler' => 'ZustandRueckmeldefehler',
|
'Rueckmeldefehler' => 'ZustandRueckmeldefehler',
|
||||||
|
'SchaltbefehlAusstehend' => 'ZustandSchaltbefehlAusstehend',
|
||||||
];
|
];
|
||||||
$integerAttribute = [
|
$integerAttribute = [
|
||||||
'Sollleistung' => 'ZustandSollleistung',
|
'Sollleistung' => 'ZustandSollleistung',
|
||||||
'Tageslaufzeit' => 'ZustandTageslaufzeit',
|
'Tageslaufzeit' => 'ZustandTageslaufzeit',
|
||||||
|
'RestMindestzeit' => 'ZustandRestMindestzeit',
|
||||||
];
|
];
|
||||||
$floatAttribute = [
|
$floatAttribute = [
|
||||||
'Istleistung' => 'ZustandIstleistung',
|
'Istleistung' => 'ZustandIstleistung',
|
||||||
@@ -838,6 +939,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
'Meldezyklus',
|
'Meldezyklus',
|
||||||
'RueckmeldungVerzoegert',
|
'RueckmeldungVerzoegert',
|
||||||
'Umschaltfreigabe',
|
'Umschaltfreigabe',
|
||||||
|
'MindestzeitAbgelaufen',
|
||||||
'VorgabeTimeout',
|
'VorgabeTimeout',
|
||||||
'Tagesplanung',
|
'Tagesplanung',
|
||||||
] as $timer) {
|
] as $timer) {
|
||||||
|
|||||||
@@ -20,10 +20,16 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "find . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n1 php -l",
|
"lint": "find . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n1 php -l",
|
||||||
|
"lint:verbraucher-einstufig": "find VerbraucherEinStufig libs/EinStufigRegler.php tests/VerbraucherEinStufig -name '*.php' -print0 | xargs -0 -n1 php -l",
|
||||||
"test": "phpunit",
|
"test": "phpunit",
|
||||||
|
"test:verbraucher-einstufig": "phpunit --configuration phpunit.verbraucher-einstufig.xml",
|
||||||
"check": [
|
"check": [
|
||||||
"@lint",
|
"@lint",
|
||||||
"@test"
|
"@test"
|
||||||
|
],
|
||||||
|
"check:verbraucher-einstufig": [
|
||||||
|
"@lint:verbraucher-einstufig",
|
||||||
|
"@test:verbraucher-einstufig"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
|||||||
@@ -2,56 +2,73 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Akzeptiert.
|
Akzeptiert, am 17. September 2026 um getrennte Mindestzeiten erweitert.
|
||||||
|
|
||||||
## Kontext
|
## Kontext
|
||||||
|
|
||||||
Das Enelix-1-Modul berechnete seinen Zustand in einem festen Intervall und
|
Das Enelix-1-Modul berechnete seinen Zustand in einem festen Intervall und
|
||||||
bildete die Lastwechselsperre ueber `Interval`, `IdleCounterMax` und weitere
|
bildete Lastwechselsperren ueber `Interval`, `IdleCounterMax` und weitere
|
||||||
zyklusabhaengige Zaehler ab. Dieses Verhalten passt nicht zur
|
zyklusabhaengige Zaehler ab. Ein erster Enelix-2-Stand ersetzte dies durch
|
||||||
ereignisbasierten Enelix-2-Kommunikation und machte reale Zeitabstaende von
|
einen allgemeinen `Umschaltabstand`. Dieser konnte unterschiedliche
|
||||||
mehreren Properties abhaengig.
|
Geraeteanforderungen fuer Ein- und Aus-Zustand nicht ausdruecken.
|
||||||
|
|
||||||
|
Bei einem asynchron schaltenden Geraet kann ausserdem der Aktorbefehl nicht als
|
||||||
|
physische Zustandsbestaetigung gelten.
|
||||||
|
|
||||||
## Entscheidung
|
## Entscheidung
|
||||||
|
|
||||||
Der neue `VerbraucherEinStufig` verwendet keine zyklische Regelberechnung.
|
Der `VerbraucherEinStufig` verwendet keine zyklische Regelberechnung.
|
||||||
Schaltkontakt, Rueckmeldung, Manager-Vorgabe, Freigabe, Vorgabeablauf und
|
Schaltkontakt, Rueckmeldung, Manager-Vorgabe, Freigabe, Vorgabeablauf und
|
||||||
Tagesplanung loesen die Regelung direkt aus.
|
Tagesplanung loesen die Regelung direkt aus.
|
||||||
|
|
||||||
Als einziger modulspezifischer Wert fuer die Lastwechselbegrenzung wird
|
Der allgemeine `Umschaltabstand` entfaellt. An seine Stelle treten:
|
||||||
`Umschaltabstand` in Sekunden registriert. Der Standardwert ist 5 Sekunden.
|
|
||||||
Das Ende der Sperre wird durch einen einmaligen Timer ausgeloest.
|
- `Mindesteinschaltdauer` in Sekunden, Standard 5,
|
||||||
|
- `Mindestausschaltdauer` in Sekunden, Standard 5.
|
||||||
|
|
||||||
|
Die passende Mindestzeit beginnt mit dem bestaetigten Zustandswechsel. Ohne
|
||||||
|
separate Rueckmeldung bestaetigt die Aktorvariable den Wechsel unmittelbar.
|
||||||
|
Mit separater Rueckmeldung beginnt die Mindestzeit erst, wenn deren Wert den
|
||||||
|
Zielzustand erreicht.
|
||||||
|
|
||||||
|
Bis dahin bleiben Istleistung und Leistungsangebot beim rueckgemeldeten
|
||||||
|
Zustand. `SchaltbefehlAusstehend=true`,
|
||||||
|
`AenderungMoeglich=false` und `Schaltbereit=false` machen die laufende
|
||||||
|
Umschaltung fuer den Manager sichtbar.
|
||||||
|
|
||||||
Das gemeinsame `Meldeintervall` bleibt erhalten, weil Vertrag `3.0`
|
Das gemeinsame `Meldeintervall` bleibt erhalten, weil Vertrag `3.0`
|
||||||
zusaetzlich zu Ereignismeldungen eine periodische Vollmeldung fordert. Dieser
|
zusaetzlich zu Ereignismeldungen eine periodische Vollmeldung fordert. Dieser
|
||||||
Timer ist kein Regelzyklus.
|
Timer ist kein Regelzyklus.
|
||||||
|
|
||||||
Die Tagesmindestlaufzeit wird in realen Sekunden und lokaler Symcon-Zeitzone
|
Die Tagesmindestlaufzeit wird in realen Sekunden und lokaler Symcon-Zeitzone
|
||||||
gezaehlt. Das Modul erzwingt den Betrieb erst zum spaetestmoeglichen Zeitpunkt,
|
gezaehlt. Bei separater Rueckmeldung zaehlt ausschliesslich der bestaetigte
|
||||||
an dem die fehlende Laufzeit vor Mitternacht noch erreicht werden kann.
|
Ein-Zustand.
|
||||||
|
|
||||||
## Alternativen
|
## Alternativen
|
||||||
|
|
||||||
- Ein fester Regelzyklus wurde verworfen, weil Reaktionszeit und Sperrzeit
|
- Ein fester Regelzyklus wurde verworfen, weil Reaktionszeit und Zeitregeln
|
||||||
wieder voneinander abhaengen wuerden.
|
wieder voneinander abhaengen wuerden.
|
||||||
- Getrennte Ein- und Ausschaltzeiten wurden verworfen, weil sie fuer diese
|
- Ein allgemeiner Umschaltabstand wurde verworfen, weil Ein- und Aus-Zustand
|
||||||
Adaption nicht freigegeben sind.
|
unterschiedliche Mindestzeiten benoetigen koennen.
|
||||||
- Die feste Nachtphase aus Enelix 1 wurde verworfen, weil sie weder
|
- Der Aktorbefehl als sofortige physische Bestaetigung wurde bei vorhandener
|
||||||
konfigurierbar noch Teil der Enelix-2-Spezifikation ist.
|
Rueckmeldung verworfen.
|
||||||
|
- Ein zusaetzlicher Rueckmelde-Timeout wurde nicht eingefuehrt. Ein
|
||||||
|
ausstehender Befehl bleibt transparent sichtbar, bis ein neues Ereignis den
|
||||||
|
Zustand klaert.
|
||||||
|
|
||||||
## Folgen
|
## Folgen
|
||||||
|
|
||||||
- Lastwechsel reagieren ohne Polling auf relevante Ereignisse.
|
- Lastwechsel reagieren ohne Polling auf relevante Ereignisse.
|
||||||
- `Interval` und `IdleCounterMax` entfallen.
|
- `Interval`, `IdleCounterMax` und `Umschaltabstand` entfallen.
|
||||||
- Der Manager sieht waehrend einer Sperre nur die aktuelle Leistung und
|
- Waerend Mindestzeiten und ausstehenden Rueckmeldungen wird nur die
|
||||||
`AenderungMoeglich=false`.
|
bestaetigte Istleistung angeboten.
|
||||||
- Ein optionaler Rueckmeldekontakt muss innerhalb des Umschaltabstands folgen.
|
- Der Manager erhaelt Status und Restmindestzeit als Zustandseintraege.
|
||||||
- Tageslaufzeit und Mindestlaufzeit sind unabhaengig von einer Zyklusdauer.
|
- Eine unerwartete Abweichung zwischen Aktor und Rueckmeldung ausserhalb eines
|
||||||
- Das Modul bleibt vollstaendig im Repository Enelix EMS; Enelix Utils erhaelt
|
laufenden Schaltvorgangs wird als Stoerung gemeldet.
|
||||||
keine geraetespezifische Logik.
|
- Tageslaufzeit und Mindestzeiten sind unabhaengig von einer Zyklusdauer.
|
||||||
|
- Das Modul bleibt vollstaendig im Repository Enelix EMS.
|
||||||
|
|
||||||
## Offene Punkte
|
## Offene Punkte
|
||||||
|
|
||||||
- Eine spaetere sperrbare Variante wird separat spezifiziert.
|
- Eine spaetere sperrbare Variante wird separat spezifiziert.
|
||||||
- Unterschiedliche Ein- und Ausschaltabstaende benoetigen eine neue
|
- Ein konfigurierbarer Rueckmelde-Timeout benoetigt eine eigene Entscheidung.
|
||||||
Architekturentscheidung.
|
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
# Migration von Verbraucher_1_Stufig
|
# Migration von Verbraucher_1_Stufig
|
||||||
|
|
||||||
Das Enelix-1-Modul wird nicht in-place aktualisiert. Das neue Modul verwendet
|
Das Enelix-1-Modul wird nicht in-place aktualisiert. Das neue Modul verwendet
|
||||||
eine neue Modul-ID, den Vertrag `3.0` und eine ereignisbasierte Regelung.
|
eine neue Modul-ID, Vertrag `3.0` und eine ereignisbasierte Regelung.
|
||||||
|
|
||||||
## Eigenschaften
|
## Eigenschaften
|
||||||
|
|
||||||
| Enelix 1 | Enelix 2 | Migration |
|
| Enelix 1 / bisheriges Enelix 2 | Aktuelles Enelix 2 | Migration |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `BoilerLeistung` | `Nennleistung` | Wert in ganzen Watt uebernehmen. |
|
| `BoilerLeistung` | `Nennleistung` | Wert in ganzen Watt uebernehmen. |
|
||||||
| `Schaltkontakt1` | `SchaltkontaktVariableID` | Boolean-Aktor uebernehmen und Aktion pruefen. |
|
| `Schaltkontakt1` | `SchaltkontaktVariableID` | Boolean-Aktor uebernehmen und Aktion pruefen. |
|
||||||
| nicht vorhanden | `SchaltkontaktInvertiert` | Nur bei umgekehrter Aktorlogik aktivieren. |
|
| nicht vorhanden | `SchaltkontaktInvertiert` | Nur bei umgekehrter Aktorlogik aktivieren. |
|
||||||
| nicht vorhanden | `RueckmeldungVariableID` | Optional eine echte Boolean-Rueckmeldung zuordnen. |
|
| nicht vorhanden | `RueckmeldungVariableID` | Optional eine echte Boolean-Rueckmeldung zuordnen. |
|
||||||
| `Mindesttlaufzeit` | `Mindestlaufzeit` | Alter Wert war in Minuten gedacht; fuer gleiches Verhalten mit 60 multiplizieren und als Sekunden eintragen. |
|
| `Mindesttlaufzeit` | `Mindestlaufzeit` | Alten Minutenwert fuer gleiches Verhalten mit 60 multiplizieren. |
|
||||||
| `Zeit_Zwischen_Zustandswechseln` | `Umschaltabstand` | Alte Minuten nicht uebernehmen. Neuen Sekundenwert bewusst festlegen; Standard ist 5 s. |
|
| `Zeit_Zwischen_Zustandswechseln` | `Mindesteinschaltdauer`, `Mindestausschaltdauer` | Alte Minuten nicht ungeprueft uebernehmen; beide Sekundenwerte geraetespezifisch setzen. |
|
||||||
| `Interval` | entfaellt | Keine zyklische Regelberechnung mehr. |
|
| `Umschaltabstand` aus fruehem Enelix 2 | `Mindesteinschaltdauer`, `Mindestausschaltdauer` | Der alte Wert wird nicht automatisch uebertragen; beide Werte haben Standard 5 s. |
|
||||||
| `IdleCounterMax` | entfaellt | Keine zyklusbasierte Idle-Erkennung mehr. |
|
| `Interval` | entfaellt | Keine zyklische Regelberechnung. |
|
||||||
|
| `IdleCounterMax` | entfaellt | Keine zyklusbasierte Idle-Erkennung. |
|
||||||
|
|
||||||
## Variablen und Verhalten
|
## Variablen und Verhalten
|
||||||
|
|
||||||
| Enelix 1 | Enelix 2 |
|
| Alt | Aktuell |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `DailyOnTime` | `Tageslaufzeit` in echten Sekunden |
|
| `DailyOnTime` | `Tageslaufzeit` in bestaetigten echten Sekunden |
|
||||||
| `IstNacht` | entfaellt; Mindestlaufzeit wird am spaetestmoeglichen Start des lokalen Kalendertags erzwungen |
|
| `IstNacht` | entfaellt; Tagesmindestlaufzeit wird spaetestmoeglich erzwungen |
|
||||||
| `IsTimerActive` | entfaellt; interne einmalige Timer |
|
| `IsTimerActive` | entfaellt; interne einmalige Timer |
|
||||||
| `Aktuelle_Leistung` | gemeinsames `Istleistung` |
|
| `Aktuelle_Leistung` | gemeinsames `Istleistung` |
|
||||||
| `Power` | gemeinsames `Sollleistung` |
|
| `Power` | gemeinsames `Sollleistung` |
|
||||||
@@ -32,25 +33,25 @@ eine neue Modul-ID, den Vertrag `3.0` und eine ereignisbasierte Regelung.
|
|||||||
| `Leistung_Delta` | entfaellt; Leistungsverteilung liegt im Manager |
|
| `Leistung_Delta` | entfaellt; Leistungsverteilung liegt im Manager |
|
||||||
| `Idle`, `IdleCounter` | entfallen |
|
| `Idle`, `IdleCounter` | entfallen |
|
||||||
|
|
||||||
Die feste Enelix-1-Nachtzeit von 22:00 bis 07:00 wird nicht uebernommen.
|
Bei konfigurierter Rueckmeldung bestimmt nicht mehr der ausgegebene
|
||||||
Enelix 2 plant nur den Zeitpunkt, ab dem die verbleibende Mindestlaufzeit vor
|
Aktorbefehl, sondern erst der rueckgemeldete Zustand die Istleistung, die
|
||||||
dem lokalen Tagesende zwingend eingeschaltet werden muss.
|
Tageslaufzeit und den Start der jeweiligen Mindestzeit.
|
||||||
|
|
||||||
## Inbetriebnahme
|
## Inbetriebnahme
|
||||||
|
|
||||||
1. Bestehende Instanzkonfiguration und aktuelle Aktorlogik dokumentieren.
|
1. Bestehende Instanzkonfiguration und Aktorlogik dokumentieren.
|
||||||
2. Neue Instanz **Verbraucher 1-Stufig** anlegen.
|
2. Neue Instanz **Verbraucher 1-Stufig** anlegen oder die bestehende
|
||||||
|
Enelix-2-Instanz aktualisieren.
|
||||||
3. Leistung, Aktor, Invertierung und optional Rueckmeldung konfigurieren.
|
3. Leistung, Aktor, Invertierung und optional Rueckmeldung konfigurieren.
|
||||||
4. Mindestlaufzeit von Minuten in Sekunden umrechnen.
|
4. Tagesmindestlaufzeit gegebenenfalls von Minuten in Sekunden umrechnen.
|
||||||
5. `Umschaltabstand` neu in Sekunden festlegen; nicht den alten Minutenwert
|
5. Mindest-Ein- und Mindest-Aus-Zeit getrennt anhand der Geraeteanforderungen
|
||||||
kopieren.
|
setzen.
|
||||||
6. Neue Instanz im Enelix-2-Manager aktiv zuordnen.
|
6. Neue Instanz im Enelix-2-Manager aktiv zuordnen.
|
||||||
7. Bei ausgeschalteter lokaler EMS-Freigabe Ein/Aus und Rueckmeldung
|
7. Ein- und Ausschalten zuerst ohne reale Last oder unter Aufsicht pruefen.
|
||||||
beaufsichtigt pruefen.
|
8. Bei separater Rueckmeldung kontrollieren, dass
|
||||||
8. Alte Instanz erst deaktivieren, danach `Aktiv` an der neuen Instanz
|
`SchaltbefehlAusstehend` erst mit deren Zustandswechsel verschwindet.
|
||||||
einschalten.
|
9. Mindestens je einen verhinderten Ein- und Ausschaltvorgang testen.
|
||||||
9. Mindestens einen Timeout, einen verhinderten Lastwechsel und einen
|
10. Tageslaufzeit und Tageswechsel kontrollieren.
|
||||||
Tageswechsel kontrollieren.
|
|
||||||
|
|
||||||
## Rueckkehr
|
## Rueckkehr
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
> Status: implementiert fuer IP-Symcon 8 und den Enelix-2-Vertrag `3.0`.
|
> Status: implementiert fuer IP-Symcon 8 und den Enelix-2-Vertrag `3.0`.
|
||||||
|
|
||||||
Das Modul schaltet einen elektrischen Ein-/Aus-Verbraucher. Im freien Zustand
|
Das Modul schaltet einen elektrischen Ein-/Aus-Verbraucher. Im freien Zustand
|
||||||
meldet es dem Manager das Leistungsangebot `[0, Nennleistung]`. Waehrend der
|
meldet es dem Manager das Leistungsangebot `[0, Nennleistung]`. Waerend einer
|
||||||
Umschaltsperre und bei lokal erzwungener Mindestlaufzeit wird nur der aktuell
|
Mindest-Ein- oder Mindest-Aus-Zeit, waehrend einer ausstehenden Rueckmeldung
|
||||||
zulaessige Leistungswert angeboten.
|
und bei lokal erzwungener Tagesmindestlaufzeit wird nur der aktuelle
|
||||||
|
bestaetigte Leistungswert angeboten.
|
||||||
|
|
||||||
## Architekturentscheidung
|
## Architekturentscheidung
|
||||||
|
|
||||||
@@ -14,18 +15,19 @@ Die Regelung ist ereignisbasiert. Neuberechnungen werden ausgeloest durch:
|
|||||||
- eine neue Manager-Vorgabe,
|
- eine neue Manager-Vorgabe,
|
||||||
- eine Aenderung an Schaltkontakt oder optionaler Rueckmeldung,
|
- eine Aenderung an Schaltkontakt oder optionaler Rueckmeldung,
|
||||||
- das Ein- oder Ausschalten der lokalen EMS-Freigabe,
|
- das Ein- oder Ausschalten der lokalen EMS-Freigabe,
|
||||||
- das Ende des Umschaltabstands,
|
- das Ende der zustandsabhaengigen Mindestzeit,
|
||||||
- das Ablaufen einer Manager-Vorgabe,
|
- das Ablaufen einer Manager-Vorgabe,
|
||||||
- den naechsten relevanten Zeitpunkt der Tageslaufzeitplanung.
|
- den naechsten relevanten Zeitpunkt der Tageslaufzeitplanung.
|
||||||
|
|
||||||
Es gibt keinen Regelzyklus, kein `Interval` und keinen `IdleCounterMax`.
|
Es gibt keinen Regelzyklus, kein `Interval`, keinen `IdleCounterMax` und
|
||||||
`Meldeintervall` bleibt als vertraglich geforderte periodische Vollmeldung
|
keinen allgemeinen `Umschaltabstand`. `Meldeintervall` bleibt als
|
||||||
bestehen und steuert keine Regelberechnung.
|
vertraglich geforderte periodische Vollmeldung bestehen und steuert keine
|
||||||
|
Regelberechnung.
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
Zu den sechs gemeinsamen Verbraucher-Properties aus
|
Zu den sechs gemeinsamen Verbraucher-Properties aus
|
||||||
[`Schnittstelle.md`](../../Schnittstelle.md) kommen sieben
|
[`Schnittstelle.md`](../../Schnittstelle.md) kommen acht
|
||||||
Modul-Properties hinzu.
|
Modul-Properties hinzu.
|
||||||
|
|
||||||
| Ident | Typ | Standard | Beschreibung |
|
| Ident | Typ | Standard | Beschreibung |
|
||||||
@@ -33,14 +35,19 @@ Modul-Properties hinzu.
|
|||||||
| `Nennleistung` | Integer | `0` | Positive elektrische Leistung in W. |
|
| `Nennleistung` | Integer | `0` | Positive elektrische Leistung in W. |
|
||||||
| `SchaltkontaktVariableID` | Integer | `0` | Boolean-Aktor mit Standard- oder benutzerdefinierter Aktion. |
|
| `SchaltkontaktVariableID` | Integer | `0` | Boolean-Aktor mit Standard- oder benutzerdefinierter Aktion. |
|
||||||
| `SchaltkontaktInvertiert` | Boolean | `false` | Kehrt die Ein-/Aus-Semantik des Aktors um. |
|
| `SchaltkontaktInvertiert` | Boolean | `false` | Kehrt die Ein-/Aus-Semantik des Aktors um. |
|
||||||
| `RueckmeldungVariableID` | Integer | `0` | Optionale Boolean-Rueckmeldung; `true` bedeutet eingeschaltet. |
|
| `RueckmeldungVariableID` | Integer | `0` | Optionale Boolean-Rueckmeldung; `true` bedeutet physisch eingeschaltet. |
|
||||||
| `Mindestlaufzeit` | Integer | `0` | Geforderte Laufzeit pro lokalem Kalendertag in Sekunden, maximal 86400. |
|
| `Mindestlaufzeit` | Integer | `0` | Geforderte Laufzeit pro lokalem Kalendertag in Sekunden, maximal 86400. |
|
||||||
| `Umschaltabstand` | Integer | `5` | Sekunden bis zum naechsten erlaubten Lastwechsel. |
|
| `Mindesteinschaltdauer` | Integer | `5` | Mindestzeit in Sekunden, die ein bestaetigter Ein-Zustand gehalten wird. |
|
||||||
| `DiagnosevariablenAnzeigen` | Boolean | `false` | Legt gemeinsame Diagnosevariablen und `Rueckmeldefehler` an. |
|
| `Mindestausschaltdauer` | Integer | `5` | Mindestzeit in Sekunden, die ein bestaetigter Aus-Zustand gehalten wird. |
|
||||||
|
| `DiagnosevariablenAnzeigen` | Boolean | `false` | Legt gemeinsame und modulspezifische Diagnosevariablen an. |
|
||||||
|
|
||||||
|
Beide Mindestzeiten duerfen `0` sein. Die lokale Aktion `Aktiv=false`
|
||||||
|
bleibt der einzige bewusste Override und schaltet sicher aus, auch wenn die
|
||||||
|
Mindesteinschaltdauer noch laeuft.
|
||||||
|
|
||||||
`EinstellungenInVisu` ist Teil der gemeinsamen Verbraucherbasis. Das Modul
|
`EinstellungenInVisu` ist Teil der gemeinsamen Verbraucherbasis. Das Modul
|
||||||
besitzt derzeit keine zusaetzlichen bedienbaren Einstellvariablen und wertet
|
besitzt derzeit keine zusaetzlichen bedienbaren Einstellvariablen und wertet
|
||||||
diese Property deshalb noch nicht weiter aus.
|
diese Property deshalb nicht weiter aus.
|
||||||
|
|
||||||
## Variablen
|
## Variablen
|
||||||
|
|
||||||
@@ -49,10 +56,10 @@ Immer sichtbar:
|
|||||||
| Ident | Typ / Zugriff | Beschreibung |
|
| Ident | Typ / Zugriff | Beschreibung |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `Aktiv` | Boolean / bedienbar | Lokale EMS-Freigabe; Startwert `false`. |
|
| `Aktiv` | Boolean / bedienbar | Lokale EMS-Freigabe; Startwert `false`. |
|
||||||
| `Schaltzustand` | Boolean / Anzeige | Rueckgemeldeter oder aus dem Aktor berechneter Zustand. |
|
| `Schaltzustand` | Boolean / Anzeige | Bestaetigter oder aus dem Aktor berechneter Zustand. |
|
||||||
| `Tageslaufzeit` | Integer / Anzeige | Laufzeit des lokalen Kalendertags in Sekunden. |
|
| `Tageslaufzeit` | Integer / Anzeige | Bestaetigte Laufzeit des lokalen Kalendertags in Sekunden. |
|
||||||
|
|
||||||
Mit `DiagnosevariablenAnzeigen` werden diese Variablen zusaetzlich angelegt:
|
Mit `DiagnosevariablenAnzeigen` werden zusaetzlich angelegt:
|
||||||
|
|
||||||
- `Istleistung`
|
- `Istleistung`
|
||||||
- `Leistungsquelle`
|
- `Leistungsquelle`
|
||||||
@@ -63,47 +70,83 @@ Mit `DiagnosevariablenAnzeigen` werden diese Variablen zusaetzlich angelegt:
|
|||||||
- `Stoerung`
|
- `Stoerung`
|
||||||
- `Stoertext`
|
- `Stoertext`
|
||||||
- `Rueckmeldefehler`
|
- `Rueckmeldefehler`
|
||||||
|
- `SchaltbefehlAusstehend`
|
||||||
|
- `RestMindestzeit`
|
||||||
|
|
||||||
Die Regellogik arbeitet mit persistenten Attributen und ist nicht von der
|
Die Regellogik arbeitet mit persistenten Attributen und ist nicht von der
|
||||||
Sichtbarkeit der Diagnosevariablen abhaengig.
|
Sichtbarkeit der Diagnosevariablen abhaengig.
|
||||||
|
|
||||||
## Zeit- und Schaltverhalten
|
## Mindest-Ein- und Mindest-Aus-Zeit
|
||||||
|
|
||||||
Nach jedem Lastwechsel wird `AenderungMoeglich=false` gemeldet und das
|
Nach einer bestaetigten Einschaltung wird fuer
|
||||||
Leistungsangebot auf den aktuellen Wert eingeschraenkt. Nach
|
`Mindesteinschaltdauer` Sekunden kein regulaeres Ausschalten angeboten. Nach
|
||||||
`Umschaltabstand` Sekunden gibt ein einmaliger Timer den naechsten
|
einer bestaetigten Ausschaltung blockiert `Mindestausschaltdauer` ein
|
||||||
Lastwechsel frei. Der Standardwert betraegt 5 Sekunden.
|
regulaeres Einschalten.
|
||||||
|
|
||||||
|
Waerend einer Mindestzeit gilt:
|
||||||
|
|
||||||
|
- `AenderungMoeglich=false`,
|
||||||
|
- `Schaltbereit=false`,
|
||||||
|
- `RestMindestzeit_s` enthaelt die verbleibenden Sekunden,
|
||||||
|
- `Leistungswerte_W` enthaelt nur die bestaetigte Istleistung.
|
||||||
|
|
||||||
|
Ein einmaliger Timer loest am Ende der Mindestzeit genau eine Neuberechnung
|
||||||
|
aus. Der fruehere allgemeine `Umschaltabstand` ist vollstaendig entfallen.
|
||||||
|
|
||||||
|
## Rueckmeldung und Schaltbereitschaft
|
||||||
|
|
||||||
|
Ohne separate Rueckmeldung wird der logische Zustand der Aktorvariable als
|
||||||
|
sofortige Schaltbestaetigung verwendet.
|
||||||
|
|
||||||
|
Mit `RueckmeldungVariableID` ist ausschliesslich deren Booleanwert fuer
|
||||||
|
`Schaltzustand`, `Istleistung_W`, Tageslaufzeit und den Beginn der
|
||||||
|
Mindestzeit massgeblich. Nach einem Aktorbefehl und vor der passenden
|
||||||
|
Rueckmeldung meldet das Modul:
|
||||||
|
|
||||||
|
- die bisherige bestaetigte Istleistung,
|
||||||
|
- `SchaltbefehlAusstehend=true`,
|
||||||
|
- `Schaltbereit=false`,
|
||||||
|
- `AenderungMoeglich=false`,
|
||||||
|
- nur den bisherigen Leistungswert in `Leistungswerte_W`.
|
||||||
|
|
||||||
|
Sobald die Rueckmeldung den Zielzustand bestaetigt, beginnt die passende
|
||||||
|
Mindest-Ein- oder Mindest-Aus-Zeit. Es gibt bewusst keinen zusaetzlichen
|
||||||
|
zyklischen Rueckmelde-Timeout. Bleibt die Rueckmeldung aus, bleibt der
|
||||||
|
Schaltbefehl sichtbar ausstehend.
|
||||||
|
|
||||||
|
Weichen Aktor- und Rueckmeldezustand ohne ausstehenden Schaltbefehl voneinander
|
||||||
|
ab, wird `Rueckmeldefehler=true`, `Verfuegbar=false` und eine Stoerung
|
||||||
|
gemeldet.
|
||||||
|
|
||||||
|
## Tagesmindestlaufzeit
|
||||||
|
|
||||||
Die Tageslaufzeit wird mit der in IP-Symcon eingestellten lokalen Zeitzone
|
Die Tageslaufzeit wird mit der in IP-Symcon eingestellten lokalen Zeitzone
|
||||||
gefuehrt. Sommer- und Winterzeit sowie der Wechsel um Mitternacht werden
|
gefuehrt. Sommer- und Winterzeit sowie der Wechsel um Mitternacht werden
|
||||||
beruecksichtigt. Ist die konfigurierte Mindestlaufzeit noch nicht erreicht,
|
beruecksichtigt. Nur der bestaetigte Ein-Zustand zaehlt.
|
||||||
wird der Verbraucher erst zum spaetestmoeglichen Zeitpunkt lokal
|
|
||||||
eingeschaltet, an dem die Restlaufzeit bis Mitternacht noch erfuellt werden
|
|
||||||
kann. In dieser Phase meldet er `AenderungMoeglich=false`.
|
|
||||||
|
|
||||||
Bei Aktivierung sehr kurz vor Tagesende kann eine unmoeglich hohe Restlaufzeit
|
Ist die konfigurierte Tagesmindestlaufzeit noch nicht erreicht, wird der
|
||||||
naturgemaess nicht mehr vollstaendig nachgeholt werden. Der Verbraucher wird
|
Verbraucher zum spaetestmoeglichen Zeitpunkt angefordert, an dem die
|
||||||
dann sofort eingeschaltet und die tatsaechliche Laufzeit erfasst.
|
Restlaufzeit bis Mitternacht noch erfuellt werden kann. Eine noch aktive
|
||||||
|
Mindestausschaltdauer hat dabei Vorrang; eine unmoegliche Restlaufzeit kann
|
||||||
## Rueckmeldung und Fehler
|
nicht rueckwirkend nachgeholt werden.
|
||||||
|
|
||||||
Ohne separate Rueckmeldung wird der Schaltzustand aus der Aktorvariable
|
|
||||||
berechnet. Mit `RueckmeldungVariableID` wartet das Modul bis zum Ende des
|
|
||||||
Umschaltabstands auf den angeforderten Zustand. Liegt er dann nicht an, werden
|
|
||||||
`Rueckmeldefehler=true`, `Stoerung=true` und `Verfuegbar=false` gemeldet.
|
|
||||||
|
|
||||||
Die Istleistung ist immer berechnet: `0` oder `Nennleistung`. Deshalb wird
|
|
||||||
`Leistungsquelle=1` gemeldet, auch wenn der Boolean-Schaltzustand ueber eine
|
|
||||||
physische Rueckmeldung erfasst wird.
|
|
||||||
|
|
||||||
## Managerkommunikation
|
## Managerkommunikation
|
||||||
|
|
||||||
Das Modul implementiert
|
Das Modul implementiert
|
||||||
`VerbraucherSchnittstelle::ManagerdatenEmpfangen()` und verwendet
|
`VerbraucherSchnittstelle::ManagerdatenEmpfangen()` und verwendet
|
||||||
ausschliesslich den Vertrag `3.0`. Der Verbraucher besitzt keine
|
ausschliesslich Vertrag `3.0`. Der Verbraucher besitzt keine
|
||||||
Manager-ID-Property. Er akzeptiert nur Manager, in deren manueller oder
|
Manager-ID-Property. Er akzeptiert nur Manager, in deren manueller oder
|
||||||
automatischer Verbraucherzuordnung seine Instanz aktiv eingetragen ist.
|
automatischer Verbraucherzuordnung seine Instanz aktiv eingetragen ist.
|
||||||
|
|
||||||
|
Neben den gemeinsamen Vertragsfeldern werden diese Zustaende gemeldet:
|
||||||
|
|
||||||
|
- `Schaltzustand`
|
||||||
|
- `SchaltbefehlAusstehend`
|
||||||
|
- `Schaltbereit`
|
||||||
|
- `RestMindestzeit_s`
|
||||||
|
- `Tageslaufzeit_s`
|
||||||
|
- `Rueckmeldefehler`
|
||||||
|
|
||||||
Rueckmeldungen erfolgen bei relevanten Ereignissen, kurz verzoegert nach einer
|
Rueckmeldungen erfolgen bei relevanten Ereignissen, kurz verzoegert nach einer
|
||||||
Manager-Vorgabe und zusaetzlich alle `Meldeintervall` Sekunden. Eine Vorgabe
|
Manager-Vorgabe und zusaetzlich alle `Meldeintervall` Sekunden. Eine Vorgabe
|
||||||
wird nach `VorgabeTimeout` Sekunden ohne Erneuerung ungueltig.
|
wird nach `VorgabeTimeout` Sekunden ohne Erneuerung ungueltig.
|
||||||
@@ -116,16 +159,14 @@ wird nach `VorgabeTimeout` Sekunden ohne Erneuerung ungueltig.
|
|||||||
2. Unter **Instanz hinzufuegen** nach **Verbraucher 1-Stufig** suchen und eine
|
2. Unter **Instanz hinzufuegen** nach **Verbraucher 1-Stufig** suchen und eine
|
||||||
Instanz anlegen.
|
Instanz anlegen.
|
||||||
3. `Nennleistung` in ganzen Watt eintragen.
|
3. `Nennleistung` in ganzen Watt eintragen.
|
||||||
4. Als `Schaltkontakt` eine Booleanvariable mit funktionsfaehiger Aktion
|
4. Als Schaltkontakt eine Booleanvariable mit funktionsfaehiger Aktion
|
||||||
auswaehlen. Bei umgekehrter Aktorlogik `SchaltkontaktInvertiert`
|
auswaehlen und bei Bedarf `SchaltkontaktInvertiert` aktivieren.
|
||||||
aktivieren.
|
|
||||||
5. Optional eine separate Boolean-Rueckmeldung auswaehlen. Dort muss
|
5. Optional eine separate Boolean-Rueckmeldung auswaehlen. Dort muss
|
||||||
`true` dem physisch eingeschalteten Verbraucher entsprechen.
|
`true` dem physisch eingeschalteten Verbraucher entsprechen.
|
||||||
6. Prioritaeten, `Mindestlaufzeit`, `Umschaltabstand`,
|
6. Tagesmindestlaufzeit, Mindest-Einschaltdauer und Mindest-Ausschaltdauer
|
||||||
`Meldeintervall` und `VorgabeTimeout` einstellen und die Konfiguration
|
passend zum angeschlossenen Geraet festlegen.
|
||||||
uebernehmen.
|
7. Die Instanz im Manager manuell aktiv zuordnen oder bei automatischer Suche
|
||||||
7. Die neue Instanz im Manager manuell aktiv zuordnen oder bei automatischer
|
in der gefundenen Liste aktivieren.
|
||||||
Suche in der gefundenen Liste aktivieren.
|
|
||||||
8. Fuer die Erstpruefung `DiagnosevariablenAnzeigen` und bei Bedarf
|
8. Fuer die Erstpruefung `DiagnosevariablenAnzeigen` und bei Bedarf
|
||||||
`LoggingEin` einschalten.
|
`LoggingEin` einschalten.
|
||||||
9. Unter Aufsicht `Aktiv` einschalten und ueber den Manager je eine Ein- und
|
9. Unter Aufsicht `Aktiv` einschalten und ueber den Manager je eine Ein- und
|
||||||
@@ -134,23 +175,37 @@ wird nach `VorgabeTimeout` Sekunden ohne Erneuerung ungueltig.
|
|||||||
## Abnahmecheckliste
|
## Abnahmecheckliste
|
||||||
|
|
||||||
- `Aktiv=false` schaltet den Ausgang aus.
|
- `Aktiv=false` schaltet den Ausgang aus.
|
||||||
- `Aktiv=true` und eine Manager-Vorgabe `Nennleistung` schalten den Ausgang
|
- Eine Ein-Vorgabe setzt zuerst den Aktor; mit separater Rueckmeldung bleibt
|
||||||
ein.
|
`SchaltbefehlAusstehend=true`, bis diese einschaltet.
|
||||||
- Direkt nach einem Wechsel ist `AenderungMoeglich=false`; nach 5 Sekunden
|
- Die Mindesteinschaltdauer beginnt erst mit der Ein-Rueckmeldung.
|
||||||
wird es mit Standardkonfiguration wieder `true`.
|
- Vor Ablauf der Mindesteinschaltdauer wird kein Ausschalten angeboten.
|
||||||
- Eine Gegenanforderung waehrend der Sperre wird vom Leistungsangebot
|
- Nach bestaetigter Ausschaltung beginnt die Mindestausschaltdauer.
|
||||||
ausgeschlossen.
|
- Vor Ablauf der Mindestausschaltdauer wird kein Einschalten angeboten.
|
||||||
- Die optionale Rueckmeldung folgt dem Ausgang innerhalb des
|
- `RestMindestzeit_s` erreicht 0 und `Schaltbereit` wird danach true.
|
||||||
Umschaltabstands; andernfalls erscheint eine Stoerung.
|
- `Tageslaufzeit` steigt nur bei bestaetigtem Ein-Zustand.
|
||||||
- `Tageslaufzeit` steigt nur im eingeschalteten Zustand und beginnt am
|
- Ein unerwarteter Unterschied zwischen Aktor und Rueckmeldung erzeugt eine
|
||||||
lokalen Tageswechsel wieder bei 0.
|
Stoerung.
|
||||||
- Nach Ablauf von `VorgabeTimeout` wird ohne neue Manager-Vorgabe
|
- Nach einem Neustart wird keine alte Manager-Vorgabe ungeprueft fortgesetzt.
|
||||||
ausgeschaltet, sobald der Umschaltabstand dies zulaesst.
|
|
||||||
- Nach Neustart oder erneuter Konfiguration wird der reale Schaltzustand
|
## Tests
|
||||||
eingelesen; alte Manager-Vorgaben werden nicht ungeprueft fortgesetzt.
|
|
||||||
|
Nur dieses Modul:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer check:verbraucher-einstufig
|
||||||
|
```
|
||||||
|
|
||||||
|
Gesamtes Repository inklusive dieser Modulsuite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer check
|
||||||
|
```
|
||||||
|
|
||||||
|
Die separate Suite und ihre Struktur sind unter
|
||||||
|
[`tests/VerbraucherEinStufig`](../../../tests/VerbraucherEinStufig/README.md)
|
||||||
|
dokumentiert.
|
||||||
|
|
||||||
## Migration
|
## Migration
|
||||||
|
|
||||||
Die Zuordnung der Enelix-1-Felder, Einheiten und bewusst entfallenen
|
Die Zuordnung der Enelix-1- und bisherigen Enelix-2-Zeitwerte steht in
|
||||||
Kommunikationsvariablen steht in
|
|
||||||
[`docs/migration/Verbraucher-1-Stufig.md`](../../migration/Verbraucher-1-Stufig.md).
|
[`docs/migration/Verbraucher-1-Stufig.md`](../../migration/Verbraucher-1-Stufig.md).
|
||||||
|
|||||||
@@ -34,6 +34,28 @@ final class EinStufigRegler
|
|||||||
return [0, $nennleistung];
|
return [0, $nennleistung];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function freigabezeitpunkt(
|
||||||
|
bool $eingeschaltet,
|
||||||
|
int $bestaetigtSeit,
|
||||||
|
int $mindesteinschaltdauer,
|
||||||
|
int $mindestausschaltdauer
|
||||||
|
): int {
|
||||||
|
if ($bestaetigtSeit < 0 || $mindesteinschaltdauer < 0 || $mindestausschaltdauer < 0) {
|
||||||
|
throw new InvalidArgumentException('Zeitwerte duerfen nicht negativ sein.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bestaetigtSeit + ($eingeschaltet ? $mindesteinschaltdauer : $mindestausschaltdauer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function restMindestzeit(int $freigabezeitpunkt, int $jetzt): int
|
||||||
|
{
|
||||||
|
if ($freigabezeitpunkt < 0 || $jetzt < 0) {
|
||||||
|
throw new InvalidArgumentException('Zeitwerte duerfen nicht negativ sein.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return max(0, $freigabezeitpunkt - $jetzt);
|
||||||
|
}
|
||||||
|
|
||||||
public static function mussMindestlaufzeitErzwingen(
|
public static function mussMindestlaufzeitErzwingen(
|
||||||
int $tageslaufzeit,
|
int $tageslaufzeit,
|
||||||
int $mindestlaufzeit,
|
int $mindestlaufzeit,
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
colors="true"
|
||||||
|
failOnWarning="true"
|
||||||
|
failOnRisky="true">
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Verbraucher 1-Stufig">
|
||||||
|
<directory>tests/VerbraucherEinStufig</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
</phpunit>
|
||||||
@@ -35,7 +35,7 @@ final class ManagerModulstrukturTest extends TestCase
|
|||||||
512,
|
512,
|
||||||
JSON_THROW_ON_ERROR
|
JSON_THROW_ON_ERROR
|
||||||
);
|
);
|
||||||
$json = json_encode($formular, JSON_THROW_ON_ERROR);
|
$json = json_encode($formular, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
foreach ([
|
foreach ([
|
||||||
'NetzleistungVariableID',
|
'NetzleistungVariableID',
|
||||||
|
|||||||
@@ -43,6 +43,25 @@ final class EinStufigReglerTest extends TestCase
|
|||||||
EinStufigRegler::leistungsangebot(3500, 1200, true, true, false);
|
EinStufigRegler::leistungsangebot(3500, 1200, true, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFreigabeVerwendetZustandsabhaengigeMindestdauer(): void
|
||||||
|
{
|
||||||
|
self::assertSame(1010, EinStufigRegler::freigabezeitpunkt(true, 1000, 10, 20));
|
||||||
|
self::assertSame(1020, EinStufigRegler::freigabezeitpunkt(false, 1000, 10, 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRestMindestzeitWirdNieNegativ(): void
|
||||||
|
{
|
||||||
|
self::assertSame(7, EinStufigRegler::restMindestzeit(1007, 1000));
|
||||||
|
self::assertSame(0, EinStufigRegler::restMindestzeit(1000, 1007));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNegativeMindestdauerWirdAbgewiesen(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
|
||||||
|
EinStufigRegler::freigabezeitpunkt(true, 1000, -1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
public function testMindestlaufzeitWirdErstZumSpaetestmoeglichenStartErzwungen(): void
|
public function testMindestlaufzeitWirdErstZumSpaetestmoeglichenStartErzwungen(): void
|
||||||
{
|
{
|
||||||
self::assertFalse(EinStufigRegler::mussMindestlaufzeitErzwingen(0, 3600, 7200));
|
self::assertFalse(EinStufigRegler::mussMindestlaufzeitErzwingen(0, 3600, 7200));
|
||||||
+52
-17
@@ -8,7 +8,7 @@ use PHPUnit\Framework\TestCase;
|
|||||||
|
|
||||||
final class VerbraucherEinStufigModulstrukturTest extends TestCase
|
final class VerbraucherEinStufigModulstrukturTest extends TestCase
|
||||||
{
|
{
|
||||||
private const MODULPFAD = __DIR__ . '/../VerbraucherEinStufig/';
|
private const MODULPFAD = __DIR__ . '/../../VerbraucherEinStufig/';
|
||||||
|
|
||||||
public function testModulIstVollstaendigUndMetadatenSindGueltig(): void
|
public function testModulIstVollstaendigUndMetadatenSindGueltig(): void
|
||||||
{
|
{
|
||||||
@@ -43,7 +43,7 @@ final class VerbraucherEinStufigModulstrukturTest extends TestCase
|
|||||||
|
|
||||||
public function testPropertiesSindRegistriertUndImFormularVorhanden(): void
|
public function testPropertiesSindRegistriertUndImFormularVorhanden(): void
|
||||||
{
|
{
|
||||||
$basis = (string) file_get_contents(__DIR__ . '/../libs/VerbraucherBasisTrait.php');
|
$basis = (string) file_get_contents(__DIR__ . '/../../libs/VerbraucherBasisTrait.php');
|
||||||
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||||
preg_match_all(
|
preg_match_all(
|
||||||
'/RegisterProperty(?:Integer|Float|Boolean|String)\(\'([^\']+)\'/',
|
'/RegisterProperty(?:Integer|Float|Boolean|String)\(\'([^\']+)\'/',
|
||||||
@@ -65,7 +65,8 @@ final class VerbraucherEinStufigModulstrukturTest extends TestCase
|
|||||||
'SchaltkontaktInvertiert',
|
'SchaltkontaktInvertiert',
|
||||||
'RueckmeldungVariableID',
|
'RueckmeldungVariableID',
|
||||||
'Mindestlaufzeit',
|
'Mindestlaufzeit',
|
||||||
'Umschaltabstand',
|
'Mindesteinschaltdauer',
|
||||||
|
'Mindestausschaltdauer',
|
||||||
'DiagnosevariablenAnzeigen',
|
'DiagnosevariablenAnzeigen',
|
||||||
],
|
],
|
||||||
$properties
|
$properties
|
||||||
@@ -77,17 +78,21 @@ final class VerbraucherEinStufigModulstrukturTest extends TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNurUmschaltabstandErsetztAlteZyklusProperties(): void
|
public function testGetrennteMindestzeitenErsetzenUmschaltabstand(): void
|
||||||
{
|
{
|
||||||
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||||
|
|
||||||
self::assertStringContainsString(
|
self::assertStringContainsString(
|
||||||
"RegisterPropertyInteger('Umschaltabstand', 5)",
|
"RegisterPropertyInteger('Mindesteinschaltdauer', 5)",
|
||||||
$modul
|
$modul
|
||||||
);
|
);
|
||||||
|
self::assertStringContainsString(
|
||||||
|
"RegisterPropertyInteger('Mindestausschaltdauer', 5)",
|
||||||
|
$modul
|
||||||
|
);
|
||||||
|
self::assertStringNotContainsString("RegisterPropertyInteger('Umschaltabstand'", $modul);
|
||||||
self::assertStringNotContainsString("RegisterPropertyInteger('Interval'", $modul);
|
self::assertStringNotContainsString("RegisterPropertyInteger('Interval'", $modul);
|
||||||
self::assertStringNotContainsString("RegisterPropertyInteger('IdleCounterMax'", $modul);
|
self::assertStringNotContainsString("RegisterPropertyInteger('IdleCounterMax'", $modul);
|
||||||
self::assertStringNotContainsString("RegisterTimer('Regelzyklus'", $modul);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRegelungReagiertAufEreignisseUndEinmalTimer(): void
|
public function testRegelungReagiertAufEreignisseUndEinmalTimer(): void
|
||||||
@@ -96,13 +101,33 @@ final class VerbraucherEinStufigModulstrukturTest extends TestCase
|
|||||||
|
|
||||||
self::assertStringContainsString('public function MessageSink', $modul);
|
self::assertStringContainsString('public function MessageSink', $modul);
|
||||||
self::assertStringContainsString('RegisterMessage($neueID, self::VM_UPDATE)', $modul);
|
self::assertStringContainsString('RegisterMessage($neueID, self::VM_UPDATE)', $modul);
|
||||||
foreach (['Umschaltfreigabe', 'VorgabeTimeout', 'Tagesplanung'] as $timer) {
|
foreach (['MindestzeitAbgelaufen', 'VorgabeTimeout', 'Tagesplanung'] as $timer) {
|
||||||
self::assertStringContainsString("'$timer'", $modul);
|
self::assertStringContainsString("'$timer'", $modul);
|
||||||
}
|
}
|
||||||
self::assertStringContainsString(
|
self::assertStringContainsString(
|
||||||
"SetTimerInterval('Meldezyklus', \$this->ReadPropertyInteger('Meldeintervall') * 1000)",
|
"SetTimerInterval('Meldezyklus', \$this->ReadPropertyInteger('Meldeintervall') * 1000)",
|
||||||
$modul
|
$modul
|
||||||
);
|
);
|
||||||
|
self::assertStringNotContainsString("case 'Umschaltfreigabe':", $modul);
|
||||||
|
self::assertStringNotContainsString("RegisterTimer('Regelzyklus'", $modul);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRueckmeldungBestimmtZustandUndSchaltbereitschaft(): void
|
||||||
|
{
|
||||||
|
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||||
|
|
||||||
|
self::assertStringContainsString(
|
||||||
|
"ReadPropertyInteger('RueckmeldungVariableID')",
|
||||||
|
$modul
|
||||||
|
);
|
||||||
|
self::assertStringContainsString('bestaetigeZustandswechsel', $modul);
|
||||||
|
self::assertStringContainsString("'Kennung' => 'SchaltbefehlAusstehend'", $modul);
|
||||||
|
self::assertStringContainsString("'Kennung' => 'Schaltbereit'", $modul);
|
||||||
|
self::assertStringContainsString("'Kennung' => 'RestMindestzeit_s'", $modul);
|
||||||
|
self::assertStringContainsString(
|
||||||
|
'setzeZustand(\'AenderungMoeglich\', $aenderungMoeglich)',
|
||||||
|
$modul
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testModulVerwendetAusschliesslichDenVertrag30(): void
|
public function testModulVerwendetAusschliesslichDenVertrag30(): void
|
||||||
@@ -117,25 +142,19 @@ final class VerbraucherEinStufigModulstrukturTest extends TestCase
|
|||||||
self::assertStringNotContainsString("RegisterPropertyInteger('ManagerID'", $modul);
|
self::assertStringNotContainsString("RegisterPropertyInteger('ManagerID'", $modul);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFachvariablenUndRueckmeldestoerungSindEnthalten(): void
|
public function testFachvariablenUndRueckmeldestatusSindEnthalten(): void
|
||||||
{
|
{
|
||||||
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||||
|
|
||||||
self::assertStringContainsString(
|
self::assertStringContainsString("RegisterVariableBoolean('Schaltzustand'", $modul);
|
||||||
"RegisterVariableBoolean('Schaltzustand'",
|
self::assertStringContainsString("RegisterVariableInteger('Tageslaufzeit'", $modul);
|
||||||
$modul
|
|
||||||
);
|
|
||||||
self::assertStringContainsString(
|
|
||||||
"RegisterVariableInteger('Tageslaufzeit'",
|
|
||||||
$modul
|
|
||||||
);
|
|
||||||
self::assertStringContainsString("'Kennung' => 'Rueckmeldefehler'", $modul);
|
self::assertStringContainsString("'Kennung' => 'Rueckmeldefehler'", $modul);
|
||||||
self::assertStringContainsString("'Kennung' => 'Tageslaufzeit_s'", $modul);
|
self::assertStringContainsString("'Kennung' => 'Tageslaufzeit_s'", $modul);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testManagerKenntDieModulIDGenauEinmal(): void
|
public function testManagerKenntDieModulIDGenauEinmal(): void
|
||||||
{
|
{
|
||||||
$manager = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
|
$manager = (string) file_get_contents(__DIR__ . '/../../Manager/module.php');
|
||||||
$modulID = '{15879A4E-D0C2-4495-83DE-46E1E462591E}';
|
$modulID = '{15879A4E-D0C2-4495-83DE-46E1E462591E}';
|
||||||
|
|
||||||
self::assertSame(1, substr_count($manager, $modulID));
|
self::assertSame(1, substr_count($manager, $modulID));
|
||||||
@@ -149,4 +168,20 @@ final class VerbraucherEinStufigModulstrukturTest extends TestCase
|
|||||||
self::assertStringContainsString('registriereVerbraucherDiagnose', $modul);
|
self::assertStringContainsString('registriereVerbraucherDiagnose', $modul);
|
||||||
self::assertStringContainsString('UnregisterVariable($ident)', $modul);
|
self::assertStringContainsString('UnregisterVariable($ident)', $modul);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSeparateSuiteIstImGesamttestEnthalten(): void
|
||||||
|
{
|
||||||
|
self::assertFileExists(__DIR__ . '/../../phpunit.verbraucher-einstufig.xml');
|
||||||
|
|
||||||
|
$composer = json_decode(
|
||||||
|
(string) file_get_contents(__DIR__ . '/../../composer.json'),
|
||||||
|
true,
|
||||||
|
512,
|
||||||
|
JSON_THROW_ON_ERROR
|
||||||
|
);
|
||||||
|
self::assertArrayHasKey('check:verbraucher-einstufig', $composer['scripts']);
|
||||||
|
|
||||||
|
$gesamt = (string) file_get_contents(__DIR__ . '/../../phpunit.xml.dist');
|
||||||
|
self::assertStringContainsString('<directory>tests</directory>', $gesamt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Tests fuer Verbraucher 1-Stufig
|
||||||
|
|
||||||
|
Die Tests des Moduls koennen unabhaengig vom restlichen Repository ausgefuehrt
|
||||||
|
werden:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer check:verbraucher-einstufig
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Befehl prueft nur den Modulcode, die reine Regellogik und diese Tests.
|
||||||
|
Der allgemeine Befehl bleibt der externe Gesamteinstieg und schliesst die
|
||||||
|
Modulsuite durch das rekursive Verzeichnis `tests` automatisch ein:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer check
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user