From 6edd688c1fba6e8ad991f6f8b5232e5419ce521c Mon Sep 17 00:00:00 2001 From: DanielHaefliger Date: Tue, 25 Aug 2026 16:42:17 +0200 Subject: [PATCH] no message --- Ladestation/module.php | 1811 ++++++++++++++----------------------- Ladestation_v2/module.php | 1115 +++++++---------------- 2 files changed, 1003 insertions(+), 1923 deletions(-) diff --git a/Ladestation/module.php b/Ladestation/module.php index 167027e..09549c8 100644 --- a/Ladestation/module.php +++ b/Ladestation/module.php @@ -1,5 +1,4 @@ RegisterPropertyString("IP_Adresse", "0.0.0.0"); $this->RegisterPropertyInteger("Ladestation", 2); @@ -23,76 +20,83 @@ class Ladestation extends IPSModule $this->RegisterPropertyInteger("Zeit_Zwischen_Zustandswechseln", 1); $this->RegisterPropertyInteger("Ein_Zeit", 0); // Recheninterval $this->RegisterPropertyInteger("Aus_Zeit", 0); // Recheninterval - // Nur für die fehlerfreie Übernahme bestehender Ladestation-Instanzen. - // Die Easee-Zugangsdaten werden nicht mehr aus dieser Property gelesen. - $this->RegisterPropertyInteger("Token_Easee", 0); - $this->RegisterPropertyInteger("Token_ECarUp", 0); // Recheninterval - $this->RegisterPropertyInteger("EaseeGatewayID", 0); - - + $this->RegisterPropertyInteger("Token_Easee", 0); // Recheninterval + $this->RegisterPropertyInteger("Token_ECarUp", 0); + $this->RegisterPropertyInteger("EaseeGatewayID", 0); // Recheninterval // Ladestationspezifische Variabeln - $this->RegisterVariableFloat("Max_Current","Maximaler Ladestrom","", 0); - IPS_SetHidden($this->GetIDForIdent("Max_Current"), true); + $this->RegisterVariableFloat("Max_Current", "Maximaler Ladestrom", "", 0); + IPS_SetHidden($this->GetIDForIdent("Max_Current") , true); $this->RegisterVariableBoolean("Is_1_ph", "Auto lädt 1-Phasig", "~Switch", false); - $this->RegisterVariableBoolean("Car_detected", "Auto erkannt","~Switch", false); + $this->RegisterVariableBoolean("Car_detected", "Auto erkannt", "~Switch", false); $this->RegisterVariableInteger("Pending_Counter", "Pending_Counter", "", 0); - IPS_SetHidden($this->GetIDForIdent("Pending_Counter"), true); + IPS_SetHidden($this->GetIDForIdent("Pending_Counter") , true); $this->RegisterVariableBoolean("Car_is_full", "Auto fertig geladen", "~Switch", false); $this->RegisterVariableBoolean("Ladebereit", "Ladestation ein", "~Switch", true); $this->EnableAction("Ladebereit"); $this->RegisterVariableBoolean("Solarladen", "Solarladen ein", "~Switch", false); $this->EnableAction("Solarladen"); $this->RegisterVariableInteger("Fahrzeugstatus", "Fahrzeugstatus", "", 0); - IPS_SetHidden($this->GetIDForIdent("Fahrzeugstatus"), true); - + IPS_SetHidden($this->GetIDForIdent("Fahrzeugstatus") , true); $this->RegisterVariableFloat("Ladeleistung_Effektiv", "Aktuelle Ladeleistung", "", 0); $this->RegisterVariableBoolean("Peak", "Peak", "", 0); - IPS_SetHidden($this->GetIDForIdent("Peak"), true); - + IPS_SetHidden($this->GetIDForIdent("Peak") , true); $this->RegisterVariableBoolean("IsTimerActive", "IsTimerActive", "", false); - IPS_SetHidden($this->GetIDForIdent("IsTimerActive"), true); - - $this->RegisterTimer("ZustandswechselTimer",0,"IPS_RequestAction(" .$this->InstanceID .', "ResetTimer", "");'); - + IPS_SetHidden($this->GetIDForIdent("IsTimerActive") , true); + $this->RegisterTimer("ZustandswechselTimer", 0, "IPS_RequestAction(" . $this->InstanceID . ', "ResetTimer", "");'); $this->RegisterVariableBoolean("IsTimerActive_Null_Timer", "IsTimerActive_Null_Timer", "", false); - IPS_SetHidden($this->GetIDForIdent("IsTimerActive_Null_Timer"), true); - - $this->RegisterTimer("Null_Timer",0,"IPS_RequestAction(" .$this->InstanceID .', "ResetNullTimer", "");'); - + IPS_SetHidden($this->GetIDForIdent("IsTimerActive_Null_Timer") , true); + $this->RegisterTimer("Null_Timer", 0, "IPS_RequestAction(" . $this->InstanceID . ', "ResetNullTimer", "");'); $this->RegisterVariableString("Letzer_User", "Letzter registrierter Benutzer"); - IPS_SetHidden($this->GetIDForIdent("Letzer_User"), true); - - - + IPS_SetHidden($this->GetIDForIdent("Letzer_User") , true); // Variabeln für Kommunkation mit Manager $this->RegisterVariableInteger("Sperre_Prio", "Priorität Sperre", "", 0); $this->EnableAction("Sperre_Prio"); - IPS_SetHidden($this->GetIDForIdent("Sperre_Prio"), true); - + IPS_SetHidden($this->GetIDForIdent("Sperre_Prio") , true); $this->RegisterVariableInteger("PV_Prio", "Priorität Solarladen", "", 0); $this->EnableAction("PV_Prio"); - IPS_SetHidden($this->GetIDForIdent("PV_Prio"), true); - + IPS_SetHidden($this->GetIDForIdent("PV_Prio") , true); $this->RegisterVariableBoolean("Idle", "Idle", "", 0); - IPS_SetHidden($this->GetIDForIdent("Idle"), true); - + IPS_SetHidden($this->GetIDForIdent("Idle") , true); $this->RegisterVariableInteger("Aktuelle_Leistung", "Aktuelle_Leistung", "", 0); - IPS_SetHidden($this->GetIDForIdent("Aktuelle_Leistung"), true); - + IPS_SetHidden($this->GetIDForIdent("Aktuelle_Leistung") , true); $this->RegisterVariableFloat("Bezogene_Energie", "Gesamter Energiebezug", "", 0); - IPS_SetHidden($this->GetIDForIdent("Bezogene_Energie"), true); - + IPS_SetHidden($this->GetIDForIdent("Bezogene_Energie") , true); $this->RegisterVariableString("PowerSteps", "Leistungsschritte"); - IPS_SetHidden($this->GetIDForIdent("PowerSteps"), true); - + IPS_SetHidden($this->GetIDForIdent("PowerSteps") , true); $this->RegisterVariableInteger("Power", "Power"); - IPS_SetHidden($this->GetIDForIdent("Power"), true); - + IPS_SetHidden($this->GetIDForIdent("Power") , true); $this->RegisterVariableBoolean("Is_Peak_Shaving", "Is_Peak_Shaving"); - IPS_SetHidden($this->GetIDForIdent("Is_Peak_Shaving"), true); - + IPS_SetHidden($this->GetIDForIdent("Is_Peak_Shaving") , true); $this->RegisterVariableInteger("Leistung_Delta", "Leistung_Delta", "", 0); - IPS_SetHidden($this->GetIDForIdent("Leistung_Delta"), true); + IPS_SetHidden($this->GetIDForIdent("Leistung_Delta") , true); + $this->RegisterVariableBoolean( + "Easee_Gateway_Connected", + "Easee Gateway verbunden", + "~Switch", + false + ); + IPS_SetHidden( + $this->GetIDForIdent("Easee_Gateway_Connected"), + true + ); + $this->SetBuffer("EaseeGatewayState", "{}"); +$this->RegisterVariableString("Token_Intern", "Internes Token Easee"); + IPS_SetHidden($this->GetIDForIdent("Token_Intern") , true); + // Hilfsvariabeln für Idle zustand + $this->RegisterPropertyInteger("IdleCounterMax", 2); + $this->RegisterVariableInteger("IdleCounter", "IdleCounter", "", 0); + IPS_SetHidden($this->GetIDForIdent("IdleCounter") , true); + $this->SetValue("IdleCounter", 0); + // Initialisiere Idle + $this->SetValue("Idle", true); + $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->EnableAction("Mindestaldestrom"); + } + public function ApplyChanges() + { + parent::ApplyChanges(); $this->RegisterVariableBoolean( "Easee_Gateway_Connected", @@ -100,34 +104,36 @@ class Ladestation extends IPSModule "~Switch", false ); - IPS_SetHidden($this->GetIDForIdent("Easee_Gateway_Connected"), true); - $this->SetBuffer("EaseeGatewayState", "{}"); + $this->SetTimerInterval( + "Timer_Do_UserCalc_EVC", + $this->ReadPropertyInteger("Interval") * 1000 + ); + $this->SetTimerInterval("Timer_Refresh_Token", 0); - // Kompatibilität mit bestehenden Instanzen der ursprünglichen Version. - // Der Inhalt wird nicht mehr verwendet; die Easee-Anmeldung übernimmt - // ausschließlich das SignalR-Gateway. - $this->RegisterVariableString("Token_Intern", "Internes Token Easee"); - IPS_SetHidden($this->GetIDForIdent("Token_Intern"), true); - - // Hilfsvariabeln für Idle zustand - $this->RegisterPropertyInteger("IdleCounterMax", 2); - $this->RegisterVariableInteger("IdleCounter", "IdleCounter", "", 0); - IPS_SetHidden($this->GetIDForIdent("IdleCounter"), true); - $this->SetValue("IdleCounter", 0); - - // Initialisiere Idle - $this->SetValue("Idle", true); - - $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->EnableAction("Mindestaldestrom"); + $stationType = $this->ReadPropertyInteger("Ladestation"); + $usesEaseeGateway = $this->UsesEaseeGateway($stationType); + IPS_SetHidden( + $this->GetIDForIdent("Easee_Gateway_Connected"), + !$usesEaseeGateway + ); + if ( + $usesEaseeGateway && + $this->GetConfiguredEaseeGatewayID() > 0 + ) { + $this->SubscribeToEaseeGateway(); + $this->GetEaseeStateFromGateway(); + } else { + $this->SetValue("Easee_Gateway_Connected", false); + } } public function GetConfigurationForm() { - $form = json_decode(file_get_contents(__DIR__ . "/form.json"), true); + $form = json_decode( + file_get_contents(__DIR__ . "/form.json"), + true + ); $fieldConfiguration = $this->GetStationFormConfiguration( $this->ReadPropertyInteger("Ladestation") ); @@ -137,7 +143,6 @@ class Ladestation extends IPSModule if (!isset($fieldConfiguration[$name])) { continue; } - foreach ($fieldConfiguration[$name] as $parameter => $value) { $element[$parameter] = $value; } @@ -147,6 +152,18 @@ class Ladestation extends IPSModule return json_encode($form); } + private function UpdateStationForm(int $stationType) + { + foreach ( + $this->GetStationFormConfiguration($stationType) + as $field => $parameters + ) { + foreach ($parameters as $parameter => $value) { + $this->UpdateFormField($field, $parameter, $value); + } + } + } + private function GetStationFormConfiguration(int $stationType): array { $configuration = [ @@ -227,7 +244,8 @@ class Ladestation extends IPSModule ]; $configuration["Username"] = [ "visible" => true, - "caption" => "eCarUp Benutzerkennung für Solarladen", + "caption" => + "eCarUp Benutzerkennung für Solarladen", ]; $configuration["Token_ECarUp"]["visible"] = true; $configuration["EaseeGatewayID"]["visible"] = true; @@ -247,348 +265,81 @@ class Ladestation extends IPSModule return $configuration; } - private function UpdateStationForm(int $stationType) - { - foreach ($this->GetStationFormConfiguration($stationType) as $field => $parameters) { - foreach ($parameters as $parameter => $value) { - $this->UpdateFormField($field, $parameter, $value); - } - } - } - - public function ApplyChanges() - { - parent::ApplyChanges(); - // Bei aktualisierten Bestandsinstanzen die in Version 1.1 neue Variable - // sofort anlegen; RegisterVariableBoolean ist idempotent. - $this->RegisterVariableBoolean( - "Easee_Gateway_Connected", - "Easee Gateway verbunden", - "~Switch", - false - ); - $this->SetTimerInterval("Timer_Do_UserCalc_EVC",$this->ReadPropertyInteger("Interval")*1000); - // Einen noch vorhandenen Token-Timer aus Altinstanzen sicher stilllegen. - $this->SetTimerInterval("Timer_Refresh_Token", 0); - $stationType = $this->ReadPropertyInteger("Ladestation"); - $usesEaseeGateway = $this->UsesEaseeGateway($stationType); - IPS_SetHidden( - $this->GetIDForIdent("Easee_Gateway_Connected"), - !$usesEaseeGateway - ); - - if (!$usesEaseeGateway || $this->ReadPropertyInteger("EaseeGatewayID") <= 0) { - $this->SetValue("Easee_Gateway_Connected", false); - } - - // erstelle einen Timer der das Request token aktualisiert wenn die station dies braucht. - // Zusätzliche Anpassungen nach Bedarf - } - - public function RequestAction($Ident, $Value) { - switch ($Ident) { - + switch ($Ident) + { case "UpdateStationForm": $this->UpdateStationForm((int)$Value); break; case "SetAktuelle_Leistung": $this->SetValue("Power", (int)$Value); - break; - + break; case "GetCurrentData": $this->SetValue("Is_Peak_Shaving", (bool)$Value); - break; - - case "Do_UserCalc": - - - if($this->Detect_Car($this->ReadPropertyInteger("Ladestation"))){ + break; + case "Do_UserCalc": // Auto und ladestrom erkennen. + if ($this->Detect_Car($this->ReadPropertyInteger("Ladestation"))) + { $this->SetAktuelle_Leistung($this->GetValue("Power")); $this->GetCurrentData($this->GetValue("Is_Peak_Shaving")); } - else{ - SetValue($this->GetIDForIdent("PowerSteps"), json_encode([0])); + else + { + SetValue($this->GetIDForIdent("PowerSteps") , json_encode([0])); $this->SetValue("PowerSteps", json_encode([0])); - - } - break; - + break; case "Sperre_Prio": $this->SetValue("Sperre_Prio", (bool)$Value); - break; - + break; case "PV_Prio": $this->SetValue("PV_Prio", (bool)$Value); - break; - + break; case "Solarladen": $this->SetValue("Solarladen", (bool)$Value); - break; - + break; case "Ladebereit": $this->SetValue("Ladebereit", (bool)$Value); - break; - + break; case "ResetTimer": - $this->ResetTimer(); - break; - + $this->ResetTimer(); + break; case "ResetNullTimer": - $this->ResetNullTimer(); - break; - + $this->ResetNullTimer(); + break; case "Refresh_Token": $this->Refresh_Token(); - break; - + break; case "Mindestaldestrom": - $this->SetValue("Mindestaldestrom", (int)$Value); - break; - + $this->SetValue("Mindestaldestrom", (int)$Value); + break; default: - throw new Exception("Invalid Ident"); + break; } } - - public function ReceiveData($JSONString) + // Erkenne, ob ein auto angeschlossen ist und mit welchem strom es lädt, setzte Statusvariabeln zur Ladestation + public function Detect_Car(int $carType) { - $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) - { - $gatewayID = $this->GetConfiguredEaseeGatewayID(); - if ($gatewayID <= 0 || !function_exists("EASEEGW_ProcessStationRequest")) { - $this->SetValue("Easee_Gateway_Connected", false); - return null; - } - - $response = EASEEGW_ProcessStationRequest( - $gatewayID, - json_encode($request) - ); - - if (!is_string($response) || $response === "") { - return null; - } - - $decoded = json_decode($response, true); - return is_array($decoded) ? $decoded : null; - } - - private function GetConfiguredEaseeGatewayID(): int - { - $gatewayID = $this->ReadPropertyInteger("EaseeGatewayID"); - if ($gatewayID <= 0 || !IPS_InstanceExists($gatewayID)) { - return 0; - } - - $gateway = IPS_GetInstance($gatewayID); - if ($gateway["ModuleInfo"]["ModuleID"] !== self::EASEE_GATEWAY_MODULE_ID) { - return 0; - } - - return $gatewayID; - } - - private function IsEaseeGatewayInstance(int $instanceID): bool - { - if ($instanceID <= 0 || !IPS_InstanceExists($instanceID)) { - return false; - } - - $instance = IPS_GetInstance($instanceID); - return $instance["ModuleInfo"]["ModuleID"] === self::EASEE_GATEWAY_MODULE_ID; - } - - private function UsesEaseeGateway(int $stationType): bool - { - return $stationType === 5 || $stationType === 6; - } - - private function UsesDirectStationDetection(int $stationType): bool - { - // Alle Stationstypen verwenden wieder die gemeinsame, ursprüngliche - // Strom-/Phasenerkennung mit Pending_Counter. - return false; - } - - 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 ([47, 109, 110, 120, 182, 183, 184, 185] 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 (!in_array($observationID, [47, 109, 110, 120, 182, 183, 184, 185], true)) { - return; - } - - $state = json_decode($this->GetBuffer("EaseeGatewayState"), true); - if (!is_array($state)) { - $state = []; - } - - if ($observationID === 109) { - $previousMode = (int)($state["109"] ?? -1); - if ((int)$value === 1 || $previousMode === 1) { - foreach ([110, 182, 183, 184, 185] as $sessionObservationID) { - $state[(string)$sessionObservationID] = 0; + if ($this->GetValue("Car_detected") == true) + { // Wenn schon auto angeschlossen, schaue ob es noch lädt + if ($this->Get_Car_Status($carType) == true) + { + $this->SetValue("Car_detected", true); + if ($this->GetValue("Max_Current") < 6) + { + $this->SetValue("Car_is_full", true); + $this->ResetTimer(); + $this->ResetNullTimer(); } } - } - - $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"]; - $currentState = in_array($chargerOpMode, [2, 3, 4, 6], true); - - if (array_key_exists("120", $state)) { - $this->SetValue( - "Ladeleistung_Effektiv", - round((float)$state["120"] * 1000) - ); - } - $this->SetValue("Fahrzeugstatus", $chargerOpMode); - - return $currentState; - } - - private function DetectEaseeCar(int $carType): bool - { - $wasDetected = $this->GetValue("Car_detected"); - $wasOnePhase = $this->GetValue("Is_1_ph"); - $carDetected = $this->Get_Car_Status($carType); - $state = json_decode($this->GetBuffer("EaseeGatewayState"), true); - if (!is_array($state)) { - $state = []; - } - - $this->SetValue("Pending_Counter", 0); - - $this->DetectEaseePhases($state); - $phaseChanged = $wasOnePhase !== $this->GetValue("Is_1_ph"); - - // Observation 47 ist der nichtflüchtige maximale Ladestrom der - // Station. Die lokal konfigurierte Obergrenze bleibt zusätzlich aktiv. - $maxCurrent = (float)$this->ReadPropertyInteger("Max_Current_abs"); - if (array_key_exists("47", $state) && (float)$state["47"] > 0) { - $maxCurrent = min($maxCurrent, (float)$state["47"]); - } - $this->SetValue("Max_Current", $maxCurrent); - - if (!$carDetected) { - $this->SetValue("Car_detected", false); - $this->SetValue("Car_is_full", false); - - $chargerOpMode = (int)($state["109"] ?? -1); - if ($chargerOpMode === 1) { + else + { + $this->SetValue("Car_detected", false); + $this->SetValue("Pending_Counter", 0); $this->SetValue("Leistung_Delta", 0); + $this->SetValue("Car_is_full", false); $this->ResetTimer(); $this->ResetNullTimer(); $this->SetValue("Is_1_ph", false); @@ -596,602 +347,145 @@ class Ladestation extends IPSModule $this->SetValue("IdleCounter", 0); $this->SetValue("Idle", true); } - - return false; } - - $this->SetValue("Car_detected", true); - $this->SetValue("Car_is_full", (int)($state["109"] ?? 0) === 4); - - if (!$wasDetected || $phaseChanged) { - if ($this->GetValue("Is_1_ph")) { - $power = $this->GetValue("Mindestaldestrom") * 230; - } else { - $power = $this->GetValue("Mindestaldestrom") * 400 * 1.71; - } - $this->SetValue("Power", $power); - } - - return true; - } - - private function DetectEaseePhases(array $state) - { - $outputPhase = (int)($state["110"] ?? 0); - - // Bei Ladepause bzw. 0 W die zuletzt erkannte Fahrzeugphase behalten. - // Die Phasenvariable darf erst durch eine echte Strommessung wechseln. - if ( - array_key_exists("120", $state) && - abs((float)$state["120"]) < 0.1 - ) { - return; - } - - $activePhaseConductors = 0; - foreach ([183, 184, 185] as $observationID) { - if (abs((float)($state[(string)$observationID] ?? 0)) >= 1.0) { - $activePhaseConductors++; - } - } - - if ($activePhaseConductors === 0) { - return; - } - - // Ein explizit einphasiger Easee-Ausgang ist auch in IT-Netzen - // einphasig, obwohl dort Strom auf zwei Leitern gemessen wird. - if ($outputPhase >= 10 && $outputPhase <= 15) { - $this->SetValue("Is_1_ph", true); - return; - } - - // Bei einem dreiphasig freigegebenen Ausgang kann ein einphasiges Auto - // trotzdem nur auf einem Leiter Strom beziehen. Observation 110 allein - // kann diesen Fahrzeugwechsel deshalb nicht erkennen. - if ($activePhaseConductors === 1) { - $this->SetValue("Is_1_ph", true); - } elseif ($activePhaseConductors >= 2) { - $this->SetValue("Is_1_ph", false); - } - } - - private function ReadJsonEndpoint(string $url, bool $useAuthentication = false) - { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - - if ($useAuthentication) { - curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - curl_setopt( - $ch, - CURLOPT_USERPWD, - $this->ReadPropertyString("Username") . ":" . - $this->ReadPropertyString("Password") - ); - } - - $response = curl_exec($ch); - $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - if (!is_string($response) || $response === "" || $httpCode >= 400) { - return null; - } - - $data = json_decode($response, true); - return is_array($data) ? $data : null; - } - - private function DetectOnePhaseFromCurrents(array $currents) - { - $activePhases = 0; - $hasMeasurements = false; - - foreach ($currents as $current) { - if (!is_numeric($current)) { - continue; - } - - $hasMeasurements = true; - if (abs((float)$current) >= 1.0) { - $activePhases++; - } - } - - if (!$hasMeasurements || $activePhases === 0) { - return null; - } - - return $activePhases === 1; - } - - private function ApplyDirectStationDetection( - bool $carDetected, - bool $carIsFull, - $isOnePhase, - float $maxCurrent, - bool $resetPendingCounter = true - ): bool { - $wasDetected = $this->GetValue("Car_detected"); - $wasOnePhase = $this->GetValue("Is_1_ph"); - - if ($resetPendingCounter) { - $this->SetValue("Pending_Counter", 0); - } - $this->SetValue("Max_Current", $maxCurrent); - - if (!$carDetected) { - $this->SetValue("Pending_Counter", 0); - $this->SetValue("Car_detected", false); - $this->SetValue("Car_is_full", false); - $this->SetValue("Leistung_Delta", 0); - $this->ResetTimer(); - $this->ResetNullTimer(); - $this->SetValue("Is_1_ph", false); - $this->SetValue("Aktuelle_Leistung", 0); - $this->SetValue("IdleCounter", 0); - $this->SetValue("Idle", true); - return false; - } - - if (is_bool($isOnePhase)) { - $this->SetValue("Is_1_ph", $isOnePhase); - } - - $this->SetValue("Car_detected", true); - $this->SetValue("Car_is_full", $carIsFull); - - $phaseChanged = $wasOnePhase !== $this->GetValue("Is_1_ph"); - if (!$wasDetected || $phaseChanged) { - if ($this->GetValue("Is_1_ph")) { - $power = $this->GetValue("Mindestaldestrom") * 230; - } else { - $power = $this->GetValue("Mindestaldestrom") * 400 * 1.71; - } - $this->SetValue("Power", $power); - } - - return true; - } - - private function GetGoEMaxCurrent(array $data): float - { - $maxCurrent = (float)$this->ReadPropertyInteger("Max_Current_abs"); - - foreach (["ama", "cbl"] as $key) { - if (isset($data[$key]) && is_numeric($data[$key])) { - $candidate = (float)$data[$key]; - if ($candidate >= 6) { - $maxCurrent = min($maxCurrent, $candidate); - } - } - } - - if (isset($data["var"]) && is_numeric($data["var"])) { - $variant = (int)$data["var"]; - if ($variant === 11) { - $maxCurrent = min($maxCurrent, 16); - } elseif ($variant === 22) { - $maxCurrent = min($maxCurrent, 32); - } - } - - return $maxCurrent; - } - - private function IsGoEChargingEnabled(array $data, int $stationType): bool - { - if ($stationType === 1 && array_key_exists("alw", $data)) { - return (int)$data["alw"] === 1; - } - - if ($stationType === 2 && array_key_exists("frc", $data)) { - return (int)$data["frc"] === 2; - } - - if (array_key_exists("alw", $data)) { - return (int)$data["alw"] === 1; - } - - return $this->GetBuffer("GoEChargingEnabled") === "1"; - } - - private function DetectGoEFullCharge( - array $data, - int $stationType, - int $carState, - $chargingPowerWatts - ): bool { - $wasFull = $this->GetValue("Car_is_full"); - - // Ein einmal sicher erkanntes Ladeende bleibt bis zum Abstecken oder - // bis zu einer tatsächlich wieder aufgenommenen Ladung erhalten. - if ($wasFull) { - if ( - is_numeric($chargingPowerWatts) && - abs((float)$chargingPowerWatts) >= 100 - ) { - $this->SetValue("Pending_Counter", 0); - return false; - } - - return true; - } - - // car=4 alleine ist nicht ausreichend: Go-E meldet diesen Status auch, - // wenn die Regelung die Ladung absichtlich unterbricht. - if ( - $carState !== 4 || - !$this->IsGoEChargingEnabled($data, $stationType) || - !is_numeric($chargingPowerWatts) || - abs((float)$chargingPowerWatts) >= 100 - ) { - $this->SetValue("Pending_Counter", 0); - return false; - } - - $counter = $this->GetValue("Pending_Counter") + 1; - $this->SetValue("Pending_Counter", $counter); - - $interval = max(1, $this->ReadPropertyInteger("Interval")); - $requiredCycles = max(1, (int)ceil(90 / $interval)); - if ($counter < $requiredCycles) { - return false; - } - - $this->SetValue("Pending_Counter", 0); - return true; - } - - private function DetectGoECar(int $stationType): bool - { - $path = $stationType === 1 ? "/mqtt?payload=" : "/api/status"; - $data = $this->ReadJsonEndpoint( - "http://" . $this->ReadPropertyString("IP_Adresse") . $path - ); - - if (!is_array($data) || !isset($data["car"]) || !is_numeric($data["car"])) { - return $this->GetValue("Car_detected"); - } - - $carState = (int)$data["car"]; - if (!in_array($carState, [1, 2, 3, 4, 5], true)) { - return $this->GetValue("Car_detected"); - } - - $this->SetValue("Fahrzeugstatus", $carState); - - $isOnePhase = null; - $chargingPowerWatts = null; - if (isset($data["nrg"]) && is_array($data["nrg"])) { - $currentFactor = $stationType === 1 ? 0.1 : 1.0; - $phaseCurrents = []; - foreach ([4, 5, 6] as $index) { - $phaseCurrents[] = isset($data["nrg"][$index]) && - is_numeric($data["nrg"][$index]) - ? (float)$data["nrg"][$index] * $currentFactor - : null; - } - $isOnePhase = $this->DetectOnePhaseFromCurrents($phaseCurrents); - - if (isset($data["nrg"][11]) && is_numeric($data["nrg"][11])) { - $powerFactor = $stationType === 1 ? 10 : 1; - $chargingPowerWatts = (float)$data["nrg"][11] * $powerFactor; - $this->SetValue( - "Ladeleistung_Effektiv", - round($chargingPowerWatts) - ); - } - } - - $carDetected = in_array($carState, [2, 3, 4, 5], true); - $carIsFull = $carDetected && $this->DetectGoEFullCharge( - $data, - $stationType, - $carState, - $chargingPowerWatts - ); - - return $this->ApplyDirectStationDetection( - $carDetected, - $carIsFull, - $isOnePhase, - $this->GetGoEMaxCurrent($data), - false - ); - } - - private function DetectSmartMeCar(): bool - { - $deviceID = trim($this->ReadPropertyString("ID")); - if ($deviceID === "") { - return $this->GetValue("Car_detected"); - } - - $chargingData = $this->ReadJsonEndpoint( - "https://api.smart-me.com/pico/charging/" . $deviceID, - true - ); - if (!is_array($chargingData)) { - return $this->GetValue("Car_detected"); - } - - $stateValue = $chargingData["State"] ?? $chargingData["state"] ?? null; - if (!is_numeric($stateValue)) { - return $this->GetValue("Car_detected"); - } - - $stationState = (int)$stateValue; - if (!in_array($stationState, [1, 2, 3, 4, 6], true)) { - return $this->GetValue("Car_detected"); - } - - $this->SetValue("Fahrzeugstatus", $stationState); - - $chargingPower = $chargingData["ActiveChargingPower"] ?? - $chargingData["activeChargingPower"] ?? null; - if (is_numeric($chargingPower)) { - $this->SetValue( - "Ladeleistung_Effektiv", - round((float)$chargingPower * 1000) - ); - } - - $isOnePhase = null; - $deviceData = $this->ReadJsonEndpoint( - "https://api.smart-me.com/Devices/" . $deviceID, - true - ); - if (is_array($deviceData)) { - $phaseCurrents = [ - $deviceData["CurrentL1"] ?? $deviceData["currentL1"] ?? null, - $deviceData["CurrentL2"] ?? $deviceData["currentL2"] ?? null, - $deviceData["CurrentL3"] ?? $deviceData["currentL3"] ?? null, - ]; - $isOnePhase = $this->DetectOnePhaseFromCurrents($phaseCurrents); - } - - $maxCurrent = (float)$this->ReadPropertyInteger("Max_Current_abs"); - foreach ([ - "MaxStationCurrent", - "maxStationCurrent", - "MaxLoadmanagementGroupCurrent", - "maxLoadmanagementGroupCurrent", - ] as $key) { - if (isset($chargingData[$key]) && is_numeric($chargingData[$key])) { - $candidate = (float)$chargingData[$key]; - if ($candidate >= 6) { - $maxCurrent = min($maxCurrent, $candidate); - } - } - } - - return $this->ApplyDirectStationDetection( - in_array($stationState, [2, 3, 4, 6], true), - false, - $isOnePhase, - $maxCurrent - ); - } - - private function SendEaseeCurrentToGateway(float $value) - { - $response = $this->SendEaseeGatewayRequest([ - "action" => "SetDynamicChargerCurrent", - "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"; - return $error; - } - - return $response["body"] ?? null; - } - - - public function Detect_Car(int $carType) - { - if($this->GetValue("Car_detected")==true){ - if($this->Get_Car_Status($carType)==true){ - $this->SetValue("Car_detected", true); - if($this->GetValue("Max_Current")<6){ - $this->SetValue("Car_is_full", true); - $this->ResetTimer(); - $this->ResetNullTimer(); - - } - - }else{ - $this->SetValue("Car_detected", false); - $this->SetValue("Pending_Counter", 0); - $this->SetValue("Leistung_Delta", 0); - $this->SetValue("Car_is_full", false); - $this->ResetTimer(); - $this->ResetNullTimer(); - $this->SetValue("Is_1_ph", false); - $this->SetValue("Aktuelle_Leistung", 0); - $this->SetValue("IdleCounter", 0); - $this->SetValue("Idle", true); - } - } - else{ - if($this->Get_Car_Status($carType)==true){ - - $this->sendPowerToStation($this->ReadPropertyInteger("Max_Current_abs")); - - $counter = $this->GetValue("Pending_Counter"); - - if($counter>(60/($this->ReadPropertyInteger("Interval")))){ - + else + { // wenn kein auto angeschlossen schaue ob eines angesteckt wurde + if ($this->Get_Car_Status($carType) == true) + { + $this->sendPowerToStation($this->ReadPropertyInteger("Max_Current_abs")); + $counter = $this->GetValue("Pending_Counter"); + if ($counter > (60 / ($this->ReadPropertyInteger("Interval")))) + { $this->SetValue("Pending_Counter", 0); - if($this->GetValue("Ladeleistung_Effektiv")>7500){ + if ($this->GetValue("Ladeleistung_Effektiv") > 7500) + { $this->SetValue("Is_1_ph", false); - $this->Calc_Max_Current(false); - + $this->Calc_Max_Current(false); } - else{ + else + { $this->SetValue("Is_1_ph", true); - $this->Calc_Max_Current(true); - + $this->Calc_Max_Current(true); } - $this->SetValue("Car_detected", true); - - if ($this->GetValue("Is_1_ph")) { - $power = $this->GetValue("Mindestaldestrom") * 230; - } - else { - $power = $this->GetValue("Mindestaldestrom") * 400 * 1.71; - } - + if ($this->GetValue("Is_1_ph")) + { + $power = $this->GetValue("Mindestaldestrom") * 230; + } + else + { + $power = $this->GetValue("Mindestaldestrom") * 400 * 1.71; + } $this->SetValue("Power", $power); - - } - else{ - $this->SetValue("Pending_Counter", $counter+1); + else + { + $this->SetValue("Pending_Counter", $counter + 1); $this->SetValue("Car_detected", false); - - } - - }else{ + } + } + else + { $this->SetValue("Car_detected", false); $this->SetValue("Leistung_Delta", 0); - $this->sendPowerToStation($this->ReadPropertyInteger("Max_Current_abs")); + $this->sendPowerToStation($this->ReadPropertyInteger("Max_Current_abs")); } } - - return $this->GetValue("Car_detected"); - + return $this->GetValue("Car_detected"); } - - - // Methode zum Setzen der PowerSteps und Timer starten public function SetTimerOn() { - // Timer setzen, der nach "Zeit_Zwischen_Zustandswechseln" abläuft + // Timer setzen, der nach "Zeit_Zwischen_Zustandswechseln" abläuft, verhindert schnelles Takten $zeitZwischenZustandswechseln = $this->ReadPropertyInteger("Zeit_Zwischen_Zustandswechseln"); - - if($zeitZwischenZustandswechseln>0){ + if ($zeitZwischenZustandswechseln > 0) + { $this->SetTimerInterval("ZustandswechselTimer", $zeitZwischenZustandswechseln * 60000); // Timer in Millisekunden // Timer-Status auf true setzen $this->SetValue("IsTimerActive", true); - }else{ + } + else + { $this->SetValue("IsTimerActive", false); - } } - - - // Methode zum Setzen der PowerSteps und Timer starten + // Methode zum Setzen eines mindeststromes beim laden, über einen timer public function SetTimerNullMindestlast($time) { - if($time>0){ + if ($time > 0) + { $this->SetTimerInterval("Null_Timer", $time * 60000); // Timer in Millisekunden // Timer-Status auf true setzen $this->SetValue("IsTimerActive_Null_Timer", true); - }else{ + } + else + { $this->SetValue("IsTimerActive_Null_Timer", false); - } } - - - // Methode zum Zurücksetzen von PowerSteps nach Ablauf des Timers + // Methode zum Zurücksetzen nach Ablauf des Timers public function ResetTimer() { // Timer stoppen $this->SetTimerInterval("ZustandswechselTimer", 0); - // Timer-Status auf false setzen $this->SetValue("IsTimerActive", false); } - - // Methode zum Zurücksetzen von PowerSteps nach Ablauf des Timers + // Methode zum Zurücksetzen nach Ablauf des Timers Mindeststroms public function ResetNullTimer() { // Timer stoppen $this->SetTimerInterval("Null_Timer", 0); - // Timer-Status auf false setzen $this->SetValue("IsTimerActive_Null_Timer", false); } - - public function Refresh_Token() - { - // Kompatibilitätsfunktion für bestehende Instanzen. Das Gateway - // verwaltet die Easee-Anmeldung und benötigt diesen Timer nicht mehr. - $this->SetTimerInterval("Timer_Refresh_Token", 0); - } - - - + // Erkenne Autostatus (ladestrom, status) auf basis des Typs public function Get_Car_Status(int $carType) { - $car_on_station = $this->GetValue("Car_detected"); - switch ($carType) { - case 1: - $ch = curl_init(); + $ch = curl_init(); + $car_on_station = $this->GetValue("Car_detected"); + switch ($carType) + { + case 1: // GO-E alt curl_setopt($ch, CURLOPT_URL, "http://" . $this->ReadPropertyString("IP_Adresse") . "/mqtt?payload="); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); - - if (json_last_error() === JSON_ERROR_NONE && isset($data["car"])) { - /*if ($data["car"] != 1) { - $this->SetValue("Car_detected", true); + if (json_last_error() === JSON_ERROR_NONE && isset($data["car"])) + { + if ($data["car"] != 1) + { + $car_on_station = true; } - else{ - $this->SetValue("Car_detected", false); - }*/ - if ($data["car"] != 1) { - $car_on_station = true; + else + { + $car_on_station = false; } - else{ - $car_on_station = false; - } - $this->SetValue("Ladeleistung_Effektiv", $data["nrg"][11]*10); + $this->SetValue("Ladeleistung_Effektiv", $data["nrg"][11] * 10); $this->SetValue("Fahrzeugstatus", $data["car"]); } - break; - - case 2: - $ch = curl_init(); + break; + case 2: // Go-E Gemini curl_setopt($ch, CURLOPT_URL, "http://" . $this->ReadPropertyString("IP_Adresse") . "/api/status"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); - - if (json_last_error() === JSON_ERROR_NONE && isset($data["car"])) { - /*if ($data["car"] != 1) { - $this->SetValue("Car_detected", true); + if (json_last_error() === JSON_ERROR_NONE && isset($data["car"])) + { + if ($data["car"] != 1) + { + $car_on_station = true; } - else{ - $this->SetValue("Car_detected", false); - }*/ - if ($data["car"] != 1) { - $car_on_station = true; - } - else{ - $car_on_station = false; + else + { + $car_on_station = false; } $this->SetValue("Ladeleistung_Effektiv", $data["nrg"][11]); $this->SetValue("Fahrzeugstatus", $data["car"]); } - break; - - case 3: - $ch = curl_init(); + break; + case 3: // Smart-Me Pico 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_USERPWD, $this->ReadPropertyString("Username") . ":" . $this->ReadPropertyString("Password")); @@ -1199,314 +493,304 @@ class Ladestation extends IPSModule $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); - - if (json_last_error() === JSON_ERROR_NONE && isset($data["State"])) { - /*if ($data["car"] != 1) { - $this->SetValue("Car_detected", true); + if (json_last_error() === JSON_ERROR_NONE && isset($data["State"])) + { + if ($data["State"] != 1) + { + $car_on_station = true; } - else{ - $this->SetValue("Car_detected", false); - }*/ - if ($data["State"] != 1) { - $car_on_station = true; + else + { + $car_on_station = false; } - else{ - $car_on_station = false; - } - $this->SetValue("Ladeleistung_Effektiv", round($data["ActiveChargingPower"]*1000)); + $this->SetValue("Ladeleistung_Effektiv", round($data["ActiveChargingPower"] * 1000)); $this->SetValue("Fahrzeugstatus", $data["State"]); - } - break; - - case 4: - $car_on_station = true; - break; - - case 5: - - // Benutzer eCarUp abfragen - + break; + case 4: // Herstellerunabh. + $car_on_station = true; + break; + case 5: // Solarladen E-Car-Up $authToken = GetValue($this->ReadPropertyInteger("Token_ECarUp")); $ID = $this->ReadPropertyString("ID"); - $apiUrl = 'https://public-api.ecarup.com/v1/station/' . $ID . '/conn'.'ectors/'. $ID . '/active-charging'; - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $apiUrl); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'accept: application/json', - 'Authorization: Bearer ' . $authToken + $apiUrl = "https://public-api.ecarup.com/v1/station/" . $ID . + "/connectors/" . $ID . "/active-charging"; + $eCarUpHandle = curl_init(); + curl_setopt($eCarUpHandle, CURLOPT_URL, $apiUrl); + curl_setopt($eCarUpHandle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($eCarUpHandle, CURLOPT_HTTPHEADER, [ + "accept: application/json", + "Authorization: Bearer " . $authToken, ]); - - $response = curl_exec($ch); - $data = json_decode($response, true); + $eCarUpResponse = curl_exec($eCarUpHandle); + $eCarUpData = json_decode($eCarUpResponse, true); + curl_close($eCarUpHandle); - curl_close($ch); - - if (json_last_error() === JSON_ERROR_NONE && isset($data["driverIdentifier"])) { - - $this->SetValue("Letzer_User", $data["driverIdentifier"]); - - if($this->ReadPropertyString("Username") === $data["driverIdentifier"]){ - $this->SetValue("Solarladen", true); - }else{ - $this->SetValue("Solarladen", false); - } + if ( + json_last_error() === JSON_ERROR_NONE && + isset($eCarUpData["driverIdentifier"]) + ) { + $this->SetValue( + "Letzer_User", + $eCarUpData["driverIdentifier"] + ); + $this->SetValue( + "Solarladen", + $this->ReadPropertyString("Username") === + $eCarUpData["driverIdentifier"] + ); } - $car_on_station = $this->GetEaseeCarStatusFromGateway($car_on_station); - + $car_on_station = + $this->GetEaseeCarStatusFromGateway($car_on_station); break; - case 6: - $car_on_station = $this->GetEaseeCarStatusFromGateway($car_on_station); - + case 6: // Easee + $car_on_station = + $this->GetEaseeCarStatusFromGateway($car_on_station); break; default: $this->SetValue("Car_detected", false); $this->SetValue("Fahrzeugstatus", -1); - break; + break; } - - return $car_on_station; } + // Methode zum Berechnen des Maximalen Stroms den eine Ladestation/Auto ziehen kann. - public function Calc_Max_Current(bool $is_1_ph) + public function Calc_Max_Current(bool $is_1_ph) { - $maxCurrent = 32; - if($is_1_ph){ - - $maxCurrent = 2.5 + ($this->GetValue("Ladeleistung_Effektiv") / 230); + $maxCurrent = 32; + if ($is_1_ph) + { + $maxCurrent = 2.5 + ($this->GetValue("Ladeleistung_Effektiv") / 230); } - else{ - - $maxCurrent = 2.5 + ($this->GetValue("Ladeleistung_Effektiv") / (1.71*400)); + else + { + $maxCurrent = 2.5 + ($this->GetValue("Ladeleistung_Effektiv") / (1.71 * 400)); } - if($maxCurrent>$this->ReadPropertyInteger("Max_Current_abs")){ - $maxCurrent = $this->ReadPropertyInteger("Max_Current_abs"); + if ($maxCurrent > $this->ReadPropertyInteger("Max_Current_abs")) + { + $maxCurrent = $this->ReadPropertyInteger("Max_Current_abs"); } - $this->SetValue("Max_Current", $maxCurrent); + $this->SetValue("Max_Current", $maxCurrent); } - + // Mehtohde zum Umrechnen Leistung/Strom public function Get_Current_From_Power(bool $is_1_ph, int $power) { - if ($is_1_ph) { + if ($is_1_ph) + { $current = round($power / 230); - } else { + } + else + { $current = round($power / (400 * 1.71)); } - return $current; } - + // Methode zum ermitteln aller möglcihen leistungsschritte mit aktuellem Maximalstrom public function Get_Array_From_Current(bool $is_1_ph, float $current, int $power, bool $timer) { $resultArray = []; - - if ($timer) { + if ($timer) + { // Timer an - if ($power === 0) { + if ($power === 0) + { // power == 0: nur eine 0 zurückgeben $resultArray[] = 0; return $resultArray; - - }else{ - // Schleife wie gehabt - for ($i = (max($this->GetValue("Mindestaldestrom") + 1, 6)); $i <= $current; $i++) { - if ($is_1_ph) { - $resultArray[] = $i * 230; - } else { - $resultArray[] = $i * 400 * 1.71; } - } - - } - + else + { + for ($i = (max($this->GetValue("Mindestaldestrom") + 1, 6));$i <= $current;$i++) + { + if ($is_1_ph) + { + $resultArray[] = $i * 230; + } + else + { + $resultArray[] = $i * 400 * 1.71; + } + } + } // power > 0: keine 0 am Anfang, Schleife normal durchlaufen - } else { - // Timer aus: wie bisher, 0 am Anfang - - if ($is_1_ph) { + + } + else + { + if ($is_1_ph) + { $resultArray[] = $this->GetValue("Mindestaldestrom") * 230; - } else { + } + else + { $resultArray[] = $this->GetValue("Mindestaldestrom") * 400 * 1.71; } - - - - // Schleife wie gehabt - for ($i = (max($this->GetValue("Mindestaldestrom") + 1, 6)); $i <= $current; $i++) { - if ($is_1_ph) { - $resultArray[] = $i * 230; - } else { - $resultArray[] = $i * 400 * 1.71; - } - } -} - return $resultArray; - } - - - - - public function SetAktuelle_Leistung(int $power) - { - // Hier eine Leistungsänderung detektieren für Idle und interne Mindestlaufzeiten - $this->CheckIdle($power); - if ($this->GetValue("Aktuelle_Leistung") != $power) { - $this->SetTimerOn(); - } - - if (($this->GetValue("Aktuelle_Leistung") == 0) && ($power>0)) { - $this->SetTimerNullMindestlast($this->ReadPropertyInteger("Ein_Zeit")); - } - elseif(($this->GetValue("Aktuelle_Leistung") > 0) && ($power==0)){ - $this->SetTimerNullMindestlast($this->ReadPropertyInteger("Aus_Zeit")); - - } - - $internalPower = GetValue($this->GetIDForIdent("Aktuelle_Leistung")); - - // Aktuelle Leistungsvorgabe setzen - SetValue($this->GetIDForIdent("Aktuelle_Leistung"), $power); - if($power <= 0){ - $this->SetValue("Pending_Counter", 0); - } - - $this->SetValue("Bezogene_Energie", ($this->GetValue("Bezogene_Energie") + ($this->GetValue("Aktuelle_Leistung")*($this->ReadPropertyInteger("Interval")/3600)))); - - if ($power != $internalPower) { - // Setze die interne Leistungsvorgabe - - // Idle für x Zyklen auf false setzen - SetValue($this->GetIDForIdent("Idle"), false); - SetValue( - $this->GetIDForIdent("IdleCounter"), - $this->ReadPropertyInteger("IdleCounterMax") - ); - } else { - // IdleCallCounter herunterzählen, wenn power == interne Leistungsvorgabe - $idleCounter = GetValue($this->GetIDForIdent("IdleCounter")); - if ($idleCounter > 0) { - $idleCounter--; - $this->SetValue("Pending_Counter", 0); - SetValue($this->GetIDForIdent("IdleCounter"), $idleCounter); - if ($idleCounter == 0) { - SetValue($this->GetIDForIdent("Idle"), true); + // Schleife wie gehabt + for ($i = (max($this->GetValue("Mindestaldestrom") + 1, 6));$i <= $current;$i++) + { + if ($is_1_ph) + { + $resultArray[] = $i * 230; + } + else + { + $resultArray[] = $i * 400 * 1.71; + } + } + } + return $resultArray; + } + public function SetAktuelle_Leistung(int $power) + { + // Hier eine Leistungsänderung detektieren für Idle und interne Mindestlaufzeiten + $this->CheckIdle($power); + if ($this->GetValue("Aktuelle_Leistung") != $power) + { + $this->SetTimerOn(); + } + // Leistung nur ändern wenn aktuell kein Timer aktiv ist + if (($this->GetValue("Aktuelle_Leistung") == 0) && ($power > 0)) + { + $this->SetTimerNullMindestlast($this->ReadPropertyInteger("Ein_Zeit")); + } + elseif (($this->GetValue("Aktuelle_Leistung") > 0) && ($power == 0)) + { + $this->SetTimerNullMindestlast($this->ReadPropertyInteger("Aus_Zeit")); + } + $internalPower = GetValue($this->GetIDForIdent("Aktuelle_Leistung")); + // Aktuelle Leistungsvorgabe setzen + SetValue($this->GetIDForIdent("Aktuelle_Leistung") , $power); + if ($power <= 0) + { + $this->SetValue("Pending_Counter", 0); + } + // Bezogene Energie aufintegrieren + $this->SetValue("Bezogene_Energie", ($this->GetValue("Bezogene_Energie") + ($this->GetValue("Aktuelle_Leistung") * ($this->ReadPropertyInteger("Interval") / 3600)))); + if ($power != $internalPower) + { + // Setze die interne Leistungsvorgabe + // Idle für x Zyklen auf false setzen + SetValue($this->GetIDForIdent("Idle") , false); + SetValue($this->GetIDForIdent("IdleCounter") , $this->ReadPropertyInteger("IdleCounterMax")); + } + else + { + // IdleCallCounter herunterzählen, wenn power == interne Leistungsvorgabe + $idleCounter = GetValue($this->GetIDForIdent("IdleCounter")); + if ($idleCounter > 0) + { + $idleCounter--; + $this->SetValue("Pending_Counter", 0); + SetValue($this->GetIDForIdent("IdleCounter") , $idleCounter); + if ($idleCounter == 0) + { + SetValue($this->GetIDForIdent("Idle") , true); } } } - // Ladeleistung setzen $peak = GetValue($this->GetIDForIdent("Peak")); $solarladen = GetValue($this->GetIDForIdent("Solarladen")); $Ladebereit = GetValue($this->GetIDForIdent("Ladebereit")); - if (!$Ladebereit) { - + if (!$Ladebereit) + { $this->sendPowerToStation(0); - - } elseif (!$peak && !$solarladen) { + } + elseif (!$peak && !$solarladen) + { // Wenn weder Peak noch Solarladen aktiv sind, setze Ladeleistung auf MaxLeistung - $this->sendPowerToStation($this->Get_Current_From_Power($this->GetValue("Is_1_ph"), $power)); - - } else { + $this->sendPowerToStation($this->Get_Current_From_Power($this->GetValue("Is_1_ph") , $power)); + } + else + { // Ansonsten setze Ladeleistung auf die aktuelle Leistungsvorgabe (Aktuelle_Leistung) - $this->sendPowerToStation($this->Get_Current_From_Power($this->GetValue("Is_1_ph"), $power)); + $this->sendPowerToStation($this->Get_Current_From_Power($this->GetValue("Is_1_ph") , $power)); } } - // Methode zum Abrufen der aktuellen Daten public function GetCurrentData(bool $Peak) { - // Überprüfen, ob der Timer aktiv ist - if ($this->GetValue("IsTimerActive")) { + if ($this->GetValue("IsTimerActive")) + { // Timer ist aktiv, PowerSteps setzen - $this->SetValue("PowerSteps", json_encode([$this->GetValue("Aktuelle_Leistung")])); - $this->SetValue("Leistung_Delta", $this->GetValue("Aktuelle_Leistung")-$this->GetValue("Ladeleistung_Effektiv")); - - return; - }else{ + $this->SetValue("PowerSteps", json_encode([$this->GetValue("Aktuelle_Leistung") ])); + $this->SetValue("Leistung_Delta", $this->GetValue("Aktuelle_Leistung") - $this->GetValue("Ladeleistung_Effektiv")); + return; + } + else + { $this->SetValue("Leistung_Delta", 0); } - // Aktuelle Properties abrufen $ladebereit = GetValue($this->GetIDForIdent("Ladebereit")); $solarladen = GetValue($this->GetIDForIdent("Solarladen")); - - // Peak-Wert speichern $this->SetValue("Peak", $Peak); - // Array für die Powersteps initialisieren $powerSteps = []; - - // Konfiguration des powerSteps-Arrays basierend auf den Properties - if (!$ladebereit || $this->GetValue("Car_is_full")) { - $powerSteps = [0]; - } elseif (!$Peak && !$solarladen) { - $powerSteps = [max($this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current"), $this->GetValue("Aktuelle_Leistung"), $this->GetValue("IsTimerActive_Null_Timer")))]; - } 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")); - } elseif ($solarladen && $Peak) { - if ($this->GetValue("Is_1_ph")) { - $powerSteps = [$this->GetValue("Mindestaldestrom") * 230]; - } else { - $powerSteps = [$this->GetValue("Mindestaldestrom") * 400 * 1.71]; - } - - } 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")); + // Konfiguration des powerSteps-Arrays basierend auf den Properties + if (!$ladebereit || $this->GetValue("Car_is_full")) + { + $powerSteps = [0]; + } + elseif (!$Peak && !$solarladen) + { + $powerSteps = [max($this->Get_Array_From_Current($this->GetValue("Is_1_ph") , $this->GetValue("Max_Current") , $this->GetValue("Aktuelle_Leistung") , $this->GetValue("IsTimerActive_Null_Timer"))) ]; + } + 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")); + } + elseif ($solarladen && $Peak) + { + if ($is_1_ph) + { + $powerSteps = [$this->GetValue("Mindestaldestrom") * 230]; } - + else + { + $powerSteps = [$this->GetValue("Mindestaldestrom") * 400 * 1.71]; + } + } + 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 in der RegisterVariable speichern - SetValue($this->GetIDForIdent("PowerSteps"), json_encode($powerSteps)); + SetValue($this->GetIDForIdent("PowerSteps") , json_encode($powerSteps)); // Rückgabe der Powersteps - if (!$this->UsesDirectStationDetection($this->ReadPropertyInteger("Ladestation"))) { - $counter = $this->GetValue("Pending_Counter"); - - if($this->GetValue("Aktuelle_Leistung")>(1.11*$this->GetValue("Ladeleistung_Effektiv"))){ - $this->SetValue("Pending_Counter", $counter+1); - - }elseif(max($this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current"), $this->GetValue("Aktuelle_Leistung"), $this->GetValue("IsTimerActive_Null_Timer")))<(1.11*$this->GetValue("Ladeleistung_Effektiv"))){ // diesen elseif ggf wieder rauslöschen - $this->SetValue("Pending_Counter", $counter+1); - - } - else{ - - $this->SetValue("Pending_Counter", 0); - - } - - if($counter>(90/($this->ReadPropertyInteger("Interval")))){ - - $this->SetValue("Pending_Counter", 0); - - if((max(json_decode($this->GetValue("PowerSteps")))) >0){ - - if($this->GetValue("Aktuelle_Leistung")>(1.11*$this->GetValue("Ladeleistung_Effektiv"))) { - - $this->Calc_Max_Current($this->GetValue("Is_1_ph")); - - - } - - } - - } - } elseif (!in_array( - $this->ReadPropertyInteger("Ladestation"), - [1, 2], - true - )) { + $counter = $this->GetValue("Pending_Counter"); + if ($this->GetValue("Aktuelle_Leistung") > (1.11 * $this->GetValue("Ladeleistung_Effektiv"))) + { + $this->SetValue("Pending_Counter", $counter + 1); + } + elseif (max($this->Get_Array_From_Current($this->GetValue("Is_1_ph") , $this->GetValue("Max_Current") , $this->GetValue("Aktuelle_Leistung") , $this->GetValue("IsTimerActive_Null_Timer"))) < (1.11 * $this->GetValue("Ladeleistung_Effektiv"))) + { // diesen elseif ggf wieder rauslöschen + $this->SetValue("Pending_Counter", $counter + 1); + } + else + { $this->SetValue("Pending_Counter", 0); } - + if ($counter > (90 / ($this->ReadPropertyInteger("Interval")))) + { + $this->SetValue("Pending_Counter", 0); + if ((max(json_decode($this->GetValue("PowerSteps")))) > 0) + { + if ($this->GetValue("Aktuelle_Leistung") > (1.11 * $this->GetValue("Ladeleistung_Effektiv"))) + { + $this->Calc_Max_Current($this->GetValue("Is_1_ph")); + } + } + } return $powerSteps; } - + // public function sendPowerToStation(float $value) { $baseUrl = ""; @@ -1562,9 +846,6 @@ class Ladestation extends IPSModule curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); - if (($stationType === 1 || $stationType === 2) && $response !== false) { - $this->SetBuffer("GoEChargingEnabled", "0"); - } curl_close($ch); return $response; } elseif ($value >= 1 && $value <= 32) { @@ -1606,9 +887,6 @@ class Ladestation extends IPSModule curl_close($ch); return "Error:" . curl_error($ch); } - if ($stationType === 1 || $stationType === 2) { - $this->SetBuffer("GoEChargingEnabled", "1"); - } curl_close($ch); return; } else { @@ -1616,24 +894,247 @@ class Ladestation extends IPSModule } } - public function CheckIdle($power) + public function Refresh_Token() { - $lastpower = GetValue($this->GetIDForIdent("Aktuelle_Leistung")); - if ($lastpower != $power) { - $this->SetValue("Idle", false); - $this->SetValue("IdleCounter",$this->ReadPropertyInteger("IdleCounterMax")); + // Easee-Zugangsdaten und Token werden ausschliesslich + // im SignalR-Gateway verwaltet. + return; + } + + private function UsesEaseeGateway(int $stationType): bool + { + return $stationType === 5 || $stationType === 6; + } + + private function IsEaseeGatewayInstance(int $instanceID): bool + { + if ($instanceID <= 0 || !IPS_InstanceExists($instanceID)) { + return false; } - // IdleCounter auslesen und verarbeiten - $idleCounter = $this->GetValue("IdleCounter"); - if ($idleCounter > 0) { - $this->SetValue("Idle", false); - $this->SetValue("IdleCounter", $idleCounter - 1); - } else { - $this->SetValue("Idle", true); + + $instance = IPS_GetInstance($instanceID); + return + $instance["ModuleInfo"]["ModuleID"] === + self::EASEE_GATEWAY_MODULE_ID; + } + + 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) + { + $gatewayID = $this->GetConfiguredEaseeGatewayID(); + if ( + $gatewayID <= 0 || + !function_exists("EASEEGW_ProcessStationRequest") + ) { + $this->SetValue("Easee_Gateway_Connected", false); + return null; + } + + $response = EASEEGW_ProcessStationRequest( + $gatewayID, + json_encode($request) + ); + if (!is_string($response) || $response === "") { + return null; + } + + $decoded = json_decode($response, true); + return is_array($decoded) ? $decoded : null; + } + + private function GetConfiguredEaseeGatewayID(): int + { + $gatewayID = $this->ReadPropertyInteger("EaseeGatewayID"); + if (!$this->IsEaseeGatewayInstance($gatewayID)) { + return 0; + } + return $gatewayID; + } + + 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 ( + [47, 104, 109, 110, 120, 182, 183, 184, 185] + 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 ( + !in_array( + $observationID, + [47, 104, 109, 110, 120, 182, 183, 184, 185], + true + ) + ) { + return; + } + $state = json_decode( + $this->GetBuffer("EaseeGatewayState"), + true + ); + if (!is_array($state)) { + $state = []; + } + + if ($observationID === 109) { + $previousMode = (int)($state["109"] ?? -1); + if ((int)$value === 1 || $previousMode === 1) { + foreach ( + [110, 182, 183, 184, 185] + as $sessionObservationID + ) { + $state[(string)$sessionObservationID] = 0; + } + } + } + + $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"]; + $currentState = + $chargerOpMode != 1 && + ($chargerOpMode != 6 || $this->GetValue("Car_detected")) && + $chargerOpMode != 7; + + 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" => "SetDynamicChargerCurrent", + "serialNumber" => strtoupper( + trim($this->ReadPropertyString("Seriennummer")) + ), + "amps" => $value, + ]); + + if (!is_array($response) || !($response["success"] ?? false)) { + return is_array($response) + ? (string)( + $response["error"] ?? + "Unbekannter Gateway-Fehler" + ) + : "Easee Gateway nicht erreichbar"; + } + + return $response["body"] ?? null; + } + + public function CheckIdle($power) + { + $lastpower = GetValue($this->GetIDForIdent("Aktuelle_Leistung")); + if ($lastpower != $power) + { + $this->SetValue("Idle", false); + $this->SetValue("IdleCounter", $this->ReadPropertyInteger("IdleCounterMax")); + } + // IdleCounter auslesen und verarbeiten + $idleCounter = $this->GetValue("IdleCounter"); + if ($idleCounter > 0) + { + $this->SetValue("Idle", false); + $this->SetValue("IdleCounter", $idleCounter - 1); + } + else + { + $this->SetValue("Idle", true); + } + } } ?> diff --git a/Ladestation_v2/module.php b/Ladestation_v2/module.php index d0f985e..7de3fed 100644 --- a/Ladestation_v2/module.php +++ b/Ladestation_v2/module.php @@ -23,8 +23,9 @@ class Ladestation_v2 extends IPSModule $this->RegisterPropertyInteger("Zeit_Zwischen_Zustandswechseln", 1); $this->RegisterPropertyInteger("Ein_Zeit", 0); // Recheninterval $this->RegisterPropertyInteger("Aus_Zeit", 0); // Recheninterval - $this->RegisterPropertyInteger("Token_ECarUp", 0); // Recheninterval - $this->RegisterPropertyInteger("EaseeGatewayID", 0); + $this->RegisterPropertyInteger("Token_Easee", 0); // Recheninterval + $this->RegisterPropertyInteger("Token_ECarUp", 0); + $this->RegisterPropertyInteger("EaseeGatewayID", 0); // Recheninterval // Ladestationspezifische Variabeln @@ -91,14 +92,19 @@ class Ladestation_v2 extends IPSModule $this->RegisterVariableInteger("Leistung_Delta", "Leistung_Delta", "", 0); IPS_SetHidden($this->GetIDForIdent("Leistung_Delta"), true); - $this->RegisterVariableBoolean( + $this->RegisterVariableBoolean( "Easee_Gateway_Connected", "Easee Gateway verbunden", "~Switch", false ); - IPS_SetHidden($this->GetIDForIdent("Easee_Gateway_Connected"), true); + IPS_SetHidden( + $this->GetIDForIdent("Easee_Gateway_Connected"), + true + ); $this->SetBuffer("EaseeGatewayState", "{}"); +$this->RegisterVariableString("Token_Intern", "Internes Token Easee"); + IPS_SetHidden($this->GetIDForIdent("Token_Intern"), true); // Hilfsvariabeln für Idle zustand $this->RegisterPropertyInteger("IdleCounterMax", 2); @@ -110,41 +116,59 @@ class Ladestation_v2 extends IPSModule $this->SetValue("Idle", true); $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->EnableAction("Mindestaldestrom"); - // Vorhandenes Gateway verwenden; nur wenn keines existiert, wird eines - // erstellt. ConnectParent() ist auch bei bereits gesetztem Parent sicher. $this->ConnectParent(self::EASEE_GATEWAY_MODULE_ID); - } - public function Migrate($JSONData) + public function ApplyChanges() { - parent::Migrate($JSONData); + parent::ApplyChanges(); - $data = json_decode($JSONData, true); - if (!is_array($data)) { - return ""; + $this->RegisterVariableBoolean( + "Easee_Gateway_Connected", + "Easee Gateway verbunden", + "~Switch", + false + ); + $this->SetTimerInterval( + "Timer_Do_UserCalc_EVC", + $this->ReadPropertyInteger("Interval") * 1000 + ); + $this->SetTimerInterval("Timer_Refresh_Token", 0); + + $stationType = $this->ReadPropertyInteger("Ladestation"); + $usesEaseeGateway = $this->UsesEaseeGateway($stationType); + IPS_SetHidden( + $this->GetIDForIdent("Easee_Gateway_Connected"), + !$usesEaseeGateway + ); + + $instance = IPS_GetInstance($this->InstanceID); + $parentID = (int)$instance["ConnectionID"]; + if ($parentID <= 0) { + $this->ConnectParent(self::EASEE_GATEWAY_MODULE_ID); + $instance = IPS_GetInstance($this->InstanceID); + $parentID = (int)$instance["ConnectionID"]; } - if (!isset($data["configuration"]) || !is_array($data["configuration"])) { - $data["configuration"] = []; + if ($usesEaseeGateway && $this->IsEaseeGatewayInstance($parentID)) { + $this->SubscribeToEaseeGateway(); + $this->GetEaseeStateFromGateway(); + } else { + $this->SetValue("Easee_Gateway_Connected", false); } - - if (array_key_exists("EaseeGatewayID", $data["configuration"])) { - return ""; - } - - // In Migrate keine Instanzfunktionen aufrufen: Die Instanzschnittstelle - // wird zu diesem Zeitpunkt erst aufgebaut. - $data["configuration"]["EaseeGatewayID"] = 0; - return json_encode($data); } public function GetConfigurationForm() { - $form = json_decode(file_get_contents(__DIR__ . "/form.json"), true); + $form = json_decode( + file_get_contents(__DIR__ . "/form.json"), + true + ); $fieldConfiguration = $this->GetStationFormConfiguration( $this->ReadPropertyInteger("Ladestation") ); @@ -154,7 +178,6 @@ class Ladestation_v2 extends IPSModule if (!isset($fieldConfiguration[$name])) { continue; } - foreach ($fieldConfiguration[$name] as $parameter => $value) { $element[$parameter] = $value; } @@ -164,6 +187,18 @@ class Ladestation_v2 extends IPSModule return json_encode($form); } + private function UpdateStationForm(int $stationType) + { + foreach ( + $this->GetStationFormConfiguration($stationType) + as $field => $parameters + ) { + foreach ($parameters as $parameter => $value) { + $this->UpdateFormField($field, $parameter, $value); + } + } + } + private function GetStationFormConfiguration(int $stationType): array { $configuration = [ @@ -241,7 +276,8 @@ class Ladestation_v2 extends IPSModule ]; $configuration["Username"] = [ "visible" => true, - "caption" => "eCarUp Benutzerkennung für Solarladen", + "caption" => + "eCarUp Benutzerkennung für Solarladen", ]; $configuration["Token_ECarUp"]["visible"] = true; $configuration["EaseeGatewayHint"]["visible"] = true; @@ -259,64 +295,49 @@ class Ladestation_v2 extends IPSModule return $configuration; } - private function UpdateStationForm(int $stationType) + public function ReceiveData($JSONString) { - foreach ($this->GetStationFormConfiguration($stationType) as $field => $parameters) { - foreach ($parameters as $parameter => $value) { - $this->UpdateFormField($field, $parameter, $value); - } + $packet = json_decode($JSONString, true); + if (!is_array($packet) || !isset($packet["Buffer"])) { + return; } - } - public function ApplyChanges() - { - parent::ApplyChanges(); - // Bei aktualisierten Bestandsinstanzen die in Version 1.1 neue Variable - // sofort anlegen; RegisterVariableBoolean ist idempotent. - $this->RegisterVariableBoolean( - "Easee_Gateway_Connected", - "Easee Gateway verbunden", - "~Switch", - false + $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()) ); - $this->SetTimerInterval("Timer_Do_UserCalc_EVC",$this->ReadPropertyInteger("Interval")*1000); - $stationType = $this->ReadPropertyInteger("Ladestation"); - $usesEaseeGateway = $this->UsesEaseeGateway($stationType); - IPS_SetHidden( - $this->GetIDForIdent("Easee_Gateway_Connected"), - !$usesEaseeGateway - ); - - $instance = IPS_GetInstance($this->InstanceID); - $parentID = (int)$instance["ConnectionID"]; - - // Bei einer aktualisierten, bisher noch unverbundenen Bestandsinstanz - // die normale IP-Symcon-Parent-Verbindung nachziehen. - if ($parentID <= 0) { - $this->ConnectParent(self::EASEE_GATEWAY_MODULE_ID); - $instance = IPS_GetInstance($this->InstanceID); - $parentID = (int)$instance["ConnectionID"]; - } - - if ($usesEaseeGateway && $this->IsEaseeGatewayInstance($parentID)) { - $this->SubscribeToEaseeGateway(); - } else { - $this->SetValue("Easee_Gateway_Connected", false); - } - - // erstelle einen Timer der das Request token aktualisiert wenn die station dies braucht. - // Zusätzliche Anpassungen nach Bedarf } - public function RequestAction($Ident, $Value) { switch ($Ident) { - case "UpdateStationForm": $this->UpdateStationForm((int)$Value); break; + case "SetAktuelle_Leistung": $this->SetValue("Power", (int)$Value); break; @@ -364,6 +385,10 @@ class Ladestation_v2 extends IPSModule $this->ResetNullTimer(); break; + case "Refresh_Token": + $this->Refresh_Token(); + break; + case "Mindestaldestrom": $this->SetValue("Mindestaldestrom", (int)$Value); break; @@ -373,652 +398,10 @@ 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); - $parentID = (int)$instance["ConnectionID"]; - if (!$this->IsEaseeGatewayInstance($parentID)) { - $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 GetConfiguredEaseeGatewayID(): int - { - $gatewayID = $this->ReadPropertyInteger("EaseeGatewayID"); - if ($gatewayID <= 0 || !IPS_InstanceExists($gatewayID)) { - return 0; - } - - $gateway = IPS_GetInstance($gatewayID); - if ($gateway["ModuleInfo"]["ModuleID"] !== self::EASEE_GATEWAY_MODULE_ID) { - return 0; - } - - return $gatewayID; - } - - private function IsEaseeGatewayInstance(int $instanceID): bool - { - if ($instanceID <= 0 || !IPS_InstanceExists($instanceID)) { - return false; - } - - $instance = IPS_GetInstance($instanceID); - return $instance["ModuleInfo"]["ModuleID"] === self::EASEE_GATEWAY_MODULE_ID; - } - - private function UsesEaseeGateway(int $stationType): bool - { - return $stationType === 5 || $stationType === 6; - } - - private function UsesDirectStationDetection(int $stationType): bool - { - // Alle Stationstypen verwenden wieder die gemeinsame, ursprüngliche - // Strom-/Phasenerkennung mit Pending_Counter. - return false; - } - - 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 ([47, 109, 110, 120, 182, 183, 184, 185] 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 (!in_array($observationID, [47, 109, 110, 120, 182, 183, 184, 185], true)) { - return; - } - - $state = json_decode($this->GetBuffer("EaseeGatewayState"), true); - if (!is_array($state)) { - $state = []; - } - - if ($observationID === 109) { - $previousMode = (int)($state["109"] ?? -1); - if ((int)$value === 1 || $previousMode === 1) { - foreach ([110, 182, 183, 184, 185] as $sessionObservationID) { - $state[(string)$sessionObservationID] = 0; - } - } - } - - $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"]; - $currentState = in_array($chargerOpMode, [2, 3, 4, 6], true); - - if (array_key_exists("120", $state)) { - $this->SetValue( - "Ladeleistung_Effektiv", - round((float)$state["120"] * 1000) - ); - } - $this->SetValue("Fahrzeugstatus", $chargerOpMode); - - return $currentState; - } - - private function DetectEaseeCar(int $carType): bool - { - $wasDetected = $this->GetValue("Car_detected"); - $wasOnePhase = $this->GetValue("Is_1_ph"); - $carDetected = $this->Get_Car_Status($carType); - $state = json_decode($this->GetBuffer("EaseeGatewayState"), true); - if (!is_array($state)) { - $state = []; - } - - $this->SetValue("Pending_Counter", 0); - - $this->DetectEaseePhases($state); - $phaseChanged = $wasOnePhase !== $this->GetValue("Is_1_ph"); - - // Observation 47 ist der nichtflüchtige maximale Ladestrom der - // Station. Die lokal konfigurierte Obergrenze bleibt zusätzlich aktiv. - $maxCurrent = (float)$this->ReadPropertyInteger("Max_Current_abs"); - if (array_key_exists("47", $state) && (float)$state["47"] > 0) { - $maxCurrent = min($maxCurrent, (float)$state["47"]); - } - $this->SetValue("Max_Current", $maxCurrent); - - if (!$carDetected) { - $this->SetValue("Car_detected", false); - $this->SetValue("Car_is_full", false); - - $chargerOpMode = (int)($state["109"] ?? -1); - if ($chargerOpMode === 1) { - $this->SetValue("Leistung_Delta", 0); - $this->ResetTimer(); - $this->ResetNullTimer(); - $this->SetValue("Is_1_ph", false); - $this->SetValue("Aktuelle_Leistung", 0); - $this->SetValue("IdleCounter", 0); - $this->SetValue("Idle", true); - } - - return false; - } - - $this->SetValue("Car_detected", true); - $this->SetValue("Car_is_full", (int)($state["109"] ?? 0) === 4); - - if (!$wasDetected || $phaseChanged) { - if ($this->GetValue("Is_1_ph")) { - $power = $this->GetValue("Mindestaldestrom") * 230; - } else { - $power = $this->GetValue("Mindestaldestrom") * 400 * 1.71; - } - $this->SetValue("Power", $power); - } - - return true; - } - - private function DetectEaseePhases(array $state) - { - $outputPhase = (int)($state["110"] ?? 0); - - // Bei Ladepause bzw. 0 W die zuletzt erkannte Fahrzeugphase behalten. - // Die Phasenvariable darf erst durch eine echte Strommessung wechseln. - if ( - array_key_exists("120", $state) && - abs((float)$state["120"]) < 0.1 - ) { - return; - } - - $activePhaseConductors = 0; - foreach ([183, 184, 185] as $observationID) { - if (abs((float)($state[(string)$observationID] ?? 0)) >= 1.0) { - $activePhaseConductors++; - } - } - - if ($activePhaseConductors === 0) { - return; - } - - // Ein explizit einphasiger Easee-Ausgang ist auch in IT-Netzen - // einphasig, obwohl dort Strom auf zwei Leitern gemessen wird. - if ($outputPhase >= 10 && $outputPhase <= 15) { - $this->SetValue("Is_1_ph", true); - return; - } - - // Bei einem dreiphasig freigegebenen Ausgang kann ein einphasiges Auto - // trotzdem nur auf einem Leiter Strom beziehen. Observation 110 allein - // kann diesen Fahrzeugwechsel deshalb nicht erkennen. - if ($activePhaseConductors === 1) { - $this->SetValue("Is_1_ph", true); - } elseif ($activePhaseConductors >= 2) { - $this->SetValue("Is_1_ph", false); - } - } - - private function ReadJsonEndpoint(string $url, bool $useAuthentication = false) - { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - - if ($useAuthentication) { - curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - curl_setopt( - $ch, - CURLOPT_USERPWD, - $this->ReadPropertyString("Username") . ":" . - $this->ReadPropertyString("Password") - ); - } - - $response = curl_exec($ch); - $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - if (!is_string($response) || $response === "" || $httpCode >= 400) { - return null; - } - - $data = json_decode($response, true); - return is_array($data) ? $data : null; - } - - private function DetectOnePhaseFromCurrents(array $currents) - { - $activePhases = 0; - $hasMeasurements = false; - - foreach ($currents as $current) { - if (!is_numeric($current)) { - continue; - } - - $hasMeasurements = true; - if (abs((float)$current) >= 1.0) { - $activePhases++; - } - } - - if (!$hasMeasurements || $activePhases === 0) { - return null; - } - - return $activePhases === 1; - } - - private function ApplyDirectStationDetection( - bool $carDetected, - bool $carIsFull, - $isOnePhase, - float $maxCurrent, - bool $resetPendingCounter = true - ): bool { - $wasDetected = $this->GetValue("Car_detected"); - $wasOnePhase = $this->GetValue("Is_1_ph"); - - if ($resetPendingCounter) { - $this->SetValue("Pending_Counter", 0); - } - $this->SetValue("Max_Current", $maxCurrent); - - if (!$carDetected) { - $this->SetValue("Pending_Counter", 0); - $this->SetValue("Car_detected", false); - $this->SetValue("Car_is_full", false); - $this->SetValue("Leistung_Delta", 0); - $this->ResetTimer(); - $this->ResetNullTimer(); - $this->SetValue("Is_1_ph", false); - $this->SetValue("Aktuelle_Leistung", 0); - $this->SetValue("IdleCounter", 0); - $this->SetValue("Idle", true); - return false; - } - - if (is_bool($isOnePhase)) { - $this->SetValue("Is_1_ph", $isOnePhase); - } - - $this->SetValue("Car_detected", true); - $this->SetValue("Car_is_full", $carIsFull); - - $phaseChanged = $wasOnePhase !== $this->GetValue("Is_1_ph"); - if (!$wasDetected || $phaseChanged) { - if ($this->GetValue("Is_1_ph")) { - $power = $this->GetValue("Mindestaldestrom") * 230; - } else { - $power = $this->GetValue("Mindestaldestrom") * 400 * 1.71; - } - $this->SetValue("Power", $power); - } - - return true; - } - - private function GetGoEMaxCurrent(array $data): float - { - $maxCurrent = (float)$this->ReadPropertyInteger("Max_Current_abs"); - - foreach (["ama", "cbl"] as $key) { - if (isset($data[$key]) && is_numeric($data[$key])) { - $candidate = (float)$data[$key]; - if ($candidate >= 6) { - $maxCurrent = min($maxCurrent, $candidate); - } - } - } - - if (isset($data["var"]) && is_numeric($data["var"])) { - $variant = (int)$data["var"]; - if ($variant === 11) { - $maxCurrent = min($maxCurrent, 16); - } elseif ($variant === 22) { - $maxCurrent = min($maxCurrent, 32); - } - } - - return $maxCurrent; - } - - private function IsGoEChargingEnabled(array $data, int $stationType): bool - { - if ($stationType === 1 && array_key_exists("alw", $data)) { - return (int)$data["alw"] === 1; - } - - if ($stationType === 2 && array_key_exists("frc", $data)) { - return (int)$data["frc"] === 2; - } - - if (array_key_exists("alw", $data)) { - return (int)$data["alw"] === 1; - } - - return $this->GetBuffer("GoEChargingEnabled") === "1"; - } - - private function DetectGoEFullCharge( - array $data, - int $stationType, - int $carState, - $chargingPowerWatts - ): bool { - $wasFull = $this->GetValue("Car_is_full"); - - // Ein einmal sicher erkanntes Ladeende bleibt bis zum Abstecken oder - // bis zu einer tatsächlich wieder aufgenommenen Ladung erhalten. - if ($wasFull) { - if ( - is_numeric($chargingPowerWatts) && - abs((float)$chargingPowerWatts) >= 100 - ) { - $this->SetValue("Pending_Counter", 0); - return false; - } - - return true; - } - - // car=4 alleine ist nicht ausreichend: Go-E meldet diesen Status auch, - // wenn die Regelung die Ladung absichtlich unterbricht. - if ( - $carState !== 4 || - !$this->IsGoEChargingEnabled($data, $stationType) || - !is_numeric($chargingPowerWatts) || - abs((float)$chargingPowerWatts) >= 100 - ) { - $this->SetValue("Pending_Counter", 0); - return false; - } - - $counter = $this->GetValue("Pending_Counter") + 1; - $this->SetValue("Pending_Counter", $counter); - - $interval = max(1, $this->ReadPropertyInteger("Interval")); - $requiredCycles = max(1, (int)ceil(90 / $interval)); - if ($counter < $requiredCycles) { - return false; - } - - $this->SetValue("Pending_Counter", 0); - return true; - } - - private function DetectGoECar(int $stationType): bool - { - $path = $stationType === 1 ? "/mqtt?payload=" : "/api/status"; - $data = $this->ReadJsonEndpoint( - "http://" . $this->ReadPropertyString("IP_Adresse") . $path - ); - - if (!is_array($data) || !isset($data["car"]) || !is_numeric($data["car"])) { - return $this->GetValue("Car_detected"); - } - - $carState = (int)$data["car"]; - if (!in_array($carState, [1, 2, 3, 4, 5], true)) { - return $this->GetValue("Car_detected"); - } - - $this->SetValue("Fahrzeugstatus", $carState); - - $isOnePhase = null; - $chargingPowerWatts = null; - if (isset($data["nrg"]) && is_array($data["nrg"])) { - $currentFactor = $stationType === 1 ? 0.1 : 1.0; - $phaseCurrents = []; - foreach ([4, 5, 6] as $index) { - $phaseCurrents[] = isset($data["nrg"][$index]) && - is_numeric($data["nrg"][$index]) - ? (float)$data["nrg"][$index] * $currentFactor - : null; - } - $isOnePhase = $this->DetectOnePhaseFromCurrents($phaseCurrents); - - if (isset($data["nrg"][11]) && is_numeric($data["nrg"][11])) { - $powerFactor = $stationType === 1 ? 10 : 1; - $chargingPowerWatts = (float)$data["nrg"][11] * $powerFactor; - $this->SetValue( - "Ladeleistung_Effektiv", - round($chargingPowerWatts) - ); - } - } - - $carDetected = in_array($carState, [2, 3, 4, 5], true); - $carIsFull = $carDetected && $this->DetectGoEFullCharge( - $data, - $stationType, - $carState, - $chargingPowerWatts - ); - - return $this->ApplyDirectStationDetection( - $carDetected, - $carIsFull, - $isOnePhase, - $this->GetGoEMaxCurrent($data), - false - ); - } - - private function DetectSmartMeCar(): bool - { - $deviceID = trim($this->ReadPropertyString("ID")); - if ($deviceID === "") { - return $this->GetValue("Car_detected"); - } - - $chargingData = $this->ReadJsonEndpoint( - "https://api.smart-me.com/pico/charging/" . $deviceID, - true - ); - if (!is_array($chargingData)) { - return $this->GetValue("Car_detected"); - } - - $stateValue = $chargingData["State"] ?? $chargingData["state"] ?? null; - if (!is_numeric($stateValue)) { - return $this->GetValue("Car_detected"); - } - - $stationState = (int)$stateValue; - if (!in_array($stationState, [1, 2, 3, 4, 6], true)) { - return $this->GetValue("Car_detected"); - } - - $this->SetValue("Fahrzeugstatus", $stationState); - - $chargingPower = $chargingData["ActiveChargingPower"] ?? - $chargingData["activeChargingPower"] ?? null; - if (is_numeric($chargingPower)) { - $this->SetValue( - "Ladeleistung_Effektiv", - round((float)$chargingPower * 1000) - ); - } - - $isOnePhase = null; - $deviceData = $this->ReadJsonEndpoint( - "https://api.smart-me.com/Devices/" . $deviceID, - true - ); - if (is_array($deviceData)) { - $phaseCurrents = [ - $deviceData["CurrentL1"] ?? $deviceData["currentL1"] ?? null, - $deviceData["CurrentL2"] ?? $deviceData["currentL2"] ?? null, - $deviceData["CurrentL3"] ?? $deviceData["currentL3"] ?? null, - ]; - $isOnePhase = $this->DetectOnePhaseFromCurrents($phaseCurrents); - } - - $maxCurrent = (float)$this->ReadPropertyInteger("Max_Current_abs"); - foreach ([ - "MaxStationCurrent", - "maxStationCurrent", - "MaxLoadmanagementGroupCurrent", - "maxLoadmanagementGroupCurrent", - ] as $key) { - if (isset($chargingData[$key]) && is_numeric($chargingData[$key])) { - $candidate = (float)$chargingData[$key]; - if ($candidate >= 6) { - $maxCurrent = min($maxCurrent, $candidate); - } - } - } - - return $this->ApplyDirectStationDetection( - in_array($stationState, [2, 3, 4, 6], true), - false, - $isOnePhase, - $maxCurrent - ); - } - - private function SendEaseeCurrentToGateway(float $value) - { - $response = $this->SendEaseeGatewayRequest([ - "action" => "SetDynamicChargerCurrent", - "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"; - return $error; - } - - return $response["body"] ?? null; - } - public function Detect_Car(int $carType) { + if($this->GetValue("Car_detected")==true){ if($this->Get_Car_Status($carType)==true){ $this->SetValue("Car_detected", true); @@ -1149,10 +532,10 @@ class Ladestation_v2 extends IPSModule public function Get_Car_Status(int $carType) { + $ch = curl_init(); $car_on_station = $this->GetValue("Car_detected"); switch ($carType) { case 1: - $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://" . $this->ReadPropertyString("IP_Adresse") . "/mqtt?payload="); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); @@ -1178,7 +561,6 @@ class Ladestation_v2 extends IPSModule break; case 2: - $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://" . $this->ReadPropertyString("IP_Adresse") . "/api/status"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); @@ -1204,7 +586,6 @@ class Ladestation_v2 extends IPSModule break; case 3: - $ch = curl_init(); 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_USERPWD, $this->ReadPropertyString("Username") . ":" . $this->ReadPropertyString("Password")); @@ -1235,47 +616,47 @@ class Ladestation_v2 extends IPSModule case 4: $car_on_station = true; break; - - case 5: - - // Benutzer eCarUp abfragen - + case 5: // Solarladen E-Car-Up $authToken = GetValue($this->ReadPropertyInteger("Token_ECarUp")); $ID = $this->ReadPropertyString("ID"); - $apiUrl = 'https://public-api.ecarup.com/v1/station/' . $ID . '/conn'.'ectors/'. $ID . '/active-charging'; - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $apiUrl); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'accept: application/json', - 'Authorization: Bearer ' . $authToken + $apiUrl = "https://public-api.ecarup.com/v1/station/" . $ID . + "/connectors/" . $ID . "/active-charging"; + $eCarUpHandle = curl_init(); + curl_setopt($eCarUpHandle, CURLOPT_URL, $apiUrl); + curl_setopt($eCarUpHandle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($eCarUpHandle, CURLOPT_HTTPHEADER, [ + "accept: application/json", + "Authorization: Bearer " . $authToken, ]); - - $response = curl_exec($ch); - $data = json_decode($response, true); + $eCarUpResponse = curl_exec($eCarUpHandle); + $eCarUpData = json_decode($eCarUpResponse, true); + curl_close($eCarUpHandle); - curl_close($ch); - - if (json_last_error() === JSON_ERROR_NONE && isset($data["driverIdentifier"])) { - - $this->SetValue("Letzer_User", $data["driverIdentifier"]); - - if($this->ReadPropertyString("Username") === $data["driverIdentifier"]){ - $this->SetValue("Solarladen", true); - }else{ - $this->SetValue("Solarladen", false); - } + if ( + json_last_error() === JSON_ERROR_NONE && + isset($eCarUpData["driverIdentifier"]) + ) { + $this->SetValue( + "Letzer_User", + $eCarUpData["driverIdentifier"] + ); + $this->SetValue( + "Solarladen", + $this->ReadPropertyString("Username") === + $eCarUpData["driverIdentifier"] + ); } - $car_on_station = $this->GetEaseeCarStatusFromGateway($car_on_station); - + $car_on_station = + $this->GetEaseeCarStatusFromGateway($car_on_station); break; - case 6: - $car_on_station = $this->GetEaseeCarStatusFromGateway($car_on_station); - + case 6: // Easee + $car_on_station = + $this->GetEaseeCarStatusFromGateway($car_on_station); break; + default: $this->SetValue("Car_detected", false); $this->SetValue("Fahrzeugstatus", -1); @@ -1464,11 +845,11 @@ class Ladestation_v2 extends IPSModule } 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")); } elseif ($solarladen && $Peak) { - if ($this->GetValue("Is_1_ph")) { - $powerSteps = [$this->GetValue("Mindestaldestrom") * 230]; - } else { - $powerSteps = [$this->GetValue("Mindestaldestrom") * 400 * 1.71]; - } + if ($is_1_ph) { + $powerSteps = [$this->GetValue("Mindestaldestrom") * 230]; + } else { + $powerSteps = [$this->GetValue("Mindestaldestrom") * 400 * 1.71]; + } } 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")); @@ -1477,8 +858,7 @@ class Ladestation_v2 extends IPSModule // PowerSteps in der RegisterVariable speichern SetValue($this->GetIDForIdent("PowerSteps"), json_encode($powerSteps)); // Rückgabe der Powersteps - if (!$this->UsesDirectStationDetection($this->ReadPropertyInteger("Ladestation"))) { - $counter = $this->GetValue("Pending_Counter"); + $counter = $this->GetValue("Pending_Counter"); if($this->GetValue("Aktuelle_Leistung")>(1.11*$this->GetValue("Ladeleistung_Effektiv"))){ $this->SetValue("Pending_Counter", $counter+1); @@ -1509,13 +889,6 @@ class Ladestation_v2 extends IPSModule } } - } elseif (!in_array( - $this->ReadPropertyInteger("Ladestation"), - [1, 2], - true - )) { - $this->SetValue("Pending_Counter", 0); - } return $powerSteps; } @@ -1575,9 +948,6 @@ class Ladestation_v2 extends IPSModule curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); - if (($stationType === 1 || $stationType === 2) && $response !== false) { - $this->SetBuffer("GoEChargingEnabled", "0"); - } curl_close($ch); return $response; } elseif ($value >= 1 && $value <= 32) { @@ -1619,9 +989,6 @@ class Ladestation_v2 extends IPSModule curl_close($ch); return "Error:" . curl_error($ch); } - if ($stationType === 1 || $stationType === 2) { - $this->SetBuffer("GoEChargingEnabled", "1"); - } curl_close($ch); return; } else { @@ -1629,6 +996,217 @@ class Ladestation_v2 extends IPSModule } } + public function Refresh_Token() + { + // Easee-Zugangsdaten und Token werden ausschliesslich + // im SignalR-Gateway verwaltet. + return; + } + + private function UsesEaseeGateway(int $stationType): bool + { + return $stationType === 5 || $stationType === 6; + } + + private function IsEaseeGatewayInstance(int $instanceID): bool + { + if ($instanceID <= 0 || !IPS_InstanceExists($instanceID)) { + return false; + } + + $instance = IPS_GetInstance($instanceID); + return + $instance["ModuleInfo"]["ModuleID"] === + self::EASEE_GATEWAY_MODULE_ID; + } + + 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); + $parentID = (int)$instance["ConnectionID"]; + if (!$this->IsEaseeGatewayInstance($parentID)) { + $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 ( + [47, 104, 109, 110, 120, 182, 183, 184, 185] + 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 ( + !in_array( + $observationID, + [47, 104, 109, 110, 120, 182, 183, 184, 185], + true + ) + ) { + return; + } + + $state = json_decode( + $this->GetBuffer("EaseeGatewayState"), + true + ); + if (!is_array($state)) { + $state = []; + } + + if ($observationID === 109) { + $previousMode = (int)($state["109"] ?? -1); + if ((int)$value === 1 || $previousMode === 1) { + foreach ( + [110, 182, 183, 184, 185] + as $sessionObservationID + ) { + $state[(string)$sessionObservationID] = 0; + } + } + } + + $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"]; + $currentState = + $chargerOpMode != 1 && + ($chargerOpMode != 6 || $this->GetValue("Car_detected")) && + $chargerOpMode != 7; + + 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" => "SetDynamicChargerCurrent", + "serialNumber" => strtoupper( + trim($this->ReadPropertyString("Seriennummer")) + ), + "amps" => $value, + ]); + + if (!is_array($response) || !($response["success"] ?? false)) { + return is_array($response) + ? (string)( + $response["error"] ?? + "Unbekannter Gateway-Fehler" + ) + : "Easee Gateway nicht erreichbar"; + } + + return $response["body"] ?? null; + } + public function CheckIdle($power) { $lastpower = GetValue($this->GetIDForIdent("Aktuelle_Leistung")); @@ -1649,3 +1227,4 @@ class Ladestation_v2 extends IPSModule } ?> +