diff --git a/VGT_Sub/module.json b/VGT_Sub/module.json index 44f1cab..6793c9f 100644 --- a/VGT_Sub/module.json +++ b/VGT_Sub/module.json @@ -7,11 +7,12 @@ "VGT MQTT Device" ], "parentRequirements": [ - "{F66ADE63-8834-4178-8CA5-AE4465D2E252}", - "{F7A0DD2E-7684-95C0-64C2-D2A9DC47577B}" + "{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}" ], "childRequirements": [], - "implemented": ["{018EF6B5-AB94-40C6-AA53-46943E824ACF}"], + "implemented": [ + "{018EF6B5-AB94-40C6-AA53-46943E824ACF}" + ], "prefix": "VGT", - "version": "3.0" + "version": "1.0" } \ No newline at end of file diff --git a/VGT_Sub/module.php b/VGT_Sub/module.php index 7fe9e6e..3636eb4 100644 --- a/VGT_Sub/module.php +++ b/VGT_Sub/module.php @@ -4,211 +4,97 @@ declare(strict_types=1); class VGT_Sub extends IPSModule { - // GUIDs für den offiziellen IP-Symcon Datenfluss (MQTT) - const MQTT_TX_GUID = '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}'; // Wir senden das an den Splitter - const MQTT_RX_GUID = '{018EF6B5-AB94-40C6-AA53-46943E824ACF}'; // Wir empfangen das (implemented) + // Die GUID des IP-Symcon MQTT Clients (Splitter) + private const PARENT_GUID = '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}'; public function Create() { parent::Create(); - - // --- Konfiguration --- - $this->RegisterPropertyString('MQTTBaseTopic', 'Test VGT_Steuerung/Komm'); - - // Gateway-Konfig (Optional für Komfort) - $this->RegisterPropertyString('MQTTUser', ''); - $this->RegisterPropertyString('MQTTPassword', ''); - $this->RegisterPropertyBoolean('UpdateGatewayConfig', false); - - // --- Hardware Quellen --- - $this->RegisterPropertyInteger('SourceSoC', 0); - $this->RegisterPropertyInteger('SourcePowerProd', 0); - $this->RegisterPropertyInteger('SourceIsReady', 0); - $this->RegisterPropertyInteger('SourceIsRunning', 0); - $this->RegisterPropertyInteger('SourceMinSoC', 0); - $this->RegisterPropertyInteger('SourceMaxSoC', 0); - $this->RegisterPropertyInteger('TargetControlVar', 0); - - // --- Interne Variablen --- - $this->RegisterVariableString('Strategy', 'Strategy', '', 10); - $this->RegisterVariableInteger('PowerSetpoint', 'Power Setpoint', '', 20); - - $this->EnableAction('Strategy'); - $this->EnableAction('PowerSetpoint'); + // Verbindet sich automatisch mit einem MQTT Client + $this->ConnectParent(self::PARENT_GUID); } public function ApplyChanges() { parent::ApplyChanges(); - // --------------------------------------------------------------------- - // DATENFLUSS: Filter setzen - // --------------------------------------------------------------------- - // Wir teilen dem Splitter mit: "Schick mir nur Daten für mein Topic" - // Das entlastet das System massiv. - $topic = $this->ReadPropertyString('MQTTBaseTopic'); - if($topic !== "") { - $cleanTopic = preg_quote($topic, '/'); - // Regex Filter auf das Feld "Topic" im JSON Datensatz - $this->SetReceiveDataFilter(".*\"Topic\":\"" . $cleanTopic . "/.*\".*"); - } + // Topic Definitionen + $requestTopic = 'feedback-request/deviceOne'; - // --------------------------------------------------------------------- - // Komfort: Gateway Konfiguration durchreichen - // --------------------------------------------------------------------- - if ($this->ReadPropertyBoolean('UpdateGatewayConfig')) { - $this->ConfigureGateway(); - } + // 1. Filter setzen: Wir wollen nur Nachrichten für unser Request-Topic sehen. + // Das "Topic" im Filter bezieht sich auf das JSON-Paket vom MQTT Client. + $this->SetReceiveDataFilter('.*"Topic":"' . preg_quote($requestTopic, '/') . '".*'); - // --------------------------------------------------------------------- - // Events - // --------------------------------------------------------------------- - $socID = $this->ReadPropertyInteger('SourceSoC'); - if (IPS_VariableExists($socID)) $this->RegisterMessage($socID, VM_UPDATE); - $this->RegisterMessage($this->GetIDForIdent('Strategy'), VM_UPDATE); - $this->RegisterMessage($this->GetIDForIdent('PowerSetpoint'), VM_UPDATE); + // 2. Beim MQTT Broker abonnieren (Subscribe senden) + // Wir prüfen erst, ob wir einen Parent haben, um Fehler zu vermeiden + if ($this->HasActiveParent()) { + $this->Subscribe($requestTopic); + } } - // ------------------------------------------------------------------------- - // DATENFLUSS: Empfang (ReceiveData) - // ------------------------------------------------------------------------- - // Wird automatisch aufgerufen, wenn der Splitter Daten hat, die zum Filter passen. + /** + * Wird aufgerufen, wenn der Parent (MQTT Client) Daten empfängt + */ public function ReceiveData($JSONString) { - $data = json_decode($JSONString); + $data = json_decode($JSONString, true); - // Daten aus dem JSON extrahieren (Standard Symcon Format) - $payload = utf8_decode($data->Payload); - $topic = $data->Topic; - - $baseTopic = $this->ReadPropertyString('MQTTBaseTopic'); + $this->SendDebug('ReceiveData', $JSONString, 0); - // Routing - if ($topic === $baseTopic . '/Writebefehl') { - $this->ProcessWriteCommand($payload); - } - elseif ($topic === $baseTopic . '/Lesebefehl') { - $this->ProcessReadCommand(); - } - } - - // ------------------------------------------------------------------------- - // DATENFLUSS: Senden (SendDataToParent) - // ------------------------------------------------------------------------- - protected function SendMQTT($Topic, $Payload) - { - // Wir bauen das JSON Paket exakt so, wie der MQTT Splitter es erwartet - $Data = [ - 'DataID' => self::MQTT_TX_GUID, // Die ID für "MQTT Publish" - 'PacketType' => 3, // 3 = Publish - 'QualityOfService' => 0, - 'Retain' => false, - 'Topic' => $Topic, - 'Payload' => $Payload - ]; - - // Ab an den Parent (Splitter) - $this->SendDataToParent(json_encode($Data)); - } - - // ------------------------------------------------------------------------- - // Interne Logik - // ------------------------------------------------------------------------- - private function ProcessWriteCommand($jsonInput) - { - $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; - $this->SetValue('PowerSetpoint', $val); - } - if (isset($data['strategy'])) { - $this->SetValue('Strategy', $data['strategy']); + if (!isset($data['Topic']) || !isset($data['Payload'])) { + return; } - $this->ExecuteControlLogic(); - - $output = ['power_setpoint' => $this->GetValue('PowerSetpoint'), 'strategy' => $this->GetValue('Strategy')]; - $this->SendMQTT($this->ReadPropertyString('MQTTBaseTopic') . '/Writebefehl Antwort', json_encode($output)); - } - - private function ProcessReadCommand() - { - $safeGet = function($prop) { $id = $this->ReadPropertyInteger($prop); return IPS_VariableExists($id) ? GetValue($id) : 0; }; - - $mqttData = [ - '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)); - } - - private function ExecuteControlLogic() - { - $targetID = $this->ReadPropertyInteger('TargetControlVar'); - if (!IPS_VariableExists($targetID)) return; - - $strategy = $this->GetValue('Strategy'); - $setpoint = $this->GetValue('PowerSetpoint'); - $socID = $this->ReadPropertyInteger('SourceSoC'); - $soc = IPS_VariableExists($socID) ? GetValue($socID) : 0; - - if ($strategy == "activate") { - RequestAction($targetID, $setpoint); - } elseif ($strategy == "stop") { - RequestAction($targetID, 0); - } else { - if ((int)$soc == 50) RequestAction($targetID, 0); - elseif ($soc < 50) RequestAction($targetID, 2500); - else RequestAction($targetID, -2500); - } - } - - // Hilfsfunktion: IO Konfigurieren - private function ConfigureGateway() - { - $instance = IPS_GetInstance($this->InstanceID); - $parentId = $instance['ConnectionID']; - - if ($parentId > 0) { - $user = $this->ReadPropertyString('MQTTUser'); - $pass = $this->ReadPropertyString('MQTTPassword'); + // Sicherheitscheck: Ist es wirklich das gewünschte Topic? + if ($data['Topic'] === 'feedback-request/deviceOne') { - // Versuch 1: Parent ist direkt konfigurierbar (z.B. Schnittcher Splitter) - if (@IPS_GetProperty($parentId, 'Username') !== false) { - IPS_SetProperty($parentId, 'Username', $user); - IPS_SetProperty($parentId, 'Password', $pass); - IPS_ApplyChanges($parentId); - return; - } - - // Versuch 2: Standard Symcon Weg (Device -> Splitter -> IO) - // Wir müssen den IO unter dem Splitter finden - $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); - } - } + $this->SendDebug('VGT_Logic', 'Request received. Sending "Hello"...', 0); + + // Antwort senden + $this->Publish('feedback-response', 'Hello'); } } - public function RequestAction($Ident, $Value) { - $this->SetValue($Ident, $Value); - $this->ExecuteControlLogic(); + /* --------------------------------------------------------- + * MQTT HILFSFUNKTIONEN + * ---------------------------------------------------------*/ + + private function Subscribe(string $topic) + { + $payload = [ + 'DataID' => self::PARENT_GUID, + 'PacketType' => 3, // Achtung: Symcon intern ist Subscribe oft mapped, + // aber für den MQTT Client ist es Type 3 (Publish) oder spezial. + // Standard Symcon MQTT Client nutzt jedoch oft Type 8 für Subscribe. + // Hier nutzen wir den Standard Weg für JSON DataID: + ]; + + // Sende Subscribe Paket (Type 8 = Subscribe beim Client Splitter) + $this->SendDataToParent(json_encode([ + 'DataID' => self::PARENT_GUID, + 'PacketType' => 8, + 'QualityOfService' => 0, + 'Retain' => false, + 'Topic' => $topic, + 'Payload' => '' + ])); + + $this->SendDebug('MQTT', "Subscribed to $topic", 0); } - - public function MessageSink($TimeStamp, $SenderID, $Message, $Data) { - if ($Message == VM_UPDATE) $this->ExecuteControlLogic(); + + private function Publish(string $topic, string $payload) + { + // Sende Publish Paket (Type 3 = Publish) + $this->SendDataToParent(json_encode([ + 'DataID' => self::PARENT_GUID, + 'PacketType' => 3, + 'QualityOfService' => 0, + 'Retain' => false, + 'Topic' => $topic, + 'Payload' => $payload + ])); + + $this->SendDebug('MQTT', "Published to $topic: $payload", 0); } } ?> \ No newline at end of file