2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-02-07 16:59:18 +00:00

HomeMatic interface moduled based on the officially documented XML-RPC API

of the EQ-3 software (CCU or LAN "Konfigurationsadapter")


git-svn-id: https://svn.fhem.de/fhem/trunk@1063 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
oliverwagner 2011-10-09 22:00:45 +00:00
parent 8a5b37e32c
commit 1fdbb01599
3 changed files with 461 additions and 0 deletions

234
fhem/contrib/HMRPC/00_HMRPC.pm Executable file
View File

@ -0,0 +1,234 @@
##############################################
#
# HomeMatic XMLRPC API Device Provider
# Written by Oliver Wagner <owagner@vapor.com>
#
# 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 <name> 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;

View File

@ -0,0 +1,105 @@
##############################################
# HMRPC Device Handler
# Written by Oliver Wagner <owagner@vapor.com>
#
# 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 <name> 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;

View File

@ -0,0 +1,122 @@
HMRPC - xmlrpc-basierte Homematic-Integration fuer fhem
=======================================================
Von Oliver Wagner <owagner@vapor.com>
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 <hmlrpc-device> req <xmlrpc-request> <parameter>
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