Module entwickeln

Moin,

am Anfang stand ein Fibaro HCL welches ich gerne an IP-Symcon anbinden wollte. Grundsätzlich geht eine Anbindung über diverse Skripte, (Z-Wave-Story-und-Meine-Entscheidung-für-s-HCL ) aber die Entwicklung eines eigenen Moduls für IP-Symcon stand schon immer auf meiner ToDo-Liste :slight_smile:
Das Entwickler-Webinar war eine tolle Hilfe für den ersten Einstieg. Schnell waren damit die ersten Schritte nachprogrammiert. Dann kam die Anforderung vom IP-Symcon aus mit weiteren Komponenten in meinem Netzwerk zu kommunizieren. Und dann waren IO’s Splitter, Virtual IO, Client Sockets und diverse andere Schlagworte auf dem Tisch.

Nachdem ich nun das halbe Forum kenne :-), glaube ich die Grundlagen für den Datenaustausch einigermaßen verstanden zu haben. Eine große Hilfe hierbei war der Modul Generator/, der ein Modul-Grundgerüst erzeugt auf welchem man dann gut aufsetzen kann.

Es sind aber noch viele Fragen offen. Ich möchte die Profis unter den Modulentwickleren hiermit einladen mich bei einigen Schritten zu meinem Modul zu begleiten. Ich versuch die gewonnenen Erkenntnisse so gut es geht zu dokumentieren und den dabei entstehende
Quellcode auf Git-Hub bereit zu stellen. Diese kann andern angehenden Modulentwicklern helfen einen Einstieg in die Modul Entwicklung zu bekommen.

Ich freue mich auf tatkräftige Unterstützung.

Grüße Detlev.

Hi,

hier der erste Schritt:
Über den Modul-Generator habe ich eine Bibliothek mit zwei Modulen erstellt:
Device (Typ Gerät) und IO (Typ I/O), der Datenfluss ist vom Modul Device zum Modul IO eingetragen.

Eine Schematische Übersicht dazu
Device-IO.pdf (96.5 KB)

der entsprechende Quellcode ist hier zu finden.

Über den erweiterten Befehl HCL_Send2 aus dem Kontext-Menu des angelegten Devices kann eine Nachricht
an das IO-Module gesendet werden (ich habe zum Test zwei Devices angelegt), dies resultiert im Log mit

01.05.2020 13:26:33 | 00000 | CUSTOM  | HCL2 IO FRWD         | von Device zu IO15181
01.05.2020 13:26:33 | 00000 | CUSTOM  | HCL2 Device RECV(15181) | return from GW
01.05.2020 13:26:33 | 00000 | CUSTOM  | HCL2 Device RECV(24482) | return from GW

zu erkennen ist, das die Antwort des IO-Modules an die Devices bei beiden Devices ankommt.

Und hier kommt meine erste Frage:
Um die Antworten gezielt an ein Device zurück zu senden ist nach meinem Verständnis ein Splitter erforderlich ,richtig?

Grüße Detlev.

Der nächste Schritt ist das Einbinden eines Splitters, dazu habe ich im Modulgenerator wieder eine Bibliothek mit folgenden Modulen angelegt:

Device ( Type Gerät ) Datenfluss zum Splitter, Splitter ( Typ Splitter ) Datenfluss zum IO und IO ( Type I/O).
Das Schema dazu ist hier Device-Splitter-IO.pdf (144 KB)

Der Quell-Code hier.

Auch hier habe ich wieder zwei Devices angelegt. Sende ich nun vom Device über den Befehl HCLD_Send aus dem Kontextmenü Daten an das IO-Device werden diese über den Splitter geführt. Im Log resultiert dies zu

