HomeBrewWired - Das Tutorial

Begonnen von Thorsten Pferdekaemper, 01 Dezember 2016, 22:00:12

Vorheriges Thema - Nächstes Thema

Thorsten Pferdekaemper

Hi,
da es jetzt doch ein paar Interessenten gibt, starte ich hiermit das HomeBrewWired-Tutorial. Das Tutorial soll helfen, eigene Geräte zu entwickeln, die zu Homematic-Wired kompatibel sind. Im Prinzip ist dafür außer einem Arduino (oder ähnlichem) und einem RS485-Bustransceiver keine Hardware notwendig. ...ach ja, die Stromversorgung natürlich noch.
Dieses Tutorial wird nicht erklären, wie man prinzipiell Homematic Wired mit FHEM betreibt. Auch die Verwendung der Arduino-IDE setze ich voraus. Also wenn noch nicht passiert, dann wären das die ersten beiden "Haus"Aufgaben, die zu erledigen sind. Siehe auch
http://www.fhemwiki.de/wiki/HomeMatic_Wired#Installation_und_Upgrade_in_FHEM
und
https://www.arduino.cc/en/Guide/HomePage.

Bitte stellt sicher, dass in FHEM die neuste HM485-Version installiert ist, auch wenn diese weit neuer ist als dieses Tutorial. Ich werde das Tutorial hin und wieder aktualisieren.

Ich empfehle dringend, für die HomeBrew-Versuche ein eigenes Testsystem zu haben. Wenn ein Gerät fehlerhaft ist, dann kann das durchaus den ganzen Bus lahmlegen. Bei mir zuhause wäre das blöd, da man dann keine Lichter mehr schalten könnte. In der nächsten Folge werde ich erklären, wie man sich ein kleines Testsystem auf einem Windows-Laptop zusammenbastelt.

Noch etwas: Dieser Thread soll nur das Tutorial selbst enthalten. Daher werde ich ihn immer wieder schließen. Alle Fragen, Wünsche und Kommentare bitte hier unterbringen: https://forum.fhem.de/index.php/topic,61781.0.html

Gruß,
  Thorsten   
FUIP

Thorsten Pferdekaemper

Sorry, es geht noch nicht so richtig los. Ich will zuerst noch beschreiben, wie man sich auf einem Windows-Rechner ein minimales HMW-Testsystem zusammenbastelt. (Wer alles auf Linux macht, oder noch einen RasPi übrig hat, der kann das natürlich überspringen. Denen mit Apple kann ich nicht helfen... )
Als Interface verwende ich einen DIGITUS DA-70157. Das hat den Vorteil, dass man nicht einmal eine 24V-Stromversorgung braucht (so lange man keine "echten" HMW-Devices an den Bus hängen will). Die Software-Seite ist mit den LAN-Teilen dafür etwas einfacher. D.h. das hier ist nur wirklich relevant, wenn man das ganze mit einem USB-RS485-Interface macht.

Erst einmal muss FHEM installiert werden wie hier beschrieben: http://www.fhemwiki.de/wiki/Windows_-_FHEM_installieren. Ich habe das mit Strawberry-Perl gemacht. Dabei sind die Ausführungen zur "COM1-Problematik" zu beachten. Die ganzen Sachen mit Dauerbetrieb und Autostart kann man natürlich weglassen. Ihr werdet FHEM sowieso des Öfteren neu starten...

Dann...

  • Die HMW-Module wie üblich installieren.
  • Die Datei <pfad-zu-fhem>\FHEM\lib\HM485\HM485d\ServerTools.pm durch die Version im Anhang ersetzen
  • Die Datei HM485DaemonStart.bat (siehe Anhang) in den fhem-Pfad kopieren, also dorthin, wo auch fhemstart.bat liegt. In der Datei "COM11" durch den Port ersetzen, an dem der RS485-Stick hängt.
  • Per Doppelklick kann man jetzt HM485DaemonStart.bat und fhemstart.bat starten.
  • Jetzt die fhem-Oberfläche aufrufen (normalerweise localhost:8083) und ein HM485_LAN-Device anlegen. Das Attribute HM485d_bind muss auf "0" gesetzt werden, da der Daemon in Windows nicht automatisch gestartet werden kann.
Damit sollte FHEM HMW- und HBW-Devices am "Digitus-Bus" erkennen. Wenn nicht, dann am besten alles nochmal stoppen und den Daemon sowie danach (!) fhem selbst wieder starten.
Natürlich wäre es schöner, wenn das ganze einfach so unter Windows laufen würde, aber ich will mich zurzeit auf die HomeBrew-Sachen konzentrieren.

Bei mir sieht das dann so aus wie auf dem angehängten Bild.
Gruß,
  Thorsten
FUIP

Thorsten Pferdekaemper

Die Minimal-Hardware
Meine Minimal-Hardware besteht aus einem Arduino Uno und einem MAX487E-Bustransceiver. Andere Transceiver gehen natürlich auch, aber dann passt das Schaltbild natürlich nicht mehr so ganz.
Also, wer einen Arduino Uno hat und einen MAX487E, der kann das ganze so zusammenstöpseln wie auf den angehängten Bildern. Hier noch ein paar Anmerkungen dazu:

  • Die Spannungsversorgung vom Bus (24V) auf keinen Fall irgendwo anschließen. Das würde dem Arduino nicht guttun und wahrscheinlich auch am USB-Port was kaputt machen. Auch die +5V vom Digitus-Stick (falls verwendet) nicht anschließen.
  • Theoretisch kann man auch GND vom Bus weglassen. Bei RS485 ist das Potential erst einmal egal, weil...
  • ...hier kein "Abschlusswiderstand" verwendet wird. Es funktioniert auch so.
So, jetzt wird es doch langsam etwas spät und ich muss morgen etwas früher raus. Zur Software kommen wir dann in den nächsten Tagen.

Gruß,
   Thorsten
FUIP

Thorsten Pferdekaemper

#3
Der Minimal-Sketch
(Für alle, die noch nicht mit Arduininisch vertraut sind: Ein Programm heißt dort "Sketch". Es geht hier also um die Software auf dem Arduino.)
Zuerst einmal müssen aber die HBWired-Libraries installiert werden. Diese befinden sich im Git hier: https://github.com/ThorstenPferdekaemper/HBWired. Am einfachsten dürfte es sein, das als zip-File herunterzuladen: https://github.com/ThorstenPferdekaemper/HBWired/archive/master.zip.
Wie man Arduino-Libraries installiert ist im Prinzip hier beschrieben: https://www.arduino.cc/en/Guide/Libraries#toc2. Dort unter "Manual installation" nachsehen.
Bei mir liegen die Teile im Verzeichnis "C:\Users\thorsten\Documents\Arduino\libraries" ("Documents" erscheint aus irgendwelchen Gründen auch als "Eigene Dokumente". Warum macht man sowas?). Das sieht dann so aus wie auf dem angehängten Bild.

Jetzt die Arduino-IDE (neu) starten und den folgenden Sketch anlegen:

#define HMW_DEVICETYPE 0xAB

#define HARDWARE_VERSION 0x01
#define FIRMWARE_VERSION 0x0100

#include "HBWSoftwareSerial.h"
#include "HBWired.h"

#define RS485_RXD 4   // Receive-Pin
#define RS485_TXD 2   // Transmit-Pin
#define RS485_TXEN 3  // Transmit-Enable

struct hbw_config {
  uint8_t  logging_time;     // 0x0001
  uint32_t central_address;  // 0x0002 - 0x0005
} config;


HBWSoftwareSerial rs485(RS485_RXD, RS485_TXD);
HBWDevice* device = NULL;

void setup()
{
  Serial.begin(19200);
  rs485.begin();

  device = new HBWDevice(HMW_DEVICETYPE, HARDWARE_VERSION, FIRMWARE_VERSION,
                         &rs485,RS485_TXEN,sizeof(config),&config,0,NULL,&Serial,
                         NULL, NULL);
  hbwdebug(F("B: 2A\n"));
}

void loop()
{
  // call the HBW loop
  device->loop();
};

