Files
Symcon_Belevo_Energiemanage…/Shelly_Parser_MQTT/module.php
2025-11-14 13:20:38 +01:00

354 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
require_once __DIR__ . '/libs/ShellyParser.php';
class Shelly_Parser_MQTT extends IPSModule
{
public function Create()
{
parent::Create();
// MQTT verbinden
$this->ConnectParent('{C6D2AEB3-6E1F-4B2E-8E69-3A1A00246850}');
// Alles abonnieren
$this->Subscribe('#');
}
public function ApplyChanges()
{
parent::ApplyChanges();
$this->ConnectParent('{C6D2AEB3-6E1F-4B2E-8E69-3A1A00246850}');
$this->Subscribe('#');
}
/* ---------------------------------------------------------
* DEBUG
* ---------------------------------------------------------*/
private function Log($title, $msg)
{
IPS_LogMessage("ShellyMQTT: $title", $msg);
$this->SendDebug($title, $msg, 0);
}
/* ---------------------------------------------------------
* MQTT SUBSCRIBE
* ---------------------------------------------------------*/
private function Subscribe(string $topic): void
{
$packet = [
'PacketType' => 8,
'QualityOfService' => 0,
'Retain' => false,
'Topic' => $topic,
'Payload' => ''
];
$this->SendDataToParent(json_encode([
'DataID' => '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}'
] + $packet));
$this->Log("Subscribe", $topic);
}
/* ---------------------------------------------------------
* MQTT PUBLISH
* ---------------------------------------------------------*/
private function Publish(string $topic, string $payload): void
{
$packet = [
'PacketType' => 3,
'QualityOfService' => 0,
'Retain' => false,
'Topic' => $topic,
'Payload' => $payload
];
$this->SendDataToParent(json_encode([
'DataID' => '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}'
] + $packet));
$this->Log("Publish", "$topic$payload");
}
/* ---------------------------------------------------------
* REQUEST ACTION (Schalten von Outputs)
* ---------------------------------------------------------*/
public function RequestAction($Ident, $Value)
{
$this->Log('RequestAction', "$Ident$Value");
if (!str_contains($Ident, '_output_')) {
throw new Exception("Unknown Ident: $Ident");
}
// Lokale Variable updaten
$varID = $this->FindVariableByIdent($Ident);
if ($varID > 0) {
SetValue($varID, $Value);
}
// Gerät + Index extrahieren
[$deviceID, $suffix] = explode('_output_', $Ident, 2);
$index = intval($suffix);
// Shelly RPC Topic
$topic = $deviceID . '/rpc';
// RPC JSON
$payload = json_encode([
'id' => 1,
'src' => 'ips',
'method' => 'Switch.Set',
'params' => [
'id' => $index,
'on' => (bool)$Value
]
]);
$this->Log("MQTT SEND", "$topic : $payload");
// Senden
$this->Publish($topic, $payload);
}
/* ---------------------------------------------------------
* RECEIVE MQTT
* ---------------------------------------------------------*/
public function ReceiveData($JSONString)
{
$this->Log('ReceiveData', $JSONString);
$data = json_decode($JSONString, true);
if (!is_array($data)) return;
$topic = $data['Topic'] ?? '';
$payload = $data['Payload'] ?? '';
if ($topic === '') return;
$this->Log('ReceiveTopic', "$topic$payload");
$parts = explode('/', $topic);
$deviceID = $parts[0] ?? '';
if ($deviceID === '') return;
// Online-Status
if (($parts[1] ?? '') === 'online') {
$this->HandleOnline($deviceID, $payload);
return;
}
// RPC Event
if (($parts[1] ?? '') === 'events' &&
($parts[2] ?? '') === 'rpc') {
$this->HandleRPC($deviceID, $payload);
return;
}
}
/* ---------------------------------------------------------
* ONLINE STATUS
* ---------------------------------------------------------*/
private function HandleOnline(string $deviceID, string $payload): void
{
$value = ($payload === 'true' || $payload === '1');
$this->Log("Online", "$deviceID = $value");
$varID = $this->EnsureBooleanVariable($deviceID, $deviceID . '_online', 'Online');
SetValue($varID, $value);
}
/* ---------------------------------------------------------
* RPC-HANDLING
* ---------------------------------------------------------*/
private function HandleRPC(string $deviceID, string $payload): void
{
$this->Log("RPC", "$deviceID : $payload");
$json = json_decode($payload, true);
if (!is_array($json)) return;
$src = $json['src'] ?? '';
if (!str_starts_with($src, 'shelly')) return;
// Typ
$type = ShellyParser::ExtractType($src);
$typeID = $this->EnsureStringVariable($deviceID, $deviceID . '_type', 'Typ');
SetValue($typeID, $type);
// Parameter
$params = $json['params'] ?? [];
$mapped = ShellyParser::MapParams($params);
// Outputs
foreach ($mapped['outputs'] as $index => $value) {
$ident = $deviceID . "_output_" . $index;
$varID = $this->EnsureBooleanVariable($deviceID, $ident, "Output $index");
SetValue($varID, $value);
}
// Inputs
foreach ($mapped['inputs'] as $index => $value) {
$ident = $deviceID . "_input_" . $index;
$varID = $this->EnsureBooleanVariable($deviceID, $ident, "Input $index");
SetValue($varID, $value);
}
// Temperatur
if ($mapped['temperature'] !== null) {
$tempID = $this->EnsureFloatVariable($deviceID, $deviceID . '_temperature', 'Temperatur');
SetValue($tempID, $mapped['temperature']);
}
}
/* ---------------------------------------------------------
* ACTIONSCRIPT UNSICHTBAR
* ---------------------------------------------------------*/
private function EnsureActionScript(): int
{
$scriptName = "~ShellyMQTT_Action_" . $this->InstanceID;
foreach (IPS_GetChildrenIDs($this->InstanceID) as $cid) {
$obj = IPS_GetObject($cid);
if ($obj['ObjectType'] === OBJECTTYPE_SCRIPT && $obj['ObjectName'] === $scriptName) {
return $cid;
}
}
// neues verstecktes Script
$scriptID = IPS_CreateScript(0);
IPS_SetName($scriptID, $scriptName);
IPS_SetParent($scriptID, $this->InstanceID);
// unsichtbar
IPS_SetHidden($scriptID, true);
IPS_SetPosition($scriptID, -9999);
// Scriptcode
$php = '<?php
$instance = ' . $this->InstanceID . ';
$object = IPS_GetObject($_IPS["VARIABLE"]);
$ident = $object["ObjectIdent"];
$value = $_IPS["VALUE"];
IPS_RequestAction($instance, $ident, $value);
?>';
IPS_SetScriptContent($scriptID, $php);
return $scriptID;
}
/* ---------------------------------------------------------
* VARIABLEN DYNAMISCH
* ---------------------------------------------------------*/
private function EnsureBooleanVariable(string $deviceID, string $ident, string $name): int
{
$folderID = $this->GetDeviceFolder($deviceID);
foreach (IPS_GetChildrenIDs($folderID) as $cid) {
$o = IPS_GetObject($cid);
if ($o['ObjectIdent'] === $ident) {
if (str_contains($ident, '_output_')) {
$actionScript = $this->EnsureActionScript();
IPS_SetVariableCustomAction($cid, $actionScript);
$var = IPS_GetVariable($cid);
if ($var['VariableProfile'] === '' && $var['VariableCustomProfile'] === '') {
IPS_SetVariableCustomProfile($cid, '~Switch');
}
}
return $cid;
}
}
// Neu erzeugen
$varID = IPS_CreateVariable(0);
IPS_SetIdent($varID, $ident);
IPS_SetName($varID, $name);
IPS_SetParent($varID, $folderID);
if (str_contains($ident, '_output_')) {
$actionScript = $this->EnsureActionScript();
IPS_SetVariableCustomAction($varID, $actionScript);
IPS_SetVariableCustomProfile($varID, '~Switch');
}
return $varID;
}
private function EnsureFloatVariable(string $deviceID, string $ident, string $name): int
{
$folderID = $this->GetDeviceFolder($deviceID);
foreach (IPS_GetChildrenIDs($folderID) as $cid) {
if (IPS_GetObject($cid)['ObjectIdent'] === $ident)
return $cid;
}
$id = $this->RegisterVariableFloat($ident, $name);
IPS_SetParent($id, $folderID);
return $id;
}
private function EnsureStringVariable(string $deviceID, string $ident, string $name): int
{
$folderID = $this->GetDeviceFolder($deviceID);
foreach (IPS_GetChildrenIDs($folderID) as $cid) {
if (IPS_GetObject($cid)['ObjectIdent'] === $ident)
return $cid;
}
$id = $this->RegisterVariableString($ident, $name);
IPS_SetParent($id, $folderID);
return $id;
}
/* ---------------------------------------------------------
* GERÄTE-ORDNER
* ---------------------------------------------------------*/
private function GetDeviceFolder(string $deviceID): int
{
$folderIdent = 'folder_' . $deviceID;
foreach (IPS_GetChildrenIDs($this->InstanceID) as $cid) {
if (IPS_GetObject($cid)['ObjectIdent'] === $folderIdent)
return $cid;
}
$folderID = IPS_CreateCategory();
IPS_SetParent($folderID, $this->InstanceID);
IPS_SetName($folderID, $deviceID);
IPS_SetIdent($folderID, $folderIdent);
return $folderID;
}
/* ---------------------------------------------------------
* VARIABLE FINDEN
* ---------------------------------------------------------*/
private function FindVariableByIdent(string $Ident)
{
foreach (IPS_GetChildrenIDs($this->InstanceID) as $folder) {
foreach (IPS_GetChildrenIDs($folder) as $cid) {
if (IPS_GetObject($cid)['ObjectIdent'] === $Ident) {
return $cid;
}
}
}
return 0;
}
}
?>