01.05.2020 13:50:42 | 00000 | CUSTOM  | Device Send(34306)   | Daten vom Device34306
01.05.2020 13:50:42 | 00000 | CUSTOM  | Splitter FRWD        | Daten vom Device34306
01.05.2020 13:50:42 | 00000 | CUSTOM  | IO FRWD              | Daten vom Device34306
01.05.2020 13:50:42 | 00000 | CUSTOM  | Splitter RECV        | Antwort vom IO
01.05.2020 13:50:42 | 00000 | CUSTOM  | Device RECV(44848)   | Antwort vom IO
01.05.2020 13:50:42 | 00000 | CUSTOM  | Device RECV(34306)   | Antwort vom IO

Hier der Befehl vom zweiten Device:

01.05.2020 13:51:42 | 00000 | CUSTOM  | Device Send(44848)   | Daten vom zweiten Device44848
01.05.2020 13:51:42 | 00000 | CUSTOM  | Splitter FRWD        | Daten vom zweiten Device44848
01.05.2020 13:51:42 | 00000 | CUSTOM  | IO FRWD              | Daten vom zweiten Device44848
01.05.2020 13:51:42 | 00000 | CUSTOM  | Splitter RECV        | Antwort vom IO
01.05.2020 13:51:42 | 00000 | CUSTOM  | Device RECV(44848)   | Antwort vom IO
01.05.2020 13:51:43 | 00000 | CUSTOM  | Device RECV(34306)   | Antwort vom IO

Hmm, es erhalten beide Devices eine Antwort vom IO-Modul, da war nicht mein Ziel.

Tja, hier meine zweite Frage:
Wie kann ich vom IO-Device gezielt ein Device ansprechen.

Grüße Detlev

Gar nicht.
Ist auch nicht notwendig.
Es gibt zwei Ansätze und je nachdem wie der Datenaustausch mit dem HCL funktioniert sind beide möglich oder sogar nötig.
Wenn das HCL dir unaufgefordert Daten liefert, also Events, dann brauchst du einen Symcon IO.
Wenn das HCL nichts selber sendet und du abfragen musst, dann kannst du einen eigenen IO bauen und PHP Funktionen benutzen um Daten zu transferieren.

Zuerst Beispiel zwei, das ist einfacher.
Dein Device sendet etwas an deinen IO und dann kannst du das Ergebnis direkt mit dem Rückgabewert von ForwardData (im IO) zurückgeben.
Fertig :slight_smile:

Bist du gezwungen einen Symcon IO zu benutzen, so kann dein Splitter natürlich das ebenso handhaben.
Dazu musst du allerdings beim Versenden über den IO auf die Antwort warten, und diese Antwort kommt in einem anderen PHP-Thread an. Deswegen musst du Buffer (Siehe Doku) benutzen um die Antwort aus dem einen Thread mit ReceiveData in deinen SendeThread von ForwardData zu bekommen.
Dazu musst du aber in ReceiveData auch unterscheiden können ob es nun eine Antwort auf die Anfrage oder ein spontanes Event war, was du empfangen hast.

Du kannst das mit dem Rückgabewert und Warten auf Antwort auch weglassen, allerdings ist es dann schwer zu erkennen ob deine Anfrage vom HCL verarbeitet wurde.

Für Events (oder auch alles, wenn du den Rückgabewert in ForwardData nicht benutzt) sendet dein Splitter über ReceiveData empfangene Daten an die Devices.
Diese benutzen einen ReceiveDataFilter, welchen du setzen musst.
Dann empfängt ein Device nur die Daten welche dem Filter entsprechen.

Das ist alles etwas viel auf einmal, ich habe hier ein Modul, welches einen eigenen IO benutzt und somit der Datenaustausch in einem PHP-Thread abläuft, ohne das man einen Buffer benutzen muss.
Events werden hier über einen Webhook unabhängig vom direkten Datenaustausch empfangen:
IPSPRTG/module.php at master · Nall-chan/IPSPRTG · GitHub
Und hier eines, wo mit buffern gearbeitet wird.
IPSOnkyoAVR/module.php at 50b4dc625b005ff2a7e7649f471c73f35d39c2bf · Nall-chan/IPSOnkyoAVR · GitHub
Ist aber schon sehr komplex, da viele Funktionen in meiner HilfsLibrary stecken.

