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