(Rein theoretisch würde das auch noch "minimaler" gehen, aber dann wird's auch wieder unübersichtlich.)

EDIT: Bei einer Arduino-IDE-Version kleiner als 1.6.6 kann es das Problem geben, dass die EEPROM.h nicht gefunden wird. Dann muss vor der Zeile

#include "HBWSoftwareSerial.h"
 
die folgende Zeile eingefügt werden:

#include <EEPROM.h>


Jetzt noch das richtige Board und den richtigen Port auswählen. Achtung: Wenn man sowohl das USB-Kabel vom Arduino als auch den USB-RS485-Adapter am Rechner hängen hat, dann werden in der Arduino-IDE beide Ports angezeigt.
Wenn alles stimmt, dann müsste sich das Programm jetzt auf den Arduino laden lassen.

Danach sollte FHEM das neue Gerät als "HMW-Generic" Device erkennen.

Gruß,
   Thorsten

FUIP

Thorsten Pferdekaemper

So, die Wochenend-Gäste sind zum Flughafen gebracht. Es kann jetzt weitergehen.
Mit dem Minimal-Sketch kann man natürlich noch nicht viel anfangen. Das Gerät taugt allenfalls als Bus-Sniffer. Dazu muss man einfach nur in der Arduino-IDE den "Seriellen Monitor" aktivieren, und schon sieht man, was so auf dem Bus abgeht.
...aber erst einmal ein paar Erklärungen dazu, wie das ganze jetzt in FHEM aussieht. Wenn alles so geklappt hat wie gedacht und der Arduino vorher einigermaßen jungfräulich war, dann sollte das Device in FHEM jetzt so aussehen wie auf dem Bild im Anhang. Ich will jetzt nicht alles erklären, da das meiste für Homematic-Wired in FHEM ganz normal ist. Ein paar Dinge sind allerdings spezifisch dafür, was wir gerade getan haben.

  • Die 42FFFFFF im Internal DEF ist die Geräteadresse. (Wenn bei jemandem nicht 42FFFFFF erscheint, dann war das EEPROM des Arduino vorher nicht "leer".) Die HBWired-Geräte haben immer zuerst einmal 42FFFFFF als Geräteadresse. Das muss man natürlich ändern, wenn man mehrere HBWired-Geräte einsetzen will. Wie das geht zeige ich später.
  • Das MODEL ist HMW_Generic. Das liegt daran, dass es für das neue Gerät noch keine Beschreibungsdatei für FHEM gibt. FHEM braucht zu jedem "Device-Type" eine .pm-Datei, die das Gerät beschreibt. An "Generic"-Geräte kann man nur raw-Messages schicken. Mehr dazu und dazu, wie man die Beschreibungsdatei erzeugt kommt ebenfalls später.
  • Das Reading R-central_address sieht wahrscheinlich in etwa so aus: "set-00000001". Das liegt ebenfalls an der fehlenden Geräte-Beschreibungsdatei.

Bevor ich weiter auf die obigen Sachen eingehe, will ich erst einmal das Coding erläutern.

#define HMW_DEVICETYPE 0xAB

Jedes Gerät hat einen "Gerätetyp". Bei Homematic-Wired wird dafür nur ein Byte benutzt, man sollte sich also anschauen, was es schon alles gibt, bevor man "seinen" Gerätetyp auswählt. Die Standard-HMW-Geräte haben Gerätetypen im Bereich von 0x10 (16) bis 0x1F (31). Bisherige Homebrew-Geräte sind hier aufgelistet: http://www.fhemwiki.de/wiki/HomeMatic_Wired#Aktoren_.2F_Sensoren.
Der Gerätetyp ist wichtig, da darüber die Beschreibungsdatei ausgewählt wird.


#define HARDWARE_VERSION 0x01
#define FIRMWARE_VERSION 0x0100

Außer dem Gerätetyp gibt es noch eine Versionierung. Das wird jedoch bisher kaum verwendet.


#include "HBWSoftwareSerial.h"

Ich verwende für die RS485-Anbindung gerne SoftwareSerial, so dass ich die Hardware-Serial für Debug-Ausgaben frei habe und außerdem mein Gerät nicht umbauen muss, wenn ich einen neuen Sketch auf den Arduino lade. Allerdings verlangt Homematic-Wired "gerade Parität", während die originale SoftwareSerial-Library kein Paritätsbit unterstützt. Daher liefere ich eine eigene HBWSoftwareSerial mit, die das Paritätsbit unterstützt. (Allerdings ohne einen Fehler zu liefern, wenn es nicht stimmt.) Zusätzlich habe ich alles rausgeworfen, was man für HMW nicht braucht. Z.B. sind 19200 Baud fest "verdrahtet". 


#include "HBWired.h"

Das bindet die HBWired-Library ein


#define RS485_RXD 4   // Receive-Pin
#define RS485_TXD 2   // Transmit-Pin
#define RS485_TXEN 3  // Transmit-Enable

Das sind die Pin-Nummern für die RS485-Schnittstelle.


struct hbw_config {
  uint8_t  logging_time;     // 0x0001
  uint32_t central_address;  // 0x0002 - 0x0005
} config;

Diese Struktur ist eine Abbildung der Geräte-Konfiguration im EEPROM. Teile des EEPROM werden im RAM gehalten, um einfacher und schneller zugreifen zu können. (Womöglich ändert sich das irgendwann.)


HBWSoftwareSerial rs485(RS485_RXD, RS485_TXD);

Das legt die serielle Schnittstelle zum Bus an.


HBWDevice* device = NULL;

Jedes Gerät muss ein HBWDevice-Objekt haben. Über dieses Objekt werden so ziemlich alle HBW-Funktionen verwaltet.


void setup()
{
  Serial.begin(19200);

Die Hardware-Serial wird für Debug-Ausgaben gestartet.
 
 
  rs485.begin();

Die (Software-)Serielle Schnittstelle zum Bus wird gestartet. Die Angabe der Baudrate fehlt, da das Teil sowieso nur 19200 kann. 


  device = new HBWDevice(HMW_DEVICETYPE, HARDWARE_VERSION, FIRMWARE_VERSION,
                         &rs485,RS485_TXEN,sizeof(config),&config,0,NULL,&Serial,
                         NULL, NULL);

Das legt jetzt das Gerät selbst als Objekt an. Die meisten Parameter dürften mit den obigen Erklärungen jetzt schon klar sein. Lediglich die 0 und die NULLs fehlen noch. Das kommt später in diesem Tutorial, wenn wir zu Kanälen und Verknüpfungen kommen. Die Ungeduldigen können sich die HBWired.h anschauen.      
      

  hbwdebug(F("B: 2A\n"));

Das ist eine Debug-Ausgabe, die nur zeigen soll, dass das Gerät gestartet wurde. Man sollte in HBW-Geräten alle Debug-Ausgaben per hbwdebug machen. Je nachdem, wie die "Debug-Serielle" gesetzt wurde (z.B. auch auf NULL) reagiert das System dann sinnvoll (hoffentlich). 


}

void loop()
{
  // call the HBW loop
  device->loop();
};

Die Methode loop sollte so oft wie möglich aufgerufen werden. Bei HBWired passiert (außer der Low-Level Kommunikation) nichts mit Interrupts. Das bedeutet, dass Nachrichten verloren gehen können, wenn loop nicht mindestens etwa alle 35ms aufgerufen wird. (Wenn ich mich nicht verrechnet habe.) Man darf also nichts "blocking" machen, schon gar nicht so etwas wie "delay(1000)" beim Auslesen von Sensoren.

Gruß,
   Thorsten

FUIP

Thorsten Pferdekaemper

Jetzt nur ein kurzer Beitrag, um die Debug-Ausgaben zu erklären. Das Folgende kommt bei mir im seriellen Monitor an, wenn das "Minimalgerät" das erste Mal mit FHEM spricht. (Ach ja: Seriellen Monitor auf 19200 Baud stellen, sonst klappt's nicht.)

B: 2A
T: FD:FF:FF:FF:FF:F8:42:FF:FF:FF:12:41:00:AB:00:01:00:48:42:57:34:30:37:33:34:37:31:F7:2C
R: FD:42:FF:FF:FF:1A:00:00:00:02:03:68:99:22
T: HWVer,Typ
T: FD:00:00:00:02:38:42:FF:FF:FF:04:AB:01:14:34
R: FD:42:FF:FF:FF:19:00:00:00:02:02:C3:60
R: ACK
R: FD:42:FF:FF:FF:1C:00:00:00:02:03:6E:1D:A8
T: FD:00:00:00:02:58:42:FF:FF:FF:0C:48:42:57:34:30:37:33:34:37:31:66:4A
R: FD:42:FF:FF:FF:19:00:00:00:02:02:C3:60
R: ACK
R: FD:42:FF:FF:FF:1E:00:00:00:02:03:68:D1:D8
T: HWVer,Typ
T: FD:00:00:00:02:78:42:FF:FF:FF:04:AB:01:8D:B2
R: FD:42:FF:FF:FF:19:00:00:00:02:02:C3:60
R: ACK
R: FD:42:FF:FF:FF:18:00:00:00:02:03:6E:55:52
T: FD:00:00:00:02:18:42:FF:FF:FF:0C:48:42:57:34:30:37:33:34:37:31:BF:66
R: FD:42:FF:FF:FF:19:00:00:00:02:02:C3:60
R: ACK
R: FD:42:FF:FF:FF:1A:00:00:00:02:03:76:69:1C
T: FWVer
T: FD:00:00:00:02:38:42:FF:FF:FF:04:01:00:54:34
R: FD:42:FF:FF:FF:19:00:00:00:02:02:C3:60
R: ACK
R: FD:42:FF:FF:FF:1C:00:00:00:02:07:57:00:00:01:02:23:D0
C: Write EEPROM
T: FD:00:00:00:02:59:42:FF:FF:FF:02:B7:58
R: FD:42:FF:FF:FF:1E:00:00:00:02:03:43:41:8A
T: FD:00:00:00:02:79:42:FF:FF:FF:02:53:9E

Das erste Zeichen steht für die Art der Zeile:

  • B (Begin) wird einmal am Anfang ausgegeben.
  • T (Transmit) sagt, dass das Gerät etwas gesendet hat. Es gibt dazu immer eine Zeile mit der Message in Hex-Codes. Manchmal gibt es dazu noch eine beschreibende Zeile, wie z.B. "HWVer,Typ" für "Hardware-Version und Gerätetyp".
  • R (Receive) steht dafür, dass das Gerät etwas empfangen hat. Dabei kommt es nicht darauf an, ob die Nachricht an das Gerät addressiert ist oder nicht. Jedes Gerät am Bus empfängt erst einmal alles. Auch bei "R"-Zeilen gibt es immer eine Hex-Version und manchmal eine beschreibende Zeile.
  • C (Comment) steht für irgendwelche Kommentare, z.B. was das Gerät gerade tut.
  • E (Error) steht für Fehlermeldungen.

Gruß,
   Thorsten
FUIP

Thorsten Pferdekaemper

#6
Bisher wurde das Gerät in FHEM zwar als Homematic-Wired-Gerät erkannt, aber nicht so ganz richtig. Es fehlt noch die Beschreibungsdatei. Im Prinzip braucht FHEM für jeden Gerätetyp (oder so) eine eigene Gerätebeschreibungsdatei im XML-Format.

Die XML-Datei für unser Gerät sieht momentan so aus:

<?xml version="1.0"?>
<device eep_size="1024" version="01">
<supported_types>
<type priority="2" id="HBW-TUTORIAL" name="Das Geraet zum HBWired-Tutorial">
<parameter const_value="0xAB" size="1" index="0"/>
<parameter const_value="0" size="1" index="1"/>
</type>
</supported_types>

<paramset id="HBW-TUTORIAL_dev_master" type="MASTER">
<parameter id="LOGGING_TIME">
<logical type="float" unit="s" default="5.0" max="25.5" min="0.1"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="0x0001"/>
</physical>
<conversion type="float_integer_scale" offset="0.0" factor="10"/>
</parameter>
<parameter id="CENTRAL_ADDRESS" hidden="true">
<logical type="integer"/>
<physical size="4" type="integer" interface="eeprom">
<address index="0x0002"/>
</physical>
</parameter>
<enforce id="CENTRAL_ADDRESS" value="1"/>
</paramset>

<channels>
<channel index="0" type="MAINTENANCE" count="1" class="maintenance" ui_flags="internal">
</channel>
<channel index="1" type="SWITCH" count="1" physical_index_offset="-1">
</channel>
</channels>

</device>

(Das Gerät hat momentan noch keine Kanäle, aber ganz ohne Kanäle funktioniert es nicht in FHEM. Die Beschreibungsdatei braucht zumindest einen Kanal.)
Legt jetzt eine Textdatei mit dem obigen Inhalt an und nennt sie "hbw_tutorial.xml". Das Teil gehört ins Verzeichnis <fhem>\FHEM\lib\HM485\Devices\xml.
Danach muss FHEM neu gestartet werden, also

shutdown restart

Beim Start prüft FHEM, ob es eine neue XML-Datei gibt (oder eine XML-Datei geändert wurde) und wandelt sie in ein internes Format um. Das kann beim ersten Mal ein paar Sekunden dauern.

Dann das Gerät in FHEM löschen und den Arduino neu starten (Reset-Knopf oder ähnlich).
Das Gerät wird jetzt (hoffentlich) in FHEM automatisch neu angelegt und sollte so aussehen wie auf dem zweiten Bild im Anhang. Insbesondere sollte jetzt bei R-central_address die Adresse des HM485_LAN-Geräts stehen. Meistens ist das 00000001, nicht wie bei mir auf dem Screenshot. Tatsächlich wird diese Adresse auch ins EEPROM des Geräts geschrieben.
Außerdem gibt es jetzt eine logging_time und einen Kanal (channel_01). Mit diesem Kanal kann man natürlich noch nichts anfangen. Das liegt daran, dass der Arduino davon noch nichts weiß und noch einiges in der Beschreibungsdatei fehlt.

EDIT: Bitte den ersten Screenshot ignorieren. Die Umwandlung der XML-Datei passiert inzwischen automatisch.

Gruß,
   Thorsten
FUIP

Thorsten Pferdekaemper

Ich habe keine Doku zu den Geräte-XMLs. Aus den XMLs der originalen HM-Wired Geräte kann man aber ein paar Sachen erraten.


<?xml version="1.0"?>
<device eep_size="1024" version="01">

Das ist wahrscheinlich die EEPROM-Größe. Es sind bisher nur Geräte mit einem 1024-Byte EEPROM bekannt.


<supported_types>
<type priority="2" id="HBW-TUTORIAL" name="Das Geraet zum HBWired-Tutorial">
<parameter const_value="0xAB" size="1" index="0"/>
<parameter const_value="0" size="1" index="1"/>
</type>
</supported_types>

Hier ist die "id" (HBW-TUTORIAL) wichtig, da das zum "MODEL" in FHEM wird. Der erste <parameter>-Eintrag ist der Gerätetyp, der mit dem Gerätetyp im Sketch übereinstimmen muss. Damit findet FHEM (oder auch die CCU) die Beschreibungsdatei.


<paramset id="HBW-TUTORIAL_dev_master" type="MASTER">

Das "MASTER" <paramset> enthält die Einstellungen zum Gerät oder auch zu einem Kanal(-Typ). Das hier ist auf der obersten Ebene und enthält daher Einstellungen, die für das ganze Gerät gelten.


<parameter id="LOGGING_TIME">
<logical type="float" unit="s" default="5.0" max="25.5" min="0.1"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="0x0001"/>
</physical>
<conversion type="float_integer_scale" offset="0.0" factor="10"/>
</parameter>

Der Parameter LOGGING_TIME gibt an, nach welcher Zeit ein Ausgang seinen Zustand bei Änderungen mitteilt. Dieser Parameter ist "logisch" ein float mit Werten zwischen 0,1 und 25,5 (Sekunden). Der Wert ist als Ganzzahl im EEPROM an Adresse 1 abgelegt und ist 1 Byte lang. Die Konversion zwischen logischem und physikalischem Wert ist einfach eine Multiplikation mit 10.
   

<parameter id="CENTRAL_ADDRESS" hidden="true">
<logical type="integer"/>
<physical size="4" type="integer" interface="eeprom">
<address index="0x0002"/>
</physical>
</parameter>
<enforce id="CENTRAL_ADDRESS" value="1"/>
</paramset>

Das hier bedeutet, dass die Adresse der Zentrale im EEPROM an Adresse 2 abgespeichert ist und 4 Byte braucht. FHEM schreibt beim "Anlernen" die Adresse der Zentrale (also des HM485_LAN-Geräts) genau dort hinein.


<channels>
<channel index="0" type="MAINTENANCE" count="1" class="maintenance" ui_flags="internal">
</channel>
<channel index="1" type="SWITCH" count="1" physical_index_offset="-1">
</channel>
</channels>
</device>

Wie schon gesagt, das Gerät hat eigentlich keine Kanäle, aber zumindest FHEM braucht hier etwas. Theoretisch ist das ein Bug, aber praktisch sind Geräte ohne Kanäle ohnehin nicht wirklich sinnvoll.

Beim Anlegen des "HBWDevice-Objekts" im Sketch kann man eine Variable angeben, die dann mit dem EEPROM synchron gehalten wird:


struct hbw_config {
  uint8_t  logging_time;     // 0x0001
  uint32_t central_address;  // 0x0002 - 0x0005
} config;


[...]

  device = new HBWDevice(HMW_DEVICETYPE, HARDWARE_VERSION, FIRMWARE_VERSION,
                         &rs485,RS485_TXEN,sizeof(config),&config,0,NULL,&Serial,
                         NULL, NULL);

Wenn von FHEM aus (und wahrscheinlich auch durch die CCU) das EEPROM des Geräts beschrieben wird, dann folgt eine 0x43-Nachricht. Diese bewirkt, dass der EEPROM-Inhalt ab Adresse 1 in die Config-Struktur kopiert wird.    

Gruß,
   Thorsten
FUIP

Thorsten Pferdekaemper

Damit es nicht total theoretisch wird soll jetzt ein Ausgangs-Kanal angelegt werden, mit dem man die interne LED an Pin 13 ein- und ausschalten kann. Viel mehr kann man auch gar nicht machen ohne weitere Hardware.

Also ladet mal folgenden Sketch auf den Arduino:

#define HMW_DEVICETYPE 0xAB

#define HARDWARE_VERSION 0x01
#define FIRMWARE_VERSION 0x0100

#include "HBWSoftwareSerial.h"
#include "HBWired.h"

//NEU
#include "HBWSwitch.h" 

#define RS485_RXD 4   // Receive-Pin
#define RS485_TXD 2   // Transmit-Pin
#define RS485_TXEN 3  // Transmit-Enable

struct hbw_config {
  uint8_t  logging_time;     // 0x0001
  uint32_t central_address;  // 0x0002 - 0x0005
  // NEU
  hbw_config_switch switchcfg;  // 0x0006
} config;


HBWSoftwareSerial rs485(RS485_RXD, RS485_TXD);
HBWDevice* device = NULL;

//NEU
HBWChannel* channels[1];

void setup()
{
  Serial.begin(19200);
  rs485.begin();

  //NEU
  channels[0] = new HBWSwitch(13,&(config.switchcfg));

  device = new HBWDevice(HMW_DEVICETYPE, HARDWARE_VERSION, FIRMWARE_VERSION,
                         &rs485,RS485_TXEN,sizeof(config),&config,
                         // NEU
                         1,channels,
                         &Serial,
                         NULL, NULL);
  hbwdebug(F("B: 2A\n"));
}


void loop()
{
  // call the HBW loop
  device->loop();
};

Wie immer folgen Erklärungen später. Erstmal nur so viel: Im Coding habe ich neue oder geänderte Sachen mit "NEU" markiert.

Die einzige Änderung, die man danach beobachten kann, ist dass jetzt die interne LED an Pin 13 leuchtet. In FHEM sieht Kanal 1 immer noch genauso "kaputt" aus wie vorher. Dank des "raw"-Kommandos kann man aber trotzdem den Kanal schalten. Auf Device-Ebene kann man bei "set" das "raw"-Kommando wählen und dann folgendes eingeben:

730000

Damit müsste die LED ausgehen. Genauso mit

7300C8

..wieder an. (Siehe auch das Bild im Anhang.)
Was die ganzen Codes bedeuten kann man hier nachlesen.
http://forum.fhem.de/index.php?action=dlattach;topic=10027.0;attach=2441
Es sind jedoch nicht alle Kommandos implementiert. Im Zweifelsfall kann man im Coding in HBWired.cpp in Methode processEvent nachlesen.
Die Kommandos oben sind in Hex-Notation und bedeuten im Einzelnen:

  • Erstes Byte: Befehlsbyte - 0x73 bedeutet "Aktor setzen"
  • Zweites Byte: Kanal - 0x00 ist der erste Kanal
  • Drittes byte: Wert - In HBWSwitch ist das so implementiert, dass 0x00 ausschaltet, alles ab 0xC9 macht ein "Toggle" und alles von 0x01 bis 0xC8 schaltet ein. Das müsste auch mit der CCU kompatibel sein.

Natürlich muss man HBW-Geräte nicht mit raw-Kommandos schalten. Wie das eleganter geht kommt in einer der nächsten Folgen.

Gruß,
   Thorsten
FUIP

Thorsten Pferdekaemper

#9
Folge 8 - Darstellung des digitalen Ausgangs in FHEM

Also entweder läuft alles perfekt, oder niemand interessiert das hier wirklich. So ganz ohne Feedback wird es etwas langweilig. Ich lasse daher jetzt große Erklärungen weg. Falls jemand Fragen hat -> https://forum.fhem.de/index.php/topic,61781.0.html.
Mal sehen, ob wir das nicht ein bisschen interaktiver hinbekommen.

Um mit dem Kanal in FHEM (oder auch in der CCU) ordentlich umgehen zu können, braucht man entsprechende Einträge in der Gerätebeschreibungsdatei. Dazu ändert mal die XML-Datei, so dass sie folgendermaßen aussieht.

<?xml version="1.0"?>
<device eep_size="1024" version="01">
<supported_types>
<type priority="2" id="HBW-TUTORIAL" name="Das Geraet zum HBWired-Tutorial">
<parameter const_value="0xAB" size="1" index="0"/>
<parameter const_value="0" size="1" index="1"/>
</type>
</supported_types>

<paramset id="HBW-TUTORIAL_dev_master" type="MASTER">
<parameter id="LOGGING_TIME">
<logical type="float" unit="s" default="5.0" max="25.5" min="0.1"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="0x0001"/>
</physical>
<conversion type="float_integer_scale" offset="0.0" factor="10"/>
</parameter>
<parameter id="CENTRAL_ADDRESS" hidden="true">
<logical type="integer"/>
<physical size="4" type="integer" interface="eeprom">
<address index="0x0002"/>
</physical>
</parameter>
<enforce id="CENTRAL_ADDRESS" value="1"/>
</paramset>

<!-- NEU: frames -->
<frames>
<frame id="LEVEL_SET" type="#x" channel_field="10" direction="to_device">
<parameter size="1.0" index="11.0" type="integer" param="LEVEL"/>
</frame>
<frame id="LEVEL_GET" type="#S" channel_field="10" direction="to_device"/>
<frame id="INFO_LEVEL" type="#i" channel_field="10" direction="from_device" event="true">
<parameter size="1.0" index="11.0" type="integer" param="LEVEL"/>
</frame>
</frames>

<channels>
<channel index="0" type="MAINTENANCE" count="1" class="maintenance" ui_flags="internal">
</channel>
<channel index="1" type="SWITCH" count="1" physical_index_offset="-1">
        <!-- NEU: Beschreibung des Kanals -->
      <paramset id="hmw_switch_ch_master" type="MASTER" address_step="2" address_start="0x06">
<parameter id="LOGGING">
<logical type="option">
<option id="OFF"/>
<option id="ON" default="true"/>
</logical>
<physical size="0.1" type="integer" interface="eeprom">
<address index="+0"/>
</physical>
</parameter>
</paramset>
<paramset id="hmw_switch_ch_values" type="VALUES">
<parameter id="STATE" operations="read,write,event" control="SWITCH.STATE">
<logical type="boolean" default="false"/>
<physical type="integer" interface="command" value_id="LEVEL">
<set request="LEVEL_SET"/>
<get request="LEVEL_GET" response="INFO_LEVEL"/>
<event frame="INFO_LEVEL"/>
</physical>
<conversion type="boolean_integer" true="200" false="0" threshold="1"/>
</parameter>
</paramset>
</channel>
</channels>

</device>

Die Datei sollte im folgenden Pfad liegen: <fhem>/FHEM/lib/HM485/Devices/xml/hbw_tutorial.xml.
Nach der Änderung muss FHEM neu gestartet werden und das Device sowie der Kanal sollten so aussehen wie in den angehägten Bildern.

Die üblichen Befehle wie "set <channel> on" oder auch "set <channel> toggle" sollten jetzt funktionieren. Das gilt auch für den Klick auf die kleine Glühbirne in der Übersicht. Außerdem kann man jetzt das "Logging" ein- und ausschalten.

Wie gesagt, diesmal keine weiteren Erklärungen. Falls etwas unklar ist, dann bitte fragen.

Gruß,
  Thorsten
FUIP

Thorsten Pferdekaemper

Folge 9 - Geräteadresse ändern

Anders als z.B. mySensors-Geräte haben Homematic-Wired-Geräte eine ab Werk festgelegte (hoffentlich) eindeutige Adresse. Diese Adresse hat vier Byte und wird benutzt, um das Gerät im Bus eindeutig zu identifizieren. Wahrscheinlich hat der Hersteller irgendwo einen Zähler und denkt nicht, jemals mehr als 4 Milliarden Geräte zu verkaufen.
Bei HBWired läuft das etwas anders: Die Geräteadresse ist in den obersten 4 Byte des EEPROM abgelegt. Das würde für ein "jungfräuliches" EEPROM bedeuten, dass das Gerät die Adresse 0xFFFFFFFF hat, was aber einen Broadcast anzeigen würde. Daher setzt HBWired die Adresse 0x42FFFFFF als Default. Mit anderen Worten: Alle HBWired-Geräte haben erst einmal die Adresse 0x42FFFFFF. Das muss man natürlich ändern.
Damit man nicht dafür einen eigenen Sketch schreiben muss, gibt es in der HBWired-Library einen Spezialbefehl. Der folgende Code als raw-Befehl an das Device geschickt ändert die Geräteadresse auf <adresse>:

4061<adresse>
...also z.B.

406142000017
...setzt die Adresse auf 0x42000017.
Danach geht das device in FHEM natürlich sofort auf "RESPONSE TIMEOUT", da FHEM natürlich noch die alte Adresse kennt. Am einfachsten löscht man jetzt das Device in FHEM und lässt es automatisch neu anlegen, indem man z.B. den Arduino neu startet.

Gruß,
  Thorsten
FUIP

Thorsten Pferdekaemper

#11
Folge 10 - Ein Tastereingang

Im Diskussions-Thread wurde gefragt, ob ich den Aufbau der XML-Datei, speziell im Zusammenhang mit den EEPROM-Inhalten, erklären könnte. Das habe ich noch vor, aber zuerst will ich zeigen, wie man einen Taster-Eingang hinzufügt und mehrere Kanäle (von jeder Sorte) unterstützt.
(Möglicherweise wird die "Doku" zu den XML-Dateien auch ein Wiki-Artikel.)

Jetzt aber erst einmal zum Taster.
Auf meinem Steckbrett verbindet der Taster GND mit dem digitalen Eingang 8. Wer gerade keinen Taster zur Hand hat kann auch einen Draht nehmen. Man sollte nur aufpassen, was man da kurzschließt. Ein Schaltbild spare ich mir.

Der Sketch dazu sieht bei mir so aus:


#define HMW_DEVICETYPE 0xAB

#define HARDWARE_VERSION 0x01
#define FIRMWARE_VERSION 0x0100

#include "HBWSoftwareSerial.h"
#include "HBWired.h"
#include "HBWSwitch.h" 

// NEU
#include "HBWKey.h"

#define RS485_RXD 4   // Receive-Pin
#define RS485_TXD 2   // Transmit-Pin
#define RS485_TXEN 3  // Transmit-Enable

struct hbw_config {
  uint8_t  logging_time;     // 0x0001
  uint32_t central_address;  // 0x0002 - 0x0005
  hbw_config_switch switchcfg;  // 0x0006 - 0x0007
  // NEU
  hbw_config_key keycfg;        // 0x0008 - 0x0009
} config;

HBWSoftwareSerial rs485(RS485_RXD, RS485_TXD);
HBWDevice* device = NULL;

//NEU (2 Kanaele statt nur 1)
HBWChannel* channels[2];

void setup()
{
  Serial.begin(19200);
  rs485.begin();

  channels[0] = new HBWSwitch(13,&(config.switchcfg));

  //NEU
  channels[1] = new HBWKey(8,&(config.keycfg));

  device = new HBWDevice(HMW_DEVICETYPE, HARDWARE_VERSION, FIRMWARE_VERSION,
                         &rs485,RS485_TXEN,sizeof(config),&config,
                         // NEU (2 statt 1 Kanal)
                         2,channels,
                         &Serial,
                         NULL, NULL);
  hbwdebug(F("B: 2A\n"));
}

void loop()
{
  // call the HBW loop
  device->loop();
};

Wie immer sind die neuen Teile markiert.

Wenn man die Taste drückt (und wieder loslässt), dann sollte man im seriellen Monitor der Arduino-IDE etwas sehen. FHEM weiß noch nichts von diesem zusätzlichen Kanal. Dazu braucht man wieder die richtige Gerätebeschreibungsdatei:

<?xml version="1.0"?>
<device eep_size="1024" version="01">
<supported_types>
<type priority="2" id="HBW-TUTORIAL" name="Das Geraet zum HBWired-Tutorial">
<parameter const_value="0xAB" size="1" index="0"/>
<parameter const_value="0" size="1" index="1"/>
</type>
</supported_types>

<paramset id="HBW-TUTORIAL_dev_master" type="MASTER">
<parameter id="LOGGING_TIME">
<logical type="float" unit="s" default="5.0" max="25.5" min="0.1"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="0x0001"/>
</physical>
<conversion type="float_integer_scale" offset="0.0" factor="10"/>
</parameter>
<parameter id="CENTRAL_ADDRESS" hidden="true">
<logical type="integer"/>
<physical size="4" type="integer" interface="eeprom">
<address index="0x0002"/>
</physical>
</parameter>
<enforce id="CENTRAL_ADDRESS" value="1"/>
</paramset>

<frames>
<frame id="LEVEL_SET" type="#x" channel_field="10" direction="to_device">
<parameter size="1.0" index="11.0" type="integer" param="LEVEL"/>
</frame>
<frame id="LEVEL_GET" type="#S" channel_field="10" direction="to_device"/>
<frame id="INFO_LEVEL" type="#i" channel_field="10" direction="from_device" event="true">
<parameter size="1.0" index="11.0" type="integer" param="LEVEL"/>
</frame>

<!-- Neu: Key Events -->
<frame id="KEY_EVENT_SHORT" type="#K" channel_field="10" direction="from_device" event="true">
<parameter const_value="0" size="0.1" index="12.0" type="integer"/>
<parameter size="0.6" index="12.2" type="integer" param="COUNTER"/>
</frame>
<frame id="KEY_EVENT_LONG" type="#K" channel_field="10" direction="from_device" event="true">
<parameter const_value="1" size="0.1" index="12.0" type="integer"/>
<parameter size="0.6" index="12.2" type="integer" param="COUNTER"/>
</frame>
</frames>

<channels>
<channel index="0" type="MAINTENANCE" count="1" class="maintenance" ui_flags="internal">
</channel>
<channel index="1" type="SWITCH" count="1" physical_index_offset="-1">
      <paramset id="hmw_switch_ch_master" type="MASTER" address_step="2" address_start="0x06">
<parameter id="LOGGING">
<logical type="option">
<option id="OFF"/>
<option id="ON" default="true"/>
</logical>
<physical size="0.1" type="integer" interface="eeprom">
<address index="+0"/>
</physical>
</parameter>
</paramset>
<paramset id="hmw_switch_ch_values" type="VALUES">
<parameter id="STATE" operations="read,write,event" control="SWITCH.STATE">
<logical type="boolean" default="false"/>
<physical type="integer" interface="command" value_id="LEVEL">
<set request="LEVEL_SET"/>
<get request="LEVEL_GET" response="INFO_LEVEL"/>
<event frame="INFO_LEVEL"/>
</physical>
<conversion type="boolean_integer" true="200" false="0" threshold="1"/>
</parameter>
</paramset>
</channel>

    <!-- Neu: Beschreibung Key-Channel -->
<channel index="2" type="KEY" count="1" physical_index_offset="-1">
<paramset id="hmw_input_ch_master" type="MASTER" address_step="1" address_start="0x08">
<parameter id="LONG_PRESS_TIME">
<logical type="float" unit="s" default="1.0" max="5.0" min="0.4"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="+1"/>
</physical>
<conversion type="float_integer_scale" factor="10"/>
<conversion type="integer_integer_map">
<value_map to_device="false" from_device="true" parameter_value="10" device_value="0xff"/>
</conversion>
</parameter>
</paramset>

<paramset id="hmw_input_ch_values" type="VALUES">
<parameter id="PRESS_SHORT" operations="event,write" loopback="true" control="BUTTON.SHORT">
<logical type="action"/>
<physical type="integer" interface="command" value_id="COUNTER">
<event frame="KEY_EVENT_SHORT"/>
</physical>
<conversion type="action_key_counter" counter_size="6" sim_counter="SIM_COUNTER"/>
</parameter>
<parameter id="PRESS_LONG" operations="event,write" loopback="true" control="BUTTON.LONG">
<logical type="action"/>
<physical type="integer" interface="command" value_id="COUNTER">
<event frame="KEY_EVENT_LONG"/>
</physical>
<conversion type="action_key_counter" counter_size="6" sim_counter="SIM_COUNTER"/>
</parameter>
</paramset>
</channel>

</channels>

</device>

Also unsere übliche hbw_tutorial.xml so ändern, dass sie so aussieht wie oben. Jetzt noch Fhem neu starten und ein bisschen warten.
Der neue Kanal müsste von Fhem automatisch hinzugefügt werden.
Wenn man dann ein paar Mal die Taste drückt, dann sollte das so aussehen wie auf den Screenshots im Anhang.

Gruß,
  Thorsten
FUIP

Thorsten Pferdekaemper

Hi,
womöglich hat jemand auch lange Tastendrücke ausprobiert. Im Prinzip hätte das funktionieren sollen, hat es aber nicht.
Da war noch ein Fehler in der HBWSwitch.h, der jetzt korrigiert ist.
...also HBWSwitch.h neu vom Git herunterladen (https://raw.githubusercontent.com/ThorstenPferdekaemper/HBWired/master/libraries/HBWSwitch/HBWSwitch.h) und dann sollte auch das gehen.
Gruß,
    Thorsten
FUIP

Thorsten Pferdekaemper

Folge 11 - Mehrere Ein- und Ausgänge

Bisher hatten wir nur einen Eingang und einen Ausgang. Auch wenn das vielleicht jetzt sowieso klar ist, will ich zeigen, wie man einfach mehrere Ein- und Ausgänge realisiert.

Damit man auch was zum Sehen und Herumspielen hat, muss man erst einmal ein bisschen Hardware basteln. Als zusätzlichen Eingang verwenden wir den digitalen Pin 7, als Ausgänge die Pins 9, 10 und 11. Letzteres ermöglicht und später quasi-analoge (also PWM) Ausgänge.
Schließt zwischen Pin 7 und GND einen weiteren Taster an und an Pin 9, 10 und 11 jeweils eine LED. Ich habe hier eine RGB-LED drangebastelt, um später vielleicht noch eine RGB-Steuerung zu demonstrieren. Vergesst nicht die Vorwiderstände, sonst müsst Ihr vielleicht einen neuen Arduino kaufen.  Der ganze Verhau sieht bei mir jetzt so aus wie auf dem ersten Bild im Anhang.

Wie üblich sind hier Sketch und XML.

#define HMW_DEVICETYPE 0xAB

#define HARDWARE_VERSION 0x01
#define FIRMWARE_VERSION 0x0100

#include "HBWSoftwareSerial.h"
#include "HBWired.h"
#include "HBWSwitch.h" 
#include "HBWKey.h"

#define RS485_RXD 4   // Receive-Pin
#define RS485_TXD 2   // Transmit-Pin
#define RS485_TXEN 3  // Transmit-Enable

struct hbw_config {
  uint8_t  logging_time;     // 0x0001
  uint32_t central_address;  // 0x0002 - 0x0005
  // NEU: Mehrere switches
  hbw_config_switch switchcfg[4];  // 0x0006 - 0x000D
  // NEU: Mehrere keys
  hbw_config_key keycfg[2];        // 0x000E - 0x0011
} config;


HBWSoftwareSerial rs485(RS485_RXD, RS485_TXD);
HBWDevice* device = NULL;

//NEU (6 Kanaele statt 2)
HBWChannel* channels[6];

void setup()
{
  Serial.begin(19200);
  rs485.begin();

  // NEU: Definition mehrerer Kanaele pro Typ
  //      (das geht natuerlich auch eleganter)
  channels[0] = new HBWSwitch(13,&(config.switchcfg[0]));
  channels[1] = new HBWSwitch(9,&(config.switchcfg[1]));
  channels[2] = new HBWSwitch(10,&(config.switchcfg[2]));
  channels[3] = new HBWSwitch(11,&(config.switchcfg[3]));
  channels[4] = new HBWKey(7,&(config.keycfg[0]));
  channels[5] = new HBWKey(8,&(config.keycfg[1]));

  device = new HBWDevice(HMW_DEVICETYPE, HARDWARE_VERSION, FIRMWARE_VERSION,
                         &rs485,RS485_TXEN,sizeof(config),&config,
                         // NEU (6 statt 2 Kanaele)
                         6,channels,
                         &Serial,
                         NULL, NULL);
  hbwdebug(F("B: 2A\n"));
}


void loop()
{
  // call the HBW loop
  device->loop();
};

...und die XML-Datei:

<?xml version="1.0"?>
<device eep_size="1024" version="01">
<supported_types>
<type priority="2" id="HBW-TUTORIAL" name="Das Geraet zum HBWired-Tutorial">
<parameter const_value="0xAB" size="1" index="0"/>
<parameter const_value="0" size="1" index="1"/>
</type>
</supported_types>

<paramset id="HBW-TUTORIAL_dev_master" type="MASTER">
<parameter id="LOGGING_TIME">
<logical type="float" unit="s" default="5.0" max="25.5" min="0.1"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="0x0001"/>
</physical>
<conversion type="float_integer_scale" offset="0.0" factor="10"/>
</parameter>
<parameter id="CENTRAL_ADDRESS" hidden="true">
<logical type="integer"/>
<physical size="4" type="integer" interface="eeprom">
<address index="0x0002"/>
</physical>
</parameter>
<enforce id="CENTRAL_ADDRESS" value="1"/>
</paramset>

<frames>
<frame id="LEVEL_SET" type="#x" channel_field="10" direction="to_device">
<parameter size="1.0" index="11.0" type="integer" param="LEVEL"/>
</frame>
<frame id="LEVEL_GET" type="#S" channel_field="10" direction="to_device"/>
<frame id="INFO_LEVEL" type="#i" channel_field="10" direction="from_device" event="true">
<parameter size="1.0" index="11.0" type="integer" param="LEVEL"/>
</frame>
<frame id="KEY_EVENT_SHORT" type="#K" channel_field="10" direction="from_device" event="true">
<parameter const_value="0" size="0.1" index="12.0" type="integer"/>
<parameter size="0.6" index="12.2" type="integer" param="COUNTER"/>
</frame>
<frame id="KEY_EVENT_LONG" type="#K" channel_field="10" direction="from_device" event="true">
<parameter const_value="1" size="0.1" index="12.0" type="integer"/>
<parameter size="0.6" index="12.2" type="integer" param="COUNTER"/>
</frame>
</frames>

<channels>
<channel index="0" type="MAINTENANCE" count="1" class="maintenance" ui_flags="internal">
</channel>
    <!-- NEU: 4 Switches statt 1 -->
<channel index="1" type="SWITCH" count="4" physical_index_offset="-1">
      <paramset id="hmw_switch_ch_master" type="MASTER" address_step="2" address_start="0x06">
<parameter id="LOGGING">
<logical type="option">
<option id="OFF"/>
<option id="ON" default="true"/>
</logical>
<physical size="0.1" type="integer" interface="eeprom">
<address index="+0"/>
</physical>
</parameter>
</paramset>
<paramset id="hmw_switch_ch_values" type="VALUES">
<parameter id="STATE" operations="read,write,event" control="SWITCH.STATE">
<logical type="boolean" default="false"/>
<physical type="integer" interface="command" value_id="LEVEL">
<set request="LEVEL_SET"/>
<get request="LEVEL_GET" response="INFO_LEVEL"/>
<event frame="INFO_LEVEL"/>
</physical>
<conversion type="boolean_integer" true="200" false="0" threshold="1"/>
</parameter>
</paramset>
</channel>

    <!-- NEU: 2 Keys statt 1, Kanal 5-6, Adresse groesser, address_step korrigiert -->
<channel index="5" type="KEY" count="2" physical_index_offset="-1">
<paramset id="hmw_input_ch_master" type="MASTER" address_step="2" address_start="0x0E">
<parameter id="LONG_PRESS_TIME">
<logical type="float" unit="s" default="1.0" max="5.0" min="0.4"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="+1"/>
</physical>
<conversion type="float_integer_scale" factor="10"/>
<conversion type="integer_integer_map">
<value_map to_device="false" from_device="true" parameter_value="10" device_value="0xff"/>
</conversion>
</parameter>
</paramset>

<paramset id="hmw_input_ch_values" type="VALUES">
<parameter id="PRESS_SHORT" operations="event,write" loopback="true" control="BUTTON.SHORT">
<logical type="action"/>
<physical type="integer" interface="command" value_id="COUNTER">
<event frame="KEY_EVENT_SHORT"/>
</physical>
<conversion type="action_key_counter" counter_size="6" sim_counter="SIM_COUNTER"/>
</parameter>
<parameter id="PRESS_LONG" operations="event,write" loopback="true" control="BUTTON.LONG">
<logical type="action"/>
<physical type="integer" interface="command" value_id="COUNTER">
<event frame="KEY_EVENT_LONG"/>
</physical>
<conversion type="action_key_counter" counter_size="6" sim_counter="SIM_COUNTER"/>
</parameter>
</paramset>
</channel>

</channels>

</device>

Was man damit macht sollte inzwischen klar sein. In FHEM sollte das dann in etwa so aussehen wie auf dem zweiten Bild im Anhang.

Das nächste Mal gibt es erst einmal ein paar Erklärungen zur XML-Datei und wie das ganze mit dem Arduino-Sketch zusammenspielt.

Gruß,
  Thorsten
FUIP

Thorsten Pferdekaemper

Folge 12 - Mehr Erklärungen zur Gerätebeschreibungsdatei

Wie versprochen gibt es jetzt ein paar Erklärungen zur Gerätebeschreibungsdatei (aka XML-Datei). Ich habe eine Weile überlegt, in welcher Form das am Besten ist. Mir fällt leider nichts besseres ein als einfach die Datei durchzugehen.
Ein paar Teile habe ich bereits beschrieben. Falls noch irgendwas unklar ist -> Diskussions-Thread. 


<?xml version="1.0"?>
<device eep_size="1024" version="01">
<supported_types>
<type priority="2" id="HBW-TUTORIAL" name="Das Geraet zum HBWired-Tutorial">
<parameter const_value="0xAB" size="1" index="0"/>
<parameter const_value="0" size="1" index="1"/>
</type>
</supported_types>

<paramset id="HBW-TUTORIAL_dev_master" type="MASTER">

Das XML-Tag <paramset> leitet eine Liste von Parametern ein. Es gibt folgende Arten, die am Attribut type erkennbar sind:

  • MASTER: Hier werden Konfigurationsparameter für das ganze Device oder für einen Kanal (bzw. mehrere Kanäle) beschrieben, je nachdem ob das paramset auf der Ebene von device oder channel liegt. Die Werte dieser Konfigurationsparameter sind normalerweise im EEPROM des Controllers (also bei uns des Arduino) gespeichert.
  • VALUES: Diese Liste beschreibt, welche Werte ein Kanal annehmen kann. Dabei kann es für einen Kanal auch mehrere verschiedene "Felder" (oder auch "Datenpunkte") geben. Das ist das, was in FHEM dann zu Readings, set-Optionen oder Events wird.
  • LINK: Hier werden die direkten Verknüpfungen (Peerings) beschrieben. Das beinhaltet sowohl Quelle und Ziel (Geräteadresse und Kanal) als auch Konfigurationsparameter zu den einzelnen Verknüpfungen. Genauso wie bei MASTER werden diese Parameter im EEPROM abgelegt.


<parameter id="LOGGING_TIME">
<logical type="float" unit="s" default="5.0" max="25.5" min="0.1"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="0x0001"/>
</physical>
<conversion type="float_integer_scale" offset="0.0" factor="10"/>
</parameter>
<parameter id="CENTRAL_ADDRESS" hidden="true">
<logical type="integer"/>
<physical size="4" type="integer" interface="eeprom">
<address index="0x0002"/>
</physical>
</parameter>
<enforce id="CENTRAL_ADDRESS" value="1"/>
</paramset>

Das ist das MASTER-paramset, welches fast jedes Device hat. Die Bedeutung wurde weiter oben bereits beschrieben. Ich will hier nur nochmals auf das EEPROM-Layout hinweisen. Mit der obigen Definition sieht das jetzt so aus:


Name            Von    Bis    Länge

<leer>          0x0000 0x0000 1 Byte
LOGGING_TIME    0x0001 0x0001 1 Byte
CENTRAL_ADDRESS 0x0002 0x0006 4 Byte



<frames>

Das XML-Tag frames leitet die Liste der Nachrichten ein, die das Device versteht bzw. die es potentiell sendet. Dabei wird nur das aufgelistet, was nicht sowieso jedes Device kann. Z.B. auf das Kommando 0x68 (h) reagiert jedes Device gleich und liefert den Gerätetyp zurück.


<frame id="LEVEL_SET" type="#x" channel_field="10" direction="to_device">
<parameter size="1.0" index="11.0" type="integer" param="LEVEL"/>
</frame>

Jeder Nachrichten-Frame hat einen Namen (hier LEVEL_SET), unter dem er später in den VALUES-paramsets referenziert wird. Die Attribute haben (soweit ich das verstehe) folgende Bedeutung:

  • type: Das Befehlsbyte der Nachricht. "#x" steht für den Buchstaben "x", also 0x78. Das ist einer der beiden Werte, die normalerweise benutzt werden, um einen Aktor direkt zu setzen.
  • channel_field zeigt die Position an, an der der Kanal zu finden ist. Alle in <frames> zu findenden Nachrichten beziehen sich auf einen Kanal und nicht auf das ganze Device. Ich glaube, dass der Wert immer 10 ist, obwohl ich die Zählung etwas seltsam finde. Meiner Meinung nach ist der Kanal normalerweise im 12. Byte (wenn man mit 0 anfängt zu zählen).
  • direction: Hier gibt es "to_device" und "from_device", je nachdem ob das Gerät diese Nachricht empfangen kann oder sendet. Anscheinend ist das aber nicht ganz so ernst zu nehmen. Bei direkten Verknüpfungen (Peerings) werden Key-Events (#K, 0x4B) vom Gerät akzeptiert, obwohl ich noch keine "to_device"-Definition für Key-Events gesehen habe.
  • parameter/size ist die Länge des zugehörigen Parameters in Bits und Bytes. Die Zahl vor dem Punkt sind die Bytes, nach dem Punkt die Bits. "1.0" steht also für 1 Byte, "0.6" steht für 6 Bits.
  • parameter/index bezeichnet die Startadresse des Parameters in der Nachricht. Dabei wird anscheinend genauso gezählt wie bei channel_field. Auch hier gilt: Vor dem Punkt sind Bytes, danach Bits. "11.0" bedeutet "fängt bei Byte 11 an", während "12.2" bedeutet "fängt bei Bit 2 von Byte 12 an".
  • parameter/type: Ich glaube, hier gibt es nur "integer". Womöglich ist die Angabe sogar egal.
  • parameter/param: Das ist der Name des Parameters. Ein Frame kann mehrere "Felder" haben, die wiederum in den VALUES-paramsets referenziert werden.


<frame id="LEVEL_GET" type="#S" channel_field="10" direction="to_device"/>

Es kann auch Frames ohne Parameter geben. Der "#S"-Befehl (0x53) bedeutet in etwa "Sende den aktuellen Zustand des Kanals". Das Gerät sendet dann normalerweise den kompletten Zustand.
   

<frame id="INFO_LEVEL" type="#i" channel_field="10" direction="from_device" event="true">
<parameter size="1.0" index="11.0" type="integer" param="LEVEL"/>
</frame>
<frame id="KEY_EVENT_SHORT" type="#K" channel_field="10" direction="from_device" event="true">
<parameter const_value="0" size="0.1" index="12.0" type="integer"/>
<parameter size="0.6" index="12.2" type="integer" param="COUNTER"/>
</frame>
<frame id="KEY_EVENT_LONG" type="#K" channel_field="10" direction="from_device" event="true">
<parameter const_value="1" size="0.1" index="12.0" type="integer"/>
<parameter size="0.6" index="12.2" type="integer" param="COUNTER"/>
</frame>
</frames>

Hier kommen noch zwei weitere Attribute dazu:

  • event: Ich vermute, dass "true" hier bedeutet, dass das Gerät solche Nachrichten auch ungefragt schickt.
  • parameter/const_value: Über const_value können Frames mit demselben Befehlsbyte (type) unterschieden werden. Sowohl KEY_EVENT_SHORT als auch KEY_EVENT_LONG werden mit #K (0x4B) geschickt. Der Unterschied ist, dass nur bei langen Tastendrücken das erste Bit im 12. Byte der Nachricht gesetzt ist.


<channels>

Hier folgt die Liste der Kanäle oder genau genommen der Kanalgruppen. Gleichartige Kanäle folgen immer direkt aufeinander und werden zusammen definiert. Jedes sinnvolle HMW-Gerät hat mindestens einen Kanal (den MAINTENANCE-Kanal nicht mitgezählt).


<channel index="0" type="MAINTENANCE" count="1" class="maintenance" ui_flags="internal">
</channel>

Dieser MAINTENANCE-Kanal ist in jedem HMW-Device vorhanden. In FHEM bewirkt dieser Kanal meiner Meinung nach gar nichts, er muss aber dennoch angegeben werden, da sonst die Umwandlung der XML-Datei in das .pm-Format nicht klappt. Wahrscheinlich gibt es irgendwas in der CCU, das diesen Kanal braucht.   


<channel index="1" type="SWITCH" count="4" physical_index_offset="-1">

Das channel-Tag leitet eine Gruppe gleichartiger Kanäle ein. Die Attribute haben nach bisheriger Erkenntnis folgende Bedeutung.

  • index: Das ist die Nummer des ersten Kanals in der Gruppe, wie sie z.B. in FHEM angezeigt wird.
  • type bezeichnet die Art des Kanals. Allerdings steckt dahinter nicht viel Semantik. Ich glaube, dass das in FHEM gar nicht verwendet wird. Die folgenden Werte kommen vor: SWITCH, KEY, INPUT_OUTPUT, DIGITAL_OUTPUT, DIGITAL_ANALOG_OUTPUT, DIGITAL_INPUT, DIGITAL_ANALOG_INPUT, BLIND, DIMMER, SENSOR, VIRTUAL_KEY, LISTENER, MAINTENANCE
  • count ist die Anzahl der Kanäle in der Gruppe. Zusammen mit index werden hier also die Kanäle 1,2,3 und 4 beschrieben.
  • physical_index_offset: In den Nachrichten, die vom Device gesendet oder empfangen werden, werden die Kanäle mit 0 beginnend gezählt. Wahrscheinlich ist es das, was mit der "-1" hier gemeint ist.


      <paramset id="hmw_switch_ch_master" type="MASTER" address_step="2" address_start="0x06">

Wie oben schon erklärt beziehen sich MASTER-Paramsets auf die Einstellungen zum Device oder zu einer Kanalgruppe. Hier sind wir auf der Ebene der Kanalgruppe.

  • address_step ist die Länge der Konfigurationsdaten eines Kanals in Byte. Hier bedeutet das: Jeder Kanal belegt im EEPROM 2 Byte.
  • address_start ist die Startadresse des Paramsets (also sozusagen der Kanalgruppe) im EEPROM. Da in der Kanalgruppe 4 Kanäle sind, bedeutet das also das Kanal 1 die Bytes an Adresse 0x06 und 0x07 belegt, Kanal 2 belegt 0x08 und 0x09, Kanal 3 0x0A und 0x0B sowie Kanal 4 0x0C und 0x0D.


<parameter id="LOGGING">

Ich glaube, dass ich schon erwähnt hatte, dass jeder Parameter einen Namen hat. Unter diesem Namen erscheint der Parameter auch in FHEM.
Parameter haben normalerweise drei Unter-Tags:

  • logical gibt sozusagen an, wie der Parameter benutzt wird, also auch wie der Parameter auf der Benutzerschnittstelle aussieht (z.B. in FHEM).
  • physical gibt die technische Sicht auf den Parameter, also bei Konfigurationsparametern, wo im EEPROM er abgelegt ist.
  • conversion gibt an, wie physical und logical zusammenhängt. Diese Angabe ist nicht immer vorhanden.


<logical type="option">
<option id="OFF"/>
<option id="ON" default="true"/>
</logical>

Ein logical-Tag hat immer ein Attribut type. Im Fall von "option" werden dann die möglichen "logischen" Werte aufgezählt. Falls nichts anderes angegeben, entsprechen die Optionen "physikalisch" den Werten ihrer Reihenfolge in der Liste. D.h. "OFF" ist 0 und "ON" ist 1. Daher ist die default-Angabe wahrscheinlich überflüssig, da ein "leeres" EEPROM sowieso überall auf 1 steht.


<physical size="0.1" type="integer" interface="eeprom">
<address index="+0"/>
</physical>

Das bedeutet, dass der Parameter im EEPROM abgelegt ist und ein Bit groß ist. Die relative Angabe bei address index bezieht sich auf den Anfang des Kanals im EEPROM, also beginnend bei address_start des Paramset und dann jeweils address_step für jeden Kanal addiert. D.h. dieser Parameter ist jeweils das erste Bit der Bytes an Adresse 0x06, 0x08, 0x0A und 0x0C.


</parameter>
</paramset>

Auch wenn das MASTER-Paramset pro SWITCH-Kanal eigentlich nur ein Bit im EEPROM braucht, belegt es trotzdem zwei Byte. Das ist vermutlich so, weil es Geräte gibt, bei denen ein Kanal sowohl Eingang als auch Ausgang sein kann und die Ausgangs-Kanäle (KEY) tatsächlich zwei Byte im EEPROM benötigen.


<paramset id="hmw_switch_ch_values" type="VALUES">
<parameter id="STATE" operations="read,write,event" control="SWITCH.STATE">

Hier fängt das VALUES-Paramset der Kanalgruppe an. Für diese Kanäle gibt es nur den Parameter STATE. In FHEM wird daraus dann das Reading state, sowie die set-Option state.
Die Attribut-Angabe operations="read,write,event" bedeutet, dass der Parameter STATE sowohl gelesen und geschrieben werden kann, als auch Events erzeugt. Auf FHEM-isch bedeutet das...

  • Es gibt ein Reading state, wegen "read" bzw. "event".
  • Das Device erzeugt state-Events, wegen "event".
  • Wenn man dem Kanal ein "get state" schickt, dann bekommt das Reading state ein Update, wegen "read"
  • Es gibt mindestens ein "set", das den Parameter beeinflusst, wegen "write". Wie das genau aussieht hängt von verschiedenen anderen Sachen ab, hauptsächlich vom Attribut control.
Das Attribut control aktiviert verschiedene hart-kodierte Besonderheiten. Zum Beispiel gibt es für SWITCH.STATE immer on, off und toggle.
         

<logical type="boolean" default="false"/>

Das sagt im Prinzip nur aus, dass dieser Parameter genau zwei Werte annehmen kann: true und false. Da das Ding aber ein "SWITCH.STATE" ist, nennt FHEM das lieber "on" und "off".             
            

<physical type="integer" interface="command" value_id="LEVEL">
<set request="LEVEL_SET"/>
<get request="LEVEL_GET" response="INFO_LEVEL"/>
<event frame="INFO_LEVEL"/>
</physical>

Im VALUES-Paramset ist die physical-Angabe (normalerweise?) die Verknüpfung zu den Frames, also zu den Nachrichten, die das Modul empfängt bzw. sendet.

  • value_id ist der Name des "Felds" im jeweiligen Frame. D.h. das, was im Frame param heißt. D.h. hier, dass der Parameter STATE aus dem Feld LEVEL im jeweiligen Frame abgeleitet wird. Es handelt sich nicht direkt um den LEVEL-Wert, da noch die Konversion von physical zu logical dazu kommt.
  • set request: Da der Parameter STATE ein write kann, muss es eine Möglichkeit geben, den Wert zu senden. Die Angabe hier ist der Name des Frames, der den Wert setzt, also im Beispiel LEVEL_SET.
  • get request/response: Das gehört jetzt zum read. Die Angabe bedeutet wohl in etwa folgendes: Wenn das Device einen LEVEL_GET-Frame mit dem passenden Kanal empfängt, dann antwortet das Device mit einem INFO_LEVEL-Frame, bei dem das "Feld" LEVEL den Wert des Parameters STATE enthält.
  • event frame: Die Angabe hier bedeutet, dass das Device auch einfach so (also ohne erst einen LEVEL_GET zu erhalten) ein INFO_LEVEL-Frame verschicken kann.


<conversion type="boolean_integer" true="200" false="0" threshold="1"/>

Das ist jetzt die "Anleitung", wie der LEVEL-Wert, der im Frame ankommt, in den "logischen" Wert des Kanals (bzw. des Parameters STATE) übersetzt wird und umgekehrt. Was hier genau angegeben werden kann, hängt wohl im Wesentlichen von der Angabe bei logical ab.
Das Beispiel her bedeutet soviel wie:

  • Beim Empfangen eines Werts wird 0 als "false" interpretiert, alles andere als "true".
  • Beim Senden eines Werts wird "false" als 0 gesendet und "true" als 200.


</parameter>
</paramset>
</channel>

  <channel index="5" type="KEY" count="2" physical_index_offset="-1">
<paramset id="hmw_input_ch_master" type="MASTER" address_step="2" address_start="0x0E">
<parameter id="LONG_PRESS_TIME">
<logical type="float" unit="s" default="1.0" max="5.0" min="0.4"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="+1"/>
</physical>
<conversion type="float_integer_scale" factor="10"/>

Jetzt fangen die KEY-Kanäle an. Mit dem bisher gesagten sollte die Bedeutung soweit einigermaßen klar sein.


<conversion type="integer_integer_map">
<value_map to_device="false" from_device="true" parameter_value="10" device_value="0xff"/>
</conversion>

Es könnte sein, dass das jetzt neu ist. Es muss nicht immer nur ein conversion-Eintrag geben. Möglicherweise kann man das beliebig verketten. value_map scheint insbesondere für einzelne spezielle Werte geeignet zu sein. Hier bedeutet es wohl soviel wie:
Das Mapping gilt nur vom Gerät nach "draußen", das sagen die to_device und from_device Angaben. Also wenn im EEPROM 0xFF steht, dann wird das auf 10 gemappt. Anders herum aber nicht die 10 auf 0xFF. Mit der conversion weiter oben, bedeutet das, dass der Default-Wert (bei "leerem" EEPROM) 1.0 ist. 
Mir ist nicht so ganz klar, warum das hier nochmal steht, da das durch logical/default sowieso so sein müsste.


</parameter>
</paramset>

<paramset id="hmw_input_ch_values" type="VALUES">
<parameter id="PRESS_SHORT" operations="event,write" loopback="true" control="BUTTON.SHORT">
<logical type="action"/>
<physical type="integer" interface="command" value_id="COUNTER">
<event frame="KEY_EVENT_SHORT"/>
</physical>
<conversion type="action_key_counter" counter_size="6" sim_counter="SIM_COUNTER"/>
</parameter>
<parameter id="PRESS_LONG" operations="event,write" loopback="true" control="BUTTON.LONG">
<logical type="action"/>
<physical type="integer" interface="command" value_id="COUNTER">
<event frame="KEY_EVENT_LONG"/>
</physical>
<conversion type="action_key_counter" counter_size="6" sim_counter="SIM_COUNTER"/>
</parameter>
</paramset>
</channel>

</channels>

</device>

Hier kommt jetzt nichts neues mehr dazu, zumindest nichts, wozu ich wirklich etwas sagen könnte. Bei so etwas wie loopback muss ich leider passen.

So, das war jetzt extrem viel Beschreibung. Das nächste Mal sollte es wieder deutlich praktischer werden.

Gruß,
  Thorsten
FUIP