diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml new file mode 100644 index 0000000..23ef350 --- /dev/null +++ b/.github/workflows/style.yml @@ -0,0 +1,16 @@ +name: Check Style + +on: [push, pull_request] + +jobs: + style: + runs-on: ubuntu-latest + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + - name: Checkout module + uses: actions/checkout@master + - name: Check style + uses: Nall-chan/action-style@master diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..e6563d0 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,14 @@ +name: Run Tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout module + uses: actions/checkout@master + with: + submodules: true + - name: Run tests + uses: symcon/action-tests@master diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3aba2ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +<<<<<<< HEAD +/docs/ +======= +docs/ +>>>>>>> 4236f2949deef76002a8103f5f36eaf5e39b2d66 +.vscode/settings.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..304d376 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "libs/helper"] + path = libs/helper + url = https://github.com/Nall-chan/SymconModulHelper +[submodule ".style"] + path = .style + url = https://github.com/Nall-chan/StylePHP +[submodule "tests/stubs"] + path = tests/stubs + url = https://github.com/Nall-chan/SymconStubs +[submodule ".vscode"] + path = .vscode + url = https://github.com/Nall-chan/SymconVSCTasks.git diff --git a/.style b/.style new file mode 160000 index 0000000..ccb6319 --- /dev/null +++ b/.style @@ -0,0 +1 @@ +Subproject commit ccb631937353df3a0c61fe19de0f60c31b8cb190 diff --git a/.vscode b/.vscode new file mode 160000 index 0000000..672a096 --- /dev/null +++ b/.vscode @@ -0,0 +1 @@ +Subproject commit 672a096abc8fcc6afa43f4aedd47821b5583b4c5 diff --git a/KLF200Configurator/README.md b/KLF200Configurator/README.md new file mode 100644 index 0000000..f984ccd --- /dev/null +++ b/KLF200Configurator/README.md @@ -0,0 +1,127 @@ +[![SDK](https://img.shields.io/badge/Symcon-PHPModul-red.svg?style=flat-square)](https://www.symcon.de/service/dokumentation/entwicklerbereich/sdk-tools/sdk-php/) +[![Version](https://img.shields.io/badge/Modul%20Version-0.80-blue.svg?style=flat-square)](https://community.symcon.de/t/modul-velux-klf200/50429) +[![Version](https://img.shields.io/badge/Symcon%20Version-5.5%20%3E-green.svg?style=flat-square)](https://www.symcon.de/service/dokumentation/installation/migrationen/v54-v55-q4-2020/) +[![License](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-green.svg?style=flat-square)](https://creativecommons.org/licenses/by-nc-sa/4.0/) +[![Check Style](https://github.com/Nall-chan/VeluxKLF200/workflows/Check%20Style/badge.svg)](https://github.com/Nall-chan/VeluxKLF200/actions) [![Run Tests](https://github.com/Nall-chan/VeluxKLF200/workflows/Run%20Tests/badge.svg)](https://github.com/Nall-chan/VeluxKLF200/actions) +[![Spenden](https://www.paypalobjects.com/de_DE/DE/i/btn/btn_donate_SM.gif)](#3-spenden) +[![Wunschliste](https://img.shields.io/badge/Wunschliste-Amazon-ff69fb.svg)](#3-spenden) + +# Velux KLF200 Configurator + +## 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. Gateway Kommandos](#6-gateway-kommandos) +- [7. PHP-Befehlsreferenz](#7-php-befehlsreferenz) +- [8. Aktionen](#8-aktionen) +- [9. Anhang](#9-anhang) + - [1. Changelog](#1-changelog) + - [2. Spenden](#2-spenden) +- [10. Lizenz](#10-lizenz) + + +## 1. Funktionsumfang + + - Auslesen und darstellen aller vom Gateway bekannten Geräte (Nodes). + - Einfaches Anlegen von neuen Instanzen in IPS. + - Anlernen und löschen von Nodes im Gateway. + +## 2. Voraussetzungen + + - IPS ab Version 5.5 + - KLF200 io-homecontrol® Gateway + - KLF muss per LAN angeschlossen sein + - KLF Firmware 2.0.0.71 oder neuer + +## 3. Software-Installation + +* Dieses Modul ist Bestandteil der [VeluxKLF200-Library](../README.md#3-software-installation). + +## 4. Einrichten der Instanzen in IP-Symcon + +Eine einfache Einrichtung ist über diese Instanz möglich. +Bei der installation aus dem Store wird das anlegen der Instanz automatisch angeboten. + +In den sich öffneden Konfigurationsfenstern ist das Passwort und die IP-Adresse bzw. der Hostname einzutragen. +``Das Standardkennwort ist dasselbe wie das auf der Rückseite des KLF200 angegebene WLAN-Kennwort.`` + +Bei der manuellen Einrichtung ist das Modul im Dialog `Instanz hinzufügen` unter den Hersteller `VELUX` zu finden. +![Instanz hinzufügen](../imgs/instanzen.png) + +Alternativ ist es auch in der Liste alle Konfiguratoren aufgeführt. +![Instanz hinzufügen](../imgs/instanzen_configurator.png) + +Es wird automatisch eine `KLF200 Gateway` Instanz erzeugt, wenn noch keine vorhanden ist. +Werden in dem sich öffnenden Konfigurationsformular keine Geräte angezeigt, so ist zuerst die IO-Instanz korrekt zu konfigurieren. +Diese kann über die Schaltfläche `Gateway konfigurieren` und dann `Schnittstelle konfigurieren` erreicht werden. + +Ist die Gateway-Instanz korrekt verbunden, wird beim öffnen des Konfigurator folgendender Dialog angezeigt. +![Konfigurator](../imgs/conf_configurator.png) + +Über das selektieren eines Eintrages in der Tabelle und betätigen des dazugehörigen `Erstellen` Button, +können Instanzen in IPS angelegt werden. + +## 5. Statusvariablen und Profile + +Dieses Modul erstellt keine Statusvariablen und Profile. + +## 6. Gateway Kommandos + +In dem Konfigurator sind über den ersten Eintrag die Gateway Kommandos erreichbar. +Mit den dort vorhandenen Schaltflächen können Geräte (Nodes) an dem Gateway an- un ab gelernt werden, sowie das Gateway selber neugestartet werden. +Durch die Schaltfläche `Suche Gerät`, wird die Gerätesuche gestartet. Alle Geräte welche sich im Anlern-Modus befinden werden hierdurch an das Gateway angelernt. +![Konfigurator](../imgs/conf_configurator1.png) + +Über die Schaltfläche `Entferne Gerät` wird eine Liste mit allen im Gateway vorhandenen Geräten (Nodes) angezeigt. +Durch auswählen eines Eintrages und betätigen von `Entferne Gerät` wird das Gerät aus dem Gateway gelöscht. +![Konfigurator](../imgs/conf_configurator2.png) + +## 7. PHP-Befehlsreferenz + +**Folgende Funktionen liefern `TRUE` bei Erfolg. +Im Fehlerfall wird eine Warnung erzeugt und `FALSE` zurückgegeben.** + +```php +bool KLF200_DiscoveryNodes(int $InstanzeID); +``` +Sucht nach neuen Geräten und lernte Diese am Gateway an. + +```php +bool KLF200_RemoveNode(int $InstanzeID, int $Node); +``` +Entfernt das angelernte Gerät mit der in '$Node' übergebenen NodeId aus dem Gateway. + +```php +bool KLF200_RebootGateway(int $InstanzeID); +``` +Startet das Gateway KLF200 neu. + + +## 8. Aktionen + +Es gibt keine speziellen Aktionen für dieses Modul. + +## 9. Anhang + +### 1. Changelog + +[Changelog der Library](../README.md#2-changelog) + +### 2. Spenden + + Die Library ist für die nicht kommerzielle Nutzung kostenlos, Schenkungen als Unterstützung für den Autor werden hier akzeptiert: + + + +[![Wunschliste](https://img.shields.io/badge/Wunschliste-Amazon-ff69fb.svg)](https://www.amazon.de/hz/wishlist/ls/YU4AI9AQT9F?ref_=wl_share) + + +## 10. Lizenz + + IPS-Modul: + [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) + \ No newline at end of file diff --git a/KLF200Configurator/form.json b/KLF200Configurator/form.json new file mode 100644 index 0000000..6c03827 --- /dev/null +++ b/KLF200Configurator/form.json @@ -0,0 +1,162 @@ +{ + "actions": [ + { + "type": "ExpansionPanel", + "caption": "Gateway commands", + "items": [ + { + "type": "RowLayout", + "name": "GatewayCommands", + "items": [ + { + "type": "Button", + "label": "Discover device", + "onClick": "KLF200_DiscoveryNodes($id);" + }, + { + "type": "PopupButton", + "caption": "Remove device", + "popup": { + "caption": "Select device to remove", + "items": [ + { + "type": "Button", + "label": "Remove device", + "onClick": "" + }, + { + "type": "List", + "name": "RemoveNode", + "add": false, + "delete": false, + "sort": { + "column": "nodeid", + "direction": "ascending" + }, + "columns": [ + { + "caption": "Node ID", + "name": "nodeid", + "width": "80px" + }, + { + "caption": "Type", + "name": "type", + "width": "200px" + }, + { + "caption": "Name", + "name": "name", + "width": "auto" + } + ], + "values": [] + }, + { + "type": "ProgressBar", + "name": "ProgressRemove", + "current": 100, + "indeterminate": true, + "visible": false, + "caption": "Waiting for finish remove." + } + ] + } + }, + { + "type": "Button", + "label": "Reboot Gateway", + "onClick": "" + }, + { + "type": "Button", + "label": "Refresh Devicelist", + "onClick": "" + } + ] + }, + { + "type": "RowLayout", + "name": "GatewayProgress", + "items": [ + { + "type": "ProgressBar", + "name": "ProgressLearn", + "current": 100, + "indeterminate": true, + "visible": false, + "caption": "Waiting for finish discovery." + } + ] + } + ] + }, + { + "type": "Configurator", + "name": "Config", + "caption": "", + "add": false, + "delete": true, + "sort": { + "column": "nodeid", + "direction": "ascending" + }, + "columns": [ + { + "caption": "Node ID", + "name": "nodeid", + "width": "80px" + }, + { + "caption": "Type", + "name": "type", + "width": "300px" + }, + { + "caption": "Name", + "name": "name", + "width": "300px" + }, + { + "caption": "Location", + "name": "location", + "width": "auto" + } + ], + "values": [] + }, + { + "type": "PopupAlert", + "name": "AlertPopup", + "visible": false, + "popup": { + "items": [ + { + "name": "AlertText", + "type": "Label", + "caption": "" + } + ] + } + }, + { + "type": "Label", + "caption": "This module is free for non-commercial use,\r\nDonations in support of the author are accepted here:" + }, + { + "type": "RowLayout", + "items": [ + { + "type": "Image", + "onClick": "echo 'https://www.paypal.com/donate?hosted_button_id=G2SLW2MEMQZH2';", + "image": "data:image/png;base64,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ZrBplgJXJ/3OA0do9FRbgAMdMwKlYjyPei1SAu5FhYGVXbSC6ZrRTMbTv5gMZgem2CeF2pdEB7F4vKvJLQHbFVdgbjvNQ5hAROuNbZCJ3eQ9wA2Kap2BBiCGPI2CdgT4rRDKUR5bdW9b2UVR2sEOmm6jcYFzmCH/HBOJg6lrJexUsVgqxjYaS9H3H8vcToYpe4BkVAfESru141QF7kfZPtkcVXvPQs1t/w64CLgUS/PakslZB9yssEVMyN2SXIvAFlU2S+O7jtDcAI4AawT2ZEvoilINoZJX/RJwMzV9at7kVoDtkz12FSGWqw+FfLgUeDOW3czFUsj1wG+wghWYvjghedfDtXcFjeUo4fOYQeQwz7VRYWcrP5k5bj6/Wfi93ezdt5dAyCPMwvZkKZk0nWw4Sc4LZmMG9YrM5R8jvBflyOPx7WFyVCwgs4AOhDGNjJALVdm1i4EPPOvxmt4JOW4MoCX0bSKYb1qOicd0erUbeAPwD5WArj5zCi948tHqbwKf4MEomKtdTWNufS3oRsAXP8WTxgDCmkLt84lLaHT9m4DrQY6bz86PF540BoCAKidjojT9YUoR6CP1IYczzpPDAK78O8kBzNuwL2yU8Y9Hf4RlEsdSl/i/oZUfhT5xhFrGpjdjYq+Wez8M3IkyTNv0T84cx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Gmx/8AQ1faHO6uaXsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTgtMDUtMjVUMTI6NDA6MTArMDI6MDB4/FYOAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE4LTA1LTI1VDEyOjQwOjEwKzAyOjAwCaHusgAAAEZ0RVh0c29mdHdhcmUASW1hZ2VNYWdpY2sgNi43LjgtOSAyMDE2LTA2LTE2IFExNiBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9yZ+a/NLYAAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAYdEVYdFRodW1iOjpJbWFnZTo6aGVpZ2h0ADUxMsDQUFEAAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgANTEyHHwD3AAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxNTI3MjQ0ODEwnCKKMAAAABN0RVh0VGh1bWI6OlNpemUAMTIuOEtCQhoRQ10AAABCdEVYdFRodW1iOjpVUkkAZmlsZTovLy4vdXBsb2Fkcy81Ni9rSWZ4SUF5LzE0ODcvODM5My1wYXlwYWxfMTAyNDYxLnBuZ6xvuXsAAAAASUVORK5CYII=" + }, + { + "type": "Image", + "onClick": "echo 'https://www.amazon.de/hz/wishlist/ls/YU4AI9AQT9F?ref_=wl_share';", + "image": "data:image/png;base64,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" + } + ] + } + ] +} \ No newline at end of file diff --git a/KLF200Configurator/locale.json b/KLF200Configurator/locale.json new file mode 100644 index 0000000..a32db96 --- /dev/null +++ b/KLF200Configurator/locale.json @@ -0,0 +1,33 @@ +{ + "translations": { + "de": { + "Gateway commands": "Gateway Kommandos", + "Discover device": "Suche Gerät", + "Remove device": "Entferne Gerät", + "Select device to remove": "Gerät zum Entfernen auswählen", + "Reboot Gateway": "Gateway neustarten", + "Refresh Devicelist": "Aktualisiere Geräteliste", + "Type": "Typ", + "Zone": "Zone", + "Name": "Name", + "Location": "Lokation", + "Instance has no active parent.": "Instanz hat keinen aktiven Parent.", + "Splitter has no IO instance.": "Splitter hat keine IO Instanz.", + "%s out of range.": "%s außerhalb des zulässigen Bereiches.", + "The view will reload after discovery is finished.": "Die Ansicht wird neu geladen, sobald die Suche abgeschlossen wurde.", + "The view will reload after remove is finished.": "Die Ansicht wird neu geladen, nachdem das Entfernen beendet wurde.", + "The KLF200 will now reboot.": "Das KLF200 wird jetzt neustarten.", + "No error.": "Kein Fehler.", + "Timeout.": "Zeitüberschreitung.", + "Waiting for finish remove.": "Warte auf entfernen.", + "Waiting for finish discovery.": "Warte auf Gerätesuche.", + "Not further defined error.": "Nicht weiter definierter Fehler.", + "Unknown command or command is not accepted at this state.": "Unbekanntes Kommando oder Kommando wird zu diesem Zeitpunkt nicht akzeptiert.", + "Error on Frame Structure.": "Fehler in Frame Struktur.", + "Busy. Try again later.": "Beschäftigt. Später nochmal versuchen.", + "Bad system table index.": "Ungültiger Index der Systemtabelle.", + "Not authenticated.": "Nicht berechtigt.", + "This module is free for non-commercial use,\r\nDonations in support of the author are accepted here:": "Dieses Modul ist für die nicht kommerzielle Nutzung kostenlos,\r\nSchenkungen als Unterstützung für den Autor werden hier akzeptiert:" + } + } +} \ No newline at end of file diff --git a/KLF200Configurator/module.json b/KLF200Configurator/module.json new file mode 100644 index 0000000..d13f2c1 --- /dev/null +++ b/KLF200Configurator/module.json @@ -0,0 +1,18 @@ +{ + "id": "{38724E6E-8202-4D37-9FA7-BDD2EDA79520}", + "name": "KLF200 Configurator", + "type": 4, + "vendor": "VELUX", + "aliases": [ + "KLF200 Configurator" + ], + "parentRequirements": [ + "{7B0F87CC-0408-4283-8E0E-2D48141E42E8}" + ], + "childRequirements": [], + "implemented": [ + "{5242DAEF-EEBD-441F-AB0B-E83C01475B65}" + ], + "prefix": "KLF200", + "url": "https://github.com/Nall-chan/VeluxKLF200/blob/master/KLF200Configurator/README.md" +} \ No newline at end of file diff --git a/KLF200Configurator/module.php b/KLF200Configurator/module.php new file mode 100644 index 0000000..ade12d5 --- /dev/null +++ b/KLF200Configurator/module.php @@ -0,0 +1,429 @@ +' . file_get_contents(__DIR__ . '/../libs/helper/BufferHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Configurator {?>' . file_get_contents(__DIR__ . '/../libs/helper/SemaphoreHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Configurator {?>' . file_get_contents(__DIR__ . '/../libs/helper/DebugHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Configurator {?>' . file_get_contents(__DIR__ . '/../libs/helper/ParentIOHelper.php') . '}'); + +/** + * @property array $Nodes + * @property bool $GetNodeInfoIsRunning + */ +class KLF200Configurator extends IPSModule +{ + use \KLF200Configurator\Semaphore, + \KLF200Configurator\BufferHelper, + \KLF200Configurator\DebugHelper, + \KLF200Configurator\InstanceStatus { + \KLF200Configurator\InstanceStatus::MessageSink as IOMessageSink; + \KLF200Configurator\InstanceStatus::RequestAction as IORequestAction; + \KLF200Configurator\DebugHelper::SendDebug as SendDebug2; + } + + /** + * Interne Funktion des SDK. + */ + public function Create() + { + parent::Create(); + $this->ConnectParent('{725D4DF6-C8FC-463C-823A-D3481A3D7003}'); + $this->GetNodeInfoIsRunning = false; + $this->Nodes = []; + $this->ParentID = 0; + } + + /** + * Interne Funktion des SDK. + */ + public function ApplyChanges() + { + $this->RegisterMessage(0, IPS_KERNELSTARTED); + $this->RegisterMessage($this->InstanceID, FM_CONNECT); + $this->RegisterMessage($this->InstanceID, FM_DISCONNECT); + + parent::ApplyChanges(); + $APICommands = [ + \KLF200\APICommand::GET_ALL_GROUPS_INFORMATION_NTF, + \KLF200\APICommand::GET_ALL_GROUPS_INFORMATION_FINISHED_NTF, + \KLF200\APICommand::GET_ALL_NODES_INFORMATION_NTF, + \KLF200\APICommand::GET_ALL_NODES_INFORMATION_FINISHED_NTF, + \KLF200\APICommand::GET_SCENE_INFORMATION_NTF, + \KLF200\APICommand::GET_SCENE_LIST_NTF, + \KLF200\APICommand::CS_DISCOVER_NODES_NTF, + \KLF200\APICommand::CS_SYSTEM_TABLE_UPDATE_NTF + ]; + + if (count($APICommands) > 0) { + foreach ($APICommands as $APICommand) { + $Lines[] = '.*"Command":' . $APICommand . '.*'; + } + $Line = implode('|', $Lines); + $this->SetReceiveDataFilter('(' . $Line . ')'); + $this->SendDebug('FILTER', $Line, 0); + } + if (IPS_GetKernelRunlevel() == KR_READY) { + $this->KernelReady(); + } + + } + + /** + * Interne Funktion des SDK. + */ + public function MessageSink($TimeStamp, $SenderID, $Message, $Data) + { + $this->IOMessageSink($TimeStamp, $SenderID, $Message, $Data); + switch ($Message) { + case IPS_KERNELSTARTED: + $this->KernelReady(); + break; + } + } + + public function RequestAction($Ident, $Value) + { + if ($this->IORequestAction($Ident, $Value)) { + return true; + } + if ($Ident == 'GetAllNodesInformation') { + if ($Value) { + if ($this->GetAllNodesInformation()) { + while ($this->GetNodeInfoIsRunning) { + IPS_Sleep(10); + } + return true; + } + } else { + return $this->GetAllNodesInformation(); + } + } + return false; + } + + public function DiscoveryNodes() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::CS_DISCOVER_NODES_REQ, "\x00"); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData->isError()) { + trigger_error($this->Translate($ResultAPIData->ErrorToString()), E_USER_NOTICE); + return false; + } + $this->UpdateFormField('GatewayCommands', 'visible', false); + $this->UpdateFormField('Config', 'visible', false); + $this->UpdateFormField('ProgressLearn', 'visible', true); + return true; + } + + public function RebootGateway() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::REBOOT_REQ); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData->isError()) { + trigger_error($this->Translate($ResultAPIData->ErrorToString()), E_USER_NOTICE); + return false; + } + return true; + } + + public function RemoveNode(int $Node) + { + if (($Node < 0) || ($Node > 199)) { + trigger_error(sprintf($this->Translate('%s out of range.'), 'Node'), E_USER_NOTICE); + return false; + } + $this->UpdateFormField('ProgressRemove', 'visible', true); + $this->UpdateFormField('RemoveNode', 'visible', false); + $Data = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + $Data[intdiv($Node, 8)] = chr(1 << ($Node % 8)); + $APIData = new \KLF200\APIData(\KLF200\APICommand::CS_REMOVE_NODES_REQ, $Data); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData->isError()) { + trigger_error($this->Translate($ResultAPIData->ErrorToString()), E_USER_NOTICE); + return false; + } + //PopupRemove + //$this->UpdateFormField('ProgressRemove', 'visible', false); + //$this->UpdateFormField('RemoveNode', 'visible', true); + return true; + } + + public function GetConfigurationForm() + { + $Form = json_decode(file_get_contents(__DIR__ . '/form.json'), true); + $NodeValues = []; + if (!$this->HasActiveParent()) { + $Form['actions'][2]['visible'] = true; + $Form['actions'][2]['popup']['items'][0]['caption'] = 'Instance has no active parent.'; + $Form['actions'][0]['items'][0]['visible'] = false; + } else { + $Splitter = IPS_GetInstance($this->InstanceID)['ConnectionID']; + $IO = IPS_GetInstance($Splitter)['ConnectionID']; + if ($IO == 0) { + $Form['actions'][2]['visible'] = true; + $Form['actions'][2]['popup']['items'][0]['caption'] = 'Splitter has no IO instance.'; + } else { + $NodeValues = $this->GetNodeConfigFormValues($Splitter); + } + } + $Form['actions'][1]['values'] = $NodeValues; + + /* $Form['actions'][0]['items'][0]['items'][0]['onClick'] = <<<'EOT' + if(KLF200_DiscoveryNodes($id)){ + echo + EOT . ' "' . $this->Translate('The view will reload after discovery is finished.') . '";}'; + */ + $DeleteNodeValues = $this->GetDeleteNodeConfigFormValues(); + $Form['actions'][0]['items'][0]['items'][1]['popup']['items'][1]['values'] = $DeleteNodeValues; + $Form['actions'][0]['items'][0]['items'][1]['popup']['items'][0]['onClick'] = <<<'EOT' + if (is_int($RemoveNode['nodeid'])){ + KLF200_RemoveNode($id,$RemoveNode['nodeid']); + } else { + EOT . ' echo "' . $this->Translate('Nothing selected.') . '";}'; + + $Form['actions'][0]['items'][0]['items'][2]['onClick'] = <<<'EOT' + if(KLF200_RebootGateway($id)){ + echo + EOT . ' "' . $this->Translate('The KLF200 will now reboot.') . '";}'; + + $Form['actions'][0]['items'][0]['items'][3]['onClick'] = <<<'EOT' + IPS_RequestAction($id,'GetAllNodesInformation',true); + EOT; + + $this->SendDebug('FORM', json_encode($Form), 0); + $this->SendDebug('FORM', json_last_error_msg(), 0); + return json_encode($Form); + } + + public function ReceiveData($JSONString) + { + $APIData = new \KLF200\APIData($JSONString); + $this->SendDebug('Event', $APIData, 1); + $this->ReceiveEvent($APIData); + } + + /** + * Wird ausgeführt wenn der Kernel hochgefahren wurde. + */ + protected function KernelReady() + { + $this->RegisterParent(); + if ($this->HasActiveParent()) { + $this->IOChangeState(IS_ACTIVE); + } + } + + /** + * Wird über den Trait InstanceStatus ausgeführt wenn sich der Status des Parent ändert. + * Oder wenn sich die Zuordnung zum Parent ändert. + * + * @param int $State Der neue Status des Parent. + */ + protected function IOChangeState($State) + { + if ($State == IS_ACTIVE) { + $this->UpdateFormField('GatewayCommands', 'visible', true); + $this->GetAllNodesInformation(); + } else { + $this->Nodes = []; + $NodeValues = []; + $Splitter = IPS_GetInstance($this->InstanceID)['ConnectionID']; + if ($Splitter > 0) { + $NodeValues = $this->GetNodeConfigFormValues($Splitter); + } + $this->UpdateFormField('Config', 'values', json_encode($NodeValues)); + $this->UpdateFormField('RemoveNode', 'values', json_encode([])); + $this->UpdateFormField('GatewayCommands', 'visible', false); + } + } + + protected function UpdateFormField($Name, $Field, $Value) + { + $this->SendDebug('Form: ' . $Name . '.' . $Field, $Value, 0); + parent::UpdateFormField($Name, $Field, $Value); + } + + protected function SendDebug($Message, $Data, $Format) + { + if (is_a($Data, '\\KLF200\\APIData')) { + /* @var $Data \KLF200\APIData */ + $this->SendDebug2($Message . ':Command', \KLF200\APICommand::ToString($Data->Command), 0); + if ($Data->isError()) { + $this->SendDebug2('Error', $Data->ErrorToString(), 0); + } elseif ($Data->Data != '') { + $this->SendDebug2($Message . ':Data', $Data->Data, $Format); + } + } else { + $this->SendDebug2($Message, $Data, $Format); + } + } + + private function ReceiveEvent(\KLF200\APIData $APIData) + { + switch ($APIData->Command) { + case \KLF200\APICommand::CS_DISCOVER_NODES_NTF: + if (!$this->GetNodeInfoIsRunning) { + IPS_RunScriptText('IPS_RequestAction(' . $this->InstanceID . ',"GetAllNodesInformation",false);'); + } + break; + case \KLF200\APICommand::CS_SYSTEM_TABLE_UPDATE_NTF: + sleep(3); + if (!$this->GetNodeInfoIsRunning) { + IPS_RunScriptText('IPS_RequestAction(' . $this->InstanceID . ',"GetAllNodesInformation",false);'); + } + break; + case \KLF200\APICommand::GET_ALL_NODES_INFORMATION_NTF: + $NodeID = ord($APIData->Data[0]); + $Name = trim(substr($APIData->Data, 4, 64)); + $NodeTypeSubType = unpack('n', substr($APIData->Data, 69, 2))[1]; + $this->SendDebug('NodeID', $NodeID, 0); + $this->SendDebug('Name', $Name, 0); + $this->SendDebug('NodeTypeSubType', $NodeTypeSubType, 0); + $this->SendDebug('SerialNumber', substr($APIData->Data, 76, 8), 1); + $this->SendDebug('BuildNumber', ord($APIData->Data[75]), 0); + $Nodes = $this->Nodes; + $Nodes[$NodeID] = [ + 'Name' => $Name, + 'NodeTypeSubType' => $NodeTypeSubType + ]; + $this->Nodes = $Nodes; + break; + case \KLF200\APICommand::GET_ALL_NODES_INFORMATION_FINISHED_NTF: + $Splitter = IPS_GetInstance($this->InstanceID)['ConnectionID']; + $NodeValues = $this->GetNodeConfigFormValues($Splitter); + $this->UpdateFormField('Config', 'values', json_encode($NodeValues)); + $this->UpdateFormField('Config', 'visible', true); + $this->UpdateFormField('GatewayCommands', 'visible', true); + $this->UpdateFormField('ProgressLearn', 'visible', false); + $DeleteNodeValues = $this->GetDeleteNodeConfigFormValues(); + $this->UpdateFormField('RemoveNode', 'values', json_encode($DeleteNodeValues)); + $this->UpdateFormField('RemoveNode', 'visible', true); + $this->UpdateFormField('ProgressRemove', 'visible', false); + $this->GetNodeInfoIsRunning = false; + break; + } + } + + private function GetInstanceList(string $GUID, int $Parent, string $ConfigParam) + { + $InstanceIDList = []; + foreach (IPS_GetInstanceListByModuleID($GUID) as $InstanceID) { + // Fremde Geräte überspringen + if (IPS_GetInstance($InstanceID)['ConnectionID'] == $Parent) { + $InstanceIDList[] = $InstanceID; + } + } + if ($ConfigParam != '') { + $InstanceIDList = array_flip(array_values($InstanceIDList)); + array_walk($InstanceIDList, [$this, 'GetConfigParam'], $ConfigParam); + } + return $InstanceIDList; + } + + private function GetConfigParam(&$item1, $InstanceID, $ConfigParam) + { + $item1 = IPS_GetProperty($InstanceID, $ConfigParam); + } + + private function GetAllNodesInformation() + { + $this->Nodes = []; + + $APIData = new \KLF200\APIData(\KLF200\APICommand::GET_ALL_NODES_INFORMATION_REQ); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData->isError()) { + return false; + } + $this->GetNodeInfoIsRunning = true; + return ord($ResultAPIData->Data[0]) == 1; + } + + /** + * Interne Funktion des SDK. + */ + private function GetNodeConfigFormValues(int $Splitter) + { + $FoundNodes = $this->Nodes; + $this->SendDebug('Found Nodes', $FoundNodes, 0); + $InstanceIDListNodes = $this->GetInstanceList('{4EBD07B1-2962-4531-AC5F-7944789A9CE5}', $Splitter, 'NodeId'); + $this->SendDebug('IPS Nodes', $InstanceIDListNodes, 0); + $NodeValues = []; + foreach ($FoundNodes as $NodeID => $Node) { + $InstanceIDNode = array_search($NodeID, $InstanceIDListNodes); + if ($InstanceIDNode !== false) { + $AddValue = [ + 'instanceID' => $InstanceIDNode, + 'nodeid' => $NodeID, + 'name' => IPS_GetName($InstanceIDNode), + 'type' => \KLF200\Node::$SubType[$Node['NodeTypeSubType']], + 'location' => stristr(IPS_GetLocation($InstanceIDNode), IPS_GetName($InstanceIDNode), true) + ]; + unset($InstanceIDListNodes[$InstanceIDNode]); + } else { + $AddValue = [ + 'instanceID' => 0, + 'nodeid' => $NodeID, + 'name' => $Node['Name'], + 'type' => \KLF200\Node::$SubType[$Node['NodeTypeSubType']], + 'location' => '' + ]; + } + $AddValue['create'] = [ + 'moduleID' => '{4EBD07B1-2962-4531-AC5F-7944789A9CE5}', + 'configuration' => ['NodeId' => $NodeID], + 'location' => ['Velux KLF200'] + ]; + + $NodeValues[] = $AddValue; + } + + foreach ($InstanceIDListNodes as $InstanceIDNode => $Node) { + $NodeValues[] = [ + 'instanceID' => $InstanceIDNode, + 'nodeid' => $Node, + 'name' => IPS_GetName($InstanceIDNode), + 'type' => 'unknown', + 'location' => stristr(IPS_GetLocation($InstanceIDNode), IPS_GetName($InstanceIDNode), true) + ]; + } + return $NodeValues; + } + + private function GetDeleteNodeConfigFormValues() + { + $NodeValues = []; + foreach ($this->Nodes as $NodeID => $Node) { + $AddValue = [ + 'nodeid' => $NodeID, + 'name' => $Node['Name'], + 'type' => \KLF200\Node::$SubType[$Node['NodeTypeSubType']] + ]; + $NodeValues[] = $AddValue; + } + + return $NodeValues; + } + + private function SendAPIData(\KLF200\APIData $APIData) + { + $this->SendDebug('ForwardData', $APIData, 1); + + try { + if (!$this->HasActiveParent()) { + throw new Exception($this->Translate('Instance has no active parent.'), E_USER_NOTICE); + } + /** @var \KLF200\APIData $ResponseAPIData */ + $ret = @$this->SendDataToParent($APIData->ToJSON('{7B0F87CC-0408-4283-8E0E-2D48141E42E8}')); + $ResponseAPIData = @unserialize($ret); + $this->SendDebug('Response', $ResponseAPIData, 1); + return $ResponseAPIData; + } catch (Exception $exc) { + $this->SendDebug('Error', $exc->getMessage(), 0); + return new \KLF200\APIData(\KLF200\APICommand::ERROR_NTF, chr(\KLF200\ErrorNTF::TIMEOUT)); + } + } +} diff --git a/KLF200Gateway/README.md b/KLF200Gateway/README.md new file mode 100644 index 0000000..2df763a --- /dev/null +++ b/KLF200Gateway/README.md @@ -0,0 +1,140 @@ +[![SDK](https://img.shields.io/badge/Symcon-PHPModul-red.svg?style=flat-square)](https://www.symcon.de/service/dokumentation/entwicklerbereich/sdk-tools/sdk-php/) +[![Version](https://img.shields.io/badge/Modul%20Version-0.80-blue.svg?style=flat-square)](https://community.symcon.de/t/modul-velux-klf200/50429) +[![Version](https://img.shields.io/badge/Symcon%20Version-5.5%20%3E-green.svg?style=flat-square)](https://www.symcon.de/service/dokumentation/installation/migrationen/v54-v55-q4-2020/) +[![License](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-green.svg?style=flat-square)](https://creativecommons.org/licenses/by-nc-sa/4.0/) +[![Check Style](https://github.com/Nall-chan/VeluxKLF200/workflows/Check%20Style/badge.svg)](https://github.com/Nall-chan/VeluxKLF200/actions) [![Run Tests](https://github.com/Nall-chan/VeluxKLF200/workflows/Run%20Tests/badge.svg)](https://github.com/Nall-chan/VeluxKLF200/actions) +[![Spenden](https://www.paypalobjects.com/de_DE/DE/i/btn/btn_donate_SM.gif)](#3-spenden) +[![Wunschliste](https://img.shields.io/badge/Wunschliste-Amazon-ff69fb.svg)](#3-spenden) + +# Velux KLF200 Gateway + +## 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) +- [8. Aktionen](#8-aktionen) +- [9. Anhang](#9-anhang) + - [1. Changelog](#1-changelog) + - [2. Spenden](#2-spenden) +- [10. Lizenz](#10-lizenz) + + +## 1. Funktionsumfang + + - Bindeglied zwischen Netzwerk und IPS-Instanzen vom Type Node und Konfigurator. + - Auslesen und darstellen von Zuständes des KLF200. + +## 2. Voraussetzungen + + - IPS ab Version 5.2 + - KLF200 io-homecontrol® Gateway + - KLF muss per LAN angeschlossen sein + - KLF Firmware 2.0.0.71 oder neuer + +## 3. Software-Installation + +* Dieses Modul ist Bestandteil der [VeluxKLF200-Library](../README.md#3-software-installation). + +## 4. Einrichten der Instanzen in IP-Symcon + +Eine einfache Einrichtung ist über die im Objektbaum unter `Konfigurator Instanzen` zu findene Instanz [KLF200 Konfigurator](../KLF200Configurator/README.md) möglich. + +Bei der manuellen Einrichtung ist das Modul im Dialog `Instanz hinzufügen` unter den Hersteller `VELUX` zu finden. +![Instanz hinzufügen](../imgs/instanzen.png) + +Alternativ ist es auch in der Liste alle Splitter aufgeführt. +![Instanz hinzufügen](../imgs/instanzen_splitter.png) + +In dem sich öffnenden Konfigurationsformular ist das Passwort einzutragen. +``Das Standardkennwort ist dasselbe wie das auf der Rückseite des KLF200 angegebene WLAN-Kennwort.`` + +## 5. Statusvariablen und Profile + +![Objektbaum Splitter](../imgs/logbaum_splitter.png) + +**Statusvariablen:** + +| Name | Typ | Ident | Beschreibung | +| :--------------- | :------ | :-------------- | :------------------------------------------------------ | +| Hardware Version | integer | HardwareVersion | Hardware-Version des KLF200 Gateways. | +| Firmware Version | string | FirmwareVersion | Firmware-Version des KLF200 Gateways. | +| Protocol Version | string | ProtocolVersion | Version des aktuell unterstützen Protokolls des KLF200. | + + +**Profile:** + +Dieses Modul erstellt keine Profile. + +## 6. WebFront + +Sollen die vorhandene Statusvariablen im WebFront angezeigt werden, so müssen diese verlinkt werden. + +## 7. PHP-Befehlsreferenz + +**Folgende Funktionen liefern 'TRUE' bei Erfolg. +Im Fehlerfall wird eine Warnung erzeugt und 'FALSE' zurückgegeben.** + +```php +bool KLF200_RequestGatewayVersion(int $InstanzeID); +``` +Liest die Hard- und Firmware Version des Gateways, und speichert das Ergebnis in den Statusvariablen. +Wird automatisch beim Verbinden mit dem Gateway ausgeführt. + +```php +bool KLF200_RequestProtocolVersion(int $InstanzeID); +``` +Liest die Protokoll Version des Gateways, und speichert das Ergebnis in der Statusvariable. +Wird automatisch beim Verbinden mit dem Gateway ausgeführt. + +```php +bool KLF200_SetGatewayTime(int $InstanzeID); +``` +Schreibt die UTC Zeit in das Gateway. +Wird automatisch beim Verbinden mit dem Gateway ausgeführt. + +```php +array KLF200_GetGatewayTime(int $InstanzeID); +``` +Liest die aktuelle Zeit aus dem Gateway, und liefert ein Array mit folgenden Feldern. +Es wird keine lokale Uhrzeit, und keine Sommerzeit unterstützt. Das bool-Feld 'DaylightSavingFlag' wird immer false sein. +```php + 'Timestamp', + 'Second', + 'Minute', + 'Hour', + 'DayOfMonth', + 'Month', + 'Year', + 'WeekDay', + 'DayOfYear', + 'DaylightSavingFlag' +``` + +## 8. Aktionen + +Es gibt keine speziellen Aktionen für dieses Modul. + +## 9. Anhang + +### 1. Changelog + +[Changelog der Library](../README.md#2-changelog) + +### 2. Spenden + + Die Library ist für die nicht kommerzielle Nutzung kostenlos, Schenkungen als Unterstützung für den Autor werden hier akzeptiert: + + + +[![Wunschliste](https://img.shields.io/badge/Wunschliste-Amazon-ff69fb.svg)](https://www.amazon.de/hz/wishlist/ls/YU4AI9AQT9F?ref_=wl_share) + + +## 10. Lizenz + + IPS-Modul: + [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) \ No newline at end of file diff --git a/KLF200Gateway/form.json b/KLF200Gateway/form.json new file mode 100644 index 0000000..57fe044 --- /dev/null +++ b/KLF200Gateway/form.json @@ -0,0 +1,73 @@ +{ + "elements": [ + { + "name": "Password", + "type": "PasswordTextBox", + "caption": "Password", + "validate": "^\\S{1,31}$" + } + ], + "actions": [ + { + "type": "Button", + "caption": "Request KLF version", + "onClick": "KLF200_RequestGatewayVersion($id);" + }, + { + "type": "Button", + "caption": "Request KLF state", + "onClick": "KLF200_ReadGatewayState($id,'ALL');" + }, + { + "type": "Label", + "caption": "This module is free for non-commercial use,\r\nDonations in support of the author are accepted here:" + }, + { + "type": "RowLayout", + "items": [ + { + "type": "Image", + "onClick": "echo 'https://www.paypal.com/donate?hosted_button_id=G2SLW2MEMQZH2';", + "image": "data:image/png;base64,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ZrBplgJXJ/3OA0do9FRbgAMdMwKlYjyPei1SAu5FhYGVXbSC6ZrRTMbTv5gMZgem2CeF2pdEB7F4vKvJLQHbFVdgbjvNQ5hAROuNbZCJ3eQ9wA2Kap2BBiCGPI2CdgT4rRDKUR5bdW9b2UVR2sEOmm6jcYFzmCH/HBOJg6lrJexUsVgqxjYaS9H3H8vcToYpe4BkVAfESru141QF7kfZPtkcVXvPQs1t/w64CLgUS/PakslZB9yssEVMyN2SXIvAFlU2S+O7jtDcAI4AawT2ZEvoilINoZJX/RJwMzV9at7kVoDtkz12FSGWqw+FfLgUeDOW3czFUsj1wG+wghWYvjghedfDtXcFjeUo4fOYQeQwz7VRYWcrP5k5bj6/Wfi93ezdt5dAyCPMwvZkKZk0nWw4Sc4LZmMG9YrM5R8jvBflyOPx7WFyVCwgs4AOhDGNjJALVdm1i4EPPOvxmt4JOW4MoCX0bSKYb1qOicd0erUbeAPwD5WArj5zCi948tHqbwKf4MEomKtdTWNufS3oRsAXP8WTxgDCmkLt84lLaHT9m4DrQY6bz86PF540BoCAKidjojT9YUoR6CP1IYczzpPDAK78O8kBzNuwL2yU8Y9Hf4RlEsdSl/i/oZUfhT5xhFrGpjdjYq+Wez8M3IkyTNv0T84cx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Gmx/8AQ1faHO6uaXsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTgtMDUtMjVUMTI6NDA6MTArMDI6MDB4/FYOAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE4LTA1LTI1VDEyOjQwOjEwKzAyOjAwCaHusgAAAEZ0RVh0c29mdHdhcmUASW1hZ2VNYWdpY2sgNi43LjgtOSAyMDE2LTA2LTE2IFExNiBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9yZ+a/NLYAAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAYdEVYdFRodW1iOjpJbWFnZTo6aGVpZ2h0ADUxMsDQUFEAAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgANTEyHHwD3AAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxNTI3MjQ0ODEwnCKKMAAAABN0RVh0VGh1bWI6OlNpemUAMTIuOEtCQhoRQ10AAABCdEVYdFRodW1iOjpVUkkAZmlsZTovLy4vdXBsb2Fkcy81Ni9rSWZ4SUF5LzE0ODcvODM5My1wYXlwYWxfMTAyNDYxLnBuZ6xvuXsAAAAASUVORK5CYII=" + }, + { + "type": "Image", + "onClick": "echo 'https://www.amazon.de/hz/wishlist/ls/YU4AI9AQT9F?ref_=wl_share';", + "image": "data:image/png;base64,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" + } + ] + } + ], + "status": [ + { + "code": 102, + "icon": "active", + "caption": "Connected" + }, + { + "code": 104, + "icon": "inactive", + "caption": "Interface closed" + }, + { + "code": 201, + "icon": "error", + "caption": "Authentication error" + }, + { + "code": 202, + "icon": "error", + "caption": "Handshake error" + }, + { + "code": 203, + "icon": "error", + "caption": "Connection lost" + }, + { + "code": 204, + "icon": "error", + "caption": "Configuration error" + } + ] +} \ No newline at end of file diff --git a/KLF200Gateway/locale.json b/KLF200Gateway/locale.json new file mode 100644 index 0000000..5e2d70b --- /dev/null +++ b/KLF200Gateway/locale.json @@ -0,0 +1,38 @@ +{ + "translations": { + "de": { + "Active": "Aktiv", + "Password": "Passwort", + "Connected": "Verbunden", + "Interface closed": "Verbindung geschlossen", + "Connection lost": "Verbindung verloren", + "Handshake error": "Fehler im Handshake", + "Authentication error": "Authentifizierungsfehler", + "Request KLF version": "Lese KLF Version", + "Request KLF state": "Lese KLF Status", + "No error.": "Kein Fehler.", + "Timeout.": "Zeitüberschreitung.", + "Not further defined error.": "Nicht weiter definierter Fehler.", + "Unknown command or command is not accepted at this state.": "Unbekanntes Kommando oder Kommando wird zu diesem Zeitpunkt nicht akzeptiert.", + "Error on Frame Structure.": "Fehler in Frame Struktur.", + "Busy. Try again later.": "Beschäftigt. Später nochmal versuchen.", + "Bad system table index.": "Ungültiger Index der Systemtabelle.", + "Not authenticated.": "Nicht berechtigt.", + "Command Version": "Kommando Version", + "Protocol Version": "Protokoll Version", + "Send is blocked for: ": "Senden ist blockiert für: ", + "Socket not connected": "Socket nicht verbunden", + "Successfully connected to KLF200.": "Erfolgreich mit KLF200 verbunden.", + "Parameter is unable to execute.": "Parameter kann nicht ausgeführt werden.", + "Execution has failed.": "Die Ausführung ist fehlgeschlagen.", + "not used.": "nicht benutzt", + "waiting for power.": "Warten auf Strom.", + "Executing.": "Ausführen.", + "done.": "fertig.", + "unknown.": "unbekannt.", + "Execution is completed with no errors.": "Ausführung wurde ohne Fehler beendet.", + "Execution is still active.": "Die Ausführung ist noch aktiv.", + "This module is free for non-commercial use,\r\nDonations in support of the author are accepted here:": "Dieses Modul ist für die nicht kommerzielle Nutzung kostenlos,\r\nSchenkungen als Unterstützung für den Autor werden hier akzeptiert:" + } + } +} \ No newline at end of file diff --git a/KLF200Gateway/module.json b/KLF200Gateway/module.json new file mode 100644 index 0000000..e6d14f0 --- /dev/null +++ b/KLF200Gateway/module.json @@ -0,0 +1,21 @@ +{ + "id": "{725D4DF6-C8FC-463C-823A-D3481A3D7003}", + "name": "KLF200 Gateway", + "type": 2, + "vendor": "VELUX", + "aliases": [ + "KLF200 Gateway" + ], + "parentRequirements": [ + "{79827379-F36E-4ADA-8A95-5F8D1DC92FA9}" + ], + "childRequirements": [ + "{5242DAEF-EEBD-441F-AB0B-E83C01475B65}" + ], + "implemented": [ + "{018EF6B5-AB94-40C6-AA53-46943E824ACF}", + "{7B0F87CC-0408-4283-8E0E-2D48141E42E8}" + ], + "prefix": "KLF200", + "url": "https://github.com/Nall-chan/VeluxKLF200/blob/master/KLF200Gateway/README.md" +} \ No newline at end of file diff --git a/KLF200Gateway/module.php b/KLF200Gateway/module.php new file mode 100644 index 0000000..2c0e953 --- /dev/null +++ b/KLF200Gateway/module.php @@ -0,0 +1,549 @@ +' . file_get_contents(__DIR__ . '/../libs/helper/BufferHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Gateway {?>' . file_get_contents(__DIR__ . '/../libs/helper/SemaphoreHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Gateway {?>' . file_get_contents(__DIR__ . '/../libs/helper/DebugHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Gateway {?>' . file_get_contents(__DIR__ . '/../libs/helper/ParentIOHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Gateway {?>' . file_get_contents(__DIR__ . '/../libs/helper/VariableHelper.php') . '}'); + +/* + * @addtogroup klf200 + * @{ + * + * @package KLF200 + * @file module.php + * @author Michael Tröger + * @copyright 2020 Michael Tröger + * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC BY-NC-SA 4.0 + * @version 1.0 + */ + +/** + * KLF200Gateway Klasse implementiert die KLF 200 API + * Erweitert IPSModule. + * + * @author Michael Tröger + * @copyright 2020 Michael Tröger + * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC BY-NC-SA 4.0 + * + * @version 1.0 + * + * @example Ohne + * + * @property string $Host + * @property string $ReceiveBuffer + * @property APIData $ReceiveAPIData + * @property APIData $ReplyAPIData + * @property array $Nodes + * @property int $WaitForNodes + * @property int $SessionId + */ +class KLF200Gateway extends IPSModule +{ + use \KLF200Gateway\Semaphore, + \KLF200Gateway\BufferHelper, + \KLF200Gateway\DebugHelper, + \KLF200Gateway\VariableHelper, + \KLF200Gateway\InstanceStatus { + \KLF200Gateway\InstanceStatus::MessageSink as IOMessageSink; + \KLF200Gateway\InstanceStatus::RegisterParent as IORegisterParent; + \KLF200Gateway\InstanceStatus::RequestAction as IORequestAction; + \KLF200Gateway\DebugHelper::SendDebug as SendDebug2; + } + + /** + * Interne Funktion des SDK. + */ + public function Create() + { + parent::Create(); + $this->RequireParent('{3CFF0FD9-E306-41DB-9B5A-9D06D38576C3}'); + $this->RegisterPropertyString('Password', ''); + $this->RegisterTimer('KeepAlive', 0, 'KLF200_ReadGatewayState($_IPS[\'TARGET\']);'); + $this->Host = ''; + $this->ReceiveBuffer = ''; + $this->ReplyAPIData = null; + $this->Nodes = []; + $this->SessionId = 1; + $this->ParentID = 0; + } + + /** + * Interne Funktion des SDK. + */ + public function MessageSink($TimeStamp, $SenderID, $Message, $Data) + { + $this->IOMessageSink($TimeStamp, $SenderID, $Message, $Data); + switch ($Message) { + case IPS_KERNELSTARTED: + $this->KernelReady(); + break; + // case IPS_KERNELSHUTDOWN: + //$this->SendDisconnect(); + // Todo + // break; + } + } + + public function RequestAction($Ident, $Value) + { + if ($this->IORequestAction($Ident, $Value)) { + return true; + } + return false; + } + + /** + * Interne Funktion des SDK. + */ + public function GetConfigurationForParent() + { + $Config = [ + 'Port' => 51200, + 'UseSSL' => true, + 'VerifyPeer'=> false, + 'VerifyHost'=> true + ]; + return json_encode($Config); + } + + /** + * Interne Funktion des SDK. + */ + public function ApplyChanges() + { + $this->RegisterMessage(0, IPS_KERNELSTARTED); + $this->RegisterMessage(0, IPS_KERNELSHUTDOWN); + + $this->RegisterMessage($this->InstanceID, FM_CONNECT); + $this->RegisterMessage($this->InstanceID, FM_DISCONNECT); + + parent::ApplyChanges(); + $this->RegisterVariableString('FirmwareVersion', $this->Translate('Firmware Version'), '', 0); + $this->RegisterVariableInteger('HardwareVersion', $this->Translate('Hardware Version'), '', 0); + $this->RegisterVariableString('ProtocolVersion', $this->Translate('Protocol Version'), '', 0); + if (IPS_GetKernelRunlevel() == KR_READY) { + $this->KernelReady(); + } + + } + + public function ReadGatewayState() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::GET_STATE_REQ); + //$APIData = new \KLF200\APIData(\KLF200\APICommand::GET_SCENE_LIST_REQ); + $ResultAPIData = $this->SendAPIData($APIData); + //todo + // brauchen wir state? Oder substate? + /* + Command Data 1 Data 2 Data 3 – 6 + GW_GET_STATE_CFM GatewayState SubState StateData + + GatewayState value Description + 0 Test mode. + 1 Gateway mode, no actuator nodes in the system table. + 2 Gateway mode, with one or more actuator nodes in the system table. + 3 Beacon mode, not configured by a remote controller. + 4 Beacon mode, has been configured by a remote controller. + 5 - 255 Reserved. + + SubState value, when + GatewayState is 1 or 2 Description + 0x00 Idle state. + 0x01 Performing task in Configuration Service handler + 0x02 Performing Scene Configuration + 0x03 Performing Information Service Configuration. + 0x04 Performing Contact input Configuration. + 0x?? In Contact input Learn state. ??? + 0x80 Performing task in Command Handler + 0x81 Performing task in Activate Group Handler + 0x82 Performing task in Activate Scene Handler + */ + } + + public function RequestGatewayVersion() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::GET_VERSION_REQ); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData->isError()) { + return false; + } + $this->SetValueString('FirmwareVersion', ord($ResultAPIData->Data[1]) . '.' . + ord($ResultAPIData->Data[2]) . '.' . + ord($ResultAPIData->Data[3]) . '.' . + ord($ResultAPIData->Data[4])); + $this->SetValueInteger('HardwareVersion', ord($ResultAPIData->Data[6])); + return true; + } + + public function RequestProtocolVersion() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::GET_PROTOCOL_VERSION_REQ); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData->isError()) { + return false; + } + $this->SetValueString( + 'ProtocolVersion', + unpack('n', substr($ResultAPIData->Data, 0, 2))[1] . '.' . + unpack('n', substr($ResultAPIData->Data, 2, 2))[1] + ); + return true; + } + + public function SetGatewayTime() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::SET_UTC_REQ, pack('N', time())); + $ResultAPIData = $this->SendAPIData($APIData); + return !$ResultAPIData->isError(); + } + + public function GetGatewayTime() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::GET_LOCAL_TIME_REQ); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData->isError()) { + return false; + } + $Result = [ + 'Timestamp' => unpack('N', substr($ResultAPIData->Data, 0, 4))[1], + 'Second' => ord($ResultAPIData->Data[4]), + 'Minute' => ord($ResultAPIData->Data[5]), + 'Hour' => ord($ResultAPIData->Data[6]), + 'DayOfMonth' => ord($ResultAPIData->Data[7]), + 'Month' => 1 + ord($ResultAPIData->Data[8]), + 'Year' => 1900 + unpack('n', substr($ResultAPIData->Data, 9, 2))[1], + 'WeekDay' => ord($ResultAPIData->Data[11]), + 'DayOfYear' => unpack('n', substr($ResultAPIData->Data, 12, 2))[1], + 'DaylightSavingFlag' => unpack('c', $ResultAPIData->Data[14])[1] + ]; + return $Result; + } + + //################# DATAPOINTS CHILDREN + + /** + * Interne Funktion des SDK. Nimmt Daten von Children entgegen und sendet Diese weiter. + * + * @param string $JSONString + * @result bool true wenn Daten gesendet werden konnten, sonst false. + */ + public function ForwardData($JSONString) + { + if ($this->GetStatus() != IS_ACTIVE) { + return serialize(new \KLF200\APIData(\KLF200\APICommand::ERROR_NTF, chr(\KLF200\ErrorNTF::TIMEOUT))); + } + $APIData = new \KLF200\APIData($JSONString); + $result = @$this->SendAPIData($APIData); + return serialize($result); + } + + //################# DATAPOINTS PARENT + + /** + * Empfängt Daten vom Parent. + * + * @param string $JSONString Das empfangene JSON-kodierte Objekt vom Parent. + * @result bool True wenn Daten verarbeitet wurden, sonst false. + */ + public function ReceiveData($JSONString) + { + $data = json_decode($JSONString); + $this->DecodeSLIPData(utf8_decode($data->Buffer)); + } + + public function RebootGateway() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::REBOOT_REQ); + $ResultAPIData = $this->SendAPIData($APIData); + return !$ResultAPIData->isError(); + } + + /** + * Wird ausgeführt wenn der Kernel hochgefahren wurde. + */ + protected function KernelReady() + { + $this->ReceiveBuffer = ''; + $this->ReplyAPIData = null; + $this->Nodes = []; + $this->RegisterParent(); + if ($this->HasActiveParent()) { + $this->IOChangeState(IS_ACTIVE); + } else { + $this->IOChangeState(IS_INACTIVE); + } + } + + protected function RegisterParent() + { + $IOId = $this->IORegisterParent(); + if ($IOId > 0) { + $this->Host = IPS_GetProperty($this->ParentID, 'Host'); + $this->SetSummary(IPS_GetProperty($IOId, 'Host')); + } else { + $this->Host = ''; + $this->SetSummary(('none')); + } + return $IOId; + } + + /** + * Wird über den Trait InstanceStatus ausgeführt wenn sich der Status des Parent ändert. + * Oder wenn sich die Zuordnung zum Parent ändert. + * + * @param int $State Der neue Status des Parent. + */ + protected function IOChangeState($State) + { + if ($State == IS_ACTIVE) { + if ($this->Connect()) { + $this->SetTimerInterval('KeepAlive', 600000); + $this->LogMessage($this->Translate('Successfully connected to KLF200.'), KL_NOTIFY); + $this->SessionId = 1; + $this->RequestProtocolVersion(); + $this->SetGatewayTime(); + $this->ReadGatewayState(); + $this->RequestGatewayVersion(); + $this->SetHouseStatusMonitor(); + } else { + $this->SetTimerInterval('KeepAlive', 0); + } + } else { + $this->SetTimerInterval('KeepAlive', 0); + $this->SetStatus(IS_INACTIVE); + } + } + + protected function SendDebug($Message, $Data, $Format) + { + if (is_a($Data, '\\KLF200\\APIData')) { + /* @var $Data \KLF200\APIData */ + $this->SendDebug2($Message . ':Command', \KLF200\APICommand::ToString($Data->Command), 0); + if ($Data->isError()) { + $this->SendDebug2('Error', $Data->ErrorToString(), 0); + } elseif ($Data->Data != '') { + $this->SendDebug2($Message . ':Data', $Data->Data, $Format); + } + } else { + $this->SendDebug2($Message, $Data, $Format); + } + } + + /* + public function GetSystemTable() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::CS_GET_SYSTEMTABLE_DATA_REQ); + $ResultAPIData = $this->SendAPIData($APIData); + //$this->lock('SendAPIData'); + // wait for finish + // 01 00 3A DC 1C 03 C0 1C 01 00 00 00 00 + //$this->unlock('SendAPIData'); + } + + public function GetSceneList() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::GET_SCENE_LIST_REQ); + $ResultAPIData = $this->SendAPIData($APIData); + } + */ + private function SetHouseStatusMonitor() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::HOUSE_STATUS_MONITOR_ENABLE_REQ); + $ResultAPIData = $this->SendAPIData($APIData); + return !$ResultAPIData->isError(); + } + + //################# PRIVATE + private function ReceiveEvent(\KLF200\APIData $APIData) + { + $this->SendAPIDataToChildren($APIData); + } + + private function Connect() + { + if (strlen($this->ReadPropertyString('Password')) > 31) { + $this->SetStatus(IS_EBASE + 4); + return false; + } + + $APIData = new \KLF200\APIData(\KLF200\APICommand::PASSWORD_ENTER_REQ, str_pad($this->ReadPropertyString('Password'), 32, "\x00")); + $ResultAPIData = $this->SendAPIData($APIData, false); + if ($ResultAPIData === false) { + $this->SetStatus(IS_EBASE + 2); + return false; + } + if ($ResultAPIData->isError()) { + $this->SetStatus(IS_EBASE + 3); + trigger_error($this->Translate($ResultAPIData->ErrorToString()), E_USER_NOTICE); + return false; + } + if ($ResultAPIData->Data != "\x00") { + $this->SendDebug('Login Error', '', 0); + $this->SetStatus(IS_EBASE + 1); + $this->LogMessage('Access denied', KL_ERROR); + return false; + } + $this->SendDebug('Login successfully', '', 0); + $this->SetStatus(IS_ACTIVE); + return true; + } + + /** + * Sendet die Events an die Children. + * + * @param \KLF200\APIData $APIData + */ + private function SendAPIDataToChildren(\KLF200\APIData $APIData) + { + $this->SendDataToChildren($APIData->ToJSON('{5242DAEF-EEBD-441F-AB0B-E83C01475B65}')); + } + + private function DecodeSLIPData($SLIPData) + { + $SLIPData = $this->ReceiveBuffer . $SLIPData; + $this->SendDebug('Input SLIP Data', $SLIPData, 1); + $Start = strpos($SLIPData, chr(0xc0)); + if ($Start === false) { + $this->SendDebug('ERROR', 'SLIP Start Marker not found', 0); + $this->ReceiveBuffer = ''; + return false; + } + if ($Start != 0) { + $this->SendDebug('WARNING', 'SLIP start is ' . $Start . ' and not 0', 0); + } + $End = strpos($SLIPData, chr(0xc0), 1); + if ($End === false) { + $this->SendDebug('WAITING', 'SLIP End Marker not found', 0); + $this->ReceiveBuffer = $SLIPData; + return false; + } + $TransportData = str_replace( + ["\xDB\xDC", "\xDB\xDD"], + ["\xC0", "\xDB"], + substr($SLIPData, $Start + 1, $End - $Start - 1) + ); + $Tail = substr($SLIPData, $End + 1); + $this->ReceiveBuffer = $Tail; + if (ord($TransportData[0]) != 0) { + $this->SendDebug('ERROR', 'Wrong ProtocolID', 0); + return false; + } + $len = ord($TransportData[1]) + 2; + if (strlen($TransportData) != $len) { + $this->SendDebug('ERROR', 'Wrong frame length', 0); + return false; + } + $Checksum = substr($TransportData, -1); + $ChecksumData = substr($TransportData, 0, -1); + //todo Checksum + $Command = unpack('n', substr($TransportData, 2, 2))[1]; + $Data = substr($TransportData, 4, $len - 5); + $APIData = new \KLF200\APIData($Command, $Data); + if ($APIData->isEvent()) { + $this->SendDebug('Event', $APIData, 1); + $this->ReceiveEvent($APIData); + } else { + $this->ReplyAPIData = $APIData; + } + if (strpos($Tail, chr(0xc0)) !== false) { + $this->SendDebug('Tail hast Start Marker', '', 0); + $this->DecodeSLIPData(''); + } + } + + /** + * Wartet auf eine Antwort einer Anfrage an den LMS. + * + * @param string $APICommand + * @result mixed + */ + private function ReadReplyAPIData() + { + for ($i = 0; $i < 2000; $i++) { + $Buffer = $this->ReplyAPIData; + if (!is_null($Buffer)) { + $this->ReplyAPIData = null; + return $Buffer; + } + usleep(1000); + } + return null; + } + + //################# SENDQUEUE + + private function GetSessionId() + { + $SessionId = ($this->SessionId + 1) & 0xffff; + $this->SessionId = $SessionId; + return pack('n', $SessionId); + } + + private function SendAPIData(\KLF200\APIData $APIData, bool $SetState = true) + { + //Statt SessionId benutzen wir einfach NodeID. + /* if (in_array($APIData->Command, [ + \KLF200\APICommand::COMMAND_SEND_REQ, + \KLF200\APICommand::STATUS_REQUEST_REQ, + \KLF200\APICommand::WINK_SEND_REQ, + \KLF200\APICommand::SET_LIMITATION_REQ, + \KLF200\APICommand::GET_LIMITATION_STATUS_REQ, + \KLF200\APICommand::MODE_SEND_REQ, + \KLF200\APICommand::ACTIVATE_SCENE_REQ, + \KLF200\APICommand::STOP_SCENE_REQ, + \KLF200\APICommand::ACTIVATE_PRODUCTGROUP_REQ + ])) { + $APIData->Data = $this->GetSessionId() . $APIData->Data; + } */ + try { + $this->SendDebug('Wait to send', $APIData, 1); + $time = microtime(true); + while (true) { + if ($this->lock('SendAPIData')) { + break; + } + if (microtime(true) - $time > 5) { + throw new Exception($this->Translate('Send is blocked for: ') . \KLF200\APICommand::ToString($APIData->Command), E_USER_ERROR); + } + } + if (!$this->HasActiveParent()) { + throw new Exception($this->Translate('Socket not connected'), E_USER_NOTICE); + } + $Data = $APIData->GetSLIPData(); + $this->SendDebug('Send', $APIData, 1); + $this->SendDebug('Send SLIP Data', $Data, 1); + $JSON['DataID'] = '{79827379-F36E-4ADA-8A95-5F8D1DC92FA9}'; + $JSON['Buffer'] = utf8_encode($Data); + $JsonString = json_encode($JSON); + $this->ReplyAPIData = null; + parent::SendDataToParent($JsonString); + $ResponseAPIData = $this->ReadReplyAPIData(); + + if ($ResponseAPIData === null) { + throw new Exception($this->Translate('Timeout.'), E_USER_NOTICE); + } + $this->SendDebug('Response', $ResponseAPIData, 1); + $this->unlock('SendAPIData'); + if ($ResponseAPIData->isError()) { + trigger_error($this->Translate($ResponseAPIData->ErrorToString()), E_USER_NOTICE); + } + return $ResponseAPIData; + } catch (Exception $exc) { + $this->SendDebug('Error', $exc->getMessage(), 0); + if ($exc->getCode() != E_USER_ERROR) { + $this->unlock('SendAPIData'); + } + trigger_error($this->Translate($exc->getMessage()), E_USER_NOTICE); + if ($SetState) { + $this->SetStatus(IS_EBASE + 3); + } + return new \KLF200\APIData(\KLF200\APICommand::ERROR_NTF, chr(\KLF200\ErrorNTF::TIMEOUT)); + } + } +} + +/* @} */ diff --git a/KLF200Node/README.md b/KLF200Node/README.md new file mode 100644 index 0000000..5f8c881 --- /dev/null +++ b/KLF200Node/README.md @@ -0,0 +1,260 @@ +[![SDK](https://img.shields.io/badge/Symcon-PHPModul-red.svg?style=flat-square)](https://www.symcon.de/service/dokumentation/entwicklerbereich/sdk-tools/sdk-php/) +[![Version](https://img.shields.io/badge/Modul%20Version-0.80-blue.svg?style=flat-square)](https://community.symcon.de/t/modul-velux-klf200/50429) +[![Version](https://img.shields.io/badge/Symcon%20Version-5.5%20%3E-green.svg?style=flat-square)](https://www.symcon.de/service/dokumentation/installation/migrationen/v54-v55-q4-2020/) +[![License](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-green.svg?style=flat-square)](https://creativecommons.org/licenses/by-nc-sa/4.0/) +[![Check Style](https://github.com/Nall-chan/VeluxKLF200/workflows/Check%20Style/badge.svg)](https://github.com/Nall-chan/VeluxKLF200/actions) [![Run Tests](https://github.com/Nall-chan/VeluxKLF200/workflows/Run%20Tests/badge.svg)](https://github.com/Nall-chan/VeluxKLF200/actions) +[![Spenden](https://www.paypalobjects.com/de_DE/DE/i/btn/btn_donate_SM.gif)](#3-spenden) +[![Wunschliste](https://img.shields.io/badge/Wunschliste-Amazon-ff69fb.svg)](#3-spenden) + +# Velux KLF200 Node + +## 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) + - [Allgemein](#allgemein) + - [Shutter](#shutter) + - [Slats](#slats) + - [Dimmer Light / Heating](#dimmer-light--heating) +- [8. Aktionen](#8-aktionen) +- [9. Anhang](#9-anhang) + - [1. Changelog](#1-changelog) + - [2. Spenden](#2-spenden) +- [10. Lizenz](#10-lizenz) + +## 1. Funktionsumfang + + - Auslesen, darstellen und Steuern von Zuständen eines Gerätes. + - Bereitstellung von PHP-Funktionen. + +## 2. Voraussetzungen + + - IPS ab Version 5.5 + - KLF200 io-homecontrol® Gateway + - KLF muss per LAN angeschlossen sein + - KLF Firmware 2.0.0.71 oder neuer + +## 3. Software-Installation + +* Dieses Modul ist Bestandteil der [VeluxKLF200-Library](../README.md#3-software-installation). + +## 4. Einrichten der Instanzen in IP-Symcon + +Eine einfache Einrichtung ist über die im Objektbaum unter `Konfigurator Instanzen` zu findene Instanz [KLF200 Konfigurator](../KLF200Configurator/README.md) möglich. + +Bei der manuellen Einrichtung ist das Modul im Dialog `Instanz hinzufügen` unter den Hersteller `VELUX` zu finden. +![Instanz hinzufügen](../imgs/instanzen.png) + +In dem sich öffnenden Konfigurationsformular ist die `Node ID` des Gerätes einzutragen. + +## 5. Statusvariablen und Profile + + +Die Statusvariablen werden je nach Geräte-Typ des Nodes angelegt oder auch wieder entfernt. +Es werden folgende Statusvariablen verwendet: 'MAIN', 'FP1', 'FP2' und 'FP3'. +Dabei entsprechen die Werte und Aktionen dem jeweiligen Funktions-Parameter, welche bei jedem Geräte-Typ (NodeSubType) unterschiedlich sind. +Entsprechend sind auch der Name und das Profil beim anlegen der Instanz je nach Geräte-Typ unterschiedlich. + +**Statusvariable MAIN:** + +| Name | Typ | Ident | Beschreibung | +| :-------------- | :------ | :---- | :-------------------------------------------------------- | +| Status | boolean | MAIN | Licht | +| Schalter | boolean | MAIN | Aktoren | +| Schloss | boolean | MAIN | Schlösser | +| Position | integer | MAIN | Alle Geräte welche eine prozentuale Position unterstützen | +| Intensität | integer | MAIN | Dimmbares Licht | +| Doppelrollladen | integer | MAIN | Nur bei Doppelrollladen vorhanden | +| Geschlossen | integer | MAIN | prozentuale Schließung von Lüftungen/Heizungen | + +**Statusvariable FP1:** + +| Name | Typ | Ident | Beschreibung | +| :------------- | :------ | :---- | :---------------------------------- | +| Orientierung | integer | FP1 | Ausrichtung von Lamellen in Prozent | +| Obere Position | integer | FP1 | Obere Position in Prozent | + +**Statusvariable FP2:** + +| Name | Typ | Ident | Beschreibung | +| :-------------- | :------ | :---- | :------------------------- | +| Untere Position | integer | FP2 | Untere Position in Prozent | + +**Statusvariable FP3:** + +| Name | Typ | Ident | Beschreibung | +| :----------- | :------ | :---- | :---------------------------------- | +| Orientierung | integer | FP3 | Ausrichtung von Lamellen in Prozent | + +**Profile**: + +| Name | Typ | verwendet von Statusvariablen (Ident) | +| :-------------------------- | :------ | :------------------------------------- | +| KLF200.Light.Reversed | boolean | MAIN | +| KLF200.Lock | boolean | MAIN | +| KLF200.Blind | integer | MAIN | +| KLF200.RollerShutter | integer | MAIN, FP1, FP2 | +| KLF200.Window | integer | MAIN | +| KLF200.Garage | integer | MAIN | +| KLF200.Light.51200.Reversed | integer | MAIN | +| KLF200.Intensity.51200 | integer | MAIN | +| KLF200.Slats | integer | FP1, FP3 | + + +## 6. WebFront + +Die direkte Darstellung im WebFront ist möglich, es wird aber empfohlen mit Links zu arbeiten. +Alle Statusvariablen sind auch bedienbar. + +## 7. PHP-Befehlsreferenz + +Alle Funktionen liefern `TRUE` wenn das Gateway den Empfang des Befehls bestätigt hat. +Anschließend führt das KLF200 die gewünschte Aktion aus. +Im Fehlerfall, oder wenn das Gateway den Befehl ablehnt, wird eine Warnung erzeugt und `FALSE` zurückgegeben. + +Alle `$Value` Variablen vom Typ integer (int) haben eine Wertebereich von 0 (`0x000`) bis 51200 (`0xC800`) für absolute Werte. + +Es kann bei allen `$Value` Variablen vom Typ integer (int) auch eine Relative Ansteuerung erfolgen. +Hierzu ist der Wertebereich von 51456 (`0xC900`) für -100% bis zu 53456 (`0xD0D0`) für +100% reserviert. + +--- + +### Allgemein + +```php +bool KLF200_RequestStatus(int $InstanzeID) +``` +Stellt eine Anfrage über den aktuellen Status des Node an das Gateway. + +```php +bool KLF200_RequestNodeInformation(int $InstanzeID) +``` +Stellt eine Anfrage über Informationen zum Node an das Gateway. + +```php +bool KLF200_SetMainParameter(int $InstanzeID, int $Value) +``` +Main Parameter auf `$Value` setzen. + +```php +bool KLF200_SetFunctionParameter1(int $InstanzeID, int $Value) +``` +Function Parameter 1 auf `$Value` setzen. + +```php +bool KLF200_SetFunctionParameter2(int $InstanzeID, int $Value) +``` +Function Parameter 2 auf `$Value` setzen. + +```php +bool KLF200_SetFunctionParameter3(int $InstanzeID, int $Value) +``` +Function Parameter 3 auf `$Value` setzen. + +```php +bool KLF200_SwitchMode(int $InstanzeID, bool $Value) +``` +Gerät wird auf den in `$Value` übergebenen Wert ein/ausgeschaltet bzw. komplett geschlossen/geöffnet. + +--- + +### Shutter + +Betrifft alle Gerätetypen, welche eine prozentuale Ansteuerung für Positionen bieten. + +```php +bool KLF200_ShutterMove(int $InstanzeID, int $Value) +``` +Gerät auf in den `$Value` übergebenen Wert setzen. + +```php +bool KLF200_ShutterMoveUp(int $InstanzeID) +``` +Öffnungsbefehl an das Gerät senden. + +```php +bool KLF200_ShutterMoveDown(int $InstanzeID) +``` +Schließbefehl an das Gerät senden. + +```php +bool KLF200_ShutterMoveStop(int $InstanzeID) +``` +Stop Befehl an das Gerät senden. + +--- + +### Slats + +```php +bool KLF200_OrientationSet(int $InstanzeID, int $Value) +``` +Öffnungswinkel auf den in `$Value` übergebenen Wert setzen. + +```php +bool KLF200_OrientationUp(int $InstanzeID) +``` +Öffnungswinkel auf 0 setzen. + +```php +bool KLF200_OrientationDown(int $InstanzeID) +``` +Öffnungswinkel auf maximum setzen. + +```php +bool KLF200_OrientationStop(int $InstanzeID) +``` +Stop Befehl an das Gerät senden. + +--- + +### Dimmer Light / Heating + +```php +bool KLF200_DimSet(int $InstanzeID, int $Value) +``` +Setzten den Dimmwert auf den in `$Value` übergebenen Wert. + +```php +bool KLF200_DimUp(int $InstanzeID) +``` +Setzten den Dimmwert auf das Maximum. + +```php +bool KLF200_DimDown(int $InstanzeID) +``` +Setzten den Dimmwert auf das Minimum. + +```php +bool KLF200_DimStop(int $InstanzeID) +``` +Stop Befehl an das Gerät senden. + +## 8. Aktionen + +Es gibt keine speziellen Aktionen für dieses Modul. + +## 9. Anhang + +### 1. Changelog + +[Changelog der Library](../README.md#2-changelog) + +### 2. Spenden + + Die Library ist für die nicht kommerzielle Nutzung kostenlos, Schenkungen als Unterstützung für den Autor werden hier akzeptiert: + + + +[![Wunschliste](https://img.shields.io/badge/Wunschliste-Amazon-ff69fb.svg)](https://www.amazon.de/hz/wishlist/ls/YU4AI9AQT9F?ref_=wl_share) + + +## 10. Lizenz + + IPS-Modul: + [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) \ No newline at end of file diff --git a/KLF200Node/form.json b/KLF200Node/form.json new file mode 100644 index 0000000..fac8095 --- /dev/null +++ b/KLF200Node/form.json @@ -0,0 +1,43 @@ +{ + "elements": [ + { + "name": "NodeId", + "type": "NumberSpinner", + "caption": "Node ID" + } + ], + "actions": [ + { + "type": "Button", + "caption": "Request device state", + "onClick": "KLF200_RequestStatus($id);" + }, + { + "type": "Button", + "caption": "Request device information", + "onClick": "KLF200_RequestNodeInformation($id);" + }, + { + "type": "TestCenter" + }, + { + "type": "Label", + "caption": "This module is free for non-commercial use,\r\nDonations in support of the author are accepted here:" + }, + { + "type": "RowLayout", + "items": [ + { + "type": "Image", + "onClick": "echo 'https://www.paypal.com/donate?hosted_button_id=G2SLW2MEMQZH2';", + "image": "data:image/png;base64,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ZrBplgJXJ/3OA0do9FRbgAMdMwKlYjyPei1SAu5FhYGVXbSC6ZrRTMbTv5gMZgem2CeF2pdEB7F4vKvJLQHbFVdgbjvNQ5hAROuNbZCJ3eQ9wA2Kap2BBiCGPI2CdgT4rRDKUR5bdW9b2UVR2sEOmm6jcYFzmCH/HBOJg6lrJexUsVgqxjYaS9H3H8vcToYpe4BkVAfESru141QF7kfZPtkcVXvPQs1t/w64CLgUS/PakslZB9yssEVMyN2SXIvAFlU2S+O7jtDcAI4AawT2ZEvoilINoZJX/RJwMzV9at7kVoDtkz12FSGWqw+FfLgUeDOW3czFUsj1wG+wghWYvjghedfDtXcFjeUo4fOYQeQwz7VRYWcrP5k5bj6/Wfi93ezdt5dAyCPMwvZkKZk0nWw4Sc4LZmMG9YrM5R8jvBflyOPx7WFyVCwgs4AOhDGNjJALVdm1i4EPPOvxmt4JOW4MoCX0bSKYb1qOicd0erUbeAPwD5WArj5zCi948tHqbwKf4MEomKtdTWNufS3oRsAXP8WTxgDCmkLt84lLaHT9m4DrQY6bz86PF540BoCAKidjojT9YUoR6CP1IYczzpPDAK78O8kBzNuwL2yU8Y9Hf4RlEsdSl/i/oZUfhT5xhFrGpjdjYq+Wez8M3IkyTNv0T84cx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Ecx3Gmx/8AQ1faHO6uaXsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTgtMDUtMjVUMTI6NDA6MTArMDI6MDB4/FYOAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE4LTA1LTI1VDEyOjQwOjEwKzAyOjAwCaHusgAAAEZ0RVh0c29mdHdhcmUASW1hZ2VNYWdpY2sgNi43LjgtOSAyMDE2LTA2LTE2IFExNiBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9yZ+a/NLYAAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAYdEVYdFRodW1iOjpJbWFnZTo6aGVpZ2h0ADUxMsDQUFEAAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgANTEyHHwD3AAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxNTI3MjQ0ODEwnCKKMAAAABN0RVh0VGh1bWI6OlNpemUAMTIuOEtCQhoRQ10AAABCdEVYdFRodW1iOjpVUkkAZmlsZTovLy4vdXBsb2Fkcy81Ni9rSWZ4SUF5LzE0ODcvODM5My1wYXlwYWxfMTAyNDYxLnBuZ6xvuXsAAAAASUVORK5CYII=" + }, + { + "type": "Image", + "onClick": "echo 'https://www.amazon.de/hz/wishlist/ls/YU4AI9AQT9F?ref_=wl_share';", + "image": "data:image/png;base64,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" + } + ] + } + ] +} \ No newline at end of file diff --git a/KLF200Node/locale.json b/KLF200Node/locale.json new file mode 100644 index 0000000..9a71092 --- /dev/null +++ b/KLF200Node/locale.json @@ -0,0 +1,41 @@ +{ + "translations": { + "de": { + "Request device state": "Lese Gerätestatus", + "Request device information": "Lese Geräte-Informationen", + "No error.": "Kein Fehler.", + "Timeout.": "Zeitüberschreitung.", + "Not further defined error.": "Nicht weiter definierter Fehler.", + "Unknown command or command is not accepted at this state.": "Unbekanntes Kommando oder Kommando wird zu diesem Zeitpunkt nicht akzeptiert.", + "Error on Frame Structure.": "Fehler in Frame Struktur.", + "Busy. Try again later.": "Beschäftigt. Später nochmal versuchen.", + "Bad system table index.": "Ungültiger Index der Systemtabelle.", + "Not authenticated.": "Nicht berechtigt.", + "Position": "Position", + "Orientation": "Orientierung", + "Intensity": "Intensität", + "State": "Status", + "Lock": "Schloss", + "Dual Roller Shutter": "Doppelrollladen", + "Switch": "Schalter", + "Upper position": "Obere Position", + "Lower position": "Untere Position", + "Closed": "Geschlossen", + "Instance does not implement this function": "Instanze unterstützt diese Funktion nicht", + "Invalid Ident": "Ungültiger Ident", + "Request rejected": "Anfrage abgelehnt", + "Invalid node index": "Ungültiger Node-Eintrag", + "Instance has no active parent.": "Instanz hat keinen aktiven Parent.", + "Parameter is unable to execute.": "Parameter kann nicht ausgeführt werden.", + "Execution has failed.": "Die Ausführung ist fehlgeschlagen.", + "not used.": "nicht benutzt", + "waiting for power.": "Warten auf Strom.", + "Executing.": "Ausführen.", + "done.": "fertig.", + "unknown.": "unbekannt.", + "Execution is completed with no errors.": "Ausführung wurde ohne Fehler beendet.", + "Execution is still active.": "Die Ausführung ist noch aktiv.", + "This module is free for non-commercial use,\r\nDonations in support of the author are accepted here:": "Dieses Modul ist für die nicht kommerzielle Nutzung kostenlos,\r\nSchenkungen als Unterstützung für den Autor werden hier akzeptiert:" + } + } +} \ No newline at end of file diff --git a/KLF200Node/module.json b/KLF200Node/module.json new file mode 100644 index 0000000..0c6a941 --- /dev/null +++ b/KLF200Node/module.json @@ -0,0 +1,18 @@ +{ + "id": "{4EBD07B1-2962-4531-AC5F-7944789A9CE5}", + "name": "KLF200 Node", + "type": 3, + "vendor": "VELUX", + "aliases": [ + "KLF200 Node" + ], + "parentRequirements": [ + "{7B0F87CC-0408-4283-8E0E-2D48141E42E8}" + ], + "childRequirements": [], + "implemented": [ + "{5242DAEF-EEBD-441F-AB0B-E83C01475B65}" + ], + "prefix": "KLF200", + "url": "https://github.com/Nall-chan/VeluxKLF200/blob/master/KLF200Node/README.md" +} \ No newline at end of file diff --git a/KLF200Node/module.php b/KLF200Node/module.php new file mode 100644 index 0000000..0deb3f8 --- /dev/null +++ b/KLF200Node/module.php @@ -0,0 +1,796 @@ +' . file_get_contents(__DIR__ . '/../libs/helper/BufferHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Node {?>' . file_get_contents(__DIR__ . '/../libs/helper/SemaphoreHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Node {?>' . file_get_contents(__DIR__ . '/../libs/helper/DebugHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Node {?>' . file_get_contents(__DIR__ . '/../libs/helper/VariableHelper.php') . '}'); +eval('declare(strict_types=1);namespace KLF200Node {?>' . file_get_contents(__DIR__ . '/../libs/helper/VariableProfileHelper.php') . '}'); + +/** + * @property char $NodeId + * @property int $SessionId + * @property int $NodeSubType + */ +class KLF200Node extends IPSModule +{ + use \KLF200Node\Semaphore, + \KLF200Node\BufferHelper, + \KLF200Node\VariableHelper, + \KLF200Node\VariableProfileHelper, + \KLF200Node\DebugHelper { + \KLF200Node\DebugHelper::SendDebug as SendDebug2; + } + + /** + * Interne Funktion des SDK. + */ + public function Create() + { + parent::Create(); + $this->ConnectParent('{725D4DF6-C8FC-463C-823A-D3481A3D7003}'); + $this->RegisterPropertyInteger('NodeId', -1); + $this->RegisterAttributeInteger('NodeSubType', -1); + $this->SessionId = 1; + $this->NodeSubType = -1; + } + + /** + * Interne Funktion des SDK. + */ + public function ApplyChanges() + { + parent::ApplyChanges(); + $APICommands = [ + \KLF200\APICommand::GET_NODE_INFORMATION_NTF, + \KLF200\APICommand::NODE_INFORMATION_CHANGED_NTF, + \KLF200\APICommand::NODE_STATE_POSITION_CHANGED_NTF, + \KLF200\APICommand::COMMAND_RUN_STATUS_NTF, + \KLF200\APICommand::COMMAND_REMAINING_TIME_NTF, + \KLF200\APICommand::SESSION_FINISHED_NTF, + \KLF200\APICommand::STATUS_REQUEST_NTF, + \KLF200\APICommand::WINK_SEND_NTF, + \KLF200\APICommand::MODE_SEND_NTF + ]; + $this->SessionId = 1; + $NodeId = $this->ReadPropertyInteger('NodeId'); + $this->NodeId = chr($NodeId); + if (($NodeId < 0) || ($NodeId > 255)) { + $Line = 'NOTHING'; + } else { + $NodeId = preg_quote(substr(json_encode(utf8_encode(chr($this->ReadPropertyInteger('NodeId')))), 0, -1)); + foreach ($APICommands as $APICommand) { + $Lines[] = '.*"Command":' . $APICommand . ',"Data":' . $NodeId . '.*'; + } + $Line = implode('|', $Lines); + } + $this->SetReceiveDataFilter('(' . $Line . ')'); + $this->SendDebug('FILTER', $Line, 0); + $this->NodeSubType = $this->ReadAttributeInteger('NodeSubType'); + $this->SetSummary(sprintf('%04X', $this->NodeSubType)); + $this->RegisterProfileInteger('KLF200.Intensity.51200', '', '', ' %', 0, 0xC800, 1); + $this->RegisterProfileInteger('KLF200.RollerShutter', 'Jalousie', '', ' %', 0, 0xC800, 1); + $this->RegisterProfileInteger('KLF200.Slats', 'Speedo', '', ' %', 0, 0xC800, 1); + $this->RegisterProfileInteger('KLF200.Blind', 'Raffstore', '', ' %', 0, 0xC800, 1); + $this->RegisterProfileInteger('KLF200.Window', 'Window', '', ' %', 0, 0xC800, 1); + $this->RegisterProfileInteger('KLF200.Heating.Reversed', 'Temperature', '', ' %', 0, 0xC800, 1); + $this->RegisterProfileInteger('KLF200.Garage', 'Garage', '', ' %', 0, 0xC800, 1); + $this->RegisterProfileInteger('KLF200.Light.51200.Reversed', 'Light', '', ' %', 0, 0xC800, 1); + $this->RegisterProfileBoolean('KLF200.Light.Reversed', 'Light', '', ''); + $this->RegisterProfileBoolean('KLF200.Lock', 'Lock', '', ''); + if (IPS_GetKernelRunlevel() == KR_READY) { + $this->RequestNodeInformation(); + } + } + + public function SwitchMode(bool $Value) + { + return $this->SetMainParameter($Value ? 0xC800 : 0x0000); + } + + public function ShutterMove(int $Value) + { + switch ($this->NodeSubType) { + case 0x0040: //Interior Venetian Blind + case 0x0080: //Roller Shutter + case 0x0081: //Adjustable slats rolling shutter + case 0x0082: //Roller Shutter With projection + case 0x00C0: //Vertical Exterior Awning + case 0x0100: //Window opener + case 0x0101: //Window opener with integrated rain sensor + case 0x0140: //Garage door opener + case 0x017A: //Garage door opener + case 0x0200: //Rolling Door Opener + case 0x01C0: //Gate opener + case 0x01FA: //Gate opener + case 0x0280: //Vertical Interior Blinds + case 0x0340: //Dual Roller Shutter + case 0x0400: //Horizontal awning + case 0x04C0: //Curtain track + case 0x0600: //Swinging Shutters + case 0x0601: //Swinging Shutter with independent handling of the leaves + case 0x0440: //Exterior Venetian blind + case 0x0480: //Louver blind + case 0x0500: //Ventilation point + case 0x0501: //Air inlet + case 0x0502: //Air transfer + case 0x0503: //Air outlet + return $this->SetMainParameter($Value); + } + trigger_error($this->Translate('Instance does not implement this function'), E_USER_NOTICE); + return false; + } + + public function ShutterMoveUp() + { + return $this->SetMainParameter(0x0000); + } + + public function ShutterMoveDown() + { + return $this->SetMainParameter(0xC800); + } + + public function ShutterMoveStop() + { + return $this->SetMainParameter(0xD200); + } + + public function OrientationSet(int $Value) + { + switch ($this->NodeSubType) { + case 0x0040: + return $this->SetFunctionParameter1($Value); + case 0x0440: + case 0x0081: + case 0x0480: + return $this->SetFunctionParameter3($Value); + } + trigger_error($this->Translate('Instance does not implement this function'), E_USER_NOTICE); + return false; + } + + public function OrientationUp() + { + return $this->SetOrientation(0x0000); + } + + public function OrientationDown() + { + return $this->SetOrientation(0xC800); + } + + public function OrientationStop() + { + return $this->SetOrientation(0xD200); + } + + public function DimSet(int $Value) + { + switch ($this->NodeSubType) { + case 0x0180: //Light + case 0x0540: //Exterior heating + case 0x057A: //Exterior heating + return $this->SetMainParameter($Value); + } + trigger_error($this->Translate('Instance does not implement this function'), E_USER_NOTICE); + return false; + } + + public function DimUp() + { + return $this->DimSet(0x0000); + } + + public function DimDown() + { + return $this->DimSet(0xC800); + } + + public function DimStop() + { + return $this->DimSet(0xD200); + } + + public function RequestAction($Ident, $Value) + { + if (IPS_GetVariable($this->GetIDForIdent($Ident))['VariableType'] == VARIABLETYPE_BOOLEAN) { + $Value = $Value ? 0xC800 : 0x0000; + } + switch ($Ident) { + case 'MAIN': + return $this->SetMainParameter($Value); + case 'FP1': + return $this->SetFunctionParameter1($Value); + case 'FP2': + return $this->SetFunctionParameter2($Value); + case 'FP3': + return $this->SetFunctionParameter3($Value); + } + echo $this->Translate('Invalid Ident'); + return; + } + + public function RequestNodeInformation() + { + $APIData = new \KLF200\APIData(\KLF200\APICommand::GET_NODE_INFORMATION_REQ, $this->NodeId); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData === null) { + return false; + } + $State = ord($ResultAPIData->Data[0]); + switch ($State) { + case 0: + return true; + case 1: + trigger_error($this->Translate('Request rejected'), E_USER_NOTICE); + return false; + case 2: + trigger_error($this->Translate('Invalid node index'), E_USER_NOTICE); + return false; + } + } + + public function RequestStatus() + { + /* + Command Data 1 – 2 Data 3 Data 4 – 23 Data 24 + GW_STATUS_REQUEST_REQ SessionID IndexArrayCount IndexArray StatusType + Data 25 Data 26 + FPI1 FPI2 + StatusType + value |Description + 0 |Request Target position + 1 |Request Current position + 2 |Request Remaining time + 3 |Request Main info. + */ + $Data = $this->NodeId . $this->GetSessionId() . chr(1) . $this->NodeId . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + $Data .= chr(1) . chr(0b11100000) . chr(0); + $APIData = new \KLF200\APIData(\KLF200\APICommand::STATUS_REQUEST_REQ, $Data); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData === null) { + return false; + } + return ord($ResultAPIData->Data[2]) == 1; + } + + public function SetMainParameter(int $Value) + { + /* + Command Data 1 – 2 Data 3 Data 4 Data 5 + GW_COMMAND_SEND_REQ SessionID CommandOriginator PriorityLevel ParameterActive + Data 6 Data 7 Data 8 - 41 Data 42 Data 43 – 62 Data 63 + FPI1 FPI2 FunctionalParameterValueArray IndexArrayCount IndexArray PriorityLevelLock + Data 64 Data 65 Data 66 + PL_0_3 PL_4_7 LockTime + */ + $Data = $this->NodeId . $this->GetSessionId(); //Data 1-2 + $Data .= chr(1) . chr(3) . chr(0); // Data 3-5 + $Data .= chr(0) . chr(0); // Data 6-7 + $Data .= pack('n', $Value); // Data 8-9 + $Data .= "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // Data 10-25 + $Data .= "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // Data 26-41 + $Data .= chr(1) . $this->NodeId . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; //Data 42-62 + $Data .= chr(0); // Data 63 + $Data .= chr(0) . chr(0) . chr(0); // Data 64-66 + $APIData = new \KLF200\APIData(\KLF200\APICommand::COMMAND_SEND_REQ, $Data); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData === null) { + return false; + } + return ord($ResultAPIData->Data[2]) == 1; + } + + public function SetFunctionParameter1(int $Value) + { + /* + Command Data 1 – 2 Data 3 Data 4 Data 5 + GW_COMMAND_SEND_REQ SessionID CommandOriginator PriorityLevel ParameterActive + Data 6 Data 7 Data 8 - 41 Data 42 Data 43 – 62 Data 63 + FPI1 FPI2 FunctionalParameterValueArray IndexArrayCount IndexArray PriorityLevelLock + Data 64 Data 65 Data 66 + PL_0_3 PL_4_7 LockTime + */ + $Data = $this->NodeId . $this->GetSessionId(); //Data 1-2 + $Data .= chr(1) . chr(3) . chr(1); // Data 3-5 + $Data .= chr(0x80) . chr(0); // Data 6-7 + $Data .= "\xD4\x00"; // Data 8-9 -> ignore + $Data .= pack('n', $Value); // Data 10-11 + $Data .= "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // Data 12-25 + $Data .= "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // Data 26-41 + $Data .= chr(1) . $this->NodeId . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; //Data 42-62 + $Data .= chr(0); // Data 63 + $Data .= chr(0) . chr(0) . chr(0); // Data 64-66 + $APIData = new \KLF200\APIData(\KLF200\APICommand::COMMAND_SEND_REQ, $Data); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData === null) { + return false; + } + return ord($ResultAPIData->Data[2]) == 1; + } + + public function SetFunctionParameter2(int $Value) + { + /* + Command Data 1 – 2 Data 3 Data 4 Data 5 + GW_COMMAND_SEND_REQ SessionID CommandOriginator PriorityLevel ParameterActive + Data 6 Data 7 Data 8 - 41 Data 42 Data 43 – 62 Data 63 + FPI1 FPI2 FunctionalParameterValueArray IndexArrayCount IndexArray PriorityLevelLock + Data 64 Data 65 Data 66 + PL_0_3 PL_4_7 LockTime + */ + $Data = $this->NodeId . $this->GetSessionId(); //Data 1-2 + $Data .= chr(1) . chr(3) . chr(2); // Data 3-5 + $Data .= chr(0x40) . chr(0); // Data 6-7 + $Data .= "\xD4\x00"; // Data 8-9 -> ignore + $Data .= "\x00\x00"; // Data 10-11 + $Data .= pack('n', $Value); // Data 12-13 + $Data .= "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // Data 14-25 + $Data .= "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // Data 26-41 + $Data .= chr(1) . $this->NodeId . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; //Data 42-62 + $Data .= chr(0); // Data 63 + $Data .= chr(0) . chr(0) . chr(0); // Data 64-66 + $APIData = new \KLF200\APIData(\KLF200\APICommand::COMMAND_SEND_REQ, $Data); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData === null) { + return false; + } + return ord($ResultAPIData->Data[2]) == 1; + } + + public function SetFunctionParameter3(int $Value) + { + /* + Command Data 1 – 2 Data 3 Data 4 Data 5 + GW_COMMAND_SEND_REQ SessionID CommandOriginator PriorityLevel ParameterActive + Data 6 Data 7 Data 8 - 41 Data 42 Data 43 – 62 Data 63 + FPI1 FPI2 FunctionalParameterValueArray IndexArrayCount IndexArray PriorityLevelLock + Data 64 Data 65 Data 66 + PL_0_3 PL_4_7 LockTime + */ + $Data = $this->NodeId . $this->GetSessionId(); //Data 1-2 + $Data .= chr(1) . chr(3) . chr(0); // Data 3-5 + $Data .= chr(0x20) . chr(0); // Data 6-7 + $Data .= "\xD4\x00"; // Data 8-9 -> ignore + $Data .= "\x00\x00\x00\x00"; // Data 10-14 + $Data .= pack('n', $Value); // Data 14-15 + $Data .= "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // Data 16-25 + $Data .= "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // Data 26-41 + $Data .= chr(1) . $this->NodeId . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; //Data 42-62 + $Data .= chr(0); // Data 63 + $Data .= chr(0) . chr(0) . chr(0); // Data 64-66 + $APIData = new \KLF200\APIData(\KLF200\APICommand::COMMAND_SEND_REQ, $Data); + $ResultAPIData = $this->SendAPIData($APIData); + if ($ResultAPIData === null) { + return false; + } + return ord($ResultAPIData->Data[2]) == 1; + } + + public function ReceiveData($JSONString) + { + $APIData = new \KLF200\APIData($JSONString); + $this->SendDebug('Event', $APIData, 1); + $this->ReceiveEvent($APIData); + } + + protected function SendDebug($Message, $Data, $Format) + { + if (is_a($Data, '\\KLF200\\APIData')) { + /* @var $Data \KLF200\APIData */ + $this->SendDebug2($Message . ':Command', \KLF200\APICommand::ToString($Data->Command), 0); + if ($Data->isError()) { + $this->SendDebug2('Error', $Data->ErrorToString(), 0); + } elseif ($Data->Data != '') { + $this->SendDebug2($Message . ':Data', $Data->Data, $Format); + } + } else { + $this->SendDebug2($Message, $Data, $Format); + } + } + + private function RegisterNodeVariables(int $NodeTypeSubType) + { + $this->NodeSubType = $NodeTypeSubType; + switch ($NodeTypeSubType) { + case 0x0040: //Interior Venetian Blind + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.Blind', 0); + $this->RegisterVariableInteger('FP1', $this->Translate('Orientation'), 'KLF200.Slats', 0); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0080: //Roller Shutter + case 0x0082: //Roller Shutter With projection + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.RollerShutter', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0081: //Adjustable slats rolling shutter + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.RollerShutter', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->RegisterVariableInteger('FP3', $this->Translate('Orientation'), 'KLF200.Slats', 0); + break; + case 0x00C0: //Vertical Exterior Awning + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.RollerShutter', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0100: //Window opener + case 0x0101: //Window opener with integrated rain sensor + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.Window', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0140: //Garage door opener + case 0x017A: //Garage door opener + case 0x0200: //Rolling Door Opener + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.Garage', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0180: //Light + $this->RegisterVariableInteger('MAIN', $this->Translate('Intensity'), 'KLF200.Light.51200.Reversed', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x01BA: //Light only supporting on/off + $this->RegisterVariableBoolean('MAIN', $this->Translate('State'), 'KLF200.Light.Reversed', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x01C0: //Gate opener + case 0x01FA: //Gate opener + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.Intensity.51200', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0240: //Door lock + case 0x0241: //Window lock + $this->RegisterVariableBoolean('MAIN', $this->Translate('Lock'), 'KLF200.Lock', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0280: //Vertical Interior Blinds + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.Blind', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0340: //Dual Roller Shutter + $this->RegisterVariableInteger('MAIN', $this->Translate('Dual Roller Shutter'), 'KLF200.RollerShutter', 0); + $this->RegisterVariableInteger('FP1', $this->Translate('Upper position'), 'KLF200.RollerShutter', 0); + $this->RegisterVariableInteger('FP2', $this->Translate('Lower position'), 'KLF200.RollerShutter', 0); + $this->UnregisterVariable('FP3'); + break; + case 0x03C0: //On/Off switch + $this->RegisterVariableBoolean('MAIN', $this->Translate('Switch'), '~Switch', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0400: //Horizontal awning + case 0x04C0: //Curtain track + case 0x0600: //Swinging Shutters + case 0x0601: //Swinging Shutter with independent handling of the leaves + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.Intensity.51200', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0440: //Exterior Venetian blind + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.Blind', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->RegisterVariableInteger('FP3', $this->Translate('Orientation'), 'KLF200.Slats', 0); + break; + case 0x0480: //Louver blind + $this->RegisterVariableInteger('MAIN', $this->Translate('Position'), 'KLF200.Intensity.51200', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->RegisterVariableInteger('FP3', $this->Translate('Orientation'), 'KLF200.Slats', 0); + break; + case 0x0500: //Ventilation point + case 0x0501: //Air inlet + case 0x0502: //Air transfer + case 0x0503: //Air outlet + $this->RegisterVariableInteger('MAIN', $this->Translate('Closed'), 'KLF200.Intensity.51200', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0540: //Exterior heating + case 0x057A: //Exterior heating + $this->RegisterVariableInteger('MAIN', $this->Translate('Closed'), 'KLF200.Intensity.51200', 0); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + case 0x0300: //Beacon + case 0x0380: //Heating Temperature Interface + case 0x0580: //Heat pump + case 0x05C0: //Intrusion alarm + default: + $this->UnregisterVariable('MAIN'); + $this->UnregisterVariable('FP1'); + $this->UnregisterVariable('FP2'); + $this->UnregisterVariable('FP3'); + break; + } + if (@$this->GetIDForIdent('MAIN') > 0) { + $this->EnableAction('MAIN'); + } + if (@$this->GetIDForIdent('FP1') > 0) { + $this->EnableAction('FP1'); + } + if (@$this->GetIDForIdent('FP2') > 0) { + $this->EnableAction('FP2'); + } + if (@$this->GetIDForIdent('FP3') > 0) { + $this->EnableAction('FP3'); + } + } + + private function SetValues(int $CurrentPosition, int $FP1CurrentPosition, int $FP2CurrentPosition, int $FP3CurrentPosition) + { + // nur absolute Werte in Variablen schreiben + $Main = @$this->GetIDForIdent('MAIN'); + if (($Main > 0) && ($CurrentPosition <= 0xC800)) { + if (IPS_GetVariable($Main)['VariableType'] == VARIABLETYPE_BOOLEAN) { + $CurrentPosition = ($CurrentPosition == 0xC800); + } + $this->SetValue('MAIN', $CurrentPosition); + } + $FP1 = @$this->GetIDForIdent('FP1'); + if (($FP1 > 0) && ($FP1CurrentPosition <= 0xC800)) { + if (IPS_GetVariable($FP1)['VariableType'] == VARIABLETYPE_BOOLEAN) { + $FP1CurrentPosition = ($FP1CurrentPosition == 0xC800); + } + $this->SetValue('FP1', $FP1CurrentPosition); + } + $FP2 = @$this->GetIDForIdent('FP2'); + if (($FP2 > 0) && ($FP2CurrentPosition <= 0xC800)) { + if (IPS_GetVariable($FP2)['VariableType'] == VARIABLETYPE_BOOLEAN) { + $FP2CurrentPosition = ($FP2CurrentPosition == 0xC800); + } + $this->SetValue('FP2', $FP2CurrentPosition); + } + $FP3 = @$this->GetIDForIdent('FP3'); + if (($FP3 > 0) && ($FP3CurrentPosition <= 0xC800)) { + if (IPS_GetVariable($FP3)['VariableType'] == VARIABLETYPE_BOOLEAN) { + $FP3CurrentPosition = ($FP3CurrentPosition == 0xC800); + } + $this->SetValue('FP3', $FP3CurrentPosition); + } + } + + private function ReceiveEvent(\KLF200\APIData $APIData) + { + switch ($APIData->Command) { + case \KLF200\APICommand::GET_NODE_INFORMATION_NTF: + $NodeID = ord($APIData->Data[0]); + /* Data 1 Data 2 - 3 Data 4 Data 5 - 68 Data 69 + NodeID Order Placement Name Velocity + Data 70 - 71 Data 72 Data 73 Data 74 Data 75 Data 76 + NodeTypeSubType ProductGroup ProductType NodeVariation PowerMode BuildNumber + Data 77 - 84 Data 85 Data 86 - 87 Data 88 - 89 Data 90 - 91 Data 92 - 93 + SerialNumber State CurrentPosition Target FP1CurrentPosition FP2CurrentPosition + Data 94 - 95 Data 96 - 97 Data 98 - 99 Data 100 - 103 Data 104 Data 105 - 125 + FP3CurrentPosition FP4CurrentPosition RemainingTime TimeStamp NbrOfAlias AliasArray + */ + + $Name = trim(substr($APIData->Data, 4, 64)); + $NodeTypeSubType = unpack('n', substr($APIData->Data, 69, 2))[1]; + $this->SendDebug('NodeID', $NodeID, 0); + $this->SendDebug('Name', $Name, 0); + $this->SendDebug('NodeTypeSubType', sprintf('%04X', $NodeTypeSubType), 0); + $this->SendDebug('NodeTypeSubType', \KLF200\Node::$SubType[$NodeTypeSubType], 0); + $this->SendDebug('NodeType', ($NodeTypeSubType >> 6), 0); + $this->SendDebug('SubType', ($NodeTypeSubType & 0x003F), 0); + /* + $this->SendDebug('ProductGroup', ord($APIData->Data[71]), 0); + $this->SendDebug('ProductType', ord($APIData->Data[72]), 0); + $this->SendDebug('NodeVariation', ord($APIData->Data[73]), 0); + $this->SendDebug('PowerMode', ord($APIData->Data[74]), 0); + $this->SendDebug('BuildNumber', ord($APIData->Data[75]), 0); + $this->SendDebug('SerialNumber', substr($APIData->Data, 76, 8), 1); + */ + $State = ord($APIData->Data[84]); + $this->SendDebug('State', \KLF200\State::ToString($State), 0); + $CurrentPosition = unpack('n', substr($APIData->Data, 85, 2))[1]; + $this->SendDebug('CurrentPosition', sprintf('%04X', $CurrentPosition), 0); + /* $Target = unpack('n', substr($APIData->Data, 87, 2))[1]; + $this->SendDebug('Target', sprintf('%04X', $Target), 0); + */ + $FP1CurrentPosition = unpack('n', substr($APIData->Data, 89, 2))[1]; + $this->SendDebug('FP1CurrentPosition', sprintf('%04X', $FP1CurrentPosition), 0); + $FP2CurrentPosition = unpack('n', substr($APIData->Data, 91, 2))[1]; + $this->SendDebug('FP2CurrentPosition', sprintf('%04X', $FP2CurrentPosition), 0); + $FP3CurrentPosition = unpack('n', substr($APIData->Data, 93, 2))[1]; + $this->SendDebug('FP3CurrentPosition', sprintf('%04X', $FP3CurrentPosition), 0); + /* + $FP4CurrentPosition = unpack('n', substr($APIData->Data, 95, 2))[1]; + $this->SendDebug('FP4CurrentPosition', sprintf('%04X', $FP4CurrentPosition), 0); + $RemainingTime = unpack('n', substr($APIData->Data, 97, 2))[1]; + $this->SendDebug('RemainingTime', $RemainingTime, 0); + $TimeStamp = unpack('N', substr($APIData->Data, 99, 4))[1]; + $this->SendDebug('TimeStamp', $TimeStamp, 0); + $this->SendDebug('TimeStamp', strftime('%H:%M:%S %d.%m.%Y', $TimeStamp), 0); + */ + if ($NodeTypeSubType != $this->NodeSubType) { + $this->WriteAttributeInteger('NodeSubType', $NodeTypeSubType); + $this->SetSummary(sprintf('%04X', $NodeTypeSubType)); + $this->RegisterNodeVariables($NodeTypeSubType); + } + $this->SetValues($CurrentPosition, $FP1CurrentPosition, $FP2CurrentPosition, $FP3CurrentPosition); + break; + /* case \KLF200\APICommand::NODE_INFORMATION_CHANGED_NTF: + break; */ + case \KLF200\APICommand::NODE_STATE_POSITION_CHANGED_NTF: + /* + Data 1 Data 2 Data 3 - 4 Data 5 - 6 + NodeID State CurrentPosition Target + Data 7 - 8 Data 9 - 10 Data 11 -12 Data 13 - 14 Data 15 - 16 + FP1CurrentPosition FP2CurrentPosition FP3CurrentPosition FP4CurrentPosition RemainingTime + Data 17 - 20 + TimeStamp + */ + $State = ord($APIData->Data[1]); + $this->SendDebug('State', \KLF200\State::ToString($State), 0); + $CurrentPosition = unpack('n', substr($APIData->Data, 2, 2))[1]; + $this->SendDebug('CurrentPosition', sprintf('%04X', $CurrentPosition), 0); + $Target = unpack('n', substr($APIData->Data, 4, 2))[1]; + $this->SendDebug('Target', sprintf('%04X', $Target), 0); + $FP1CurrentPosition = unpack('n', substr($APIData->Data, 6, 2))[1]; + $this->SendDebug('FP1CurrentPosition', sprintf('%04X', $FP1CurrentPosition), 0); + $FP2CurrentPosition = unpack('n', substr($APIData->Data, 8, 2))[1]; + $this->SendDebug('FP2CurrentPosition', sprintf('%04X', $FP2CurrentPosition), 0); + $FP3CurrentPosition = unpack('n', substr($APIData->Data, 10, 2))[1]; + /* + $this->SendDebug('FP3CurrentPosition', sprintf('%04X', $FP3CurrentPosition), 0); + $FP4CurrentPosition = unpack('n', substr($APIData->Data, 12, 2))[1]; + $this->SendDebug('FP4CurrentPosition', sprintf('%04X', $FP4CurrentPosition), 0); + */ + $RemainingTime = unpack('n', substr($APIData->Data, 14, 2))[1]; + $this->SendDebug('RemainingTime', $RemainingTime, 0); + $TimeStamp = unpack('N', substr($APIData->Data, 16, 4))[1]; + $this->SendDebug('TimeStamp', $TimeStamp, 0); + $this->SendDebug('TimeStamp', strftime('%H:%M:%S %d.%m.%Y', $TimeStamp), 0); + if ($State == \KLF200\State::DONE) { + // Wert $CurrentPosition umrechnen und setzen + $this->SetValues($CurrentPosition, $FP1CurrentPosition, $FP2CurrentPosition, $FP3CurrentPosition); + } + break; + case \KLF200\APICommand::COMMAND_RUN_STATUS_NTF: + // 00 06 01 00 00 FF FF 01 02 0E 00 00 00 + /* + Command Data 1 - 2 Data 3 Data 4 + GW_COMMAND_RUN_STATUS_NTF SessionID StatusID Index + Data 5 Data 6 – 7 + NodeParameter ParameterValue + Data 8 Data 9 Data 10 - 13 + RunStatus StatusReply InformationCode + */ + $NodeParameter = ord($APIData->Data[4]); + $this->SendDebug('NodeParameter', $NodeParameter, 0); + $ParameterValue = unpack('n', substr($APIData->Data, 5, 2))[1]; + $this->SendDebug('ParameterValue', sprintf('%04X', $ParameterValue), 0); + $RunStatus = ord($APIData->Data[7]); + $this->SendDebug('RunStatus', \KLF200\RunStatus::ToString($RunStatus), 0); + $StatusReply = ord($APIData->Data[8]); + $this->SendDebug('StatusReply', \KLF200\StatusReply::ToString($StatusReply), 0); + if ($RunStatus == \KLF200\RunStatus::EXECUTION_FAILED) { + trigger_error($this->Translate(\KLF200\RunStatus::ToString($RunStatus)), E_USER_NOTICE); + return; + } + + break; + case \KLF200\APICommand::COMMAND_REMAINING_TIME_NTF: + break; + case \KLF200\APICommand::SESSION_FINISHED_NTF: + break; + case \KLF200\APICommand::STATUS_REQUEST_NTF: + //00 00 01 00 01 02 FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + //01 05 01 01 00 01 01 + + /* + Command Data 1 – 2 Data 3 Data 4 Data 5 Data 6 + GW_STATUS_REQUEST_NTF SessionID StatusID NodeIndex RunStatus StatusReply + Data 7 + StatusType + * 0 = “Target Position” or + * 1 = “Current Position” or + * 2 = “Remaining Time” + * 01 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * Data 8 Data 9 - 59 + * StatusCount ParameterData + * + Data 7 + StatusType + * 3 = “Main Info” + * Data 8 - 9 Data 10 - 11 Data 12 - 13 + * TargetPosition CurrentPosition RemainingTime + * Data 14 - 17 Data 18 + * LastMasterExecutionAddress LastCommandOriginator + */ + $StatusID = ord($APIData->Data[2]); + $this->SendDebug('StatusID', $StatusID, 0); + $NodeIndex = ord($APIData->Data[3]); + $this->SendDebug('NodeIndex', $NodeIndex, 0); + $RunStatus = ord($APIData->Data[4]); + $this->SendDebug('RunStatus', \KLF200\RunStatus::ToString($RunStatus), 0); + $StatusReply = ord($APIData->Data[5]); + $this->SendDebug('StatusReply', \KLF200\StatusReply::ToString($StatusReply), 0); + $StatusType = ord($APIData->Data[6]); + $this->SendDebug('StatusType', $StatusType, 0); + if ($StatusType == 0xFF) { + $this->SendDebug('Error', \KLF200\StatusReply::ToString($StatusReply), 0); + trigger_error($this->Translate(\KLF200\StatusReply::ToString($StatusReply)), E_USER_NOTICE); + return; + } + if ($StatusType == 0x01) { + $ParameterCount = ord($APIData->Data[7]); + $this->SendDebug('ParameterCount', $ParameterCount, 0); + $ParameterData = substr($APIData->Data, 8); + $Data = [ + 0 => 0xF7FF, + 1 => 0xF7FF, + 2 => 0xF7FF, + 3 => 0xF7FF + ]; + for ($index = 0; $index < $ParameterCount; $index++) { + $Data[ord($ParameterData[$index * 3])] = unpack('n', substr($ParameterData, ($index * 3) + 1, 2))[1]; + } + $this->SetValues($Data[0], $Data[1], $Data[2], $Data[3]); + } + break; + case \KLF200\APICommand::WINK_SEND_NTF: + break; + case \KLF200\APICommand::MODE_SEND_NTF: + break; + } + } + + private function GetSessionId() + { + $SessionId = ($this->SessionId + 1) & 0xff; + $this->SessionId = $SessionId; + return chr($SessionId); + } + + private function SendAPIData(\KLF200\APIData $APIData) + { + if ($this->NodeId == chr(-1)) { + return null; + } + $this->SendDebug('ForwardData', $APIData, 1); + + try { + if (!$this->HasActiveParent()) { + throw new Exception($this->Translate('Instance has no active parent.'), E_USER_NOTICE); + } + /** @var \KLF200\APIData $ResponseAPIData */ + $ret = @$this->SendDataToParent($APIData->ToJSON('{7B0F87CC-0408-4283-8E0E-2D48141E42E8}')); + $ResponseAPIData = @unserialize($ret); + $this->SendDebug('Response', $ResponseAPIData, 1); + if ($ResponseAPIData->isError()) { + trigger_error($this->Translate($ResponseAPIData->ErrorToString()), E_USER_NOTICE); + return null; + } + return $ResponseAPIData; + } catch (Exception $exc) { + $this->SendDebug('Error', $exc->getMessage(), 0); + return null; + } + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..63f44cc --- /dev/null +++ b/LICENSE @@ -0,0 +1,360 @@ +Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International +Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial-ShareAlike 4.0 International Public License +("Public License"). To the extent this Public License may be +interpreted as a contract, You are granted the Licensed Rights in +consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the +Licensor receives from making the Licensed Material available under +these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-NC-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution, NonCommercial, and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + l. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + m. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + n. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-NC-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..48903b9 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +[![SDK](https://img.shields.io/badge/Symcon-PHPModul-red.svg?style=flat-square)](https://www.symcon.de/service/dokumentation/entwicklerbereich/sdk-tools/sdk-php/) +[![Version](https://img.shields.io/badge/Modul%20Version-0.80-blue.svg?style=flat-square)](https://community.symcon.de/t/modul-velux-klf200/50429) +[![Version](https://img.shields.io/badge/Symcon%20Version-5.5%20%3E-green.svg?style=flat-square)](https://www.symcon.de/service/dokumentation/installation/migrationen/v54-v55-q4-2020/) +[![License](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-green.svg?style=flat-square)](https://creativecommons.org/licenses/by-nc-sa/4.0/) +[![Check Style](https://github.com/Nall-chan/VeluxKLF200/workflows/Check%20Style/badge.svg)](https://github.com/Nall-chan/VeluxKLF200/actions) [![Run Tests](https://github.com/Nall-chan/VeluxKLF200/workflows/Run%20Tests/badge.svg)](https://github.com/Nall-chan/VeluxKLF200/actions) +[![Spenden](https://www.paypalobjects.com/de_DE/DE/i/btn/btn_donate_SM.gif)](#3-spenden) +[![Wunschliste](https://img.shields.io/badge/Wunschliste-Amazon-ff69fb.svg)](#3-spenden) +# VeluxKLF200 + +Diese Implementierung der API von dem Velux KLF200 Gateway +ermöglicht die Einbindung von allen io-homecontrol® Geräten, welche von diesem Gateway unterstützt werden. + + +## Dokumentation + +**Inhaltsverzeichnis** + +- [1. Funktionsumfang](#1-funktionsumfang) + - [KLF200 Configurator](#klf200-configurator) + - [KLF200 Gateway](#klf200-gateway) + - [KLF200 Node](#klf200-node) +- [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. Anhang](#5-anhang) + - [1. GUID der Module](#1-guid-der-module) + - [2. Changelog](#2-changelog) + - [3. Spenden](#3-spenden) +- [6. Lizenz](#6-lizenz) + +## 1. Funktionsumfang + +### [KLF200 Configurator](KLF200Configurator/README.md) + - Einfaches Anlegen von Geräte-Instanzen in IPS. + +### [KLF200 Gateway](KLF200Gateway/README.md) + - Bindeglied zwischen dem KLF200 und den Geräte und Konfigurator-Instanzen. + +### [KLF200 Node](KLF200Node/README.md) + - Bildet ein Gerät (Node) des KLF200 in Symcon ab. + +## 2. Voraussetzungen + + - IPS 5.5 oder höher + - KLF200 io-homecontrol® Gateway + - KLF muss per LAN angeschlossen sein + - KLF Firmware 2.0.0.71 oder neuer + +## 3. Software-Installation + + Über den `Module-Store` in IPS das Modul `VELUX KLF200` hinzufügen. + **Bei kommerzieller Nutzung (z.B. als Errichter oder Integrator) wenden Sie sich bitte an den Autor.** + +## 4. Einrichten der Instanzen in IP-Symcon + +Details sind direkt in der Dokumentation der jeweiligen Module beschrieben. +Es wird empfohlen die Einrichtung mit der Konfigurator-Instanz zu starten ([KLF200 Configurator:](KLF200Configurator/README.md)). + +## 5. Anhang + +### 1. GUID der Module + + +| Modul | Typ | Prefix | GUID | +| ------------------- | ------------ | ------ | -------------------------------------- | +| KLF200 Configurator | Configurator | KLF200 | {38724E6E-8202-4D37-9FA7-BDD2EDA79520} | +| KLF200 Gateway | Splitter | KLF200 | {725D4DF6-C8FC-463C-823A-D3481A3D7003} | +| KLF200 Node | Device | KLF200 | {4EBD07B1-2962-4531-AC5F-7944789A9CE5} | + +### 2. Changelog + + Version 0.80: + - Doku aktualisiert. + - + + Version 0.71: + - Fehlermeldung wenn Timeout beim Verbindungsaufbau auftrat. + - Gateway zeigte beim Symcon start verbunden an, auch wenn ClientSocket gestört war. + + Version 0.7: + - PTLS entfernt. + + Version 0.6: + - Button im Splitter für das Lesen der Firmwareversion war ohne Funktion. + - Fehler im Konfigurator, wenn Namen der Geräte einen Umlaut enthielt. + - Der Konfigurator hat eine neuen Button, um im laufenden Betrieb die Geräteliste neu zu laden. + + Version 0.5: + - Öffentliche Betaversion + - Dokumentation erstellt + + Version 0.1: + - Testversion + + +### 3. Spenden + + Die Library ist für die nicht kommerzielle Nutzung kostenlos, Schenkungen als Unterstützung für den Autor werden hier akzeptiert: + + + +[![Wunschliste](https://img.shields.io/badge/Wunschliste-Amazon-ff69fb.svg)](https://www.amazon.de/hz/wishlist/ls/YU4AI9AQT9F?ref_=wl_share) + +## 6. Lizenz + + [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) \ No newline at end of file diff --git a/imgs/conf_configurator.png b/imgs/conf_configurator.png new file mode 100644 index 0000000..303a6fa Binary files /dev/null and b/imgs/conf_configurator.png differ diff --git a/imgs/conf_configurator1.png b/imgs/conf_configurator1.png new file mode 100644 index 0000000..6c6b500 Binary files /dev/null and b/imgs/conf_configurator1.png differ diff --git a/imgs/conf_configurator2.png b/imgs/conf_configurator2.png new file mode 100644 index 0000000..dc3ba2e Binary files /dev/null and b/imgs/conf_configurator2.png differ diff --git a/imgs/instanzen.png b/imgs/instanzen.png new file mode 100644 index 0000000..7f38588 Binary files /dev/null and b/imgs/instanzen.png differ diff --git a/imgs/instanzen_configurator.png b/imgs/instanzen_configurator.png new file mode 100644 index 0000000..1ac6232 Binary files /dev/null and b/imgs/instanzen_configurator.png differ diff --git a/imgs/instanzen_splitter.png b/imgs/instanzen_splitter.png new file mode 100644 index 0000000..5cd435b Binary files /dev/null and b/imgs/instanzen_splitter.png differ diff --git a/imgs/logbaum_splitter.png b/imgs/logbaum_splitter.png new file mode 100644 index 0000000..1dec525 Binary files /dev/null and b/imgs/logbaum_splitter.png differ diff --git a/library.json b/library.json new file mode 100644 index 0000000..9044175 --- /dev/null +++ b/library.json @@ -0,0 +1,12 @@ +{ + "id": "{7D6B48F7-9957-4E50-B98B-B9831963D802}", + "author": "Michael Tröger", + "name": "Velux Library", + "url": "https://github.com/Nall-chan/VeluxKLF200/", + "compatibility": { + "version": "5.5" + }, + "version": "0.80", + "build": 80, + "date": 1605267164 +} \ No newline at end of file diff --git a/libs/KLF200Class.php b/libs/KLF200Class.php new file mode 100644 index 0000000..dac8b31 --- /dev/null +++ b/libs/KLF200Class.php @@ -0,0 +1,886 @@ + + * @copyright 2020 Michael Tröger + * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC BY-NC-SA 4.0 + * @version 1.1 + * @example Ohne + */ + +class APIData +{ + /** + * Alle Kommandos als Array. + * + * @var int + */ + public $Command; + + /** + * Alle Daten des Kommandos. + * + * @var string + */ + public $Data; + public $LastError = \KLF200\ErrorNTF::NO_ERROR; + + public function __construct($Command, $Data = '') + { + if (is_string($Command)) { + $Data = json_decode($Command, true); + $this->Command = $Data['Command']; + $this->Data = utf8_decode($Data['Data']); + return; + } + $this->Command = $Command; + if ($Command == \KLF200\APICommand::ERROR_NTF) { + $this->LastError = ord($Data); + $this->Data = ''; + } else { + $this->Data = $Data; + } + } + + public function ToJSON(string $GUID) + { + $Data['DataID'] = $GUID; + $Data['Command'] = $this->Command; + $Data['Data'] = utf8_encode($this->Data); + return json_encode($Data); + } + + public function GetSLIPData() + { + $TransportData = "\x00" . chr(strlen((string) $this->Data) + 3) . pack('n', $this->Command) . (string) $this->Data; + + $cs = 0; + for ($i = 1; $i < strlen($TransportData); $i++) { + $cs ^= ord($TransportData[$i]); + } + $TransportData .= chr($cs); + return "\xc0" . str_replace(["\xDB", "\xC0"], ["\xDB\xDD", "\xDB\xDC"], $TransportData) . "\xc0"; + } + + public function isEvent() + { + return \KLF200\APICommand::isEvent($this->Command); + } + + public function isError() + { + return $this->LastError != \KLF200\ErrorNTF::NO_ERROR; + } + + public function ErrorToString() + { + return \KLF200\ErrorNTF::ToString($this->LastError); + } +} + +class ErrorNTF +{ + public const NO_ERROR = -1; + public const NOT_DEFINED = 0; // Not further defined error. + public const UNKNOWN_COMMAND = 1; // Unknown Command or command is not accepted at this state. + public const ERROR_ON_FRAME_STRUCTURE = 2; // ERROR on Frame Structure. + public const BUSY = 7; // Busy. Try again later. + public const BAD_SYSTEM_TABLE_INDEX = 8; // Bad system table index. + public const NOT_AUTHENTICATED = 12; // Not authenticated. + public const TIMEOUT = 99; + + public static function ToString(int $Error) + { + switch ($Error) { + case self::NO_ERROR: + return 'No error.'; + case self::TIMEOUT: + return 'Timeout.'; + case self::NOT_DEFINED: + return 'Not further defined error.'; + case self::UNKNOWN_COMMAND: + return 'Unknown command or command is not accepted at this state.'; + case self::ERROR_ON_FRAME_STRUCTURE: + return 'Error on Frame Structure.'; + case self::BUSY: + return 'Busy. Try again later.'; + case self::BAD_SYSTEM_TABLE_INDEX: + return 'Bad system table index.'; + case self::NOT_AUTHENTICATED: + return 'Not authenticated.'; + } + } +} + +class State +{ + public const NON_EXECUTING = 0; + public const ERROR_WHILE_EXECUTION = 1; + public const NOT_USED = 2; + public const WAITING_FOR_POWER = 3; + public const EXECUTING = 4; + public const DONE = 5; + public const UNKNOWN = 255; + + public static function ToString(int $State) + { + switch ($State) { + case self::NON_EXECUTING: + return 'Parameter is unable to execute.'; + case self::ERROR_WHILE_EXECUTION: + return 'Execution has failed.'; + case self::NOT_USED: + return 'not used.'; + case self::WAITING_FOR_POWER: + return 'waiting for power.'; + case self::EXECUTING: + return 'Executing.'; + case self::DONE: + return 'done.'; + case self::UNKNOWN: + return 'unknown.'; + default: + return 'unknown state value: 0x' . sprintf('%02X', $State); + } + } +} + +class RunStatus +{ + public const EXECUTION_COMPLETED = 0; + public const EXECUTION_FAILED = 1; + public const EXECUTION_ACTIVE = 2; + + public static function ToString(int $RunStatus) + { + switch ($RunStatus) { + case self::EXECUTION_COMPLETED: + return 'Execution is completed with no errors.'; + case self::EXECUTION_FAILED: + return 'Execution has failed.'; + case self::EXECUTION_ACTIVE: + return 'Execution is still active.'; + } + } +} + +class StatusReply +{ + public const UNKNOWN_STATUS_REPLY = 0x00; + public const COMMAND_COMPLETED_OK = 0x01; + public const NO_CONTACT = 0x02; + public const MANUALLY_OPERATED = 0x03; + public const BLOCKED = 0x04; + public const WRONG_SYSTEMKEY = 0x05; + public const PRIORITY_LEVEL_LOCKED = 0x06; + public const REACHED_WRONG_POSITION = 0x07; + public const ERROR_DURING_EXECUTION = 0x08; + public const NO_EXECUTION = 0x09; + public const CALIBRATING = 0x0A; + public const POWER_CONSUMPTION_TOO_HIGH = 0x0B; + public const POWER_CONSUMPTION_TOO_LOW = 0x0C; + public const LOCK_POSITION_OPEN = 0x0D; + public const MOTION_TIME_TOO_LONG__COMMUNICATION_ENDED = 0x0E; + public const THERMAL_PROTECTION = 0x0F; + public const PRODUCT_NOT_OPERATIONAL = 0x10; + public const FILTER_MAINTENANCE_NEEDED = 0x11; + public const BATTERY_LEVEL = 0x12; + public const TARGET_MODIFIED = 0x13; + public const MODE_NOT_IMPLEMENTED = 0x14; + public const COMMAND_INCOMPATIBLE_TO_MOVEMENT = 0x15; + public const USER_ACTION = 0x16; + public const DEAD_BOLT_ERROR = 0x17; + public const AUTOMATIC_CYCLE_ENGAGED = 0x18; + public const WRONG_LOAD_CONNECTED = 0x19; + public const COLOUR_NOT_REACHABLE = 0x1A; + public const TARGET_NOT_REACHABLE = 0x1B; + public const BAD_INDEX_RECEIVED = 0x1C; + public const COMMAND_OVERRULED = 0x1D; + public const NODE_WAITING_FOR_POWER = 0x1E; + public const INFORMATION_CODE = 0xDF; + public const PARAMETER_LIMITED = 0xE0; + public const LIMITATION_BY_LOCAL_USER = 0xE1; + public const LIMITATION_BY_USER = 0xE2; + public const LIMITATION_BY_RAIN = 0xE3; + public const LIMITATION_BY_TIMER = 0xE4; + public const LIMITATION_BY_UPS = 0xE6; + public const LIMITATION_BY_UNKNOWN_DEVICE = 0xE7; + public const LIMITATION_BY_SAAC = 0xEA; + public const LIMITATION_BY_WIND = 0xEB; + public const LIMITATION_BY_MYSELF = 0xEC; + public const LIMITATION_BY_AUTOMATIC_CYCLE = 0xED; + public const LIMITATION_BY_EMERGENCY = 0xEE; + + public static function ToString(int $StatusReply) + { + switch ($StatusReply) { + case self::UNKNOWN_STATUS_REPLY: + return 'unknown reply'; + case self::COMMAND_COMPLETED_OK: + return 'no errors detected'; + case self::NO_CONTACT: + return 'no communication to node'; + case self::MANUALLY_OPERATED: + return 'manually operated by a user'; + case self::BLOCKED: + return 'node has been blocked by an object'; + case self::WRONG_SYSTEMKEY: + return 'the node contains a wrong system key'; + case self::PRIORITY_LEVEL_LOCKED: + return 'the node is locked on this priority level'; + case self::REACHED_WRONG_POSITION: + return 'node has stopped in another position than expected'; + case self::ERROR_DURING_EXECUTION: + return 'an error has occurred during execution of command'; + case self::NO_EXECUTION: + return 'no movement of the node parameter'; + case self::CALIBRATING: + return 'the node is calibrating the parameters'; + case self::POWER_CONSUMPTION_TOO_HIGH: + return 'the node power consumption is too high'; + case self::POWER_CONSUMPTION_TOO_LOW: + return 'the node power consumption is too low'; + case self::LOCK_POSITION_OPEN: + return 'door lock errors. (Door open during lock command)'; + case self::MOTION_TIME_TOO_LONG__COMMUNICATION_ENDED: + return 'the target was not reached in time'; + case self::THERMAL_PROTECTION: + return 'the node has gone into thermal protection mode'; + case self::PRODUCT_NOT_OPERATIONAL: + return 'the node is not currently operational'; + case self::FILTER_MAINTENANCE_NEEDED: + return 'the filter needs maintenance'; + case self::BATTERY_LEVEL: + return 'the battery level is low'; + case self::TARGET_MODIFIED: + return 'the node has modified the target value of the command'; + case self::MODE_NOT_IMPLEMENTED: + return 'this node does not support the mode received'; + case self::COMMAND_INCOMPATIBLE_TO_MOVEMENT: + return 'the node is unable to move in the right direction'; + case self::USER_ACTION: + return 'dead bolt is manually locked during unlock command'; + case self::DEAD_BOLT_ERROR: + return 'dead bolt error'; + case self::AUTOMATIC_CYCLE_ENGAGED: + return 'the node has gone into automatic cycle mode'; + case self::WRONG_LOAD_CONNECTED: + return 'wrong load on node'; + case self::COLOUR_NOT_REACHABLE: + return 'that node is unable to reach received colour code'; + case self::TARGET_NOT_REACHABLE: + return 'the node is unable to reach received target position'; + case self::BAD_INDEX_RECEIVED: + return 'io-protocol has received an invalid index'; + case self::COMMAND_OVERRULED: + return 'that the command was overruled by a new command'; + case self::NODE_WAITING_FOR_POWER: + return 'that the node reported waiting for power'; + case self::INFORMATION_CODE: + return 'an unknown error code received'; + case self::PARAMETER_LIMITED: + return 'the parameter was limited by an unknown device'; + case self::LIMITATION_BY_LOCAL_USER: + return 'the parameter was limited by local button'; + case self::LIMITATION_BY_USER: + return 'the parameter was limited by a remote control'; + case self::LIMITATION_BY_RAIN: + return 'the parameter was limited by a rain sensor'; + case self::LIMITATION_BY_TIMER: + return 'the parameter was limited by a timer'; + case self::LIMITATION_BY_UPS: + return 'the parameter was limited by a power supply'; + case self::LIMITATION_BY_UNKNOWN_DEVICE: + return 'the parameter was limited by an unknown device'; + case self::LIMITATION_BY_SAAC: + return 'the parameter was limited by a standalone automatic controller'; + case self::LIMITATION_BY_WIND: + return 'the parameter was limited by a wind sensor'; + case self::LIMITATION_BY_MYSELF: + return 'the parameter was limited by the node itself'; + case self::LIMITATION_BY_AUTOMATIC_CYCLE: + return 'the parameter was limited by an automatic cycle'; + case self::LIMITATION_BY_EMERGENCY: + return 'the parameter was limited by an emergency'; + } + } +} + +class APICommand +{ + public const ERROR_NTF = 0x0000; //Provides information on what triggered the error. + public const REBOOT_REQ = 0x0001; //Request gateway to reboot. + public const REBOOT_CFM = 0x0002; //Acknowledge to REBOOT_REQ command. + public const SET_FACTORY_DEFAULT_REQ = 0x0003; //Request gateway to clear system table, scene table and set Ethernet settings to factory default. Gateway will reboot. + public const SET_FACTORY_DEFAULT_CFM = 0x0004; //Acknowledge to SET_FACTORY_DEFAULT_REQ command. + public const GET_VERSION_REQ = 0x0008; //Request version information. + public const GET_VERSION_CFM = 0x0009; //Acknowledge to GET_VERSION_REQ command. + public const GET_PROTOCOL_VERSION_REQ = 0x000A; //Request KLF 200 API protocol version. + public const GET_PROTOCOL_VERSION_CFM = 0x000B; //Acknowledge to GET_PROTOCOL_VERSION_REQ command. + public const GET_STATE_REQ = 0x000C; //Request the state of the gateway + public const GET_STATE_CFM = 0x000D; //Acknowledge to GET_STATE_REQ command. + public const LEAVE_LEARN_STATE_REQ = 0x000E; //Request gateway to leave learn state. + public const LEAVE_LEARN_STATE_CFM = 0x000F; //Acknowledge to LEAVE_LEARN_STATE_REQ command. + public const GET_NETWORK_SETUP_REQ = 0x00E0; //Request network parameters. + public const GET_NETWORK_SETUP_CFM = 0x00E1; //Acknowledge to GET_NETWORK_SETUP_REQ. + public const SET_NETWORK_SETUP_REQ = 0x00E2; //Set network parameters. + public const SET_NETWORK_SETUP_CFM = 0x00E3; //Acknowledge to SET_NETWORK_SETUP_REQ. + public const CS_GET_SYSTEMTABLE_DATA_REQ = 0x0100; //Request a list of nodes in the gateways system table. + public const CS_GET_SYSTEMTABLE_DATA_CFM = 0x0101; //Acknowledge to CS_GET_SYSTEMTABLE_DATA_REQ + public const CS_GET_SYSTEMTABLE_DATA_NTF = 0x0102; //Acknowledge to CS_GET_SYSTEM_TABLE_DATA_REQList of nodes in the gateways systemtable. + public const CS_DISCOVER_NODES_REQ = 0x0103; //Start CS DiscoverNodes macro in KLF200. + public const CS_DISCOVER_NODES_CFM = 0x0104; //Acknowledge to CS_DISCOVER_NODES_REQ command. + public const CS_DISCOVER_NODES_NTF = 0x0105; //Acknowledge to CS_DISCOVER_NODES_REQ command. + public const CS_REMOVE_NODES_REQ = 0x0106; //Remove one or more nodes in the systemtable. + public const CS_REMOVE_NODES_CFM = 0x0107; //Acknowledge to CS_REMOVE_NODES_REQ. + public const CS_VIRGIN_STATE_REQ = 0x0108; //Clear systemtable and delete system key. + public const CS_VIRGIN_STATE_CFM = 0x0109; //Acknowledge to CS_VIRGIN_STATE_REQ. + public const CS_CONTROLLER_COPY_REQ = 0x010A; //Setup KLF200 to get or give a system to or from another io-homecontrol® remote control. By a system means all nodes in the systemtable and the system key. + public const CS_CONTROLLER_COPY_CFM = 0x010B; //Acknowledge to CS_CONTROLLER_COPY_REQ. + public const CS_CONTROLLER_COPY_NTF = 0x010C; //Acknowledge to CS_CONTROLLER_COPY_REQ. + public const CS_CONTROLLER_COPY_CANCEL_NTF = 0x010D; //Cancellation of system copy to other controllers. + public const CS_RECEIVE_KEY_REQ = 0x010E; //Receive system key from another controller. + public const CS_RECEIVE_KEY_CFM = 0x010F; //Acknowledge to CS_RECEIVE_KEY_REQ. + public const CS_RECEIVE_KEY_NTF = 0x0110; //Acknowledge to CS_RECEIVE_KEY_REQ with status. + public const CS_PGC_JOB_NTF = 0x0111; //Information on Product Generic Configuration job initiated by press on PGC button. + public const CS_SYSTEM_TABLE_UPDATE_NTF = 0x0112; //Broadcasted to all clients and gives information about added and removed actuator nodes in system table. + public const CS_GENERATE_NEW_KEY_REQ = 0x0113; //Generate new system key and update actuators in systemtable. + public const CS_GENERATE_NEW_KEY_CFM = 0x0114; //Acknowledge to CS_GENERATE_NEW_KEY_REQ. + public const CS_GENERATE_NEW_KEY_NTF = 0x0115; //Acknowledge to CS_GENERATE_NEW_KEY_REQ with status. + public const CS_REPAIR_KEY_REQ = 0x0116; //Update key in actuators holding an old key. + public const CS_REPAIR_KEY_CFM = 0x0117; //Acknowledge to CS_REPAIR_KEY_REQ. + public const CS_REPAIR_KEY_NTF = 0x0118; //Acknowledge to CS_REPAIR_KEY_REQ with status. + public const CS_ACTIVATE_CONFIGURATION_MODE_REQ = 0x0119; //Request one or more actuator to open for configuration. + public const CS_ACTIVATE_CONFIGURATION_MODE_CFM = 0x011A; //Acknowledge to CS_ACTIVATE_CONFIGURATION_MODE_REQ. + public const GET_NODE_INFORMATION_REQ = 0x0200; //Request extended information of one specific actuator node. + public const GET_NODE_INFORMATION_CFM = 0x0201; //Acknowledge to GET_NODE_INFORMATION_REQ. + public const GET_NODE_INFORMATION_NTF = 0x0210; //Acknowledge to GET_NODE_INFORMATION_REQ. + public const GET_ALL_NODES_INFORMATION_REQ = 0x0202; //Request extended information of all nodes. + public const GET_ALL_NODES_INFORMATION_CFM = 0x0203; //Acknowledge to GET_ALL_NODES_INFORMATION_REQ + public const GET_ALL_NODES_INFORMATION_NTF = 0x0204; //Acknowledge to GET_ALL_NODES_INFORMATION_REQ. Holds node information + public const GET_ALL_NODES_INFORMATION_FINISHED_NTF = 0x0205; //Acknowledge to GET_ALL_NODES_INFORMATION_REQ. No more nodes. + public const SET_NODE_VARIATION_REQ = 0x0206; //Set node variation. + public const SET_NODE_VARIATION_CFM = 0x0207; //Acknowledge to SET_NODE_VARIATION_REQ. + public const SET_NODE_NAME_REQ = 0x0208; //Set node name. + public const SET_NODE_NAME_CFM = 0x0209; //Acknowledge to SET_NODE_NAME_REQ. + public const SET_NODE_VELOCITY_REQ = 0x020A; //Set node velocity. + public const SET_NODE_VELOCITY_CFM = 0x020B; //Acknowledge to SET_NODE_VELOCITY_REQ. + public const NODE_INFORMATION_CHANGED_NTF = 0x020C; //Information has been updated. + public const NODE_STATE_POSITION_CHANGED_NTF = 0x0211; //Information has been updated. + public const SET_NODE_ORDER_AND_PLACEMENT_REQ = 0x020D; //Set search order and room placement. + public const SET_NODE_ORDER_AND_PLACEMENT_CFM = 0x020E; //Acknowledge to SET_NODE_ORDER_AND_PLACEMENT_REQ. + public const GET_GROUP_INFORMATION_REQ = 0x0220; //Request information about all defined groups. + public const GET_GROUP_INFORMATION_CFM = 0x0221; //Acknowledge to GET_GROUP_INFORMATION_REQ. + public const GET_GROUP_INFORMATION_NTF = 0x0230; //Acknowledge to GET_NODE_INFORMATION_REQ. + public const SET_GROUP_INFORMATION_REQ = 0x0222; //Change an existing group. + public const SET_GROUP_INFORMATION_CFM = 0x0223; //Acknowledge to SET_GROUP_INFORMATION_REQ. + public const GROUP_INFORMATION_CHANGED_NTF = 0x0224; //Broadcast to all, about group information of a group has been changed. + public const DELETE_GROUP_REQ = 0x0225; //Delete a group. + public const DELETE_GROUP_CFM = 0x0226; //Acknowledge to DELETE_GROUP_INFORMATION_REQ. + public const NEW_GROUP_REQ = 0x0227; //Request new group to be created. + public const NEW_GROUP_CFM = 0x0228; + public const GET_ALL_GROUPS_INFORMATION_REQ = 0x0229; //Request information about all defined groups. + public const GET_ALL_GROUPS_INFORMATION_CFM = 0x022A; //Acknowledge to GET_ALL_GROUPS_INFORMATION_REQ. + public const GET_ALL_GROUPS_INFORMATION_NTF = 0x022B; //Acknowledge to GET_ALL_GROUPS_INFORMATION_REQ. + public const GET_ALL_GROUPS_INFORMATION_FINISHED_NTF = 0x022C; //Acknowledge to GET_ALL_GROUPS_INFORMATION_REQ. + public const GROUP_DELETED_NTF = 0x022D; //GROUP_DELETED_NTF is broadcasted to all, when a group has been removed. + public const HOUSE_STATUS_MONITOR_ENABLE_REQ = 0x0240; //Enable house status monitor. + public const HOUSE_STATUS_MONITOR_ENABLE_CFM = 0x0241; //Acknowledge to HOUSE_STATUS_MONITOR_ENABLE_REQ. + public const HOUSE_STATUS_MONITOR_DISABLE_REQ = 0x0242; //Disable house status monitor. + public const HOUSE_STATUS_MONITOR_DISABLE_CFM = 0x0243; //Acknowledge to HOUSE_STATUS_MONITOR_DISABLE_REQ. + public const COMMAND_SEND_REQ = 0x0300; //Send activating command direct to one or more io-homecontrol® nodes. + public const COMMAND_SEND_CFM = 0x0301; //Acknowledge to COMMAND_SEND_REQ. + public const COMMAND_RUN_STATUS_NTF = 0x0302; //Gives run status for io-homecontrol® node. + public const COMMAND_REMAINING_TIME_NTF = 0x0303; //Gives remaining time before io-homecontrol® node enter target position. + public const SESSION_FINISHED_NTF = 0x0304; //Command send, Status request, Wink, Mode or Stop session is finished. + public const STATUS_REQUEST_REQ = 0x0305; //Get status request from one or more io-homecontrol® nodes. + public const STATUS_REQUEST_CFM = 0x0306; //Acknowledge to STATUS_REQUEST_REQ. + public const STATUS_REQUEST_NTF = 0x0307; //Acknowledge to STATUS_REQUEST_REQ. Status request from one or more io-homecontrol® nodes. + public const WINK_SEND_REQ = 0x0308; //Request from one or more io-homecontrol® nodes to Wink. + public const WINK_SEND_CFM = 0x0309; //Acknowledge to WINK_SEND_REQ + public const WINK_SEND_NTF = 0x030A; //Status info for performed wink request. + public const SET_LIMITATION_REQ = 0x0310; //Set a parameter limitation in an actuator. + public const SET_LIMITATION_CFM = 0x0311; //Acknowledge to SET_LIMITATION_REQ. + public const GET_LIMITATION_STATUS_REQ = 0x0312; //Get parameter limitation in an actuator. + public const GET_LIMITATION_STATUS_CFM = 0x0313; //Acknowledge to GET_LIMITATION_STATUS_REQ. + public const LIMITATION_STATUS_NTF = 0x0314; //Hold information about limitation. + public const MODE_SEND_REQ = 0x0320; //Send Activate Mode to one or more io-homecontrol® nodes. + public const MODE_SEND_CFM = 0x0321; //Acknowledge to MODE_SEND_REQ + public const MODE_SEND_NTF = 0x0322; //Notify with Mode activation info. + public const INITIALIZE_SCENE_REQ = 0x0400; //Prepare gateway to record a scene. + public const INITIALIZE_SCENE_CFM = 0x0401; //Acknowledge to INITIALIZE_SCENE_REQ. + public const INITIALIZE_SCENE_NTF = 0x0402; //Acknowledge to INITIALIZE_SCENE_REQ. + public const INITIALIZE_SCENE_CANCEL_REQ = 0x0403; //Cancel record scene process. + public const INITIALIZE_SCENE_CANCEL_CFM = 0x0404; //Acknowledge to INITIALIZE_SCENE_CANCEL_REQ command. + public const RECORD_SCENE_REQ = 0x0405; //Store actuator positions changes since INITIALIZE_SCENE, as a scene. + public const RECORD_SCENE_CFM = 0x0406; //Acknowledge to RECORD_SCENE_REQ. + public const RECORD_SCENE_NTF = 0x0407; //Acknowledge to RECORD_SCENE_REQ. + public const DELETE_SCENE_REQ = 0x0408; //Delete a recorded scene. + public const DELETE_SCENE_CFM = 0x0409; //Acknowledge to DELETE_SCENE_REQ. + public const RENAME_SCENE_REQ = 0x040A; //Request a scene to be renamed. + public const RENAME_SCENE_CFM = 0x040B; //Acknowledge to RENAME_SCENE_REQ. + public const GET_SCENE_LIST_REQ = 0x040C; //Request a list of scenes. + public const GET_SCENE_LIST_CFM = 0x040D; //Acknowledge to GET_SCENE_LIST. + public const GET_SCENE_LIST_NTF = 0x040E; //Acknowledge to GET_SCENE_LIST. + public const GET_SCENE_INFORMATION_REQ = 0x040F; //Request extended information for one given scene. + public const GET_SCENE_INFORMATION_CFM = 0x0410; //Acknowledge to GET_SCENE_INFORMATION_REQ. + public const GET_SCENE_INFORMATION_NTF = 0x0411; //Acknowledge to GET_SCENE_INFORMATION_REQ. + public const ACTIVATE_SCENE_REQ = 0x0412; //Request gateway to enter a scene. + public const ACTIVATE_SCENE_CFM = 0x0413; //Acknowledge to ACTIVATE_SCENE_REQ. + public const STOP_SCENE_REQ = 0x0415; //Request all nodes in a given scene to stop at their current position. + public const STOP_SCENE_CFM = 0x0416; //Acknowledge to STOP_SCENE_REQ. + public const SCENE_INFORMATION_CHANGED_NTF = 0x0419; //A scene has either been changed or removed. + public const ACTIVATE_PRODUCTGROUP_REQ = 0x0447; //Activate a product group in a given direction. + public const ACTIVATE_PRODUCTGROUP_CFM = 0x0448; //Acknowledge to ACTIVATE_PRODUCTGROUP_REQ. + public const ACTIVATE_PRODUCTGROUP_NTF = 0x0449; //Acknowledge to ACTIVATE_PRODUCTGROUP_REQ. + public const GET_CONTACT_INPUT_LINK_LIST_REQ = 0x0460; //Get list of assignments to all Contact Input to scene or product group. + public const GET_CONTACT_INPUT_LINK_LIST_CFM = 0x0461; //Acknowledge to GET_CONTACT_INPUT_LINK_LIST_REQ. + public const SET_CONTACT_INPUT_LINK_REQ = 0x0462; //Set a link from a Contact Input to a scene or product group. + public const SET_CONTACT_INPUT_LINK_CFM = 0x0463; //Acknowledge to SET_CONTACT_INPUT_LINK_REQ. + public const REMOVE_CONTACT_INPUT_LINK_REQ = 0x0464; //Remove a link from a Contact Input to a scene. + public const REMOVE_CONTACT_INPUT_LINK_CFM = 0x0465; //Acknowledge to REMOVE_CONTACT_INPUT_LINK_REQ. + public const GET_ACTIVATION_LOG_HEADER_REQ = 0x0500; //Request header from activation log. + public const GET_ACTIVATION_LOG_HEADER_CFM = 0x0501; //Confirm header from activation log. + public const CLEAR_ACTIVATION_LOG_REQ = 0x0502; //Request clear all data in activation log. + public const CLEAR_ACTIVATION_LOG_CFM = 0x0503; //Confirm clear all data in activation log. + public const GET_ACTIVATION_LOG_LINE_REQ = 0x0504; //Request line from activation log. + public const GET_ACTIVATION_LOG_LINE_CFM = 0x0505; //Confirm line from activation log. + public const ACTIVATION_LOG_UPDATED_NTF = 0x0506; //Confirm line from activation log. + public const GET_MULTIPLE_ACTIVATION_LOG_LINES_REQ = 0x0507; //Request lines from activation log. + public const GET_MULTIPLE_ACTIVATION_LOG_LINES_NTF = 0x0508; //Error log data from activation log. + public const GET_MULTIPLE_ACTIVATION_LOG_LINES_CFM = 0x0509; //Confirm lines from activation log. + public const SET_UTC_REQ = 0x2000; //Request to set UTC time. + public const SET_UTC_CFM = 0x2001; //Acknowledge to SET_UTC_REQ. + public const RTC_SET_TIME_ZONE_REQ = 0x2002; //Set time zone and daylight savings rules. + public const RTC_SET_TIME_ZONE_CFM = 0x2003; //Acknowledge to RTC_SET_TIME_ZONE_REQ. + public const GET_LOCAL_TIME_REQ = 0x2004; //Request the local time based on current time zone and daylight savings rules. + public const GET_LOCAL_TIME_CFM = 0x2005; //Acknowledge to RTC_SET_TIME_ZONE_REQ. + public const IGNORE_2120 = 0x2120; + public const IGNORE_2121 = 0x2121; + public const IGNORE_2122 = 0x2122; + public const IGNORE_2123 = 0x2123; + public const IGNORE_2124 = 0x2124; + public const PASSWORD_ENTER_REQ = 0x3000; //Enter password to authenticate request + public const PASSWORD_ENTER_CFM = 0x3001; //Acknowledge to PASSWORD_ENTER_REQ + public const PASSWORD_CHANGE_REQ = 0x3002; //Request password change. + public const PASSWORD_CHANGE_CFM = 0x3003; //Acknowledge to PASSWORD_CHANGE_REQ. + public const PASSWORD_CHANGE_NTF = 0x3004; //Acknowledge to PASSWORD_CHANGE_REQ. Broadcasted to all connected clients. + + public static $NotifyCommand = [ + self::CS_GET_SYSTEMTABLE_DATA_NTF, + self::CS_DISCOVER_NODES_NTF, + self::CS_CONTROLLER_COPY_NTF, + self::CS_CONTROLLER_COPY_CANCEL_NTF, + self::CS_RECEIVE_KEY_NTF, + self::CS_PGC_JOB_NTF, + self::CS_SYSTEM_TABLE_UPDATE_NTF, + self::CS_GENERATE_NEW_KEY_NTF, + self::CS_REPAIR_KEY_NTF, + self::GET_NODE_INFORMATION_NTF, + self::GET_ALL_NODES_INFORMATION_NTF, + self::GET_ALL_NODES_INFORMATION_FINISHED_NTF, + self::NODE_INFORMATION_CHANGED_NTF, + self::NODE_STATE_POSITION_CHANGED_NTF, + self::GET_GROUP_INFORMATION_NTF, + self::GROUP_INFORMATION_CHANGED_NTF, + self::GET_ALL_GROUPS_INFORMATION_NTF, + self::GET_ALL_GROUPS_INFORMATION_FINISHED_NTF, + self::GROUP_DELETED_NTF, + self::COMMAND_RUN_STATUS_NTF, + self::COMMAND_REMAINING_TIME_NTF, + self::SESSION_FINISHED_NTF, + self::STATUS_REQUEST_NTF, + self::WINK_SEND_NTF, + self::LIMITATION_STATUS_NTF, + self::MODE_SEND_NTF, + self::INITIALIZE_SCENE_NTF, + self::RECORD_SCENE_NTF, + self::GET_SCENE_LIST_NTF, + self::GET_SCENE_INFORMATION_NTF, + self::SCENE_INFORMATION_CHANGED_NTF, + self::ACTIVATE_PRODUCTGROUP_NTF, + self::ACTIVATION_LOG_UPDATED_NTF, + self::GET_MULTIPLE_ACTIVATION_LOG_LINES_NTF, + self::PASSWORD_CHANGE_NTF, + self::IGNORE_2120, + self::IGNORE_2121, + self::IGNORE_2122, + self::IGNORE_2123, + self::IGNORE_2124 + ]; + + public static function isEvent(int $APICommand) + { + return in_array($APICommand, self::$NotifyCommand); + } + + public static function ToString($APICommand) + { + switch ($APICommand) { + case self::ERROR_NTF: + return 'ERROR_NTF'; + case self::REBOOT_REQ: + return 'REBOOT_REQ'; + case self::REBOOT_CFM: + return 'REBOOT_CFM'; + case self::SET_FACTORY_DEFAULT_REQ: + return 'SET_FACTORY_DEFAULT_REQ'; + case self::SET_FACTORY_DEFAULT_CFM: + return 'SET_FACTORY_DEFAULT_CFM'; + case self::GET_VERSION_REQ: + return 'GET_VERSION_REQ'; + case self::GET_VERSION_CFM: + return 'GET_VERSION_CFM'; + case self::GET_PROTOCOL_VERSION_REQ: + return 'GET_PROTOCOL_VERSION_REQ'; + case self::GET_PROTOCOL_VERSION_CFM: + return 'GET_PROTOCOL_VERSION_CFM'; + case self::GET_STATE_REQ: + return 'GET_STATE_REQ'; + case self::GET_STATE_CFM: + return 'GET_STATE_CFM'; + case self::LEAVE_LEARN_STATE_REQ: + return 'LEAVE_LEARN_STATE_REQ'; + case self::LEAVE_LEARN_STATE_CFM: + return 'LEAVE_LEARN_STATE_CFM'; + case self::GET_NETWORK_SETUP_REQ: + return 'GET_NETWORK_SETUP_REQ'; + case self::GET_NETWORK_SETUP_CFM: + return 'GET_NETWORK_SETUP_CFM'; + case self::SET_NETWORK_SETUP_REQ: + return 'SET_NETWORK_SETUP_REQ'; + case self::SET_NETWORK_SETUP_CFM: + return 'SET_NETWORK_SETUP_CFM'; + case self::CS_GET_SYSTEMTABLE_DATA_REQ: + return 'CS_GET_SYSTEMTABLE_DATA_REQ'; + case self::CS_GET_SYSTEMTABLE_DATA_CFM: + return 'CS_GET_SYSTEMTABLE_DATA_CFM'; + case self::CS_GET_SYSTEMTABLE_DATA_NTF: + return 'CS_GET_SYSTEMTABLE_DATA_NTF'; + case self::CS_DISCOVER_NODES_REQ: + return 'CS_DISCOVER_NODES_REQ'; + case self::CS_DISCOVER_NODES_CFM: + return 'CS_DISCOVER_NODES_CFM'; + case self::CS_DISCOVER_NODES_NTF: + return 'CS_DISCOVER_NODES_NTF'; + case self::CS_REMOVE_NODES_REQ: + return 'CS_REMOVE_NODES_REQ'; + case self::CS_REMOVE_NODES_CFM: + return 'CS_REMOVE_NODES_CFM'; + case self::CS_VIRGIN_STATE_REQ: + return 'CS_VIRGIN_STATE_REQ'; + case self::CS_VIRGIN_STATE_CFM: + return 'CS_VIRGIN_STATE_CFM'; + case self::CS_CONTROLLER_COPY_REQ: + return 'CS_CONTROLLER_COPY_REQ'; + case self::CS_CONTROLLER_COPY_CFM: + return 'CS_CONTROLLER_COPY_CFM'; + case self::CS_CONTROLLER_COPY_NTF: + return 'CS_CONTROLLER_COPY_NTF'; + case self::CS_CONTROLLER_COPY_CANCEL_NTF: + return 'CS_CONTROLLER_COPY_CANCEL_NTF'; + case self::CS_RECEIVE_KEY_REQ: + return 'CS_RECEIVE_KEY_REQ'; + case self::CS_RECEIVE_KEY_CFM: + return 'CS_RECEIVE_KEY_CFM'; + case self::CS_RECEIVE_KEY_NTF: + return 'CS_RECEIVE_KEY_NTF'; + case self::CS_PGC_JOB_NTF: + return 'CS_PGC_JOB_NTF'; + case self::CS_SYSTEM_TABLE_UPDATE_NTF: + return 'CS_SYSTEM_TABLE_UPDATE_NTF'; + case self::CS_GENERATE_NEW_KEY_REQ: + return 'CS_GENERATE_NEW_KEY_REQ'; + case self::CS_GENERATE_NEW_KEY_CFM: + return 'CS_GENERATE_NEW_KEY_CFM'; + case self::CS_GENERATE_NEW_KEY_NTF: + return 'CS_GENERATE_NEW_KEY_NTF'; + case self::CS_REPAIR_KEY_REQ: + return 'CS_REPAIR_KEY_REQ'; + case self::CS_REPAIR_KEY_CFM: + return 'CS_REPAIR_KEY_CFM'; + case self::CS_REPAIR_KEY_NTF: + return 'CS_REPAIR_KEY_NTF'; + case self::CS_ACTIVATE_CONFIGURATION_MODE_REQ: + return 'CS_ACTIVATE_CONFIGURATION_MODE_REQ'; + case self::CS_ACTIVATE_CONFIGURATION_MODE_CFM: + return 'CS_ACTIVATE_CONFIGURATION_MODE_CFM'; + case self::GET_NODE_INFORMATION_REQ: + return 'GET_NODE_INFORMATION_REQ'; + case self::GET_NODE_INFORMATION_CFM: + return 'GET_NODE_INFORMATION_CFM'; + case self::GET_NODE_INFORMATION_NTF: + return 'GET_NODE_INFORMATION_NTF'; + case self::GET_ALL_NODES_INFORMATION_REQ: + return 'GET_ALL_NODES_INFORMATION_REQ'; + case self::GET_ALL_NODES_INFORMATION_CFM: + return 'GET_ALL_NODES_INFORMATION_CFM'; + case self::GET_ALL_NODES_INFORMATION_NTF: + return 'GET_ALL_NODES_INFORMATION_NTF'; + case self::GET_ALL_NODES_INFORMATION_FINISHED_NTF: + return 'GET_ALL_NODES_INFORMATION_FINISHED_NTF'; + case self::SET_NODE_VARIATION_REQ: + return 'SET_NODE_VARIATION_REQ'; + case self::SET_NODE_VARIATION_CFM: + return 'SET_NODE_VARIATION_CFM'; + case self::SET_NODE_NAME_REQ: + return 'SET_NODE_NAME_REQ'; + case self::SET_NODE_NAME_CFM: + return 'SET_NODE_NAME_CFM'; + case self::SET_NODE_VELOCITY_REQ: + return 'SET_NODE_VELOCITY_REQ'; + case self::SET_NODE_VELOCITY_CFM: + return 'SET_NODE_VELOCITY_CFM'; + case self::NODE_INFORMATION_CHANGED_NTF: + return 'NODE_INFORMATION_CHANGED_NTF'; + case self::NODE_STATE_POSITION_CHANGED_NTF: + return 'NODE_STATE_POSITION_CHANGED_NTF'; + case self::SET_NODE_ORDER_AND_PLACEMENT_REQ: + return 'SET_NODE_ORDER_AND_PLACEMENT_REQ'; + case self::SET_NODE_ORDER_AND_PLACEMENT_CFM: + return 'SET_NODE_ORDER_AND_PLACEMENT_CFM'; + case self::GET_GROUP_INFORMATION_REQ: + return 'GET_GROUP_INFORMATION_REQ'; + case self::GET_GROUP_INFORMATION_CFM: + return 'GET_GROUP_INFORMATION_CFM'; + case self::GET_GROUP_INFORMATION_NTF: + return 'GET_GROUP_INFORMATION_NTF'; + case self::SET_GROUP_INFORMATION_REQ: + return 'SET_GROUP_INFORMATION_REQ'; + case self::SET_GROUP_INFORMATION_CFM: + return 'SET_GROUP_INFORMATION_CFM'; + case self::GROUP_INFORMATION_CHANGED_NTF: + return 'GROUP_INFORMATION_CHANGED_NTF'; + case self::DELETE_GROUP_REQ: + return 'DELETE_GROUP_REQ'; + case self::DELETE_GROUP_CFM: + return 'DELETE_GROUP_CFM'; + case self::NEW_GROUP_REQ: + return 'NEW_GROUP_REQ'; + case self::NEW_GROUP_CFM: + return 'NEW_GROUP_CFM'; + case self::GET_ALL_GROUPS_INFORMATION_REQ: + return 'GET_ALL_GROUPS_INFORMATION_REQ'; + case self::GET_ALL_GROUPS_INFORMATION_CFM: + return 'GET_ALL_GROUPS_INFORMATION_CFM'; + case self::GET_ALL_GROUPS_INFORMATION_NTF: + return 'GET_ALL_GROUPS_INFORMATION_NTF'; + case self::GET_ALL_GROUPS_INFORMATION_FINISHED_NTF: + return 'GET_ALL_GROUPS_INFORMATION_FINISHED_NTF'; + case self::GROUP_DELETED_NTF: + return 'GROUP_DELETED_NTF'; + case self::HOUSE_STATUS_MONITOR_ENABLE_REQ: + return 'HOUSE_STATUS_MONITOR_ENABLE_REQ'; + case self::HOUSE_STATUS_MONITOR_ENABLE_CFM: + return 'HOUSE_STATUS_MONITOR_ENABLE_CFM'; + case self::HOUSE_STATUS_MONITOR_DISABLE_REQ: + return 'HOUSE_STATUS_MONITOR_DISABLE_REQ'; + case self::HOUSE_STATUS_MONITOR_DISABLE_CFM: + return 'HOUSE_STATUS_MONITOR_DISABLE_CFM'; + case self::COMMAND_SEND_REQ: + return 'COMMAND_SEND_REQ'; + case self::COMMAND_SEND_CFM: + return 'COMMAND_SEND_CFM'; + case self::COMMAND_RUN_STATUS_NTF: + return 'COMMAND_RUN_STATUS_NTF'; + case self::COMMAND_REMAINING_TIME_NTF: + return 'COMMAND_REMAINING_TIME_NTF'; + case self::SESSION_FINISHED_NTF: + return 'SESSION_FINISHED_NTF'; + case self::STATUS_REQUEST_REQ: + return 'STATUS_REQUEST_REQ'; + case self::STATUS_REQUEST_CFM: + return 'STATUS_REQUEST_CFM'; + case self::STATUS_REQUEST_NTF: + return 'STATUS_REQUEST_NTF'; + case self::WINK_SEND_REQ: + return 'WINK_SEND_REQ'; + case self::WINK_SEND_CFM: + return 'WINK_SEND_CFM'; + case self::WINK_SEND_NTF: + return 'WINK_SEND_NTF'; + case self::SET_LIMITATION_REQ: + return 'SET_LIMITATION_REQ'; + case self::SET_LIMITATION_CFM: + return 'SET_LIMITATION_CFM'; + case self::GET_LIMITATION_STATUS_REQ: + return 'GET_LIMITATION_STATUS_REQ'; + case self::GET_LIMITATION_STATUS_CFM: + return 'GET_LIMITATION_STATUS_CFM'; + case self::LIMITATION_STATUS_NTF: + return 'LIMITATION_STATUS_NTF'; + case self::MODE_SEND_REQ: + return 'MODE_SEND_REQ'; + case self::MODE_SEND_CFM: + return 'MODE_SEND_CFM'; + case self::MODE_SEND_NTF: + return 'MODE_SEND_NTF'; + case self::INITIALIZE_SCENE_REQ: + return 'INITIALIZE_SCENE_REQ'; + case self::INITIALIZE_SCENE_CFM: + return 'INITIALIZE_SCENE_CFM'; + case self::INITIALIZE_SCENE_NTF: + return 'INITIALIZE_SCENE_NTF'; + case self::INITIALIZE_SCENE_CANCEL_REQ: + return 'INITIALIZE_SCENE_CANCEL_REQ'; + case self::INITIALIZE_SCENE_CANCEL_CFM: + return 'INITIALIZE_SCENE_CANCEL_CFM'; + case self::RECORD_SCENE_REQ: + return 'RECORD_SCENE_REQ'; + case self::RECORD_SCENE_CFM: + return 'RECORD_SCENE_CFM'; + case self::RECORD_SCENE_NTF: + return 'RECORD_SCENE_NTF'; + case self::DELETE_SCENE_REQ: + return 'DELETE_SCENE_REQ'; + case self::DELETE_SCENE_CFM: + return 'DELETE_SCENE_CFM'; + case self::RENAME_SCENE_REQ: + return 'RENAME_SCENE_REQ'; + case self::RENAME_SCENE_CFM: + return 'RENAME_SCENE_CFM'; + case self::GET_SCENE_LIST_REQ: + return 'GET_SCENE_LIST_REQ'; + case self::GET_SCENE_LIST_CFM: + return 'GET_SCENE_LIST_CFM'; + case self::GET_SCENE_LIST_NTF: + return 'GET_SCENE_LIST_NTF'; + case self::GET_SCENE_INFORMATION_REQ: + return 'GET_SCENE_INFORMATION_REQ'; + case self::GET_SCENE_INFORMATION_CFM: + return 'GET_SCENE_INFORMATION_CFM'; + case self::GET_SCENE_INFORMATION_NTF: + return 'GET_SCENE_INFORMATION_NTF'; + case self::ACTIVATE_SCENE_REQ: + return 'ACTIVATE_SCENE_REQ'; + case self::ACTIVATE_SCENE_CFM: + return 'ACTIVATE_SCENE_CFM'; + case self::STOP_SCENE_REQ: + return 'STOP_SCENE_REQ'; + case self::STOP_SCENE_CFM: + return 'STOP_SCENE_CFM'; + case self::SCENE_INFORMATION_CHANGED_NTF: + return 'SCENE_INFORMATION_CHANGED_NTF'; + case self::ACTIVATE_PRODUCTGROUP_REQ: + return 'ACTIVATE_PRODUCTGROUP_REQ'; + case self::ACTIVATE_PRODUCTGROUP_CFM: + return 'ACTIVATE_PRODUCTGROUP_CFM'; + case self::ACTIVATE_PRODUCTGROUP_NTF: + return 'ACTIVATE_PRODUCTGROUP_NTF'; + case self::GET_CONTACT_INPUT_LINK_LIST_REQ: + return 'GET_CONTACT_INPUT_LINK_LIST_REQ'; + case self::GET_CONTACT_INPUT_LINK_LIST_CFM: + return 'GET_CONTACT_INPUT_LINK_LIST_CFM'; + case self::SET_CONTACT_INPUT_LINK_REQ: + return 'SET_CONTACT_INPUT_LINK_REQ'; + case self::SET_CONTACT_INPUT_LINK_CFM: + return 'SET_CONTACT_INPUT_LINK_CFM'; + case self::REMOVE_CONTACT_INPUT_LINK_REQ: + return 'REMOVE_CONTACT_INPUT_LINK_REQ'; + case self::REMOVE_CONTACT_INPUT_LINK_CFM: + return 'REMOVE_CONTACT_INPUT_LINK_CFM'; + case self::GET_ACTIVATION_LOG_HEADER_REQ: + return 'GET_ACTIVATION_LOG_HEADER_REQ'; + case self::GET_ACTIVATION_LOG_HEADER_CFM: + return 'GET_ACTIVATION_LOG_HEADER_CFM'; + case self::CLEAR_ACTIVATION_LOG_REQ: + return 'CLEAR_ACTIVATION_LOG_REQ'; + case self::CLEAR_ACTIVATION_LOG_CFM: + return 'CLEAR_ACTIVATION_LOG_CFM'; + case self::GET_ACTIVATION_LOG_LINE_REQ: + return 'GET_ACTIVATION_LOG_LINE_REQ'; + case self::GET_ACTIVATION_LOG_LINE_CFM: + return 'GET_ACTIVATION_LOG_LINE_CFM'; + case self::ACTIVATION_LOG_UPDATED_NTF: + return 'ACTIVATION_LOG_UPDATED_NTF'; + case self::GET_MULTIPLE_ACTIVATION_LOG_LINES_REQ: + return 'GET_MULTIPLE_ACTIVATION_LOG_LINES_REQ'; + case self::GET_MULTIPLE_ACTIVATION_LOG_LINES_NTF: + return 'GET_MULTIPLE_ACTIVATION_LOG_LINES_NTF'; + case self::GET_MULTIPLE_ACTIVATION_LOG_LINES_CFM: + return 'GET_MULTIPLE_ACTIVATION_LOG_LINES_CFM'; + case self::SET_UTC_REQ: + return 'SET_UTC_REQ'; + case self::SET_UTC_CFM: + return 'SET_UTC_CFM'; + case self::RTC_SET_TIME_ZONE_REQ: + return 'RTC_SET_TIME_ZONE_REQ'; + case self::RTC_SET_TIME_ZONE_CFM: + return 'RTC_SET_TIME_ZONE_CFM'; + case self::GET_LOCAL_TIME_REQ: + return 'GET_LOCAL_TIME_REQ'; + case self::GET_LOCAL_TIME_CFM: + return 'GET_LOCAL_TIME_CFM'; + case self::PASSWORD_ENTER_REQ: + return 'PASSWORD_ENTER_REQ'; + case self::PASSWORD_ENTER_CFM: + return 'PASSWORD_ENTER_CFM'; + case self::PASSWORD_CHANGE_REQ: + return 'PASSWORD_CHANGE_REQ'; + case self::PASSWORD_CHANGE_CFM: + return 'PASSWORD_CHANGE_CFM'; + case self::PASSWORD_CHANGE_NTF: + return 'PASSWORD_CHANGE_NTF'; + case self::IGNORE_2120: + return 'IGNORE_2120'; + case self::IGNORE_2121: + return 'IGNORE_2121'; + case self::IGNORE_2122: + return 'IGNORE_2122'; + case self::IGNORE_2123: + return 'IGNORE_2123'; + case self::IGNORE_2124: + return 'IGNORE_2124'; + } + } +} + +class Node +{ + public static $SubType = [ + 0x0040 => 'Interior Venetian Blind', + 0x0080 => 'Roller Shutter', + 0x0081 => 'Adjustable slats rolling shutter', + 0x0082 => 'Roller Shutter With projection', + 0x00C0 => 'Vertical Exterior Awning', + 0x0100 => 'Window opener', + 0x0101 => 'Window opener with integrated rain sensor', + 0x0140 => 'Garage door opener', + 0x017A => 'Garage door opener', + 0x0180 => 'Light', + 0x01BA => 'Light only supporting on/off', + 0x01C0 => 'Gate opener', + 0x01FA => 'Gate opener', + 0x0200 => 'Rolling Door Opener', + 0x0240 => 'Door lock', + 0x0241 => 'Window lock', + 0x0280 => 'Vertical Interior Blinds', + 0x0300 => 'Beacon', + 0x0340 => 'Dual Roller Shutter', + 0x0380 => 'Heating Temperature Interface', + 0x03C0 => 'On/Off switch', + 0x0400 => 'Horizontal awning', + 0x0440 => 'Exterior Venetian blind', + 0x0480 => 'Louver blind', + 0x04C0 => 'Curtain track', + 0x0500 => 'Ventilation point', + 0x0501 => 'Air inlet', + 0x0502 => 'Air transfer', + 0x0503 => 'Air outlet', + 0x0540 => 'Exterior heating', + 0x057A => 'Exterior heating', + 0x0580 => 'Heat pump', + 0x05C0 => 'Intrusion alarm', + 0x0600 => 'Swinging Shutters', + 0x0601 => 'Swinging Shutter with independent handling of the leaves' + ]; +} + +/* @} */ diff --git a/libs/helper b/libs/helper new file mode 160000 index 0000000..6cb1dce --- /dev/null +++ b/libs/helper @@ -0,0 +1 @@ +Subproject commit 6cb1dce113b8fe3135d8ccb6aa06b7b89c73d1db diff --git a/tests/LibraryTest.php b/tests/LibraryTest.php new file mode 100644 index 0000000..316da36 --- /dev/null +++ b/tests/LibraryTest.php @@ -0,0 +1,27 @@ +validateLibrary(__DIR__ . '/..'); + } + + public function testValidateKLF200Gateway(): void + { + $this->validateModule(__DIR__ . '/../KLF200Gateway'); + } + + public function testValidateKLF200Configurator(): void + { + $this->validateModule(__DIR__ . '/../KLF200Configurator'); + } + public function testValidateKLF200Node(): void + { + $this->validateModule(__DIR__ . '/../KLF200Node'); + } +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml new file mode 100644 index 0000000..51b6d5b --- /dev/null +++ b/tests/phpunit.xml @@ -0,0 +1,11 @@ + + + + + ../VeluxKLF200 + + + + + + diff --git a/tests/stubs b/tests/stubs new file mode 160000 index 0000000..9ee089a --- /dev/null +++ b/tests/stubs @@ -0,0 +1 @@ +Subproject commit 9ee089af18a9f19b8046b2f0672939121f4f3d42