Michael

Hi Michael,
danke für die umfangreiche Antwort und den Hinweis auf deine Lösungen.
Ich habe mich nun erstmal auf Variante zwei (mein IO kommuniziert mit dem HCL) eingeschossen.

Die Filterung ist nun in den Devices mittels SetReceiveDataFilter aktiviert.

Wenn ich es richtig verstehe muß ich im „Daten-Array“ über welches die Module kommunizieren ein ID-Flag integrieren, so dass das Device-Module seine Daten wieder erkennt. Ich habe es über eine Property des Device-Moduls gelöst.

public function ApplyChanges()
		{
			//Never delete this line!
			$id= $this->ReadPropertyInteger("HclId");
			$this->SetReceiveDataFilter(".*\"HclId\":$id.*");
			parent::ApplyChanges();
		}

Ist dies die übliche Vorgehensweise, oder geht das auch eleganter. Jedes an meinem Splitter hängende Module muß ja nun den gesamten Json-String nach dem Muster durchsuchen.

Ich glaube den Sinn des Splitters habe ich doch noch nicht ganz verstanden. Aktuell nimmt mein Splitter die Daten entgegen und liefert diese an den IO aus, die vom IO kommenden Daten werden ebenfalls vom Splitter wieder an das Device geschickt.
Eine Verarbeitung erfolgt im Splitter nicht. Somit könnte ich doch auch auf den Splitter verzichten, oder?

Kannst Du mir ein Beispiel nennen, wo der Einsatz des Splitters erforderlich ist?

Der aktualisierte Code ist wieder online: github
Grüße Detlev

Wenn diese ID immer im Datensatz vorkommt, dann nutze sie.
Das ist das übliche vorgehen, so eine ID zu benutzen, welche dann in der Instanz konfiguriert wird.

Eine pauschale Aussage zur Nutzung eines Splitters ist schwer.
Du brauchst ihn zwingend bei serieller Kommunikation welche entsprechend getimed werden muss wenn mehrere Devices Daten versenden wollen (serielle Verbindung). Oder das Endgerät z.b. eine fortlaufende FrameId als Zähler benutzt.
Oder auch wenn du empfangene Daten vorher aufbereiten bzw. zu gesendeten Daten zuordnen willst (von einen Thread in den anderen übertragen).
Michael

Hi,
die Kommunikation zwischen meinen Devices und dem IO-Modul über den Splitter läuft nun, danke für die Hilfe. Via

$this->SetReceiveDataFilter(".*\"HclId\":$id.*");

habe ich im Device einen Filter gesetzt, so dass jedes Device nur auf die Daten reagiert, für die es auch zuständig ist.
Sobald der Code vorzeigbar wird, stelle ich ihn wieder zur Verfügung.

Nun würde ich gerne einen Konfigurator mit einbinden, der das Anlegen der Devices übernimmt.
Das Konfigurator-Modul habe ich schon angelegt. Wie stelle ich aber nun die Kommunikation zu meinem IO-Modul her?
Binde ich den Konfigurator ähnlich wie das Device in den Datenfluss ein?
parentRequirements vom Konfigurator zeigt auf implemented vom Splitter und childRequirements vom Splitter zeigt auf implemented vom Konfigurator?

Konfigurator

{
    "id": "{9A8475E2-D8FC-D63F-2036-3F04B625895E}",
    "name": "HCLCONF",
    "type": 4,
    "vendor": "",
    "aliases": [],
    "parentRequirements": [
        "{10F1AAA4-1735-784E-54EE-F3F28C7AA90A}"
    ],
    "childRequirements": [],
    "implemented": [
        "{2D6EFEFD-65DA-32F6-AC43-89AB4E6B4C8A}"
    ],
    "prefix": "HCLK",
    "url": ""
}

