From fa8fa05e15d0c6084b378e9987e701da161fbbab Mon Sep 17 00:00:00 2001 From: "belevo\\mh" Date: Tue, 26 May 2026 09:39:54 +0200 Subject: [PATCH] no message --- MQTTBatterySDL/README.md | 67 ++++++++++ MQTTBatterySDL/form.json | 66 ++++++++++ MQTTBatterySDL/module.json | 28 +++++ MQTTBatterySDL/module.php | 248 +++++++++++++++++++++++++++++++++++++ 4 files changed, 409 insertions(+) create mode 100644 MQTTBatterySDL/README.md create mode 100644 MQTTBatterySDL/form.json create mode 100644 MQTTBatterySDL/module.json create mode 100644 MQTTBatterySDL/module.php diff --git a/MQTTBatterySDL/README.md b/MQTTBatterySDL/README.md new file mode 100644 index 0000000..e3abe65 --- /dev/null +++ b/MQTTBatterySDL/README.md @@ -0,0 +1,67 @@ +# Manager_1 +Beschreibung des Moduls. + +### Inhaltsverzeichnis + +1. [Funktionsumfang](#1-funktionsumfang) +2. [Voraussetzungen](#2-voraussetzungen) +3. [Software-Installation](#3-software-installation) +4. [Einrichten der Instanzen in IP-Symcon](#4-einrichten-der-instanzen-in-ip-symcon) +5. [Statusvariablen und Profile](#5-statusvariablen-und-profile) +6. [WebFront](#6-webfront) +7. [PHP-Befehlsreferenz](#7-php-befehlsreferenz) + +### 1. Funktionsumfang + +* + +### 2. Voraussetzungen + +- IP-Symcon ab Version 7.1 + +### 3. Software-Installation + +* Über den Module Store das 'Manager_1'-Modul installieren. +* Alternativ über das Module Control folgende URL hinzufügen + +### 4. Einrichten der Instanzen in IP-Symcon + + Unter 'Instanz hinzufügen' kann das 'Manager_1'-Modul mithilfe des Schnellfilters gefunden werden. + - Weitere Informationen zum Hinzufügen von Instanzen in der [Dokumentation der Instanzen](https://www.symcon.de/service/dokumentation/konzepte/instanzen/#Instanz_hinzufügen) + +__Konfigurationsseite__: + +Name | Beschreibung +-------- | ------------------ + | + | + +### 5. Statusvariablen und Profile + +Die Statusvariablen/Kategorien werden automatisch angelegt. Das Löschen einzelner kann zu Fehlfunktionen führen. + +#### Statusvariablen + +Name | Typ | Beschreibung +------ | ------- | ------------ + | | + | | + +#### Profile + +Name | Typ +------ | ------- + | + | + +### 6. WebFront + +Die Funktionalität, die das Modul im WebFront bietet. + +### 7. PHP-Befehlsreferenz + +`boolean GEF_BeispielFunktion(integer $InstanzID);` +Erklärung der Funktion. + +Beispiel: +`GEF_BeispielFunktion(12345);` \ No newline at end of file diff --git a/MQTTBatterySDL/form.json b/MQTTBatterySDL/form.json new file mode 100644 index 0000000..3efb1b6 --- /dev/null +++ b/MQTTBatterySDL/form.json @@ -0,0 +1,66 @@ +{ + "elements": [ + { + "type": "ValidationTextBox", + "name": "TopicSuffix", + "caption": "Topic Suffix / x" + }, + { + "type": "SelectVariable", + "name": "ReqActionID", + "caption": "Ausgabe-Variable / RequestAction" + }, + { + "type": "NumberSpinner", + "name": "TargetSoC", + "caption": "Ziel-Ladestand %", + "minimum": 0, + "maximum": 100 + }, + { + "type": "NumberSpinner", + "name": "ChargePower", + "caption": "Ladeleistung in W", + "minimum": 0, + "maximum": 100000 + }, + { + "type": "NumberSpinner", + "name": "DischargePower", + "caption": "Entladeleistung in W", + "minimum": 0, + "maximum": 100000 + }, + { + "type": "NumberSpinner", + "name": "MaxPowerSetpoint", + "caption": "Max. Power Setpoint", + "minimum": 0, + "maximum": 100000 + } + ], + "actions": [ + { + "type": "Button", + "caption": "Topics neu abonnieren", + "onClick": "MBSDL_Subscribe($id);" + } + ], + "status": [ + { + "code": 101, + "icon": "active", + "caption": "Instanz aktiv" + }, + { + "code": 102, + "icon": "inactive", + "caption": "Topic Suffix fehlt" + }, + { + "code": 103, + "icon": "error", + "caption": "Keine Ausgabe-Variable gewählt" + } + ] +} \ No newline at end of file diff --git a/MQTTBatterySDL/module.json b/MQTTBatterySDL/module.json new file mode 100644 index 0000000..1ec55e1 --- /dev/null +++ b/MQTTBatterySDL/module.json @@ -0,0 +1,28 @@ +{ + "id": "{1ED8ACD7-E679-EF98-07EE-E381148A768F}", + "author": "Mike", + "name": "MQTT Battery SDL", + "url": "", + "version": "1.0", + "build": 1, + "date": 20260518, + "modules": [ + { + "id": "{B6B6C1A8-1234-4F4A-9C77-222222222222}", + "name": "MQTT Battery SDL", + "type": 3, + "vendor": "Custom", + "aliases": [ + "MQTT Battery SDL" + ], + "parentRequirements": [ + "{C6D2AEB3-6E1F-4B2E-8E69-3A1A00246850}" + ], + "childRequirements": [], + "implemented": [ + "{D6F8C9A1-2A3B-4C5D-9E0F-333333333333}" + ], + "prefix": "MBSDL" + } + ] +} \ No newline at end of file diff --git a/MQTTBatterySDL/module.php b/MQTTBatterySDL/module.php new file mode 100644 index 0000000..20c2493 --- /dev/null +++ b/MQTTBatterySDL/module.php @@ -0,0 +1,248 @@ +RegisterPropertyString('TopicSuffix', ''); + + $this->RegisterPropertyInteger('ReqActionID', 0); + + $this->RegisterPropertyInteger('TargetSoC', 50); + $this->RegisterPropertyInteger('ChargePower', 2500); + $this->RegisterPropertyInteger('DischargePower', 2500); + $this->RegisterPropertyInteger('MaxPowerSetpoint', 10000); + + $this->RegisterVariableFloat('PowerProduction', 'Power Production', '', 10); + $this->RegisterVariableBoolean('IsReady', 'Is Ready', '', 20); + $this->RegisterVariableBoolean('IsRunning', 'Is Running', '', 30); + $this->RegisterVariableFloat('StateOfCharge', 'State of Charge', '', 40); + $this->RegisterVariableFloat('MinSoC', 'Min SoC', '', 50); + $this->RegisterVariableFloat('MaxSoC', 'Max SoC', '', 60); + + $this->RegisterVariableInteger('PowerSetpoint', 'Power Setpoint', '', 70); + $this->EnableAction('PowerSetpoint'); + + $this->RegisterVariableString('Strategy', 'Strategy', '', 80); + $this->EnableAction('Strategy'); + + $this->RegisterVariableString('LastReadResponse', 'Letzte Lese-Antwort', '', 90); + $this->RegisterVariableString('LastWriteResponse', 'Letzte Steuer-Antwort', '', 100); + + $this->ConnectParent('{C6D2AEB3-6E1F-4B2E-8E69-3A1A00246850}'); + } + + public function ApplyChanges() + { + parent::ApplyChanges(); + + if ($this->ReadPropertyString('TopicSuffix') == '') { + $this->SetStatus(102); + return; + } + + if ($this->ReadPropertyInteger('ReqActionID') <= 0) { + $this->SetStatus(103); + return; + } + + $this->SetStatus(101); + $this->Subscribe(); + } + + public function RequestAction($Ident, $Value) + { + switch ($Ident) { + case 'PowerSetpoint': + $max = $this->ReadPropertyInteger('MaxPowerSetpoint'); + + if ($Value > $max) { + $Value = $max; + } + + if ($Value < ($max * -1)) { + $Value = $max * -1; + } + + SetValue($this->GetIDForIdent('PowerSetpoint'), $Value); + $this->RunBatteryControl(); + break; + + case 'Strategy': + SetValueString($this->GetIDForIdent('Strategy'), $Value); + $this->RunBatteryControl(); + break; + } + } + + public function Subscribe() + { + $suffix = $this->ReadPropertyString('TopicSuffix'); + + if ($suffix == '') { + return; + } + + $topics = [ + 'feedback-request/' . $suffix, + 'remote-control-request/' . $suffix + ]; + + foreach ($topics as $topic) { + $this->SendDataToParent(json_encode([ + 'DataID' => '{D6F8C9A1-2A3B-4C5D-9E0F-333333333333}', + 'PacketType' => 8, + 'Topic' => $topic, + 'QoS' => 0 + ])); + } + } + + public function ReceiveData($JSONString) + { + $data = json_decode($JSONString, true); + + if (!is_array($data)) { + return; + } + + $topic = $data['Topic'] ?? ''; + $payload = $data['Buffer'] ?? ''; + + $suffix = $this->ReadPropertyString('TopicSuffix'); + + if ($topic == 'feedback-request/' . $suffix) { + $this->HandleReadRequest(); + return; + } + + if ($topic == 'remote-control-request/' . $suffix) { + $this->HandleRemoteControlRequest($payload); + return; + } + } + + private function Publish(string $topic, string $payload) + { + $this->SendDataToParent(json_encode([ + 'DataID' => '{D6F8C9A1-2A3B-4C5D-9E0F-333333333333}', + 'PacketType' => 3, + 'Topic' => $topic, + 'Payload' => $payload, + 'QoS' => 0, + 'Retain' => false + ])); + } + + private function HandleReadRequest() + { + $suffix = $this->ReadPropertyString('TopicSuffix'); + + $data = [ + 'power_production' => GetValue($this->GetIDForIdent('PowerProduction')), + 'is_ready' => GetValue($this->GetIDForIdent('IsReady')), + 'is_running' => GetValue($this->GetIDForIdent('IsRunning')), + 'state_of_charge' => GetValue($this->GetIDForIdent('StateOfCharge')), + 'min_soc' => GetValue($this->GetIDForIdent('MinSoC')), + 'max_soc' => GetValue($this->GetIDForIdent('MaxSoC')) + ]; + + $json = json_encode($data, JSON_PRETTY_PRINT); + + SetValueString($this->GetIDForIdent('LastReadResponse'), $json); + + $this->Publish('feedback-response/' . $suffix, $json); + } + + private function HandleRemoteControlRequest(string $payload) + { + if ($payload == '') { + return; + } + + $data = json_decode($payload, true); + + if (!is_array($data)) { + return; + } + + if (isset($data['power_setpoint'])) { + $max = $this->ReadPropertyInteger('MaxPowerSetpoint'); + $val = (int)$data['power_setpoint']; + + if ($val > $max) { + $val = $max; + } + + if ($val < ($max * -1)) { + $val = $max * -1; + } + + SetValue($this->GetIDForIdent('PowerSetpoint'), $val); + } + + if (isset($data['strategy'])) { + SetValueString($this->GetIDForIdent('Strategy'), (string)$data['strategy']); + } + + $this->RunBatteryControl(); + + $suffix = $this->ReadPropertyString('TopicSuffix'); + + $output = [ + 'power_setpoint' => GetValue($this->GetIDForIdent('PowerSetpoint')), + 'strategy' => GetValueString($this->GetIDForIdent('Strategy')) + ]; + + $json = json_encode($output, JSON_PRETTY_PRINT); + + SetValueString($this->GetIDForIdent('LastWriteResponse'), $json); + + $this->Publish('remote-control-response/' . $suffix, $json); + } + + private function RunBatteryControl() + { + $strategy = GetValueString($this->GetIDForIdent('Strategy')); + $setpoint = GetValue($this->GetIDForIdent('PowerSetpoint')); + $soc = GetValue($this->GetIDForIdent('StateOfCharge')); + + $targetSoC = $this->ReadPropertyInteger('TargetSoC'); + $chargePower = $this->ReadPropertyInteger('ChargePower'); + $dischargePower = $this->ReadPropertyInteger('DischargePower'); + $reqActionID = $this->ReadPropertyInteger('ReqActionID'); + + if ($reqActionID <= 0 || !IPS_ObjectExists($reqActionID)) { + $this->SendDebug('RunBatteryControl', 'Keine gültige ReqActionID gewählt', 0); + return; + } + + if ($strategy == 'activate') { + RequestAction($reqActionID, $setpoint * -1); + return; + } + + if ($strategy == 'stop') { + RequestAction($reqActionID, 0); + SetValue($this->GetIDForIdent('PowerProduction'), 0); + return; + } + + if ((int)$soc == $targetSoC) { + RequestAction($reqActionID, 0); + return; + } + + if ($soc < $targetSoC) { + RequestAction($reqActionID, abs($chargePower)); + return; + } + + if ($soc > $targetSoC) { + RequestAction($reqActionID, abs($dischargePower) * -1); + return; + } + } +} \ No newline at end of file