diff --git a/VGT_Sub/module.json b/VGT_Sub/module.json index 5cfc1cf..ef50d59 100644 --- a/VGT_Sub/module.json +++ b/VGT_Sub/module.json @@ -6,9 +6,12 @@ "aliases": [ "VGT MQTT Device" ], - "parentRequirements": ["{F7A0DD2E-7684-95C0-64C2-D2A9DC47577B}"], + "parentRequirements": [ + "{F7A0DD2E-7684-95C0-64C2-D2A9DC47577B}", + "{F66ADE63-8834-4178-8CA5-AE4465D2E252}" + ], "childRequirements": [], "implemented": ["{018EF6B5-AB94-40C6-AA53-46943E824ACF}"], "prefix": "VGT", - "version": "1.6" + "version": "2.1" } \ No newline at end of file diff --git a/VGT_Sub/module.php b/VGT_Sub/module.php index 73d46df..7ef4f7e 100644 --- a/VGT_Sub/module.php +++ b/VGT_Sub/module.php @@ -7,20 +7,16 @@ class VGT_Sub extends IPSModule public function Create() { parent::Create(); - - // --------------------------------------------------------------------- - // 1. MQTT Verbindungsdaten (für den IO/Socket) - // --------------------------------------------------------------------- + + // --- Konfiguration --- $this->RegisterPropertyString('MQTTBaseTopic', 'Test VGT_Steuerung/Komm'); - $this->RegisterPropertyString('MQTTClientID', 'symcon_vgt_client'); + + // --- Optionale Gateway-Konfig (Nur für IO/Socket relevant) --- $this->RegisterPropertyString('MQTTUser', ''); $this->RegisterPropertyString('MQTTPassword', ''); - // Checkbox: Sollen die IO-Daten überschrieben werden? - $this->RegisterPropertyBoolean('UpdateGatewayConfig', true); - - // --------------------------------------------------------------------- - // 2. Hardware Verknüpfungen - // --------------------------------------------------------------------- + $this->RegisterPropertyBoolean('UpdateGatewayConfig', false); + + // --- Hardware Quellen --- $this->RegisterPropertyInteger('SourceSoC', 0); $this->RegisterPropertyInteger('SourcePowerProd', 0); $this->RegisterPropertyInteger('SourceIsReady', 0); @@ -29,16 +25,9 @@ class VGT_Sub extends IPSModule $this->RegisterPropertyInteger('SourceMaxSoC', 0); $this->RegisterPropertyInteger('TargetControlVar', 0); - // --------------------------------------------------------------------- - // 3. Interne Variablen - // --------------------------------------------------------------------- + // --- Interne Variablen --- $this->RegisterVariableString('Strategy', 'Strategy', '', 10); $this->RegisterVariableInteger('PowerSetpoint', 'Power Setpoint', '', 20); - - // Visu (Nur zur Anzeige) - $this->RegisterVariableFloat('Visu_SoC', 'State of Charge', '~Intensity.100', 30); - $this->RegisterVariableFloat('Visu_Production', 'Power Production', '~Watt', 40); - $this->RegisterVariableBoolean('Visu_IsReady', 'Is Ready', '~Switch', 50); $this->EnableAction('Strategy'); $this->EnableAction('PowerSetpoint'); @@ -48,106 +37,36 @@ class VGT_Sub extends IPSModule { parent::ApplyChanges(); - // --------------------------------------------------------------------- - // A. Filter setzen (Subscribe) - // --------------------------------------------------------------------- + // 1. Filter setzen (Abonnieren) $topic = $this->ReadPropertyString('MQTTBaseTopic'); if($topic !== "") { $cleanTopic = preg_quote($topic, '/'); - // Filter für den Splitter: Alles empfangen, was mit BaseTopic beginnt + // Dieser Filter funktioniert für beide Splitter-Varianten $this->SetReceiveDataFilter(".*\"Topic\":\"" . $cleanTopic . "/.*\".*"); } - // --------------------------------------------------------------------- - // B. Gateway (IO/Socket) Konfiguration aktualisieren - // --------------------------------------------------------------------- + // 2. Gateway Config (Optional) if ($this->ReadPropertyBoolean('UpdateGatewayConfig')) { $this->ConfigureGateway(); } - // --------------------------------------------------------------------- - // C. Events registrieren - // --------------------------------------------------------------------- + // 3. Events registrieren $socID = $this->ReadPropertyInteger('SourceSoC'); - if (IPS_VariableExists($socID)) { - $this->RegisterMessage($socID, VM_UPDATE); - } + if (IPS_VariableExists($socID)) $this->RegisterMessage($socID, VM_UPDATE); $this->RegisterMessage($this->GetIDForIdent('Strategy'), VM_UPDATE); $this->RegisterMessage($this->GetIDForIdent('PowerSetpoint'), VM_UPDATE); } - /** - * Sucht den IO (Socket) und setzt User/Passwort/ClientID - */ - private function ConfigureGateway() - { - // 1. Parent (Splitter) holen - $instance = IPS_GetInstance($this->InstanceID); - $splitterID = $instance['ConnectionID']; - - if ($splitterID > 0) { - // 2. Grandparent (IO / Client Socket) holen - $splitter = IPS_GetInstance($splitterID); - $ioID = $splitter['ConnectionID']; - - if ($ioID > 0) { - $user = $this->ReadPropertyString('MQTTUser'); - $pass = $this->ReadPropertyString('MQTTPassword'); - $client = $this->ReadPropertyString('MQTTClientID'); - - $hasChanged = false; - - // --- 1. Benutzer/Passwort am IO setzen (meist MQTT Client Socket) --- - // Prüfen ob Properties existieren, um Fehler zu vermeiden - if (@IPS_GetProperty($ioID, 'Username') !== false) { - if (IPS_GetProperty($ioID, 'Username') != $user) { - IPS_SetProperty($ioID, 'Username', $user); - $hasChanged = true; - } - } - if (@IPS_GetProperty($ioID, 'Password') !== false) { - if (IPS_GetProperty($ioID, 'Password') != $pass) { - IPS_SetProperty($ioID, 'Password', $pass); - $hasChanged = true; - } - } - - // --- 2. ClientID am Splitter setzen (oft ist ClientID im Splitter, nicht IO) --- - // Falls ClientID im Splitter definiert ist: - if (@IPS_GetProperty($splitterID, 'ClientID') !== false) { - if (IPS_GetProperty($splitterID, 'ClientID') != $client) { - IPS_SetProperty($splitterID, 'ClientID', $client); - IPS_ApplyChanges($splitterID); // Splitter sofort übernehmen - } - } - // Falls ClientID doch am IO ist: - elseif (@IPS_GetProperty($ioID, 'ClientID') !== false) { - if (IPS_GetProperty($ioID, 'ClientID') != $client) { - IPS_SetProperty($ioID, 'ClientID', $client); - $hasChanged = true; - } - } - - // IO Änderungen speichern - if ($hasChanged) { - IPS_ApplyChanges($ioID); - } - } - } - } - - // ------------------------------------------------------------------------- - // MQTT Logik - // ------------------------------------------------------------------------- public function ReceiveData($JSONString) { $data = json_decode($JSONString); + + // Payload Dekodierung $payload = utf8_decode($data->Payload); $topic = $data->Topic; + $baseTopic = $this->ReadPropertyString('MQTTBaseTopic'); - //$this->SendDebug('MQTT Received', "Topic: $topic | Payload: $payload", 0); - if ($topic === $baseTopic . '/Writebefehl') { $this->ProcessWriteCommand($payload); } @@ -156,38 +75,14 @@ class VGT_Sub extends IPSModule } } - public function MessageSink($TimeStamp, $SenderID, $Message, $Data) - { - if ($Message == VM_UPDATE) { - $socID = $this->ReadPropertyInteger('SourceSoC'); - if ($SenderID == $socID) { - $this->SetValue('Visu_SoC', $Data[0]); - } - $this->ExecuteControlLogic(); - } - } - - public function RequestAction($Ident, $Value) - { - switch ($Ident) { - case 'Strategy': - case 'PowerSetpoint': - $this->SetValue($Ident, $Value); - $this->ExecuteControlLogic(); - break; - } - } - private function ProcessWriteCommand($jsonInput) { - if ($jsonInput == "") return; $data = json_decode($jsonInput, true); if ($data === null) return; if (isset($data['power_setpoint'])) { $val = $data['power_setpoint']; - if ($val > 6000) $val = 6000; - elseif ($val < -6000) $val = -6000; + if ($val > 6000) $val = 6000; elseif ($val < -6000) $val = -6000; $this->SetValue('PowerSetpoint', $val); } if (isset($data['strategy'])) { @@ -196,26 +91,21 @@ class VGT_Sub extends IPSModule $this->ExecuteControlLogic(); - $output = [ - 'power_setpoint' => $this->GetValue('PowerSetpoint'), - 'strategy' => $this->GetValue('Strategy') - ]; + $output = ['power_setpoint' => $this->GetValue('PowerSetpoint'), 'strategy' => $this->GetValue('Strategy')]; $this->SendMQTT($this->ReadPropertyString('MQTTBaseTopic') . '/Writebefehl Antwort', json_encode($output)); } private function ProcessReadCommand() { - $data = $this->GatherHardwareData(); - $this->SetValue('Visu_Production', $data['raw_prod']); - $this->SetValue('Visu_IsReady', $data['is_ready']); - + $safeGet = function($prop) { $id = $this->ReadPropertyInteger($prop); return IPS_VariableExists($id) ? GetValue($id) : 0; }; + $mqttData = [ - 'power_production' => -1 * $data['raw_prod'], - 'is_ready' => $data['is_ready'], - 'is_running' => $data['is_running'], - 'state_of_charge' => $data['soc'], - 'min_soc' => $data['min_soc'], - 'max_soc' => $data['max_soc'] + 'power_production' => -1 * $safeGet('SourcePowerProd'), + 'is_ready' => (bool)$safeGet('SourceIsReady'), + 'is_running' => (bool)$safeGet('SourceIsRunning'), + 'state_of_charge' => $safeGet('SourceSoC'), + 'min_soc' => $safeGet('SourceMinSoC'), + 'max_soc' => $safeGet('SourceMaxSoC') ]; $this->SendMQTT($this->ReadPropertyString('MQTTBaseTopic') . '/Lesebefehl Antwort', json_encode($mqttData)); } @@ -235,47 +125,65 @@ class VGT_Sub extends IPSModule } elseif ($strategy == "stop") { RequestAction($targetID, 0); } else { - if ((int)$soc == 50) { - RequestAction($targetID, 0); - } elseif ($soc < 50) { - RequestAction($targetID, 2500); - } else { - RequestAction($targetID, -2500); - } + if ((int)$soc == 50) RequestAction($targetID, 0); + elseif ($soc < 50) RequestAction($targetID, 2500); + else RequestAction($targetID, -2500); } } - private function GatherHardwareData() - { - $safeGet = function($propName) { - $id = $this->ReadPropertyInteger($propName); - return IPS_VariableExists($id) ? GetValue($id) : 0; - }; - return [ - 'raw_prod' => $safeGet('SourcePowerProd'), - 'is_ready' => (bool)$safeGet('SourceIsReady'), - 'is_running' => (bool)$safeGet('SourceIsRunning'), - 'soc' => $safeGet('SourceSoC'), - 'min_soc' => $safeGet('SourceMinSoC'), - 'max_soc' => $safeGet('SourceMaxSoC'), - ]; - } - protected function SendMQTT($Topic, $Payload) { - // DataID für Symcon MQTT Publish. - // Falls der Schnittcher-Splitter eine andere ID will, könnte hier ein Fehler kommen. - // In den meisten Fällen funktioniert diese ID aber auch mit kompatiblen Splittern. - $Data['DataID'] = '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}'; + // Standard DataID für MQTT. Funktioniert meistens auch mit dem 77B-Modul. + $Data['DataID'] = '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}'; + $Data['PacketType'] = 3; + $Data['QualityOfService'] = 0; + $Data['Retain'] = false; + $Data['Topic'] = $Topic; + $Data['Payload'] = $Payload; + $this->SendDataToParent(json_encode($Data)); + } + + // Automatische Konfiguration des Parents (falls aktiviert) + private function ConfigureGateway() + { + $instance = IPS_GetInstance($this->InstanceID); + $parentId = $instance['ConnectionID']; // Das ist dein 77B Modul oder der native Splitter - $Data['PacketType'] = 3; - $Data['QualityOfService'] = 0; - $Data['Retain'] = false; - $Data['Topic'] = $Topic; - $Data['Payload'] = $Payload; - - $JSON = json_encode($Data); - $this->SendDataToParent($JSON); + if ($parentId > 0) { + // Wir versuchen, User/Pass auf dem Parent zu setzen (manche Module haben das direkt) + // Oder wir suchen den IO darunter. + + $user = $this->ReadPropertyString('MQTTUser'); + $pass = $this->ReadPropertyString('MQTTPassword'); + + // Check: Hat der Parent direkt Username/Password? (Schnittcher Modul hat das oft) + if (@IPS_GetProperty($parentId, 'Username') !== false) { + IPS_SetProperty($parentId, 'Username', $user); + IPS_SetProperty($parentId, 'Password', $pass); + IPS_ApplyChanges($parentId); + return; + } + + // Wenn nicht, suchen wir den IO darunter (Native Symcon Logik) + $parentInfo = IPS_GetInstance($parentId); + $ioID = $parentInfo['ConnectionID']; + if ($ioID > 0) { + if (@IPS_GetProperty($ioID, 'Username') !== false) { + IPS_SetProperty($ioID, 'Username', $user); + IPS_SetProperty($ioID, 'Password', $pass); + IPS_ApplyChanges($ioID); + } + } + } + } + + public function RequestAction($Ident, $Value) { + $this->SetValue($Ident, $Value); + $this->ExecuteControlLogic(); + } + + public function MessageSink($TimeStamp, $SenderID, $Message, $Data) { + if ($Message == VM_UPDATE) $this->ExecuteControlLogic(); } } ?> \ No newline at end of file