Device

{
    "id": "{F3B81A0E-E285-F942-9FB0-051C1D3D4CA6}",
    "name": "HCLD",
    "type": 3,
    "vendor": "",
    "aliases": [],
    "parentRequirements": [
        "{10F1AAA4-1735-784E-54EE-F3F28C7AA90A}"
    ],
    "childRequirements": [],
    "implemented": [
        "{2D6EFEFD-65DA-32F6-AC43-89AB4E6B4C8A}"
    ],
    "prefix": "HCLD",
    "url": ""
}

Splitter

{
    "id": "{1FF709CD-6A4E-2924-186F-2A582C21020B}",
    "name": "HCLSPLIT",
    "type": 2,
    "vendor": "",
    "aliases": [],
    "parentRequirements": [
        "{B8E06686-57C1-9885-31E1-7E64EC58025D}"
    ],
    "childRequirements": [
        "{2D6EFEFD-65DA-32F6-AC43-89AB4E6B4C8A}"
    ],
    "implemented": [
        "{10F1AAA4-1735-784E-54EE-F3F28C7AA90A}",
        "{16DA84B2-ED83-604B-D217-F520A8589DFC}"
    ],
    "prefix": "HCLS",
    "url": ""
}

Und damit die Antworten auf von Splitter auf die Anfragen vom Konfigurator auch nur diesen erreichen setze ich wieder Filter ein?

Grüße Detlev.

Ja, du kannst es wie beim Device umsetzen.
Meistens will der Konfigurator ja etwas anderes empfangen und somit ist der Filter anders.
Schlau wäre es, wenn dein Splitter Antworten direkt im ForwardData per Return zurückgibt.
So das im Konfigurator die Methode SenDataToParent dies Antwort liefert.
Dann braucht es kein ReceiveData im Konfigurator.
Michael

Hi,

habe gerade beruflich viel um die Ohren, somit geht es hier leider langsamer weiter als mir lieb ist :frowning:

Aber ich bin einen Schritt weiter gekommen, es gibt nun einen Konfigurator der die Devices aus dem HCL ausliest und in einer Liste darstellt. Rudimentäre Funktionen zum Anlegen der Devices als Instanz sind ebenfalls integriert. Ein Fibaro Switch kann schon geschaltet werden.

An einer Stelle komme ich aber nicht weiter, wenn ich den Konfigurator schließe und erneut wieder öffne, hat er vergessen welche Devices schon als Instanz angelegt wurden.

Ich glaube mich erinnern zu können, das die erste Version meines Konfigurator die angelegten Instanzen eigenständig gefunden hat, nun macht er es nicht mehr, ich kann aber keinen Ansatz finden, wo der Fehler liegt. Vielleicht hat jemand einenTip für mich :slight_smile:
Den Code habe ich wieder hier https://github.com/boni127/HCL3 bereitgestellt.

Freue mich über jeden hilfreichen Tip
Grüße Detlev.

Du musst selber alle vorhandenen Instanzen aus IPS auslesen (auf Basis der guid), filtern auf die welche an deinem Parent hängen, und dann diesen Eintrag mit der Instanz ID ergänzen.
Nur wenn die Instanz ID vorhanden ist, zeigt der Konfigurator die im System vorhandenen Instanzen an.
Michael

Hi Michael,
danke für die schnelle Antwort.
Es funktioniert nun:
Mit


foreach (IPS_GetInstanceListByModuleID("{5C71DE68-3DB9-2B14-58E3-C3A6D5A426C2}") as $guid_instance_id) {
				$CreatedInstances[IPS_GetProperty($guid_instance_id, "HclId")] = $guid_instance_id;
				}

baue ich mir ein Array mit der InstanceID und der ID im HCL. Beim Anlegen der Instanz-Liste im Konfigurator weise ich dann den entsprechenden HCL-Devices die InstanzID zu:


