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:
parent
8a5b37e32c
commit
1fdbb01599
234
fhem/contrib/HMRPC/00_HMRPC.pm
Executable file
234
fhem/contrib/HMRPC/00_HMRPC.pm
Executable 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;
|
105
fhem/contrib/HMRPC/01_HMDEV.pm
Normal file
105
fhem/contrib/HMRPC/01_HMDEV.pm
Normal 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;
|
122
fhem/contrib/HMRPC/HMRPC.txt
Normal file
122
fhem/contrib/HMRPC/HMRPC.txt
Normal 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user