no message

This commit is contained in:
dh
2026-08-06 08:27:18 +02:00
parent 313c42d925
commit 650ea80e2b
8 changed files with 1086 additions and 290 deletions
+12
View File
@@ -0,0 +1,12 @@
# Easee SignalR Gateway
Das Gateway hält eine gemeinsame SignalR-Verbindung zum Easee-Konto und verteilt
die Observations 109 (Charger Op Mode) und 120 (Total Power) an alle verbundenen
Ladestation_v2-Instanzen.
Benutzername und Passwort werden ausschließlich im Gateway eingetragen. Mehrere
Easee-Ladestationen desselben Kontos verwenden dasselbe Gateway. Für ein weiteres
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.
+52
View File
@@ -0,0 +1,52 @@
{
"elements": [
{
"type": "CheckBox",
"name": "Active",
"caption": "Gateway aktiv"
},
{
"type": "ValidationTextBox",
"name": "Username",
"caption": "Easee Benutzername"
},
{
"type": "PasswordTextBox",
"name": "Password",
"caption": "Easee Passwort"
},
{
"type": "CheckBox",
"name": "VerifyCertificate",
"caption": "TLS-Zertifikat prüfen"
},
{
"type": "Label",
"caption": "Ein Gateway wird von allen Easee-Ladestationen desselben Kontos gemeinsam verwendet."
}
],
"actions": [
{
"type": "Button",
"caption": "Verbindung neu aufbauen",
"onClick": "IPS_RequestAction($id, \"Reconnect\", 0);"
}
],
"status": [
{
"code": 201,
"icon": "error",
"caption": "Easee-Zugangsdaten fehlen"
},
{
"code": 202,
"icon": "error",
"caption": "Easee-Anmeldung fehlgeschlagen"
},
{
"code": 203,
"icon": "error",
"caption": "SignalR-Verbindung fehlgeschlagen"
}
]
}
+21
View File
@@ -0,0 +1,21 @@
{
"id": "{7A1F1D4B-3E7D-4A3B-9E54-61C8C3B7F201}",
"name": "EaseeGateway",
"type": 2,
"vendor": "Belevo AG",
"aliases": [
"Easee SignalR Gateway"
],
"parentRequirements": [
"{79827379-F36E-4ADA-8A95-5F8D1DC92FA9}"
],
"childRequirements": [
"{A8EF2E37-6B35-4E92-A7D1-4F8172790BA2}"
],
"implemented": [
"{018EF6B5-AB94-40C6-AA53-46943E824ACF}",
"{BC4E9B83-9C9B-44C3-A902-5CEB45B0E5B1}"
],
"prefix": "EASEEGW",
"url": ""
}
+717
View File
@@ -0,0 +1,717 @@
<?php
class EaseeGateway extends IPSModule
{
private const WEBSOCKET_MODULE_ID = "{D68FD31F-0E90-7019-F16C-1949BD3079EF}";
private const SIMPLE_RX_DATA_ID = "{018EF6B5-AB94-40C6-AA53-46943E824ACF}";
private const SIMPLE_TX_DATA_ID = "{79827379-F36E-4ADA-8A95-5F8D1DC92FA9}";
private const CHILD_REQUEST_DATA_ID = "{BC4E9B83-9C9B-44C3-A902-5CEB45B0E5B1}";
private const CHILD_EVENT_DATA_ID = "{A8EF2E37-6B35-4E92-A7D1-4F8172790BA2}";
private const SIGNALR_BASE_URL = "https://streams.easee.com/hubs/chargers";
private const API_BASE_URL = "https://api.easee.com";
private const RECORD_SEPARATOR = "\x1e";
public function Create()
{
parent::Create();
$this->RegisterPropertyBoolean("Active", true);
$this->RegisterPropertyString("Username", "");
$this->RegisterPropertyString("Password", "");
$this->RegisterPropertyBoolean("VerifyCertificate", true);
$this->RegisterAttributeString("ObservationCache", "{}");
$this->RegisterVariableBoolean("Connected", "SignalR verbunden", "~Switch", false);
$this->RegisterVariableInteger("SubscriptionCount", "Abonnierte Ladestationen", "", 0);
$this->RegisterVariableString("LastError", "Letzter Fehler", "", 0);
$this->RegisterTimer(
"MaintainConnectionTimer",
0,
'IPS_RequestAction(' . $this->InstanceID . ', "MaintainConnection", 0);'
);
$this->RegisterTimer(
"TokenRefreshTimer",
0,
'IPS_RequestAction(' . $this->InstanceID . ', "RefreshToken", 0);'
);
$this->RequireParent(self::WEBSOCKET_MODULE_ID);
}
public function ApplyChanges()
{
parent::ApplyChanges();
$this->SetTimerInterval("MaintainConnectionTimer", 0);
$this->SetTimerInterval("TokenRefreshTimer", 0);
$this->SetBuffer("SignalRReady", "0");
$this->SetBuffer("ReceiveBuffer", "");
$this->SetBuffer("Subscriptions", "{}");
$this->SetBuffer("SubscribedThisConnection", "{}");
$this->SetBuffer("PendingInvocations", "{}");
$this->SetValue("SubscriptionCount", 0);
$this->SetConnected(false);
if (!$this->ReadPropertyBoolean("Active")) {
$this->SetStatus(104);
return;
}
if (
trim($this->ReadPropertyString("Username")) === "" ||
$this->ReadPropertyString("Password") === ""
) {
$this->SetStatus(201);
$this->SetLastError("Easee-Benutzername oder Passwort fehlt");
return;
}
$this->SetTimerInterval("MaintainConnectionTimer", 10000);
$this->SetTimerInterval("TokenRefreshTimer", 1800000);
if (!$this->RefreshCredentials(false)) {
$this->SetStatus(202);
return;
}
$this->ConnectSignalR();
}
public function RequestAction($Ident, $Value)
{
switch ($Ident) {
case "MaintainConnection":
$this->MaintainConnection();
break;
case "RefreshToken":
if ($this->RefreshCredentials(false)) {
$this->ConnectSignalR();
}
break;
case "Reconnect":
$this->SetBuffer("AccessToken", "");
$this->SetBuffer("RefreshToken", "");
if ($this->RefreshCredentials(false)) {
$this->ConnectSignalR();
}
break;
default:
throw new Exception("Unbekannte Aktion");
}
}
public function GetConfigurationForParent()
{
return json_encode([
"Active" => $this->ReadPropertyBoolean("Active"),
"URL" => $this->GetBuffer("WebSocketURL"),
"VerifyCertificate" => $this->ReadPropertyBoolean("VerifyCertificate"),
"Headers" => "[]",
]);
}
public function ReceiveData($JSONString)
{
$packet = json_decode($JSONString, true);
if (!is_array($packet) || !isset($packet["Buffer"])) {
return;
}
$this->SetBuffer("LastReceive", (string)time());
$buffer = $this->GetBuffer("ReceiveBuffer") . $packet["Buffer"];
$frames = explode(self::RECORD_SEPARATOR, $buffer);
$this->SetBuffer("ReceiveBuffer", array_pop($frames));
foreach ($frames as $frame) {
if ($frame !== "") {
$this->HandleSignalRFrame($frame);
}
}
}
public function ForwardData($JSONString)
{
$packet = json_decode($JSONString, true);
if (!is_array($packet) || !isset($packet["Buffer"])) {
return json_encode(["success" => false, "error" => "Ungültiges Datenpaket"]);
}
$request = json_decode($packet["Buffer"], true);
if (!is_array($request) || !isset($request["action"])) {
return json_encode(["success" => false, "error" => "Ungültige Gateway-Anfrage"]);
}
$serialNumber = strtoupper(trim((string)($request["serialNumber"] ?? "")));
switch ($request["action"]) {
case "Subscribe":
if ($serialNumber === "") {
return json_encode(["success" => false, "error" => "Seriennummer fehlt"]);
}
$this->RegisterSubscription($serialNumber);
return $this->CreateStateResponse($serialNumber);
case "GetState":
if ($serialNumber === "") {
return json_encode(["success" => false, "error" => "Seriennummer fehlt"]);
}
$this->RegisterSubscription($serialNumber);
return $this->CreateStateResponse($serialNumber);
case "SetCurrent":
return json_encode(
$this->SetDynamicChargerCurrent(
$serialNumber,
(float)($request["amps"] ?? -1)
)
);
default:
return json_encode(["success" => false, "error" => "Unbekannte Gateway-Aktion"]);
}
}
private function MaintainConnection()
{
if (!$this->ReadPropertyBoolean("Active")) {
return;
}
$now = time();
$lastNegotiation = (int)$this->GetBuffer("LastNegotiation");
$lastReceive = (int)$this->GetBuffer("LastReceive");
$ready = $this->GetBuffer("SignalRReady") === "1";
if ($ready) {
$this->SendSignalRFrame(["type" => 6]);
if ($lastReceive > 0 && ($now - $lastReceive) <= 90) {
return;
}
$this->SetConnected(false);
$this->SetBuffer("SignalRReady", "0");
}
if (($now - $lastNegotiation) >= 45) {
$this->ConnectSignalR();
return;
}
$this->SendHandshake();
}
private function ConnectSignalR()
{
if (!$this->EnsureAccessToken()) {
$this->SetStatus(202);
return false;
}
$this->SetBuffer("LastNegotiation", (string)time());
$token = $this->GetBuffer("AccessToken");
$response = $this->HttpRequest(
"POST",
self::SIGNALR_BASE_URL . "/negotiate?negotiateVersion=1",
"",
$token
);
if ($response["httpCode"] === 401 && $this->RefreshCredentials(false)) {
$token = $this->GetBuffer("AccessToken");
$response = $this->HttpRequest(
"POST",
self::SIGNALR_BASE_URL . "/negotiate?negotiateVersion=1",
"",
$token
);
}
if (!$response["success"]) {
$this->SetStatus(203);
$this->SetLastError(
"SignalR-Aushandlung fehlgeschlagen: HTTP " . $response["httpCode"] .
($response["error"] !== "" ? " / " . $response["error"] : "")
);
return false;
}
$data = json_decode($response["body"], true);
$connectionToken = is_array($data)
? ($data["connectionToken"] ?? $data["connectionId"] ?? "")
: "";
if ($connectionToken === "") {
$this->SetStatus(203);
$this->SetLastError("SignalR-Antwort enthält kein Verbindungstoken");
return false;
}
$webSocketURL =
"wss://streams.easee.com/hubs/chargers?id=" . rawurlencode($connectionToken) .
"&access_token=" . rawurlencode($token);
$this->SetBuffer("WebSocketURL", $webSocketURL);
$this->SetBuffer("SignalRReady", "0");
$this->SetBuffer("ReceiveBuffer", "");
$this->SetBuffer("SubscribedThisConnection", "{}");
$this->SetBuffer("PendingInvocations", "{}");
$this->SetConnected(false);
if (!$this->ConfigureWebSocketParent($webSocketURL)) {
$this->SetStatus(203);
return false;
}
$this->SendHandshake();
return true;
}
private function ConfigureWebSocketParent(string $url): bool
{
$instance = IPS_GetInstance($this->InstanceID);
$parentID = (int)$instance["ConnectionID"];
if ($parentID <= 0) {
$this->SetLastError("WebSocket-Client ist nicht verbunden");
return false;
}
IPS_SetProperty($parentID, "Active", true);
IPS_SetProperty($parentID, "URL", $url);
IPS_SetProperty($parentID, "VerifyCertificate", $this->ReadPropertyBoolean("VerifyCertificate"));
IPS_SetProperty($parentID, "Headers", "[]");
IPS_ApplyChanges($parentID);
return true;
}
private function SendHandshake()
{
$this->SendRawToWebSocket(
json_encode(["protocol" => "json", "version" => 1]) . self::RECORD_SEPARATOR
);
}
private function SendSignalRFrame(array $frame)
{
$this->SendRawToWebSocket(json_encode($frame) . self::RECORD_SEPARATOR);
}
private function SendRawToWebSocket(string $payload)
{
return $this->SendDataToParent(json_encode([
"DataID" => self::SIMPLE_TX_DATA_ID,
"Buffer" => $payload,
]));
}
private function HandleSignalRFrame(string $frame)
{
if ($frame === "{}") {
$this->SetBuffer("SignalRReady", "1");
$this->SetStatus(102);
$this->SetLastError("");
$this->SetConnected(true);
$this->SendAllSubscriptions();
return;
}
$message = json_decode($frame, true);
if (!is_array($message)) {
$this->SetLastError("Ungültige SignalR-Nachricht");
return;
}
if (isset($message["error"])) {
$this->SetStatus(203);
$this->SetLastError("SignalR-Handshake: " . $message["error"]);
return;
}
$type = (int)($message["type"] ?? 0);
if ($type === 6) {
return;
}
if ($type === 7) {
$this->SetBuffer("SignalRReady", "0");
$this->SetConnected(false);
$this->SetLastError("SignalR-Verbindung wurde beendet");
return;
}
$serialNumber = "";
if ($type === 3 && isset($message["invocationId"])) {
$pending = $this->ReadBufferArray("PendingInvocations");
$invocationID = (string)$message["invocationId"];
$serialNumber = (string)($pending[$invocationID] ?? "");
unset($pending[$invocationID]);
$this->SetBuffer("PendingInvocations", json_encode($pending));
}
if (isset($message["arguments"])) {
$this->ExtractObservations($message["arguments"], $serialNumber);
}
if (isset($message["result"])) {
$this->ExtractObservations($message["result"], $serialNumber);
}
}
private function RegisterSubscription(string $serialNumber)
{
$subscriptions = $this->ReadBufferArray("Subscriptions");
if (!isset($subscriptions[$serialNumber])) {
$subscriptions[$serialNumber] = true;
$this->SetBuffer("Subscriptions", json_encode($subscriptions));
$this->SetValue("SubscriptionCount", count($subscriptions));
}
if ($this->GetBuffer("SignalRReady") === "1") {
$this->SendSubscription($serialNumber);
}
}
private function SendAllSubscriptions()
{
foreach (array_keys($this->ReadBufferArray("Subscriptions")) as $serialNumber) {
$this->SendSubscription($serialNumber);
}
}
private function SendSubscription(string $serialNumber)
{
$sent = $this->ReadBufferArray("SubscribedThisConnection");
if (isset($sent[$serialNumber])) {
return;
}
$invocationID = (string)(((int)$this->GetBuffer("InvocationID")) + 1);
$this->SetBuffer("InvocationID", $invocationID);
$pending = $this->ReadBufferArray("PendingInvocations");
$pending[$invocationID] = $serialNumber;
$this->SetBuffer("PendingInvocations", json_encode($pending));
$this->SendSignalRFrame([
"type" => 1,
"invocationId" => $invocationID,
"target" => "SubscribeWithCurrentState",
"arguments" => [$serialNumber, true],
]);
$sent[$serialNumber] = true;
$this->SetBuffer("SubscribedThisConnection", json_encode($sent));
}
private function ExtractObservations($node, string $serialNumber = "", int $depth = 0)
{
if (!is_array($node) || $depth > 12) {
return;
}
foreach (["serialNumber", "SerialNumber", "chargerId", "ChargerId", "mid", "Mid"] as $key) {
if (isset($node[$key]) && is_scalar($node[$key])) {
$candidate = strtoupper((string)$node[$key]);
if ($this->IsSubscribedSerial($candidate)) {
$serialNumber = $candidate;
break;
}
}
}
if ($this->IsList($node)) {
foreach ($node as $item) {
if (is_string($item)) {
$candidate = strtoupper($item);
if ($this->IsSubscribedSerial($candidate)) {
$serialNumber = $candidate;
}
}
}
}
$observationID = null;
foreach (["id", "Id", "observationId", "ObservationId"] as $key) {
if (array_key_exists($key, $node) && is_numeric($node[$key])) {
$observationID = (int)$node[$key];
break;
}
}
$valueExists = false;
$value = null;
foreach (["value", "Value"] as $key) {
if (array_key_exists($key, $node)) {
$valueExists = true;
$value = $node[$key];
break;
}
}
if (
$serialNumber !== "" &&
$valueExists &&
($observationID === 109 || $observationID === 120)
) {
$this->PublishObservation($serialNumber, $observationID, $value);
}
foreach ($node as $item) {
if (is_array($item)) {
$this->ExtractObservations($item, $serialNumber, $depth + 1);
}
}
}
private function PublishObservation(string $serialNumber, int $observationID, $value)
{
$cache = $this->ReadAttributeArray("ObservationCache");
if (!isset($cache[$serialNumber]) || !is_array($cache[$serialNumber])) {
$cache[$serialNumber] = [];
}
$cache[$serialNumber][(string)$observationID] = $value;
$cache[$serialNumber]["updated"] = time();
$this->WriteAttributeString("ObservationCache", json_encode($cache));
$this->SendDataToChildren(json_encode([
"DataID" => self::CHILD_EVENT_DATA_ID,
"Buffer" => json_encode([
"type" => "Observation",
"serialNumber" => $serialNumber,
"id" => $observationID,
"value" => $value,
"timestamp" => time(),
]),
]));
}
private function CreateStateResponse(string $serialNumber): string
{
$cache = $this->ReadAttributeArray("ObservationCache");
return json_encode([
"success" => true,
"connected" => $this->GetBuffer("SignalRReady") === "1",
"state" => $cache[$serialNumber] ?? [],
]);
}
private function SetDynamicChargerCurrent(string $serialNumber, float $amps): array
{
if ($serialNumber === "") {
return ["success" => false, "error" => "Seriennummer fehlt"];
}
if ($amps < 0 || $amps > 32) {
return ["success" => false, "error" => "Strom muss zwischen 0 und 32 A liegen"];
}
return $this->AuthorizedApiRequest(
"POST",
"/api/chargers/" . rawurlencode($serialNumber) .
"/commands/set_dynamic_charger_current",
json_encode(["amps" => $amps, "minutes" => 0])
);
}
private function AuthorizedApiRequest(string $method, string $path, string $body): array
{
if (!$this->EnsureAccessToken()) {
return ["success" => false, "error" => "Kein Easee-Access-Token"];
}
$response = $this->HttpRequest(
$method,
self::API_BASE_URL . $path,
$body,
$this->GetBuffer("AccessToken")
);
if ($response["httpCode"] === 401 && $this->RefreshCredentials(false)) {
$response = $this->HttpRequest(
$method,
self::API_BASE_URL . $path,
$body,
$this->GetBuffer("AccessToken")
);
}
if (!$response["success"]) {
$error = $response["error"];
if ($error === "") {
$error = "Easee-HTTP-Fehler " . $response["httpCode"];
}
return [
"success" => false,
"error" => $error,
"httpCode" => $response["httpCode"],
];
}
return [
"success" => true,
"httpCode" => $response["httpCode"],
"body" => $response["body"],
];
}
public function RefreshCredentials(bool $reconnect = true): bool
{
$tokens = null;
$accessToken = $this->GetBuffer("AccessToken");
$refreshToken = $this->GetBuffer("RefreshToken");
if ($accessToken !== "" && $refreshToken !== "") {
$tokens = $this->RequestTokenPair(
self::API_BASE_URL . "/api/accounts/refresh_token",
[
"accessToken" => $accessToken,
"refreshToken" => $refreshToken,
],
$accessToken
);
}
if ($tokens === null) {
$tokens = $this->RequestTokenPair(
self::API_BASE_URL . "/api/accounts/login",
[
"userName" => $this->ReadPropertyString("Username"),
"password" => $this->ReadPropertyString("Password"),
]
);
}
if ($tokens === null || !isset($tokens["accessToken"])) {
$this->SetLastError("Easee-Anmeldung oder Token-Erneuerung fehlgeschlagen");
$this->SetStatus(202);
return false;
}
$this->SetBuffer("AccessToken", (string)$tokens["accessToken"]);
if (isset($tokens["refreshToken"])) {
$this->SetBuffer("RefreshToken", (string)$tokens["refreshToken"]);
}
$this->SetBuffer("TokenUpdated", (string)time());
if ($reconnect) {
$this->ConnectSignalR();
}
return true;
}
private function EnsureAccessToken(): bool
{
if ($this->GetBuffer("AccessToken") !== "") {
return true;
}
return $this->RefreshCredentials(false);
}
private function RequestTokenPair(string $url, array $payload, string $bearerToken = "")
{
$response = $this->HttpRequest(
"POST",
$url,
json_encode($payload),
$bearerToken
);
if (!$response["success"]) {
return null;
}
$data = json_decode($response["body"], true);
return is_array($data) ? $data : null;
}
private function HttpRequest(
string $method,
string $url,
string $body = "",
string $bearerToken = ""
): array {
$headers = [
"Accept: application/json",
"Content-Type: application/json",
];
if ($bearerToken !== "") {
$headers[] = "Authorization: Bearer " . $bearerToken;
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $body,
]);
$responseBody = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
"success" => $responseBody !== false && $curlError === "" &&
$httpCode >= 200 && $httpCode < 300,
"body" => $responseBody === false ? "" : $responseBody,
"error" => $curlError,
"httpCode" => $httpCode,
];
}
private function SetConnected(bool $connected)
{
$this->SetValue("Connected", $connected);
$this->SendDataToChildren(json_encode([
"DataID" => self::CHILD_EVENT_DATA_ID,
"Buffer" => json_encode([
"type" => "GatewayStatus",
"connected" => $connected,
]),
]));
}
private function SetLastError(string $message)
{
$this->SetValue("LastError", $message);
if ($message !== "") {
$this->SendDebug("EaseeGateway", $message, 0);
}
}
private function IsSubscribedSerial(string $candidate): bool
{
return isset($this->ReadBufferArray("Subscriptions")[$candidate]);
}
private function IsList(array $value): bool
{
$index = 0;
foreach ($value as $key => $_) {
if ($key !== $index++) {
return false;
}
}
return true;
}
private function ReadAttributeArray(string $name): array
{
$value = json_decode($this->ReadAttributeString($name), true);
return is_array($value) ? $value : [];
}
private function ReadBufferArray(string $name): array
{
$value = json_decode($this->GetBuffer($name), true);
return is_array($value) ? $value : [];
}
}
+21 -54
View File
@@ -1,67 +1,34 @@
# Manager_1 # Ladestation_v2
Beschreibung des Moduls.
### Inhaltsverzeichnis Das Modul steuert verschiedene Ladestationstypen.
1. [Funktionsumfang](#1-funktionsumfang) ## Easee
2. [Voraussetzungen](#2-voraussetzungen)
3. [Software-Installation](#3-software-installation)
4. [Einrichten der Instanzen in IP-Symcon](#4-einrichten-der-instanzen-in-ip-symcon)
5. [Statusvariablen und Profile](#5-statusvariablen-und-profile)
6. [WebFront](#6-webfront)
7. [PHP-Befehlsreferenz](#7-php-befehlsreferenz)
### 1. Funktionsumfang Die Ladestationstypen 5 und 6 verwenden das übergeordnete
`Easee SignalR Gateway`.
* - Typ 5: Easee mit zusätzlicher eCarUp-Benutzerprüfung für Solarladen
- Typ 6: Easee ohne eCarUp-Benutzerprüfung
### 2. Voraussetzungen Die Easee-Zugangsdaten werden ausschließlich im Gateway eingetragen. In der
Ladestationsinstanz muss die Easee-Seriennummer hinterlegt sein.
- IP-Symcon ab Version 7.1 Das Gateway liefert:
### 3. Software-Installation - Observation 109: Fahrzeug-/Ladestatus
- Observation 120: aktuelle Gesamtleistung in kW
* Über den Module Store das 'Manager_1'-Modul installieren. Stromvorgaben von 0 bis 32 A werden ebenfalls über das Gateway gesendet.
* Alternativ über das Module Control folgende URL hinzufügen
### 4. Einrichten der Instanzen in IP-Symcon ## Andere Ladestationstypen
Unter 'Instanz hinzufügen' kann das 'Manager_1'-Modul mithilfe des Schnellfilters gefunden werden. Die Typen 1 bis 4 arbeiten unverändert und benötigen kein Easee-Gateway.
- Weitere Informationen zum Hinzufügen von Instanzen in der [Dokumentation der Instanzen](https://www.symcon.de/service/dokumentation/konzepte/instanzen/#Instanz_hinzufügen)
__Konfigurationsseite__: ## Einrichtung
Name | Beschreibung 1. Eine Instanz `Easee SignalR Gateway` anlegen.
-------- | ------------------ 2. Easee-Benutzername und Passwort im Gateway eintragen.
| 3. In `Ladestation_v2` Typ 5 oder 6 sowie die Easee-Seriennummer wählen.
| 4. Die Ladestationsinstanz mit dem gewünschten Gateway verbinden.
### 5. Statusvariablen und Profile Mehrere Easee-Ladestationen desselben Kontos verwenden gemeinsam ein Gateway.
Die Statusvariablen/Kategorien werden automatisch angelegt. Das Löschen einzelner kann zu Fehlfunktionen führen.
#### Statusvariablen
Name | Typ | Beschreibung
------ | ------- | ------------
| |
| |
#### Profile
Name | Typ
------ | -------
|
|
### 6. WebFront
Die Funktionalität, die das Modul im WebFront bietet.
### 7. PHP-Befehlsreferenz
`boolean GEF_BeispielFunktion(integer $InstanzID);`
Erklärung der Funktion.
Beispiel:
`GEF_BeispielFunktion(12345);`
+18 -24
View File
@@ -10,7 +10,7 @@
"value": 1 "value": 1
}, },
{ {
"caption": "Go-E Germini / Germini Flex", "caption": "Go-E Gemini / Gemini Flex",
"value": 2 "value": 2
}, },
{ {
@@ -34,7 +34,7 @@
{ {
"type": "NumberSpinner", "type": "NumberSpinner",
"name": "IdleCounterMax", "name": "IdleCounterMax",
"caption": "Zyklen zwischen zwei Leistungsänderungen (Multipliziert sich mit Interval)", "caption": "Zyklen zwischen zwei Leistungsänderungen (multipliziert mit Intervall)",
"suffix": "" "suffix": ""
}, },
{ {
@@ -52,7 +52,7 @@
{ {
"type": "NumberSpinner", "type": "NumberSpinner",
"name": "Zeit_Zwischen_Zustandswechseln", "name": "Zeit_Zwischen_Zustandswechseln",
"caption": "(Veraltet, auf 0 lassen, wird in nächster Version entfehrnt) Mindestlaufzeit des Verbrauchers bei Lastschaltung ", "caption": "(Veraltet, auf 0 lassen) Mindestlaufzeit des Verbrauchers bei Lastschaltung",
"suffix": "" "suffix": ""
}, },
{ {
@@ -71,42 +71,36 @@
"type": "ValidationTextBox", "type": "ValidationTextBox",
"name": "IP_Adresse", "name": "IP_Adresse",
"caption": "IP-Adresse Ladestation" "caption": "IP-Adresse Ladestation"
}, },
{ {
"type": "ValidationTextBox", "type": "ValidationTextBox",
"caption": "Geräte-ID Smart-Me / ECarUp / Easee", "name": "ID",
"name": "ID" "caption": "Geräte-ID Smart-Me / eCarUp / Easee"
}, },
{ {
"type": "ValidationTextBox", "type": "ValidationTextBox",
"caption": "Seriennummer Smart-Me / ECarUp / Easee", "name": "Seriennummer",
"name": "Seriennummer" "caption": "Seriennummer Smart-Me / eCarUp / Easee"
}, },
{ {
"type": "ValidationTextBox", "type": "ValidationTextBox",
"caption": "Username / Benutzernahme Solarladen", "name": "Username",
"name": "Username" "caption": "Benutzername Smart-Me / Benutzerkennung Solarladen"
},
{ "type": "PasswordTextBox",
"caption": "Password -> Bei Anwendung mit Pico-Stationen",
"name": "Password"
}, },
{ {
"type": "SelectVariable", "type": "PasswordTextBox",
"name": "Token_Easee", "name": "Password",
"caption": "Variable API-Token für Easee", "caption": "Passwort Smart-Me"
"suffix": ""
}, },
{ {
"type": "SelectVariable", "type": "SelectVariable",
"name": "Token_ECarUp", "name": "Token_ECarUp",
"caption": "Variable API-Token für ECarUp", "caption": "Variable API-Token für eCarUp",
"suffix": "" "suffix": ""
},
{
"type": "Label",
"caption": "Die Typen 5 und 6 verwenden das verbundene Easee SignalR Gateway. Easee-Zugangsdaten werden im Gateway konfiguriert."
} }
] ]
} }
+7 -3
View File
@@ -4,9 +4,13 @@
"type": 3, "type": 3,
"vendor": "Belevo AG", "vendor": "Belevo AG",
"aliases": [], "aliases": [],
"parentRequirements": [], "parentRequirements": [
"{BC4E9B83-9C9B-44C3-A902-5CEB45B0E5B1}"
],
"childRequirements": [], "childRequirements": [],
"implemented": [], "implemented": [
"{A8EF2E37-6B35-4E92-A7D1-4F8172790BA2}"
],
"prefix": "GEF", "prefix": "GEF",
"url": "" "url": ""
} }
+238 -209
View File
@@ -2,6 +2,10 @@
class Ladestation_v2 extends IPSModule class Ladestation_v2 extends IPSModule
{ {
private const EASEE_GATEWAY_MODULE_ID = "{7A1F1D4B-3E7D-4A3B-9E54-61C8C3B7F201}";
private const EASEE_GATEWAY_REQUEST_ID = "{BC4E9B83-9C9B-44C3-A902-5CEB45B0E5B1}";
private const EASEE_GATEWAY_EVENT_ID = "{A8EF2E37-6B35-4E92-A7D1-4F8172790BA2}";
public function Create() public function Create()
{ {
@@ -19,7 +23,6 @@ class Ladestation_v2 extends IPSModule
$this->RegisterPropertyInteger("Zeit_Zwischen_Zustandswechseln", 1); $this->RegisterPropertyInteger("Zeit_Zwischen_Zustandswechseln", 1);
$this->RegisterPropertyInteger("Ein_Zeit", 0); // Recheninterval $this->RegisterPropertyInteger("Ein_Zeit", 0); // Recheninterval
$this->RegisterPropertyInteger("Aus_Zeit", 0); // Recheninterval $this->RegisterPropertyInteger("Aus_Zeit", 0); // Recheninterval
$this->RegisterPropertyInteger("Token_Easee", 0); // Recheninterval
$this->RegisterPropertyInteger("Token_ECarUp", 0); // Recheninterval $this->RegisterPropertyInteger("Token_ECarUp", 0); // Recheninterval
@@ -87,8 +90,14 @@ class Ladestation_v2 extends IPSModule
$this->RegisterVariableInteger("Leistung_Delta", "Leistung_Delta", "", 0); $this->RegisterVariableInteger("Leistung_Delta", "Leistung_Delta", "", 0);
IPS_SetHidden($this->GetIDForIdent("Leistung_Delta"), true); IPS_SetHidden($this->GetIDForIdent("Leistung_Delta"), true);
$this->RegisterVariableString("Token_Intern", "Internes Token Easee"); $this->RegisterVariableBoolean(
IPS_SetHidden($this->GetIDForIdent("Token_Intern"), true); "Easee_Gateway_Connected",
"Easee Gateway verbunden",
"~Switch",
false
);
IPS_SetHidden($this->GetIDForIdent("Easee_Gateway_Connected"), true);
$this->SetBuffer("EaseeGatewayState", "{}");
// Hilfsvariabeln für Idle zustand // Hilfsvariabeln für Idle zustand
$this->RegisterPropertyInteger("IdleCounterMax", 2); $this->RegisterPropertyInteger("IdleCounterMax", 2);
@@ -100,8 +109,6 @@ class Ladestation_v2 extends IPSModule
$this->SetValue("Idle", true); $this->SetValue("Idle", true);
$this->RegisterTimer("Timer_Do_UserCalc_EVC",$this->ReadPropertyInteger("Interval")*1000,"IPS_RequestAction(" .$this->InstanceID .', "Do_UserCalc", "");'); $this->RegisterTimer("Timer_Do_UserCalc_EVC",$this->ReadPropertyInteger("Interval")*1000,"IPS_RequestAction(" .$this->InstanceID .', "Do_UserCalc", "");');
$this->RegisterTimer("Timer_Refresh_Token",0,"IPS_RequestAction(" .$this->InstanceID .', "Refresh_Token", "");');
$this->RegisterVariableInteger("Mindestaldestrom", "Mindestaldestrom", "", 0); $this->RegisterVariableInteger("Mindestaldestrom", "Mindestaldestrom", "", 0);
$this->EnableAction("Mindestaldestrom"); $this->EnableAction("Mindestaldestrom");
@@ -111,15 +118,30 @@ class Ladestation_v2 extends IPSModule
{ {
parent::ApplyChanges(); parent::ApplyChanges();
$this->SetTimerInterval("Timer_Do_UserCalc_EVC",$this->ReadPropertyInteger("Interval")*1000); $this->SetTimerInterval("Timer_Do_UserCalc_EVC",$this->ReadPropertyInteger("Interval")*1000);
$stationType = $this->ReadPropertyInteger("Ladestation");
$usesEaseeGateway = $stationType === 5 || $stationType === 6;
IPS_SetHidden(
$this->GetIDForIdent("Easee_Gateway_Connected"),
!$usesEaseeGateway
);
// erstelle einen Timer der das Request token aktualisiert wenn die station dies braucht. $instance = IPS_GetInstance($this->InstanceID);
if($this->ReadPropertyInteger("Ladestation")==6){ $parentID = (int)$instance["ConnectionID"];
$this->Refresh_Token();
$this->SetTimerInterval("Timer_Refresh_Token",1800000);
if ($usesEaseeGateway) {
if ($parentID <= 0) {
$this->ConnectParent(self::EASEE_GATEWAY_MODULE_ID);
}
$this->SubscribeToEaseeGateway();
} elseif ($parentID > 0) {
$parent = IPS_GetInstance($parentID);
if ($parent["ModuleInfo"]["ModuleID"] === self::EASEE_GATEWAY_MODULE_ID) {
IPS_DisconnectInstance($this->InstanceID);
}
$this->SetValue("Easee_Gateway_Connected", false);
} }
// erstelle einen Timer der das Request token aktualisiert wenn die station dies braucht.
// Zusätzliche Anpassungen nach Bedarf // Zusätzliche Anpassungen nach Bedarf
} }
@@ -175,10 +197,6 @@ class Ladestation_v2 extends IPSModule
$this->ResetNullTimer(); $this->ResetNullTimer();
break; break;
case "Refresh_Token":
$this->Refresh_Token();
break;
case "Mindestaldestrom": case "Mindestaldestrom":
$this->SetValue("Mindestaldestrom", (int)$Value); $this->SetValue("Mindestaldestrom", (int)$Value);
break; break;
@@ -188,6 +206,191 @@ class Ladestation_v2 extends IPSModule
} }
} }
public function ReceiveData($JSONString)
{
$packet = json_decode($JSONString, true);
if (!is_array($packet) || !isset($packet["Buffer"])) {
return;
}
$message = json_decode($packet["Buffer"], true);
if (!is_array($message) || !isset($message["type"])) {
return;
}
if ($message["type"] === "GatewayStatus") {
$this->SetValue(
"Easee_Gateway_Connected",
(bool)($message["connected"] ?? false)
);
return;
}
if (
$message["type"] !== "Observation" ||
strtoupper((string)($message["serialNumber"] ?? "")) !==
strtoupper($this->ReadPropertyString("Seriennummer"))
) {
return;
}
$this->StoreEaseeObservation(
(int)($message["id"] ?? 0),
$message["value"] ?? null,
(int)($message["timestamp"] ?? time())
);
}
private function SubscribeToEaseeGateway()
{
$serialNumber = strtoupper(trim($this->ReadPropertyString("Seriennummer")));
if ($serialNumber === "") {
return;
}
$response = $this->SendEaseeGatewayRequest([
"action" => "Subscribe",
"serialNumber" => $serialNumber,
]);
$this->ApplyEaseeGatewayResponse($response);
}
private function GetEaseeStateFromGateway()
{
$serialNumber = strtoupper(trim($this->ReadPropertyString("Seriennummer")));
if ($serialNumber === "") {
return;
}
$response = $this->SendEaseeGatewayRequest([
"action" => "GetState",
"serialNumber" => $serialNumber,
]);
$this->ApplyEaseeGatewayResponse($response);
}
private function SendEaseeGatewayRequest(array $request)
{
$instance = IPS_GetInstance($this->InstanceID);
if ((int)$instance["ConnectionID"] <= 0) {
$this->SetValue("Easee_Gateway_Connected", false);
return null;
}
$response = $this->SendDataToParent(json_encode([
"DataID" => self::EASEE_GATEWAY_REQUEST_ID,
"Buffer" => json_encode($request),
]));
if (!is_string($response) || $response === "") {
return null;
}
$decoded = json_decode($response, true);
return is_array($decoded) ? $decoded : null;
}
private function ApplyEaseeGatewayResponse($response)
{
if (!is_array($response)) {
$this->SetValue("Easee_Gateway_Connected", false);
return;
}
if (array_key_exists("connected", $response)) {
$this->SetValue(
"Easee_Gateway_Connected",
(bool)$response["connected"]
);
}
if (!isset($response["state"]) || !is_array($response["state"])) {
return;
}
$updated = (int)($response["state"]["updated"] ?? time());
foreach ([109, 120] as $observationID) {
$key = (string)$observationID;
if (array_key_exists($key, $response["state"])) {
$this->StoreEaseeObservation(
$observationID,
$response["state"][$key],
$updated
);
}
}
}
private function StoreEaseeObservation(int $observationID, $value, int $timestamp)
{
if ($observationID !== 109 && $observationID !== 120) {
return;
}
$state = json_decode($this->GetBuffer("EaseeGatewayState"), true);
if (!is_array($state)) {
$state = [];
}
$state[(string)$observationID] = $value;
$state["updated"] = $timestamp;
$this->SetBuffer("EaseeGatewayState", json_encode($state));
if ($observationID === 109) {
$this->SetValue("Fahrzeugstatus", (int)$value);
} elseif ($observationID === 120) {
$this->SetValue("Ladeleistung_Effektiv", round((float)$value * 1000));
}
}
private function GetEaseeCarStatusFromGateway(bool $currentState): bool
{
$this->GetEaseeStateFromGateway();
$state = json_decode($this->GetBuffer("EaseeGatewayState"), true);
if (!is_array($state) || !array_key_exists("109", $state)) {
return $currentState;
}
$chargerOpMode = (int)$state["109"];
if (
$chargerOpMode != 1 &&
($chargerOpMode != 6 || $this->GetValue("Car_detected") == true) &&
$chargerOpMode != 7
) {
$currentState = true;
} else {
$currentState = false;
}
if (array_key_exists("120", $state)) {
$this->SetValue(
"Ladeleistung_Effektiv",
round((float)$state["120"] * 1000)
);
}
$this->SetValue("Fahrzeugstatus", $chargerOpMode);
return $currentState;
}
private function SendEaseeCurrentToGateway(float $value)
{
$response = $this->SendEaseeGatewayRequest([
"action" => "SetCurrent",
"serialNumber" => strtoupper(trim($this->ReadPropertyString("Seriennummer"))),
"amps" => $value,
]);
if (!is_array($response) || !($response["success"] ?? false)) {
$error = is_array($response)
? (string)($response["error"] ?? "Unbekannter Gateway-Fehler")
: "Easee Gateway nicht erreichbar";
IPS_LogMessage("Ladestation_v2", $error);
return $error;
}
return $response["body"] ?? null;
}
public function Detect_Car(int $carType) public function Detect_Car(int $carType)
{ {
@@ -322,10 +525,10 @@ class Ladestation_v2 extends IPSModule
public function Get_Car_Status(int $carType) public function Get_Car_Status(int $carType)
{ {
$ch = curl_init();
$car_on_station = $this->GetValue("Car_detected"); $car_on_station = $this->GetValue("Car_detected");
switch ($carType) { switch ($carType) {
case 1: case 1:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://" . $this->ReadPropertyString("IP_Adresse") . "/mqtt?payload="); curl_setopt($ch, CURLOPT_URL, "http://" . $this->ReadPropertyString("IP_Adresse") . "/mqtt?payload=");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch); $response = curl_exec($ch);
@@ -351,6 +554,7 @@ class Ladestation_v2 extends IPSModule
break; break;
case 2: case 2:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://" . $this->ReadPropertyString("IP_Adresse") . "/api/status"); curl_setopt($ch, CURLOPT_URL, "http://" . $this->ReadPropertyString("IP_Adresse") . "/api/status");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch); $response = curl_exec($ch);
@@ -376,6 +580,7 @@ class Ladestation_v2 extends IPSModule
break; break;
case 3: case 3:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.smart-me.com/pico/charging/" . $this->ReadPropertyString("ID")); curl_setopt($ch, CURLOPT_URL, "https://api.smart-me.com/pico/charging/" . $this->ReadPropertyString("ID"));
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $this->ReadPropertyString("Username") . ":" . $this->ReadPropertyString("Password")); curl_setopt($ch, CURLOPT_USERPWD, $this->ReadPropertyString("Username") . ":" . $this->ReadPropertyString("Password"));
@@ -438,83 +643,12 @@ class Ladestation_v2 extends IPSModule
} }
} }
//Aktueller Zustand Ladestation abfragen (Leistung, auto eingesteckt?) $car_on_station = $this->GetEaseeCarStatusFromGateway($car_on_station);
$ch2 = curl_init();
$url2 = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/state";
curl_setopt_array($ch2, [
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ".GetValue($this->ReadPropertyInteger("Token_Easee"))."",
"content-type: application/*+json"
],
]);
curl_setopt($ch2, CURLOPT_URL, $url2);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
$response_easee = curl_exec($ch2);
curl_close($ch2);
$easee_data = json_decode($response_easee, true);
if (json_last_error() === JSON_ERROR_NONE && isset($easee_data["chargerOpMode"])) {
if ($easee_data["chargerOpMode"] != 1 && (($easee_data["chargerOpMode"] != 6)||($this->GetValue("Car_detected")==true)) && $easee_data["chargerOpMode"] != 7) {
$car_on_station = true;
}
else{
$car_on_station = false;
}
$this->SetValue("Ladeleistung_Effektiv", round($easee_data["totalPower"]*1000));
$this->SetValue("Fahrzeugstatus", $easee_data["chargerOpMode"]);
}
break; break;
case 6: case 6:
$car_on_station = $this->GetEaseeCarStatusFromGateway($car_on_station);
//Aktueller Zustand Ladestation abfragen (Leistung, auto eingesteckt?)
$ch2 = curl_init();
$url2 = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/state";
curl_setopt_array($ch2, [
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ".$this->GetBuffer("Token_Intern")."",
"content-type: application/*+json"
],
]);
curl_setopt($ch2, CURLOPT_URL, $url2);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
$response_easee = curl_exec($ch2);
curl_close($ch2);
$easee_data = json_decode($response_easee, true);
if (json_last_error() === JSON_ERROR_NONE && isset($easee_data["chargerOpMode"])) {
if ($easee_data["chargerOpMode"] != 1 && (($easee_data["chargerOpMode"] != 6)||($this->GetValue("Car_detected")==true)) && $easee_data["chargerOpMode"] != 7) {
$car_on_station = true;
}
else{
$car_on_station = false;
}
$this->SetValue("Ladeleistung_Effektiv", round($easee_data["totalPower"]*1000));
$this->SetValue("Fahrzeugstatus", $easee_data["chargerOpMode"]);
}
break; break;
@@ -706,11 +840,11 @@ class Ladestation_v2 extends IPSModule
} elseif (!$Peak && $solarladen) { } elseif (!$Peak && $solarladen) {
$powerSteps = $this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current"), $this->GetValue("Aktuelle_Leistung"), $this->GetValue("IsTimerActive_Null_Timer")); $powerSteps = $this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current"), $this->GetValue("Aktuelle_Leistung"), $this->GetValue("IsTimerActive_Null_Timer"));
} elseif ($solarladen && $Peak) { } elseif ($solarladen && $Peak) {
if ($is_1_ph) { if ($this->GetValue("Is_1_ph")) {
$powerSteps = [$this->GetValue("Mindestaldestrom") * 230]; $powerSteps = [$this->GetValue("Mindestaldestrom") * 230];
} else { } else {
$powerSteps = [$this->GetValue("Mindestaldestrom") * 400 * 1.71]; $powerSteps = [$this->GetValue("Mindestaldestrom") * 400 * 1.71];
} }
} else { } else {
$powerSteps = $this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current"), $this->GetValue("Aktuelle_Leistung"), $this->GetValue("IsTimerActive_Null_Timer")); $powerSteps = $this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current"), $this->GetValue("Aktuelle_Leistung"), $this->GetValue("IsTimerActive_Null_Timer"));
@@ -758,6 +892,16 @@ class Ladestation_v2 extends IPSModule
{ {
$baseUrl = ""; $baseUrl = "";
$stationType = $this->ReadPropertyInteger("Ladestation"); $stationType = $this->ReadPropertyInteger("Ladestation");
if ($stationType === 5 || $stationType === 6) {
if (
$stationType === 5 &&
$this->ReadPropertyString("Username") != $this->GetValue("Letzer_User")
) {
return;
}
return $this->SendEaseeCurrentToGateway($value);
}
switch ($stationType) { switch ($stationType) {
case 1: case 1:
@@ -772,12 +916,6 @@ class Ladestation_v2 extends IPSModule
case 4: case 4:
// Nichts zu tun für Dummy station // Nichts zu tun für Dummy station
return; return;
case 5:
// Keine base Url nötig
break;
case 6:
// Keine base Url nötig
break;
} }
$ch = curl_init(); $ch = curl_init();
@@ -801,42 +939,6 @@ class Ladestation_v2 extends IPSModule
case 4: case 4:
// Nichts zu tun für Dummy station // Nichts zu tun für Dummy station
return; return;
case 5:
if($this->ReadPropertyString("Username") != $this->GetValue("Letzer_User")){
return;
}
$url = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/commands/set_dynamic_charger_current";
curl_setopt_array($ch, [
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":0}",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ".GetValue($this->ReadPropertyInteger("Token_Easee"))."",
"content-type: application/*+json"
],
]);
break;
case 6:
$url = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/commands/set_dynamic_charger_current";
curl_setopt_array($ch, [
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":0}",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ".$this->GetBuffer("Token_Intern")."",
"content-type: application/*+json"
],
]);
break;
default: default:
return "Invalid station type."; return "Invalid station type.";
} }
@@ -864,42 +966,6 @@ class Ladestation_v2 extends IPSModule
case 4: case 4:
// Nichts zu tun für Dummy station // Nichts zu tun für Dummy station
return; return;
case 5:
if($this->ReadPropertyString("Username") != $this->GetValue("Letzer_User")){
return;
}
$url2 = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/commands/set_dynamic_charger_current";
curl_setopt_array($ch, [
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":0}",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ".GetValue($this->ReadPropertyInteger("Token_Easee"))."",
"content-type: application/*+json"
],
]);
break;
case 6:
$url2 = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/commands/set_dynamic_charger_current";
curl_setopt_array($ch, [
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":0}",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ".$this->GetBuffer("Token_Intern")."",
"content-type: application/*+json"
],
]);
break;
default: default:
return "Invalid station type."; return "Invalid station type.";
} }
@@ -927,43 +993,6 @@ class Ladestation_v2 extends IPSModule
} }
} }
public function Refresh_Token(){
$payload = json_encode([
'userName' => $this->ReadPropertyString("Username"),
'password' => $this->ReadPropertyString("Password"),
]);
// cURL-Handle initialisieren
$ch = curl_init('https://api.easee.com/api/accounts/login');
// cURL-Optionen setzen
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, // Antwort als String zurückgeben
CURLOPT_POST => true, // POST-Methode verwenden
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $payload, // JSON-Body übergeben
]);
// Anfrage ausführen und Antwort speichern
$response = curl_exec($ch);
// Auf Fehler prüfen
if ($response === false) {
echo 'cURL-Fehler: ' . curl_error($ch);
curl_close($ch);
exit;
}
// cURL-Handle schließen
curl_close($ch);
$this->SetBuffer("Token_Intern", (json_decode($response, true)['accessToken']));
}
public function CheckIdle($power) public function CheckIdle($power)
{ {
$lastpower = GetValue($this->GetIDForIdent("Aktuelle_Leistung")); $lastpower = GetValue($this->GetIDForIdent("Aktuelle_Leistung"));