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
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
+22 -130
View File
@@ -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] = [
$this->SendDebug(
"DynamicCurrent",
json_encode([
"serialNumber" => $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)
),
"minutes" => 0,
"command" => "send",
]),
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"),
]),
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,
]);
-1
View File
@@ -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)) {