foreach (json_decode($response) as $entity) {
				IPS_LogMessage("HCLCONF return id", $entity->id);
				if (isset($CreatedInstances[$entity->id])) {
					$dummy = $CreatedInstances[$entity->id];
					} 
				else {
					$dummy = 0;
				}
				if ($entity->parentId>=1) {
					$AddValue = [
						'instanceID' => $dummy,
						'zid' => $entity->id,
						'name' => $entity->name,
						'company' =>  $entity->properties->zwaveCompany,
						'zwaveinfo' => $entity->properties->zwaveInfo,
						'softwareversion' => $entity->properties->zwaveVersion,
						'create' => [
							'moduleID' => '{5C71DE68-3DB9-2B14-58E3-C3A6D5A426C2}',
							'configuration' => [
									'HclId' => $entity->id,
									'zwaveCompany' => $entity->properties->zwaveCompany
							],
							'location' => $tree_position,
							]
						];
					$Form['actions'][0]['values'][] = $AddValue;
					}
				}

Wenn nun aber ein User händisch eine Instanz doppelt anlegt, gewinnt die Instanz, die von IPS_GetInstanceListByModuleID als letztes aufgelistet wird. Muß so etwas abgefangen werden?

Und gleich noch eine Frage. Es gibt ja eine unüberschaubare Fülle an Zwave-Geräten. Würdest Du jedes Gerät über ein eigenes Modul abbilden oder mit einem „Universal-Modul“ beim Anlegen die entsprechenden Eigenschaften setzen?

Grüße Detlev.

Ja. Ist ja auch kein großes Problem.
Ich habe es so umgesetzt, das ich alle gemeldeten Geräte von der Hardware durchgehe und mit der Config bestücke, dabei wird eine eventuelle vorhande Instanz ID zugeordnet.
Diese Instanz ID entferne ich dann aus dem Array der bekannten Instanzen.
Der Rest des Array wird dann ohne Create der Liste hinzugefügt, somit sind die Einträge rot. Das sind dann alle doppelte Instanzen wie auch Instanzen welche in der Hardware nicht mehr vorhanden sind.
Beispiel:
VeluxKLF200/module.php at 40422704a9fd86a2d59e6f85bb42aafadd5f7a8e · Nall-chan/VeluxKLF200 · GitHub

Pauschal schwer zu sagen.
Sofern es der Datenaustausch mit der Hardware hergibt das möglichst universell zu bauen; dann nur eine generische Instanz.
Wenn jeder Typ aber ganz anders zu händeln ist, dann nach einzelne nach Typ.
VELUX und Xiaomi Aqara Module sind generische Device Instanzen.
Einzelnen Device Instanzen habe ich bisher eigentlich nur in Modulen benutzt, wo eine Hardware zu viele gemischte Funktionen hat, um eine logisch sinnvolle Trennung zu bekommen.
So zum Beispiel bei Kodi oder den Onkyo/Pioneer Receiver.
Dort wären alle Funktionen in einer Instanz einfach zu viel.
Michael

Moin, nach langer Pause, habe ich endlich wieder Zeit gefunden um an meinem Modul weiter zu arbeiten :slight_smile:

Da kommt auch schon das erste Problem:

Ich habe mich entscheiden, für die unterschiedlichen Z-Wave Devices am Home Center Lite eigene Module anzulegen.

Somit habe ich ein Device-Modul kopiert, eine neu generierte GUID über das Syscom Portal erzeugt und in die Modul.json eingetragen.
Versuche ich nun über den Konfiguration eine neue Instanz dieses Moduls anzulegen erhalte ich die Meldung

<br />
<b>Fatal error</b>:  Uncaught Error: Class 'fibaroBinarySwitch' not found in /-:3
Stack trace:
#0 {main}
  thrown in <b>/-</b> on line <b>3</b><br />
 (Code: -32603)

