diff --git a/EaseeGateway/README.md b/EaseeGateway/README.md index 9e4e8ef..426ae66 100644 --- a/EaseeGateway/README.md +++ b/EaseeGateway/README.md @@ -11,14 +11,10 @@ Easee-Konto wird eine weitere Gateway-Instanz benötigt. Stromvorgaben werden von den Ladestationsinstanzen an das Gateway übergeben und von dort über die Easee-REST-API gesendet. -Bei 0 A setzt das Gateway das dynamische Stromlimit auf 0. Positive Werte werden -direkt als neues dynamisches Stromlimit gesendet. Positive Vorgaben unter 6 A -werden auf den technischen Mindestladestrom von 6 A angehoben. - -Die Gültigkeitsdauer des dynamischen Stromlimits wird aus dem Berechnungsintervall -der Ladestation abgeleitet. Das Gateway sendet nur bei einer Sollwertänderung, -bei fehlender Bestätigung durch Observation 48 oder rechtzeitig vor Ablauf der -Gültigkeitsdauer erneut. Die Mindestgültigkeit beträgt zwei Minuten. +Jeder Aufruf der Ladestationsinstanz wird sofort als +`set_dynamic_charger_current` an Easee weitergegeben. Wie in der ursprünglichen +Version wird dabei `minutes: 0` gesendet. Das Gateway puffert, begrenzt oder +wiederholt die Stromvorgabe nicht selbst. Der benötigte WebSocket-Client wird nach dem Speichern der Easee-Zugangsdaten automatisch erstellt und konfiguriert. Im WebSocket-Client muss keine URL diff --git a/EaseeGateway/module.php b/EaseeGateway/module.php index d615d93..b425f1b 100644 --- a/EaseeGateway/module.php +++ b/EaseeGateway/module.php @@ -50,7 +50,6 @@ class EaseeGateway extends IPSModule $this->SetBuffer("Subscriptions", "{}"); $this->SetBuffer("SubscribedThisConnection", "{}"); $this->SetBuffer("PendingInvocations", "{}"); - $this->SetBuffer("CurrentCommandStates", "{}"); $this->SetValue("SubscriptionCount", 0); $this->SetConnected(false); @@ -211,8 +210,7 @@ class EaseeGateway extends IPSModule return json_encode( $this->SetDynamicChargerCurrent( $serialNumber, - (float)($request["amps"] ?? -1), - max(1, (int)($request["pollIntervalSeconds"] ?? 5)) + (float)($request["amps"] ?? -1) ) ); @@ -501,7 +499,7 @@ class EaseeGateway extends IPSModule if ( $serialNumber !== "" && $valueExists && - ($observationID === 48 || $observationID === 109 || $observationID === 120) + ($observationID === 109 || $observationID === 120) ) { $this->PublishObservation($serialNumber, $observationID, $value); } @@ -545,11 +543,7 @@ class EaseeGateway extends IPSModule ]); } - private function SetDynamicChargerCurrent( - string $serialNumber, - float $amps, - int $pollIntervalSeconds - ): array + private function SetDynamicChargerCurrent(string $serialNumber, float $amps): array { if ($serialNumber === "") { return ["success" => false, "error" => "Seriennummer fehlt"]; @@ -558,133 +552,26 @@ class EaseeGateway extends IPSModule return ["success" => false, "error" => "Strom muss zwischen 0 und 32 A liegen"]; } - $requestedAmps = $amps; - if ($amps > 0 && $amps < 6) { - $amps = 6; - } - - $timing = $this->CalculateCurrentCommandTiming($pollIntervalSeconds); - $states = $this->ReadBufferArray("CurrentCommandStates"); - $state = isset($states[$serialNumber]) && is_array($states[$serialNumber]) - ? $states[$serialNumber] - : []; - - $now = time(); - $lastAttempt = (int)($state["lastAttempt"] ?? 0); - $lastSuccess = (int)($state["lastSuccess"] ?? 0); - $desiredChanged = - !array_key_exists("amps", $state) || - abs((float)$state["amps"] - $amps) > 0.01 || - (int)($state["validityMinutes"] ?? 0) !== $timing["validityMinutes"]; - - $cache = $this->ReadAttributeArray("ObservationCache"); - $observedAmps = isset($cache[$serialNumber]["48"]) - ? (float)$cache[$serialNumber]["48"] - : null; - $confirmed = - $observedAmps !== null && - abs($observedAmps - $amps) <= 0.1; - - $refreshDue = - $lastSuccess <= 0 || - ($now - $lastSuccess) >= $timing["refreshAfterSeconds"]; - $retryDue = - !$confirmed && - ($lastAttempt <= 0 || ($now - $lastAttempt) >= $timing["retryAfterSeconds"]); - - $states[$serialNumber] = [ - "amps" => $amps, - "validityMinutes" => $timing["validityMinutes"], - "refreshAfterSeconds" => $timing["refreshAfterSeconds"], - "lastAttempt" => $lastAttempt, - "lastSuccess" => $lastSuccess, - ]; - - if (!$desiredChanged && !$refreshDue && !$retryDue) { - $this->SetBuffer("CurrentCommandStates", json_encode($states)); - $this->SendDebug( - "DynamicCurrent", - json_encode([ - "serialNumber" => $serialNumber, - "requestedAmps" => $requestedAmps, - "effectiveAmps" => $amps, - "observedAmps" => $observedAmps, - "confirmed" => $confirmed, - "command" => "skipped", - "refreshInSeconds" => max( - 0, - $timing["refreshAfterSeconds"] - ($now - $lastSuccess) - ), - ]), - 0 - ); - - return [ - "success" => true, - "skipped" => true, - "confirmed" => $confirmed, - "observedAmps" => $observedAmps, - ]; - } - $this->SendDebug( "DynamicCurrent", json_encode([ "serialNumber" => $serialNumber, - "requestedAmps" => $requestedAmps, - "effectiveAmps" => $amps, - "observedAmps" => $observedAmps, - "confirmed" => $confirmed, - "validityMinutes" => $timing["validityMinutes"], - "refreshAfterSeconds" => $timing["refreshAfterSeconds"], - "command" => $desiredChanged - ? "changed" - : ($refreshDue ? "refresh" : "retry"), + "amps" => $amps, + "minutes" => 0, + "command" => "send", ]), 0 ); - $states[$serialNumber]["lastAttempt"] = $now; - $result = $this->AuthorizedApiRequest( + return $this->AuthorizedApiRequest( "POST", "/api/chargers/" . rawurlencode($serialNumber) . "/commands/set_dynamic_charger_current", json_encode([ - "amps" => (int)round($amps), - "minutes" => $timing["validityMinutes"], + "amps" => $amps, + "minutes" => 0, ]) ); - - if ($result["success"] ?? false) { - $states[$serialNumber]["lastSuccess"] = $now; - } - $this->SetBuffer("CurrentCommandStates", json_encode($states)); - - return $result; - } - - private function CalculateCurrentCommandTiming(int $pollIntervalSeconds): array - { - $pollIntervalSeconds = max(1, $pollIntervalSeconds); - - // Mindestens zwei Minuten Gültigkeit. Bei langen Abrufintervallen gilt - // der Sollwert mindestens vier Zyklen lang. - $validitySeconds = max(120, $pollIntervalSeconds * 4); - $validityMinutes = (int)ceil($validitySeconds / 60); - $actualValiditySeconds = $validityMinutes * 60; - - // Mindestens 30 Sekunden bzw. zwei Abrufzyklen Sicherheitsabstand. - $safetyMarginSeconds = max(30, $pollIntervalSeconds * 2); - $refreshAfterSeconds = max( - $pollIntervalSeconds, - $actualValiditySeconds - $safetyMarginSeconds - ); - - return [ - "validityMinutes" => $validityMinutes, - "refreshAfterSeconds" => $refreshAfterSeconds, - "retryAfterSeconds" => max(10, min(30, $pollIntervalSeconds * 2)), - ]; } private function AuthorizedApiRequest(string $method, string $path, string $body): array @@ -697,7 +584,8 @@ class EaseeGateway extends IPSModule $method, self::API_BASE_URL . $path, $body, - $this->GetBuffer("AccessToken") + $this->GetBuffer("AccessToken"), + "application/*+json" ); if ($response["httpCode"] === 401 && $this->RefreshCredentials(false)) { @@ -705,7 +593,8 @@ class EaseeGateway extends IPSModule $method, self::API_BASE_URL . $path, $body, - $this->GetBuffer("AccessToken") + $this->GetBuffer("AccessToken"), + "application/*+json" ); } @@ -802,11 +691,12 @@ class EaseeGateway extends IPSModule string $method, string $url, string $body = "", - string $bearerToken = "" + string $bearerToken = "", + string $contentType = "application/json" ): array { $headers = [ "Accept: application/json", - "Content-Type: application/json", + "Content-Type: " . $contentType, ]; if ($bearerToken !== "") { $headers[] = "Authorization: Bearer " . $bearerToken; @@ -816,9 +706,11 @@ class EaseeGateway extends IPSModule curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_ENCODING => "", CURLOPT_TIMEOUT => 30, CURLOPT_FOLLOWLOCATION => true, - CURLOPT_MAXREDIRS => 5, + CURLOPT_MAXREDIRS => 10, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => $body, ]); diff --git a/Ladestation_v2/module.php b/Ladestation_v2/module.php index bab5b2d..e1d02a5 100644 --- a/Ladestation_v2/module.php +++ b/Ladestation_v2/module.php @@ -574,7 +574,6 @@ class Ladestation_v2 extends IPSModule "action" => "SetDynamicChargerCurrent", "serialNumber" => strtoupper(trim($this->ReadPropertyString("Seriennummer"))), "amps" => $value, - "pollIntervalSeconds" => max(1, $this->ReadPropertyInteger("Interval")), ]); if (!is_array($response) || !($response["success"] ?? false)) {