diff --git a/EaseeGateway/module.php b/EaseeGateway/module.php index 8144b02..7a5ebb3 100644 --- a/EaseeGateway/module.php +++ b/EaseeGateway/module.php @@ -16,6 +16,7 @@ class EaseeGateway extends IPSModule private const SIGNALR_BASE_URL = 'https://streams.easee.com/hubs/chargers'; private const API_BASE_URL = 'https://api.easee.com'; private const RECORD_SEPARATOR = "\x1e"; + private const CACHE_MAXIMALALTER_SEKUNDEN = 60; public function Create(): void { @@ -112,7 +113,10 @@ class EaseeGateway extends IPSModule return; case 'RefreshToken': - if ($this->erneuereZugangsdaten(false)) { + if ( + $this->erneuereZugangsdaten(false) + && $this->GetBuffer('SignalRReady') !== '1' + ) { $this->verbindeSignalR(); } return; @@ -346,7 +350,7 @@ class EaseeGateway extends IPSModule $this->SetBuffer('ReceiveBuffer', ''); $this->SetBuffer('SubscribedThisConnection', '{}'); $this->SetBuffer('PendingInvocations', '{}'); - $this->WriteAttributeString('ObservationCache', '{}'); + $this->markiereBeobachtungscacheVeraltet(); $this->setzeVerbunden(false); if (!$this->konfiguriereWebSocketParent($url)) { @@ -572,12 +576,30 @@ class EaseeGateway extends IPSModule ); } + private function markiereBeobachtungscacheVeraltet(): void + { + $cache = $this->leseAttributArray('ObservationCache'); + foreach ($cache as &$zustand) { + if (is_array($zustand)) { + $zustand['updated'] = 0; + } + } + unset($zustand); + $this->WriteAttributeString( + 'ObservationCache', + json_encode($cache, JSON_THROW_ON_ERROR) + ); + } + private function stelleAktuellenStatusSicher(string $seriennummer): void { $cache = $this->leseAttributArray('ObservationCache'); $zustand = $cache[$seriennummer] ?? []; + $cacheIstAktuell = is_array($zustand) + && array_key_exists('109', $zustand) + && (int) ($zustand['updated'] ?? 0) >= time() - self::CACHE_MAXIMALALTER_SEKUNDEN; if ( - (is_array($zustand) && array_key_exists('109', $zustand)) + $cacheIstAktuell || $this->ReadPropertyBoolean('Testmodus') || $this->GetBuffer('SignalRReady') !== '1' ) { @@ -848,7 +870,11 @@ class EaseeGateway extends IPSModule private function setzeVerbunden(bool $verbunden): void { + $warVerbunden = (bool) $this->GetValue('Connected'); $this->SetValue('Connected', $verbunden); + if ($warVerbunden === $verbunden) { + return; + } $this->SendDataToChildren(json_encode([ 'DataID' => self::CHILD_EVENT_DATA_ID, 'Buffer' => json_encode([ diff --git a/LadestationStandAlone/module.php b/LadestationStandAlone/module.php index de4abeb..ca9e82a 100644 --- a/LadestationStandAlone/module.php +++ b/LadestationStandAlone/module.php @@ -87,6 +87,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell $this->RegisterAttributeInteger('LetzteManagerID', 0); $this->RegisterAttributeInteger('LetzteVorgabeZeit', 0); $this->RegisterAttributeInteger('LetzterGesetzterStrom', -1); + $this->RegisterAttributeBoolean('LadeleistungSeitFreigabeErkannt', false); $this->RegisterTimer( 'Statuszyklus', @@ -120,6 +121,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell // Ein persistiertes Ladeende nach Neueinrichtung oder Modulupdate einmal neu pruefen. $this->setzeZustand('FahrzeugGeladen', false); $this->WriteAttributeInteger('LetzterGesetzterStrom', -1); + $this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false); try { $this->pruefeKonfiguration(); @@ -307,8 +309,14 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell $rohstatus, $this->ReadAttributeBoolean('ZustandFahrzeugVerbunden'), $this->ReadAttributeBoolean('ZustandFahrzeugGeladen'), - $this->ReadAttributeInteger('LetzterGesetzterStrom') >= 6 + $this->ReadAttributeInteger('LetzterGesetzterStrom') >= 6, + $this->ReadAttributeBoolean('LadeleistungSeitFreigabeErkannt') ); + if (!$status['FahrzeugVerbunden']) { + $this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false); + } elseif ($status['Ladestrom_A'] >= 3.5) { + $this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', true); + } foreach ($status as $ident => $wert) { $this->setzeZustand($ident, $wert); @@ -342,6 +350,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell if ($strom === $this->ReadAttributeInteger('LetzterGesetzterStrom')) { return; } + $this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false); $transport = new LadestationTransport( fn (array $anfrage): string => $this->fuehreHttpAnfrageAus($anfrage) diff --git a/libs/LadestationRegler.php b/libs/LadestationRegler.php index 1f6df48..98ff067 100644 --- a/libs/LadestationRegler.php +++ b/libs/LadestationRegler.php @@ -28,8 +28,9 @@ final class LadestationRegler ]; /** - * Die Schwelle, die Voll-Erkennung und der Zuschlag von 2.5 A stammen - * aus der Enelix-1-Ladestation und bleiben bewusst kompatibel. + * Die Schwelle und der Zuschlag von 2.5 A stammen aus der Enelix-1-Ladestation. + * Ein Ladeende wird erst nach zuvor gemessener Ladeleistung angenommen, damit + * die Anlaufzeit nach einer neuen Stromvorgabe nicht als volles Fahrzeug gilt. * * @param array{Rohstatus: int, Istleistung_W: float} $rohstatus * @return array @@ -38,7 +39,8 @@ final class LadestationRegler array $rohstatus, bool $warVerbunden, bool $warGeladen = false, - bool $ladungErwartet = true + bool $ladungErwartet = true, + bool $ladeleistungWurdeErkannt = false ): array { $verbunden = $rohstatus['Rohstatus'] !== 1; $leistung = max(0.0, $rohstatus['Istleistung_W']); @@ -52,7 +54,10 @@ final class LadestationRegler $ladestrom = $leistung / $nenner; $ermittelterMaximalstrom = 2.5 + $ladestrom; $geladen = $warGeladen - || ($warVerbunden && $ladungErwartet && $ermittelterMaximalstrom < 6.0); + || ($warVerbunden + && $ladungErwartet + && $ladeleistungWurdeErkannt + && $ermittelterMaximalstrom < 6.0); } if (!$verbunden) { diff --git a/tests/LadestationReglerTest.php b/tests/LadestationReglerTest.php index c210246..19c8335 100644 --- a/tests/LadestationReglerTest.php +++ b/tests/LadestationReglerTest.php @@ -33,12 +33,23 @@ final class LadestationReglerTest extends TestCase self::assertEqualsWithDelta(10.0, $status['Ladestrom_A'], 0.01); } - public function testGeladenWirdNachVerbindungUnterSechsAmpereErkannt(): void + public function testAnlaufOhneGemesseneLadeleistungGiltNichtAlsLadeende(): void { $status = LadestationRegler::normalisiereStatus([ 'Rohstatus' => 2, 'Istleistung_W' => 0.0, - ], true); + ], true, false, true, false); + + self::assertFalse($status['FahrzeugGeladen']); + self::assertSame(2, $status['Fahrzeugstatus']); + } + + public function testGeladenWirdErstNachZuvorGemessenerLadeleistungErkannt(): void + { + $status = LadestationRegler::normalisiereStatus([ + 'Rohstatus' => 2, + 'Istleistung_W' => 0.0, + ], true, false, true, true); self::assertTrue($status['FahrzeugGeladen']); self::assertSame(4, $status['Fahrzeugstatus']); diff --git a/tests/Symcon/modules/LadestationStandAlone.php b/tests/Symcon/modules/LadestationStandAlone.php index c5aed63..dacba81 100644 --- a/tests/Symcon/modules/LadestationStandAlone.php +++ b/tests/Symcon/modules/LadestationStandAlone.php @@ -228,4 +228,34 @@ return static function (TestContext $test): void { 'alw=0' ), 'Peak mit Solarladen muss die Ladestation stoppen.'); }); + + $test->runCase('Sollwert bleibt waehrend der Ladeanlaufzeit gueltig', static function ( + TestContext $test + ) use ($goEAntwort, $stationen, $anforderungSenden): void { + $stationId = $stationen['go-e Gemini']; + IPS_SetProperty($stationId, 'Testantwort', $goEAntwort(2, 0)); + IPS_ApplyChanges($stationId); + IPS_RequestAction($stationId, 'Aktiv', true); + IPS_RequestAction($stationId, 'Solarladen', true); + + $anforderungSenden($stationId, 3850); + IPS_RequestAction($stationId, 'Statuszyklus', false); + + $test->assertSame( + false, + GetValue($test->objectByIdent('FahrzeugGeladen', $stationId)) + ); + $test->assertSame( + true, + GetValue($test->objectByIdent('SollwertGueltig', $stationId)) + ); + $test->assertSame( + 3850, + GetValue($test->objectByIdent('Sollleistung', $stationId)) + ); + $test->assertSame( + '[0,1450,1690,1930,2170,2410,2650,2890,3130,3370,3610,3850]', + (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId)) + ); + }); };