Das gleiche passiert auch, wenn ich im Objektbaum versuche die Instanz anzulegen.

Ich vermute, in den modul.json -Files etwas falsch konfiguriert zu haben, kann aber keinen Fehler finden

hier die entsprechenden Modul.json-Files

{
    "id": "{518933FA-6D8A-2ADA-5774-8413A058EA2A}",
    "name": "HCLSPLIT",
    "type": 2,
    "vendor": "Fibaro",
    "aliases": ["Fibaro Homecenter Lite Splitter"],
    "parentRequirements": [
        "{6DEAE164-60E6-BF7A-53B7-A24588741E4D}"
    ],
    "childRequirements": [
        "{66D69490-42AD-A343-0F75-185A31AA08BA}"
    ],
    "implemented": [
        "{D443C558-D963-4B2A-D017-B97CF576CB3A}",
        "{1590D5D7-2F2F-BF7F-3C3A-D9581874BF46}"
    ],
    "prefix": "HCL",
    "url": ""
}

{
    "id": "{0BE7FA45-4866-1A55-2736-D0CAB9C70680}",
    "name": "HCLIO",
    "type": 1,
    "vendor": "Fibaro",
    "aliases": [ "Fibaro Homecenter Lite IO" ],
    "parentRequirements": [],
    "childRequirements": [
        "{D443C558-D963-4B2A-D017-B97CF576CB3A}"
    ],
    "implemented": [
        "{6DEAE164-60E6-BF7A-53B7-A24588741E4D}"
    ],
    "prefix": "HCL",
    "url": ""
}

{
    "id": "{5A6B59F8-7B6D-15F3-72A8-57AE88D93593}",
    "name": "HCLK",
    "type": 4,
    "vendor": "Fibaro",
    "aliases": ["Fibaro Homecenter Lite Configurator"],
    "parentRequirements": [
        "{1590D5D7-2F2F-BF7F-3C3A-D9581874BF46}"
    ],
    "childRequirements": [],
    "implemented": [
        "{66D69490-42AD-A343-0F75-185A31AA08BA}"
    ],
    "prefix": "HCL",
    "url": ""
}

{
    "id": "{5C71DE68-3DB9-2B14-58E3-C3A6D5A426C2}",
    "name": "HCLD",
    "type": 3,
    "vendor": "Fibaro",
    "aliases": ["Fibaro Homecenter Lite Device"],
    "parentRequirements": [
        "{1590D5D7-2F2F-BF7F-3C3A-D9581874BF46}"
    ],
    "childRequirements": [],
    "implemented": [
        "{66D69490-42AD-A343-0F75-185A31AA08BA}"
    ],
    "prefix": "HCL",
    "url": ""
}

{
    "id": "{7EAB4612-1574-CDFA-E25D-3F27F6CF1C3C}",
    "name": "fibaroBinarySwitch",
    "type": 3,
    "vendor": "Fibaro",
    "aliases": ["Fibaro Homecenter Lite Binary Switch"],
    "parentRequirements": [
        "{1590D5D7-2F2F-BF7F-3C3A-D9581874BF46}"
    ],
    "childRequirements": [],
    "implemented": [
        "{66D69490-42AD-A343-0F75-185A31AA08BA}"
    ],
    "prefix": "HCL",
    "url": ""
}

Vielleicht hat ja jemand eine Idee wo hier der Fehler liegt. Schönen Sonntag noch
Grüße Detlev

Du hast vermutlich in der dazugehörigen module.php den Namen deiner Klasse nicht mit geändert. Der muss dem Namen aus der module.json entsprechen.
Michael

Hi Michael,

genau das war es:

<?php
	class HCLD extends IPSModule {

		public function Create()
		{
			//Never delete this line!
			parent::Create();

gegen

<?php
	class fibaroBinarySwitch extends IPSModule {

		public function Create()
		{
			//Never delete this line!
			parent::Create();

getaucht, nun funktioniert es, danke!