no message

This commit is contained in:
dh
2026-08-06 10:59:08 +02:00
parent d37fa96a31
commit 97ce906c91
3 changed files with 123 additions and 5 deletions
+5
View File
@@ -15,6 +15,11 @@ 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.
Der benötigte WebSocket-Client wird nach dem Speichern der Easee-Zugangsdaten
automatisch erstellt und konfiguriert. Im WebSocket-Client muss keine URL
eingetragen werden.
+117 -5
View File
@@ -50,6 +50,7 @@ class EaseeGateway extends IPSModule
$this->SetBuffer("Subscriptions", "{}");
$this->SetBuffer("SubscribedThisConnection", "{}");
$this->SetBuffer("PendingInvocations", "{}");
$this->SetBuffer("CurrentCommandStates", "{}");
$this->SetValue("SubscriptionCount", 0);
$this->SetConnected(false);
@@ -210,7 +211,8 @@ class EaseeGateway extends IPSModule
return json_encode(
$this->SetDynamicChargerCurrent(
$serialNumber,
(float)($request["amps"] ?? -1)
(float)($request["amps"] ?? -1),
max(1, (int)($request["pollIntervalSeconds"] ?? 5))
)
);
@@ -499,7 +501,7 @@ class EaseeGateway extends IPSModule
if (
$serialNumber !== "" &&
$valueExists &&
($observationID === 109 || $observationID === 120)
($observationID === 48 || $observationID === 109 || $observationID === 120)
) {
$this->PublishObservation($serialNumber, $observationID, $value);
}
@@ -543,7 +545,11 @@ class EaseeGateway extends IPSModule
]);
}
private function SetDynamicChargerCurrent(string $serialNumber, float $amps): array
private function SetDynamicChargerCurrent(
string $serialNumber,
float $amps,
int $pollIntervalSeconds
): array
{
if ($serialNumber === "") {
return ["success" => false, "error" => "Seriennummer fehlt"];
@@ -557,22 +563,128 @@ class EaseeGateway extends IPSModule
$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"),
]),
0
);
return $this->AuthorizedApiRequest(
$states[$serialNumber]["lastAttempt"] = $now;
$result = $this->AuthorizedApiRequest(
"POST",
"/api/chargers/" . rawurlencode($serialNumber) .
"/commands/set_dynamic_charger_current",
json_encode(["amps" => $amps, "minutes" => 0])
json_encode([
"amps" => (int)round($amps),
"minutes" => $timing["validityMinutes"],
])
);
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
+1
View File
@@ -574,6 +574,7 @@ 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)) {