no message

This commit is contained in:
2026-02-10 15:21:37 +01:00
parent 851dc7d70e
commit 912e3ad3df
2 changed files with 71 additions and 184 deletions

View File

@@ -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"
}

View File

@@ -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);
}
}
?>