no message
This commit is contained in:
@@ -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
@@ -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,
|
||||
]);
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user