From 04c4765a1f0797ac61306dd7fc9cf14406f94df6 Mon Sep 17 00:00:00 2001 From: oliverwagner Date: Sun, 9 Oct 2011 22:00:45 +0000 Subject: [PATCH] HomeMatic interface moduled based on the officially documented XML-RPC API of the EQ-3 software (CCU or LAN "Konfigurationsadapter") git-svn-id: https://fhem.svn.sourceforge.net/svnroot/fhem/trunk/fhem@1063 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- contrib/HMRPC/00_HMRPC.pm | 234 ++++++++++++++++++++++++++++++++++++++ contrib/HMRPC/01_HMDEV.pm | 105 +++++++++++++++++ contrib/HMRPC/HMRPC.txt | 122 ++++++++++++++++++++ 3 files changed, 461 insertions(+) create mode 100755 contrib/HMRPC/00_HMRPC.pm create mode 100644 contrib/HMRPC/01_HMDEV.pm create mode 100644 contrib/HMRPC/HMRPC.txt diff --git a/contrib/HMRPC/00_HMRPC.pm b/contrib/HMRPC/00_HMRPC.pm new file mode 100755 index 000000000..f5e81acb7 --- /dev/null +++ b/contrib/HMRPC/00_HMRPC.pm @@ -0,0 +1,234 @@ +############################################## +# +# HomeMatic XMLRPC API Device Provider +# Written by Oliver Wagner +# +# V0.1 +# +############################################## +# +# This module implements the documented XML-RPC based API +# of the Homematic system software (currently offered as +# part of the CCU1 and of the LAN config adapter software) +# +# This module operates a http server to receive incoming +# xmlrpc event notifications from the HM software. +# +# Individual devices are then handled by 01_HMDEV.pm +# +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); +use RPC::XML::Server; +use RPC::XML::Client; +use Dumpvalue; + +my $dumper=new Dumpvalue; +$dumper->veryCompact(1); + +sub HMRPC_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "HMRPC_Define"; + $hash->{ShutdownFn} = "HMRPC_Shutdown"; + $hash->{ReadFn} = "HMRPC_Read"; + $hash->{SetFn} = "HMRPC_Set"; + $hash->{Clients} = ":HMDEV:"; +} + +##################################### +sub +HMRPC_Shutdown($) +{ + my ($hash) = @_; + # Uninitialize again + $hash->{client}->send_request("init",$hash->{callbackurl},""); + return undef; +} + +##################################### +sub +HMRPC_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + if(@a != 4) { + my $msg = "wrong syntax: define HMRPC remote_host remote_port"; + Log 2, $msg; + return $msg; + } + + $hash->{serveraddr}=$a[2]; + $hash->{serverport}=$a[3]; + + $hash->{client}=RPC::XML::Client->new("http://$a[2]:$a[3]/"); + my $callbackport=5400+$hash->{serverport}; + $hash->{server}=RPC::XML::Server->new(port=>$callbackport); + if(!ref($hash->{server})) + { + # Creating the server failed, perhaps because the port was + # already in use. Just return the message + Log 1,"Can't create HMRPC callback server on port $callbackport. Port in use?"; + return $hash->{server}; + } + + $hash->{server}->{fhemdef}=$hash; + + # Add the XMLRPC methods we do expose + $hash->{server}->add_method( + {name=>"event",signature=> ["string string string string int","string string string string double","string string string string boolean"],code=>\&HMRPC_EventCB} + ); + # + # Dummy implementation, always return an empty array + # + $hash->{server}->add_method( + {name=>"listDevices",signature=>["array string"],code=>sub{return RPC::XML::array->new()} } + ); + # + # TOFIX! We can use this to store device types, autocreate devices and other niceties + # + $hash->{server}->add_method( + {name=>"newDevices",signature=>["array string array"],code=>sub{return RPC::XML::array->new()} } + ); + + $hash->{STATE} = "Initialized"; + $hash->{SERVERSOCKET}=$hash->{server}->{__daemon}; + $hash->{FD}=$hash->{SERVERSOCKET}->fileno(); + $hash->{PORT}=$hash->{server}->{__daemon}->sockport(); + + # This will also register the callback + HMRPC_CheckCallback($hash); + + $selectlist{"$hash->{serveraddr}.$hash->{serverport}"} = $hash; + + # + # All is well + # + return 0; +} + +sub +HMRPC_CheckCallback($) +{ + my ($hash) = @_; + # We recheck the callback every 15 minutes. If we didn't receive anything + # inbetween, we re-init just to make sure (CCU reboots etc.) + InternalTimer(gettimeofday()+(15*60), "HMRPC_CheckCallback", $hash, 0); + if(!$hash->{lastcallbackts}) + { + HMRPC_RegisterCallback($hash); + return; + } + my $age=int(gettimeofday()-$hash->{lastcallbackts}); + if($age>(15*60)) + { + Log 5,"HMRPC Last callback received more than $age seconds ago, re-init-ing"; + HMRPC_RegisterCallback($hash); + } +} + +sub +HMRPC_RegisterCallback($) +{ + my ($hash) = @_; + + # + # We need to find out our local address. In order to do so, + # we establish a dummy connection to the remote xmlrpc server + # and then look at the local socket address assigned to us. + # + my $dummysock=IO::Socket::INET->new(PeerAddr=>$hash->{serveraddr},PeerPort=>$hash->{serverport}); + $hash->{callbackurl}="http://".$dummysock->sockhost().":".$hash->{PORT}."/fhemhmrpc"; + $dummysock->close(); + Log(2, "HMRPC callback listening on $hash->{callbackurl}"); + # We need to fork here, as the xmlrpc server will synchronously call us + if(!fork()) + { + $hash->{client}->send_request("init",$hash->{callbackurl},"cb"); + Log(2, "HMRPC callback initialized"); + exit(0); + } +} + +##################################### +sub +HMRPC_EventCB($$$$$) +{ + my ($server,$cb,$devid,$attr,$val)=@_; + + Log(5, "Processing event setting $devid->$attr=$val" ); + Dispatch($server->{fhemdef},"HMDEV $devid $attr $val",undef); + $server->{fhemdef}->{lastcallbackts}=gettimeofday(); +} + +sub +HMRPC_Read($) +{ + my ($hash) = @_; + + # + # Handle an incoming callback + # + my $conn=$hash->{server}->{__daemon}->accept(); + $conn->timeout(3); + $hash->{server}->process_request($conn); + $conn->close; + undef $conn; +} + +################################ +# +# +sub +HMRPC_Set($@) +{ + my ($hash, @a) = @_; + + #return "invalid set specification @a" if(@a != 4 && @a != 5); + + my $cmd=$a[1]; + + if($cmd eq "req") + { + # Send a raw xmlrpc request and return the result in + # text form. This is mainly useful for diagnostics. + shift @a; + shift @a; + my $ret=$hash->{client}->simple_request(@a); + # We convert using Dumpvalue. As this only prints, we need + # to temporarily redirect STDOUT + my $res=""; + open(my $temp,"+>",\$res); + my $oldout=select($temp); + $dumper->dumpValue($ret); + close(select($oldout)); + return $res; + } + + my $ret; + if(@a==5) + { + my $paramset={$a[3]=>$a[4]}; + + $ret=$hash->{client}->simple_request("putParamset",$a[1],$a[2],$paramset); + } + else + { + $ret=$hash->{client}->simple_request("setValue",$a[1],$a[2],$a[3]); + } + + if($ret) + { + return $ret->{faultCode}.": ".$ret->{faultString}; + } + else + { + return undef; + } +} + +1; diff --git a/contrib/HMRPC/01_HMDEV.pm b/contrib/HMRPC/01_HMDEV.pm new file mode 100644 index 000000000..41ba7b009 --- /dev/null +++ b/contrib/HMRPC/01_HMDEV.pm @@ -0,0 +1,105 @@ +############################################## +# HMRPC Device Handler +# Written by Oliver Wagner +# +# V0.2 +# +############################################## +# +# This module handles individual devices via the +# HMRPC provider. +# +package main; + +use strict; +use warnings; + +sub +HMDEV_Initialize($) +{ + my ($hash) = @_; + + $hash->{Match} = "^HMDEV .* .* .*"; + $hash->{DefFn} = "HMDEV_Define"; + $hash->{ParseFn} = "HMDEV_Parse"; + $hash->{SetFn} = "HMDEV_Set"; + $hash->{AttrList} = "IODev do_not_notify:0,1"; +} + +############################# +sub +HMDEV_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + my $name = $hash->{NAME}; + + return "wrong syntax: define HMDEV deviceaddress" if int(@a)!=3; + + my $addr=uc($a[2]); + + $hash->{hmaddr}=$addr; + $modules{HMDEV}{defptr}{$addr} = $hash; + AssignIoPort($hash); + + Log 5,"Assigned $name to $hash->{IODev}->{NAME}"; + + return undef; +} + + + +############################# +sub +HMDEV_Parse($$) +{ + my ($hash, $msg) = @_; + + my @mp=split(" ",$msg); + my $addr=$mp[1]; + + $hash=$modules{HMDEV}{defptr}{$addr}; + if(!$hash) + { + Log(2,"Received callback for unknown device $msg"); + return "UNDEFINED HMDEV_$addr HMDEV $addr"; + } + + # + # Ok update the relevant reading + # + my @changed; + my $currentval=$hash->{READINGS}{$mp[2]}{VAL}; + $hash->{READINGS}{$mp[2]}{TIME}=TimeNow(); + # Note that we always trigger a change on PRESS_LONG/PRESS_SHORT events + # (they are sent whenever a button is presed, and there is no change back) + if(!$currentval || ($currentval ne $mp[3]) || ($currentval =~ m/^PRESS_/)) + { + push @changed, "$mp[2]: $mp[3]"; + $hash->{READINGS}{$mp[2]}{VAL}=$mp[3]; + } + $hash->{CHANGED}=\@changed; + + return $hash->{NAME}; +} + +################################ +sub +HMDEV_Set($@) +{ + my ($hash, @a) = @_; + + return "invalid set call @a" if(@a != 3 && @a != 4); + # We delegate this call to the IODev, after having added the device address + if(@a==4) + { + return HMRPC_Set($hash->{IODev},$hash->{IODev}->{NAME},$hash->{hmaddr},$a[1],$a[2],$a[3]); + } + else + { + return HMRPC_Set($hash->{IODev},$hash->{IODev}->{NAME},$hash->{hmaddr},$a[1],$a[2]); + } +} + + +1; diff --git a/contrib/HMRPC/HMRPC.txt b/contrib/HMRPC/HMRPC.txt new file mode 100644 index 000000000..86c838484 --- /dev/null +++ b/contrib/HMRPC/HMRPC.txt @@ -0,0 +1,122 @@ +HMRPC - xmlrpc-basierte Homematic-Integration fuer fhem +======================================================= +Von Oliver Wagner + +V0.2 + +Uebersicht +---------- +HMRPC ist ein Modul zur Integration des Homematic-Systems der Firma EQ-3 +mit fhem. Es verfolgt im Gegensatz zu den bereits vorhandenen CUL_HM/HMLAN- +Modulen einen anderen Ansatz: Statt direkt mit der Funk-Hardware zu +kommunizieren, verwendet es die offizielle bereitgestellte xmlrpc-basierte +API der EQ-3-Software (siehe [1]). Daraus ergeben sich Vorteile und +Nachteile: So sind implizit alle derzeitigen und auch zukuenftigen Geraete +vollumfaenglich unterstuetzt, auch die RS485 Wired-Module. + +Der wesentliche Nachteil, oder zumindestens eine Vorraussetzung, ist, dass +man eine Instanz der xmlrpc-Server benoetigt. Dazu gibt es aktuell drei +Moeglichkeiten: + +1) auf der CCU1 selbst laufen "rfd" fuer die Funkkommunikation und +"hs485d" fuer die Wired-Kommunikaiton. + +Eine Uebersicht der Softwarearchitektur der CCU1 findet sich unter [2] + +2) als Teil der Verwaltungssoftware fuer den HM-LAN-Aadapter (siehe [3]) gibt +es einen xmlrpc-Dienst fuer Funkkommunikation als Windows-Service. Dieser +entspricht dem "rfd" auf der CCU1. + +3) Nutzung des "rfd" aus einem CCU1-Firmware-Image mittels "qemu-arm" + +Es ist aber nicht auszuschliessen, das EQ-3 in Zukunft z.B. einen rfd fuer +Linux/x86 veroeffentlicht. + + +Geschichte und Status +--------------------- +Diese Module sind aus der Middleware "HMCompanion" [4] entstanden, die ich mir +fuer die HM-Integration in meinen Haussteuerungswildwuchs geschrieben habe. + +HMRPC hat aktuell eher experimentellen Charakter. Ohne genaue Kenntnisse +von fhem, perl und HM-Internas haben die Module nur eingeschraenkten Nutzwert, +die Veroeffentlichung dient erstmal nur dazu, fruehes Feedback zur +Implementierung zu bekommen. + +Das ist im iebrigen mein erstes nicht komplett triviales Stueck perl-code -- +ueber Hinweise diesbezueglich wuerde ich mich ebenso freuen wie ueber allgemeines +Feedback zu HMRPC. + + +Benutzung +--------- +Es gibt zwei Module: + +00_HMRPC.pm ist der Provider fuer die Kommunikation mit eineml +xmlrpc-Service + +01_HMDEV.pm ist jeweils die Abstraktion eines einzelnen Devices + +Beispielkonfiguration fuer fhem: + +# Wired-Schnittstelle auf einer CCU1 mit IP 192.168.5.2) +define hmw HMRPC 192.168.5.2 2000 +# Ein Kanal eines Wired-Aktors +define light_buero_olli HMDEV GEQ0009019:3 + +Nutzung dann z.B. mit + +set light_buero_olli STATE false + +Ein putParamset (Konfigurationsupdate) wird dann durch zusätzliche Angabe +der Paramset-ID generiert: + +set light_buero_olli MASTER LOGGING 0 + +Die Attribute eines Geraetes entsprechen den in dem Dokument unter [1] +"HomeMatic-Script Dokumentation: Teil 4 - Datenpunkte" beschriebenen. +Die Inhalte der Paramsets sind aktuell nicht dokumentiert, man muss diese +anhand des xmlrpc-Requests getParamsetDescription oder durch Browsen der +XML-Beschreibungen im /firmware-Verzeichnis der CCU-Software +ermitteln. + +Über die set-Methode des HMRPC-Devices lassen sich auch andere weitere +Operationen durchführen: + +set req + +generiert einen direkten XMLRPC-Request und gibt das Ergebnis in Textform +zurück. Das dient im wesentlichen Diagnose/Entwicklungszwecken. Beispiel: + +set hmw req getDeviceDescription IEQ0208603 + +Weitergehende Funktionen wie synchrones Abfragen von Werten oder Paramsets +oder Statii des jeweiligen Service (Meldungen etc.) sind geplant, aber noch +nicht implementiert. + + +Design +------ +Ich habe ueberlegt, ob HMRPC als Provider für CUL_HM dienen koennte, habe aber +keine praktikable Loesung dafür gefunden -- HMDEV ist aktuell im Vergleich zu +CUL_HM sehr dumm und dient mehr oder weniger nur als Cache für Adresse und +Readings. + +HMRPC meldet sich beim jeweiligen Service per "init" an und erhält dann per +xmlrpc-Callback Mitteilungen über Zustandsaenderungen. Wird der Service neu +gestartet (CCU Reboot o.ae.), ist diese Anmeldung hinfaellig. Es gibt aktuell +keine gute Methode, dies festzustelle -- als Workaround meldet sich HMRPC +15 Minuten nach dem letzten empfangenen Callback neu an. Je nach Art der +verwendeten Aktoren in einer Installation kann diese Zeit sehr kurz sein +und daher unnoetige re-inits verursachen. Diese scheinen aber grundsaetzlich kein +Problem auf der Service-Seite darzustellen. + + +Anhang +------ + +[1] http://www.homematic.com/index.php?id=156 +[2] http://www.homematic-wiki.info/mw/index.php/HomeMatic_Software +[3] http://www.homematic.com/index.php?id=644 +[4] http://www.fhz-forum.de/viewtopic.php?f=26&t=4639 +