no message

This commit is contained in:
dh
2026-08-06 11:10:38 +02:00
parent 97ce906c91
commit 87ebda1e4f
3 changed files with 23 additions and 136 deletions
+4 -8
View File
@@ -11,14 +11,10 @@ Easee-Konto wird eine weitere Gateway-Instanz benötigt.
Stromvorgaben werden von den Ladestationsinstanzen an das Gateway übergeben und Stromvorgaben werden von den Ladestationsinstanzen an das Gateway übergeben und
von dort über die Easee-REST-API gesendet. von dort über die Easee-REST-API gesendet.
Bei 0 A setzt das Gateway das dynamische Stromlimit auf 0. Positive Werte werden Jeder Aufruf der Ladestationsinstanz wird sofort als
direkt als neues dynamisches Stromlimit gesendet. Positive Vorgaben unter 6 A `set_dynamic_charger_current` an Easee weitergegeben. Wie in der ursprünglichen
werden auf den technischen Mindestladestrom von 6 A angehoben. Version wird dabei `minutes: 0` gesendet. Das Gateway puffert, begrenzt oder
wiederholt die Stromvorgabe nicht selbst.
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.
Der benötigte WebSocket-Client wird nach dem Speichern der Easee-Zugangsdaten Der benötigte WebSocket-Client wird nach dem Speichern der Easee-Zugangsdaten
automatisch erstellt und konfiguriert. Im WebSocket-Client muss keine URL automatisch erstellt und konfiguriert. Im WebSocket-Client muss keine URL
+19 -127
View File
@@ -50,7 +50,6 @@ class EaseeGateway extends IPSModule
$this->SetBuffer("Subscriptions", "{}"); $this->SetBuffer("Subscriptions", "{}");
$this->SetBuffer("SubscribedThisConnection", "{}"); $this->SetBuffer("SubscribedThisConnection", "{}");
$this->SetBuffer("PendingInvocations", "{}"); $this->SetBuffer("PendingInvocations", "{}");
$this->SetBuffer("CurrentCommandStates", "{}");
$this->SetValue("SubscriptionCount", 0); $this->SetValue("SubscriptionCount", 0);
$this->SetConnected(false); $this->SetConnected(false);
@@ -211,8 +210,7 @@ class EaseeGateway extends IPSModule
return json_encode( return json_encode(
$this->SetDynamicChargerCurrent( $this->SetDynamicChargerCurrent(
$serialNumber, $serialNumber,
(float)($request["amps"] ?? -1), (float)($request["amps"] ?? -1)
max(1, (int)($request["pollIntervalSeconds"] ?? 5))
) )
); );
@@ -501,7 +499,7 @@ class EaseeGateway extends IPSModule
if ( if (
$serialNumber !== "" && $serialNumber !== "" &&
$valueExists && $valueExists &&
($observationID === 48 || $observationID === 109 || $observationID === 120) ($observationID === 109 || $observationID === 120)
) { ) {
$this->PublishObservation($serialNumber, $observationID, $value); $this->PublishObservation($serialNumber, $observationID, $value);
} }
@@ -545,11 +543,7 @@ class EaseeGateway extends IPSModule
]); ]);
} }
private function SetDynamicChargerCurrent( private function SetDynamicChargerCurrent(string $serialNumber, float $amps): array
string $serialNumber,
float $amps,
int $pollIntervalSeconds
): array
{ {
if ($serialNumber === "") { if ($serialNumber === "") {
return ["success" => false, "error" => "Seriennummer fehlt"]; 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"]; 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( $this->SendDebug(
"DynamicCurrent", "DynamicCurrent",
json_encode([ json_encode([
"serialNumber" => $serialNumber, "serialNumber" => $serialNumber,
"requestedAmps" => $requestedAmps, "amps" => $amps,
"effectiveAmps" => $amps, "minutes" => 0,
"observedAmps" => $observedAmps, "command" => "send",
"confirmed" => $confirmed,
"validityMinutes" => $timing["validityMinutes"],
"refreshAfterSeconds" => $timing["refreshAfterSeconds"],
"command" => $desiredChanged
? "changed"
: ($refreshDue ? "refresh" : "retry"),
]), ]),
0 0
); );
$states[$serialNumber]["lastAttempt"] = $now; return $this->AuthorizedApiRequest(
$result = $this->AuthorizedApiRequest(
"POST", "POST",
"/api/chargers/" . rawurlencode($serialNumber) . "/api/chargers/" . rawurlencode($serialNumber) .
"/commands/set_dynamic_charger_current", "/commands/set_dynamic_charger_current",
json_encode([ json_encode([
"amps" => (int)round($amps), "amps" => $amps,
"minutes" => $timing["validityMinutes"], "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 private function AuthorizedApiRequest(string $method, string $path, string $body): array
@@ -697,7 +584,8 @@ class EaseeGateway extends IPSModule
$method, $method,
self::API_BASE_URL . $path, self::API_BASE_URL . $path,
$body, $body,
$this->GetBuffer("AccessToken") $this->GetBuffer("AccessToken"),
"application/*+json"
); );
if ($response["httpCode"] === 401 && $this->RefreshCredentials(false)) { if ($response["httpCode"] === 401 && $this->RefreshCredentials(false)) {
@@ -705,7 +593,8 @@ class EaseeGateway extends IPSModule
$method, $method,
self::API_BASE_URL . $path, self::API_BASE_URL . $path,
$body, $body,
$this->GetBuffer("AccessToken") $this->GetBuffer("AccessToken"),
"application/*+json"
); );
} }
@@ -802,11 +691,12 @@ class EaseeGateway extends IPSModule
string $method, string $method,
string $url, string $url,
string $body = "", string $body = "",
string $bearerToken = "" string $bearerToken = "",
string $contentType = "application/json"
): array { ): array {
$headers = [ $headers = [
"Accept: application/json", "Accept: application/json",
"Content-Type: application/json", "Content-Type: " . $contentType,
]; ];
if ($bearerToken !== "") { if ($bearerToken !== "") {
$headers[] = "Authorization: Bearer " . $bearerToken; $headers[] = "Authorization: Bearer " . $bearerToken;
@@ -816,9 +706,11 @@ class EaseeGateway extends IPSModule
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method, CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_ENCODING => "",
CURLOPT_TIMEOUT => 30, CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true, CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5, CURLOPT_MAXREDIRS => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_HTTPHEADER => $headers, CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $body, CURLOPT_POSTFIELDS => $body,
]); ]);
-1
View File
@@ -574,7 +574,6 @@ class Ladestation_v2 extends IPSModule
"action" => "SetDynamicChargerCurrent", "action" => "SetDynamicChargerCurrent",
"serialNumber" => strtoupper(trim($this->ReadPropertyString("Seriennummer"))), "serialNumber" => strtoupper(trim($this->ReadPropertyString("Seriennummer"))),
"amps" => $value, "amps" => $value,
"pollIntervalSeconds" => max(1, $this->ReadPropertyInteger("Interval")),
]); ]);
if (!is_array($response) || !($response["success"] ?? false)) { if (!is_array($response) || !($response["success"] ?? false)) {