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
|
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
|
||||||
|
|||||||
+22
-130
@@ -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;
|
$this->SendDebug(
|
||||||
if ($amps > 0 && $amps < 6) {
|
"DynamicCurrent",
|
||||||
$amps = 6;
|
json_encode([
|
||||||
}
|
"serialNumber" => $serialNumber,
|
||||||
|
|
||||||
$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,
|
"amps" => $amps,
|
||||||
"validityMinutes" => $timing["validityMinutes"],
|
"minutes" => 0,
|
||||||
"refreshAfterSeconds" => $timing["refreshAfterSeconds"],
|
"command" => "send",
|
||||||
"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
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
return [
|
return $this->AuthorizedApiRequest(
|
||||||
"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(
|
|
||||||
"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,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user