| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- ##############################################
- # $Id: 93_FHEM2FHEM.pm 15006 2017-09-05 09:37:33Z rudolfkoenig $
- package main;
- use strict;
- use warnings;
- use Time::HiRes qw(gettimeofday);
- use HttpUtils;
- sub FHEM2FHEM_Read($);
- sub FHEM2FHEM_Ready($);
- sub FHEM2FHEM_OpenDev($$);
- sub FHEM2FHEM_CloseDev($);
- sub FHEM2FHEM_Disconnected($);
- sub FHEM2FHEM_Define($$);
- sub FHEM2FHEM_Undef($$);
- sub
- FHEM2FHEM_Initialize($)
- {
- my ($hash) = @_;
- # Provider
- $hash->{ReadFn} = "FHEM2FHEM_Read";
- $hash->{WriteFn} = "FHEM2FHEM_Write";
- $hash->{ReadyFn} = "FHEM2FHEM_Ready";
- $hash->{SetFn} = "FHEM2FHEM_Set";
- $hash->{AttrFn} = "FHEM2FHEM_Attr";
- $hash->{noRawInform} = 1;
- # Normal devices
- $hash->{DefFn} = "FHEM2FHEM_Define";
- $hash->{UndefFn} = "FHEM2FHEM_Undef";
- $hash->{AttrList}= "addStateEvent:1,0 dummy:1,0 disable:0,1 ".
- "disabledForIntervals eventOnly:1,0 excludeEvents";
- }
- #####################################
- sub
- FHEM2FHEM_Define($$)
- {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- if(@a < 4 || @a > 5 || !($a[3] =~ m/^(LOG|RAW):(.*)$/)) {
- my $msg = "wrong syntax: define <name> FHEM2FHEM host[:port][:SSL] ".
- "[LOG:regexp|RAW:device] {portpasswort}";
- Log3 $hash, 2, $msg;
- return $msg;
- }
- $hash->{informType} = $1;
- if($1 eq "LOG") {
- $hash->{regexp} = $2;
- } else {
- my $rdev = $2;
- my $iodev = $defs{$rdev};
- return "Undefined local device $rdev" if(!$iodev);
- $hash->{rawDevice} = $rdev;
- $hash->{Clients} = $iodev->{Clients};
- $hash->{Clients} = $modules{$iodev->{TYPE}}{Clients}
- if(!$hash->{Clients});
- }
- my $dev = $a[2];
- if($dev =~ m/^(.*):SSL$/) {
- $dev = $1;
- $hash->{SSL} = 1;
- }
- if($dev !~ m/^.+:[0-9]+$/) { # host:port
- $dev = "$dev:7072";
- $hash->{Host} = $dev;
- }
- if($hash->{OLDDEF} && $hash->{OLDDEF} =~ m/^([^ \t]+)/) {; # Forum #30242
- delete($readyfnlist{"$hash->{NAME}.$1"});
- }
- $hash->{Host} = $dev;
- $hash->{portpassword} = $a[4] if(@a == 5);
- FHEM2FHEM_CloseDev($hash); # Modify...
- return FHEM2FHEM_OpenDev($hash, 0);
- }
- #####################################
- sub
- FHEM2FHEM_Undef($$)
- {
- my ($hash, $arg) = @_;
- FHEM2FHEM_CloseDev($hash);
- return undef;
- }
- sub
- FHEM2FHEM_Write($$)
- {
- my ($hash,$fn,$msg) = @_;
- my $dev = $hash->{Host};
- if(!$hash->{TCPDev2}) {
- my $conn;
- if($hash->{SSL}) {
- $conn = IO::Socket::SSL->new(PeerAddr => $dev);
- } else {
- $conn = IO::Socket::INET->new(PeerAddr => $dev);
- }
- return if(!$conn); # Hopefuly it is reported elsewhere
- $hash->{TCPDev2} = $conn;
- syswrite($hash->{TCPDev2}, $hash->{portpassword} . "\n")
- if($hash->{portpassword});
- }
- my $rdev = $hash->{rawDevice};
- syswrite($hash->{TCPDev2}, "iowrite $rdev $fn $msg\n");
- }
- #####################################
- # called from the global loop, when the select for hash->{FD} reports data
- sub
- FHEM2FHEM_Read($)
- {
- my ($hash) = @_;
- my $buf = FHEM2FHEM_SimpleRead($hash);
- my $name = $hash->{NAME};
- ###########
- # Lets' try again: Some drivers return len(0) on the first read...
- if(defined($buf) && length($buf) == 0) {
- $buf = FHEM2FHEM_SimpleRead($hash);
- }
- if(!defined($buf) || length($buf) == 0) {
- FHEM2FHEM_Disconnected($hash);
- return;
- }
- return if(IsDisabled($name));
- my $excl = AttrVal($name, "excludeEvents", undef);
- my $data = $hash->{PARTIAL};
- #Log3 $hash, 5, "FHEM2FHEM/RAW: $data/$buf";
- $data .= $buf;
- while($data =~ m/\n/) {
- my $rmsg;
- ($rmsg,$data) = split("\n", $data, 2);
- $rmsg =~ s/\r//;
- if($hash->{informType} eq "LOG") {
- my ($type, $rname, $msg) = split(" ", $rmsg, 3);
- next if(!defined($msg)); # Bogus data
- my $re = $hash->{regexp};
- next if($re && !($rname =~ m/^$re$/ || "$rname:$msg" =~ m/^$re$/));
- next if($excl && ($rname =~ m/^$excl$/ || "$rname:$msg" =~ m/^$excl$/));
- Log3 $name, 4, "$rname: $rmsg";
- if(!$defs{$rname}) {
- $defs{$rname}{NAME} = $rname;
- $defs{$rname}{TYPE} = $type;
- $defs{$rname}{STATE} = $msg;
- $defs{$rname}{FAKEDEVICE} = 1; # Avoid set/attr/delete/etc in notify
- $defs{$rname}{TEMPORARY} = 1; # Do not save it
- DoTrigger($rname, $msg);
- delete($defs{$rname});
- delete($attr{$rname}); # Forum #73490
- } else {
- if(AttrVal($name,"eventOnly",0)) {
- DoTrigger($rname, $msg);
- } else {
- if($msg =~ m/^([^:]*): (.*)$/) {
- readingsSingleUpdate($defs{$rname}, $1, $2, 1);
- } else {
- readingsSingleUpdate($defs{$rname}, "state", $msg, 1);
- }
- }
- }
- } else { # RAW
- my ($type, $rname, $msg) = split(" ", $rmsg, 3);
- my $rdev = $hash->{rawDevice};
- next if($rname ne $rdev);
- Log3 $name, 4, "$name: $rmsg";
- Dispatch($defs{$rdev}, $msg, undef);
- }
- }
- $hash->{PARTIAL} = $data;
- }
- #####################################
- sub
- FHEM2FHEM_Ready($)
- {
- my ($hash) = @_;
- return FHEM2FHEM_OpenDev($hash, 1);
- }
- ########################
- sub
- FHEM2FHEM_CloseDev($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $dev = $hash->{Host};
- return if(!$dev);
-
- $hash->{TCPDev}->close() if($hash->{TCPDev});
- $hash->{TCPDev2}->close() if($hash->{TCPDev2});
- delete($hash->{NEXT_OPEN});
- delete($hash->{TCPDev});
- delete($hash->{TCPDev2});
- delete($selectlist{"$name.$dev"});
- delete($readyfnlist{"$name.$dev"});
- delete($hash->{FD});
- }
- ########################
- sub
- FHEM2FHEM_OpenDev($$)
- {
- my ($hash, $reopen) = @_;
- my $dev = $hash->{Host};
- my $name = $hash->{NAME};
- $hash->{PARTIAL} = "";
- Log3 $name, 3, "FHEM2FHEM opening $name at $dev"
- if(!$reopen);
- return if($hash->{NEXT_OPEN} && time() <= $hash->{NEXT_OPEN});
- return if(IsDisabled($name));
- my $doTailWork = sub($$$) {
- my ($h, $err, undef) = @_;
- if($err) {
- Log3($name, 3, "Can't connect to $dev: $!") if(!$reopen);
- $readyfnlist{"$name.$dev"} = $hash;
- $hash->{STATE} = "disconnected";
- $hash->{NEXT_OPEN} = time()+60;
- return;
- }
- my $conn = $h->{conn};
- delete($hash->{NEXT_OPEN});
- $conn->setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1);
- $hash->{TCPDev} = $conn;
- $hash->{FD} = $conn->fileno();
- delete($readyfnlist{"$name.$dev"});
- $selectlist{"$name.$dev"} = $hash;
- if($reopen) {
- Log3 $name, 1, "FHEM2FHEM $dev reappeared ($name)";
- } else {
- Log3 $name, 3, "FHEM2FHEM device opened ($name)";
- }
- $hash->{STATE}= "connected";
- DoTrigger($name, "CONNECTED") if($reopen);
- syswrite($hash->{TCPDev}, $hash->{portpassword} . "\n")
- if($hash->{portpassword});
- my $type = AttrVal($hash->{NAME},"addStateEvent",0) ? "onWithState" : "on";
- my $msg = $hash->{informType} eq "LOG" ?
- "inform $type $hash->{regexp}" : "inform raw";
- syswrite($hash->{TCPDev}, $msg . "\n");
- };
- return HttpUtils_Connect({ # Nonblocking
- url => $hash->{SSL} ? "https://$dev/" : "http://$dev/",
- NAME => $name,
- noConn2 => 1,
- callback=> $doTailWork
- });
- }
- sub
- FHEM2FHEM_Disconnected($)
- {
- my $hash = shift;
- my $dev = $hash->{Host};
- my $name = $hash->{NAME};
- return if(!defined($hash->{FD})); # Already deleted
- Log3 $name, 1, "$dev disconnected, waiting to reappear";
- FHEM2FHEM_CloseDev($hash);
- $readyfnlist{"$name.$dev"} = $hash; # Start polling
- $hash->{STATE} = "disconnected";
- return if(IsDisabled($name)); #Forum #39386
- # Without the following sleep the open of the device causes a SIGSEGV,
- # and following opens block infinitely. Only a reboot helps.
- sleep(5);
- DoTrigger($name, "DISCONNECTED");
- }
- ########################
- sub
- FHEM2FHEM_SimpleRead($)
- {
- my ($hash) = @_;
- my $buf;
- if(!defined(sysread($hash->{TCPDev}, $buf, 256))) {
- FHEM2FHEM_Disconnected($hash);
- return undef;
- }
- return $buf;
- }
- sub
- FHEM2FHEM_Set($@)
- {
- my ($hash, @a) = @_;
- return "set needs at least one parameter" if(@a < 2);
- return "Unknown argument $a[1], choose one of reopen:noArg"
- if($a[1] ne "reopen");
-
- FHEM2FHEM_CloseDev($hash);
- FHEM2FHEM_OpenDev($hash, 0);
- return undef;
- }
- sub
- FHEM2FHEM_Attr(@)
- {
- my ($type, $devName, $attrName, @param) = @_;
- my $hash = $defs{$devName};
- return undef if($attrName ne "addStateEvent");
- $attr{$devName}{$attrName} = 1;
- FHEM2FHEM_CloseDev($hash);
- FHEM2FHEM_OpenDev($hash, 1);
- return undef;
- }
- 1;
- =pod
- =item helper
- =item summary connect two FHEM instances
- =item summary_DE verbindet zwei FHEM Installationen
- =begin html
- <a name="FHEM2FHEM"></a>
- <h3>FHEM2FHEM</h3>
- <ul>
- FHEM2FHEM is a helper module to connect separate FHEM installations.
- <br><br>
- <a name="FHEM2FHEMdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> FHEM2FHEM <host>[:<portnr>][:SSL]
- [LOG:regexp|RAW:devicename] {portpassword}
- </code>
- <br>
- <br>
- Connect to the <i>remote</i> FHEM on <host>. <portnr> is a telnet
- port on the remote FHEM, defaults to 7072. The optional :SSL suffix is
- needed, if the remote FHEM configured SSL for this telnet port. In this case
- the IO::Socket::SSL perl module must be installed for the local host too.<br>
- Note: if the remote FHEM is on a separate host, the telnet port on the remote
- FHEM musst be specified with the global option.<br>
- The next parameter specifies the connection
- type:
- <ul>
- <li>LOG<br>
- Using this type you will receive all events generated by the remote FHEM,
- just like when using the <a href="#inform">inform on</a> command, and you
- can use these events just like any local event for <a
- href="#FileLog">FileLog </a> or <a href="#notify">notify</a>.
- The regexp will prefilter the events distributed locally, for the syntax
- see the notify definition.<br>
- Drawbacks: the remote devices wont be created locally, so list wont
- show them and it is not possible to manipulate them from the local
- FHEM. It is possible to create a device with the same name on both FHEM
- instances, but if both of them receive the same event (e.g. because both
- of them have a CUL attached), then all associated FileLogs/notifys will be
- triggered twice.<br>
- If the remote device is created with the same name locally (e.g. as dummy),
- then the local readings are also updated.
- </li>
- <li>RAW<br>
- By using this type the local FHEM will receive raw events from the remote
- FHEM device <i>devicename</i>, just like if it would be attached to the
- local FHEM.
- Drawback: only devices using the Dispatch function (CUL, FHZ, CM11,
- SISPM, RFXCOM, TCM, TRX, TUL) generate raw messages, and you must create a
- FHEM2FHEM instance for each remote device.<br>
- <i>devicename</i> must exist on the local
- FHEM server too with the same name and same type as the remote device, but
- with the device-node "none", so it is only a dummy device.
- All necessary attributes (e.g. <a href="#rfmode">rfmode</a> if the remote
- CUL is in HomeMatic mode) must also be set for the local device.
- Do not reuse a real local device, else duplicate filtering (see dupTimeout)
- won't work correctly.
- </li>
- </ul>
- The last parameter specifies an optional portpassword, if the remote server
- activated <a href="#portpassword">portpassword</a>.
- <br>
- Examples:
- <ul>
- <code>define ds1 FHEM2FHEM 192.168.178.22:7072 LOG:.*</code><br>
- <br>
- <code>define RpiCUL CUL none 0000</code><br>
- <code>define ds2 FHEM2FHEM 192.168.178.22:7072 RAW:RpiCUL</code><br>
- and on the RPi (192.168.178.22):<br>
- <code>rename CUL_0 RpiCUL</code><br>
- </ul>
- </ul>
- <br>
- <a name="FHEM2FHEMset"></a>
- <b>Set </b>
- <ul>
- <li>reopen<br>
- Reopens the connection to the device and reinitializes it.</li><br>
- </ul>
- <a name="FHEM2FHEMget"></a>
- <b>Get</b> <ul>N/A</ul><br>
- <a name="FHEM2FHEMattr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#dummy">dummy</a></li>
- <li><a href="#disable">disable</a></li>
- <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
- <li><a name="#eventOnly">eventOnly</a><br>
- if set, generate only events, do not set corresponding readings.
- This is a compatibility feature, available only for LOG-Mode.
- </li>
- <li><a name="#addStateEvent">addStateEvent</a><br>
- if set, state events are transmitted correctly. Notes: this is relevant
- only with LOG mode, setting it will generate an additional "reappeared"
- Log entry, and the remote FHEM must support inform onWithState (i.e. must
- be up to date).
- </li>
- <li><a name="#excludeEvents">excludeEvents <regexp></a>
- do not publish events matching <regexp>
- </li>
- </ul>
- </ul>
- =end html
- =begin html_DE
- <a name="FHEM2FHEM"></a>
- <h3>FHEM2FHEM</h3>
- <ul>
- FHEM2FHEM ist ein Hilfsmodul, um mehrere FHEM-Installationen zu verbinden.
- <br><br>
- <a name="FHEM2FHEMdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> FHEM2FHEM <host>[:<portnr>][:SSL] [LOG:regexp|RAW:devicename] {portpassword}
- </code>
- <br>
- <br>
- Zum <i>remote (entfernten)</i> FHEM auf Rechner <host> verbinden.
- <portnr> ist der telnetPort des remote FHEM, Standardport ist 7072.
- Der Zusatz :SSL wird benötigt, wenn das remote FHEM
- SSL-Verschlüsselung voraussetzt. Auch auf dem lokalen Host muss dann
- das Perl-Modul IO::Socket::SSL installiert sein.<br>
- Anmerkung: Wenn das remote FHEM auf einem eigenen Host läuft, muss
- "telnetPort" des remote FHEM als global festgelegt sein. <br>
- Der nächste Parameter spezifiziert den Verbindungs-Typ:
- <ul>
- <li>LOG<br>
- Bei Verwendung dieses Verbindungstyps werden alle Ereignisse (Events) der
- remote FHEM-Installation empfangen. Die Ereignisse sehen aus wie die, die
- nach <a href="#inform">inform on</a> Befehl erzeugt werden. Sie können
- wie lokale Ereignisse durch <a href="#FileLog">FileLog </a> oder <a
- href="#notify">notify</a> genutzt werden und mit einem regulären
- Ausdruck gefiltert werden. Die Syntax dafür ist unter der
- notify-Definition beschrieben.<br>
- Einschränkungen: die Geräte der remote Installation werden nicht
- lokal angelegt und können weder mit list angezeigt noch lokal
- angesprochen werden. Auf beiden FHEM-Installationen können
- Geräte gleichen Namens angelegt werden, aber wenn beide dasselbe
- Ereignis empfangen (z.B. wenn an beiden Installationen CULs angeschlossen
- sind), werden alle FileLogs und notifys doppelt ausgelöst.<br>
- Falls man lokal Geräte mit dem gleichen Namen (z.Bsp. als dummy)
- angelegt hat, dann werden die Readings von dem lokalen Gerät
- aktualisiert.
- </li>
- <li>RAW<br>
- Bei diesem Verbindungstyp werden unaufbereitete Ereignisse (raw messages)
- des remote FHEM-Geräts <i>devicename</i> genau so empfangen, als
- wäre das Gerät lokal verbunden.<br>
- Einschränkungen: nur Geräte, welche die "Dispatch-Funktion"
- unterstützen (CUL, FHZ, CM11, SISPM, RFXCOM, TCM, TRX, TUL) erzeugen
- raw messages, und für jedes entfernte Gerät muss ein eigenes
- FHEM2FHEM Objekt erzeugt werden.<br>
- <i>devicename</i> muss mit demselben Namen und Typ wie das Remote Devive
- angelegt sein, aber als Dummy, d.h. als device-node "none".
- Zusätzlich müssen alle notwendigen Attribute lokal gesetzt sein
- (z.B. <a href="#rfmode">rfmode</a>, wenn die remote CUL im HomeMatic-Modus
- läuft). Die Verwendung bereits bestehender lokaler Geräte ist zu
- vermeiden, weil sonst die Duplikatsfilterung nicht richtig funktioniert
- (siehe dupTimeout). </li>
- </ul>
- Der letzte Parameter enthält das Passwort des Remote-Servers, wenn dort
- eines aktiviert ist <a href="#portpassword">portpassword</a>.
- <br>
- Beispiele:
- <ul>
- <code>define ds1 FHEM2FHEM 192.168.178.22:7072 LOG:.*</code><br>
- <br>
- <code>define RpiCUL CUL none 0000</code><br>
- <code>define ds2 FHEM2FHEM 192.168.178.22:7072 RAW:RpiCUL</code><br> und auf dem RPi (192.168.178.22):<br>
- <code>rename CUL_0 RpiCUL</code><br>
- </ul>
- </ul>
- <br>
- <a name="FHEM2FHEMset"></a>
- <b>Set </b>
- <ul>
- <li>reopen<br>
- Öffnet die Verbindung erneut.</li>
- </ul>
- <a name="FHEM2FHEMget"></a>
- <b>Get</b> <ul>N/A</ul><br>
- <a name="FHEM2FHEMattr"></a>
- <b>Attribute</b>
- <ul>
- <li><a href="#dummy">dummy</a></li>
- <li><a href="#disable">disable</a></li>
- <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
- <li><a name="#eventOnly">eventOnly</a><br>
- falls gesetzt, werden nur die Events generiert, und es wird kein
- Reading aktualisiert. Ist nur im LOG-Mode aktiv.
- </li>
- <li><a name="#addStateEvent">addStateEvent</a><br>
- falls gesetzt, werden state Events als solche uebertragen. Zu beachten:
- das Attribut ist nur für LOG-Mode relevant, beim Setzen wird eine
- zusätzliche reopened Logzeile generiert, und die andere Seite muss
- aktuell sein.
- </li>
- <li><a name="#excludeEvents">excludeEvents <regexp></a>
- die auf das <regexp> zutreffende Events werden nicht
- bereitgestellt.
- </li>
- </ul>
- </ul>
- =end html_DE
- =cut
|