From 2ab1967bb376d156db185ca03bcd60e2087bd236 Mon Sep 17 00:00:00 2001 From: zap <> Date: Tue, 17 Apr 2018 11:47:40 +0000 Subject: [PATCH] HMCCU: Support for multiple FHEM instances git-svn-id: https://svn.fhem.de/fhem/trunk@16629 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/88_HMCCU.pm | 86 ++++++++++------------------ fhem/FHEM/88_HMCCURPCPROC.pm | 108 +++++++++++++++++++---------------- 3 files changed, 90 insertions(+), 105 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 8a5005166..4e1e7581f 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - change: 88_HMCCU: minor changes - feature: 93_DbRep: new function dbValue, DbReadingsVal (blocking) - bugfix: 93_DbLog: 3.10.7, create addLog-event if reading was not found - bugfix: 73_GardenaSmartBridge: add error trigger for notify sub diff --git a/fhem/FHEM/88_HMCCU.pm b/fhem/FHEM/88_HMCCU.pm index 8c510a748..8db630ca7 100755 --- a/fhem/FHEM/88_HMCCU.pm +++ b/fhem/FHEM/88_HMCCU.pm @@ -4,7 +4,7 @@ # # $Id$ # -# Version 4.2.004 +# Version 4.2.005 # # Module for communication between FHEM and Homematic CCU2. # @@ -105,7 +105,7 @@ my %HMCCU_CUST_CHN_DEFAULTS; my %HMCCU_CUST_DEV_DEFAULTS; # HMCCU version -my $HMCCU_VERSION = '4.2.004'; +my $HMCCU_VERSION = '4.2.005'; # Default RPC port (BidCos-RF) my $HMCCU_RPC_PORT_DEFAULT = 2001; @@ -267,7 +267,6 @@ sub HMCCU_GetAddress ($$$$); sub HMCCU_IsDevAddr ($$); sub HMCCU_IsChnAddr ($$); sub HMCCU_SplitChnAddr ($); -sub HMCCU_GetCCUObjectAttribute ($$$); sub HMCCU_FindClientDevices ($$$$); sub HMCCU_GetRPCDevice ($$$); sub HMCCU_FindIODevice ($); @@ -424,6 +423,7 @@ sub HMCCU_Define ($$) $hash->{version} = $HMCCU_VERSION; $hash->{ccutype} = 'CCU2'; $hash->{RPCState} = "inactive"; + $hash->{NOTIFYDEV} = "global,TYPE=(HMCCU|HMCCUDEV|HMCCUCHN)"; Log3 $name, 1, "HMCCU: Device $name. Initialized version $HMCCU_VERSION"; my ($devcnt, $chncnt, $ifcount) = HMCCU_GetDeviceList ($hash); @@ -497,6 +497,9 @@ sub HMCCU_Attr ($@) if (HMCCU_IsRPCServerRunning ($hash, undef, undef)); } } + if ($attrval =~ /(extrpc|intrpc)/) { + HMCCU_Log ($hash, 1, "RPC server mode $1 is deprecated. Please use procrpc instead", 0); + } } elsif ($attrname eq 'rpcdevice') { return "HMCCU: Can't find HMCCURPC device $attrval" @@ -1211,12 +1214,10 @@ sub HMCCU_Set ($@) return HMCCU_SetError ($hash, $usage) if (!defined ($program)); - my $url = qq(http://$host:8181/do.exe?r1=dom.GetObject("$program").ProgramExecute()); - $response = GetFileFromURL ($url, $ccureqtimeout); - $response =~ m/(.*)<\/r1>/; - my $value = $1; + my $cmd = qq(dom.GetObject("$program").ProgramExecute()); + my $value = HMCCU_HMCommand ($hash, $cmd, 0); - return HMCCU_SetState ($hash, "OK") if (defined ($value) && $value ne '' && $value ne 'null'); + return HMCCU_SetState ($hash, "OK") if (defined ($value)); return HMCCU_SetError ($hash, "Program execution error"); } elsif ($opt eq 'hmscript') { @@ -1674,7 +1675,7 @@ sub HMCCU_Get ($@) my $ch = $defs{$dev}; my $ct = uc($ch->{ccutype}); my $fw = defined ($ch->{firmware}) ? $ch->{firmware} : 'N/A'; - next if (!exists ($hash->{hmccu}{type}{$ct})); + next if (!exists ($hash->{hmccu}{type}{$ct}) || $ct !~ /$dtexp/); $result .= sprintf "%-25s %-20s %-7s %-9s %-10s\n", $ch->{NAME}, $ct, $fw, $hash->{hmccu}{type}{$ct}{download}, $hash->{hmccu}{type}{$ct}{firmware}, $hash->{hmccu}{type}{$ct}{date}; @@ -4429,28 +4430,6 @@ sub HMCCU_SplitChnAddr ($) return ($dev, $chn); } -###################################################################### -# Query object attribute from CCU. Attribute must be a valid method -# for specified object, i.e. Address() -###################################################################### - -sub HMCCU_GetCCUObjectAttribute ($$$) -{ - my ($hash, $object, $attr) = @_; - my $name = $hash->{NAME}; - my $ccureqtimeout = AttrVal ($name, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST); - - my $url = 'http://'.$hash->{host}.':8181/do.exe?r1=dom.GetObject("'.$object.'").'.$attr; - my $response = GetFileFromURL ($url, $ccureqtimeout); - if (defined ($response) && $response !~ /null(.+)<\/r1>/) { - return $1; - } - } - - return undef; -} - ###################################################################### # Get list of client devices matching the specified criteria. # If no criteria is specified all device names will be returned. @@ -5234,6 +5213,11 @@ sub HMCCU_HMScriptExt ($$$) my $host = $hash->{host}; my $code = $hmscript; my $scrname = ''; + + if (!exists ($hash->{host})) { + Log3 $name, 2, "HMCCU: CCU host name not defined. Name=$name Type=".$hash->{TYPE}; + return "ERROR: CCU host name not defined"; + } # Check for internal script if ($hmscript =~ /^!(.*)$/) { @@ -5317,14 +5301,13 @@ sub HMCCU_GetDatapoint ($@) { my ($hash, $param) = @_; my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; my $fnc = "GetDatapoint"; my $hmccu_hash; my $value = ''; $hmccu_hash = HMCCU_GetHash ($hash); return (-3, $value) if (!defined ($hmccu_hash)); - return (-4, $value) if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'deleted'); + return (-4, $value) if ($hash->{TYPE} ne 'HMCCU' && $hash->{ccudevstate} eq 'deleted'); my $readingformat = HMCCU_GetAttrReadingFormat ($hash, $hmccu_hash); my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints ( @@ -5332,28 +5315,22 @@ sub HMCCU_GetDatapoint ($@) my $ccuget = HMCCU_GetAttribute ($hmccu_hash, $hash, 'ccuget', 'Value'); my $ccureqtimeout = AttrVal ($hmccu_hash->{NAME}, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST); - my $url = 'http://'.$hmccu_hash->{host}.':8181/do.exe?r1=dom.GetObject("'; + my $cmd = ''; my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $param, $HMCCU_FLAG_INTERFACE); + return (-1, $value) if ($flags != $HMCCU_FLAGS_IACD && $flags != $HMCCU_FLAGS_NCD); + if ($flags == $HMCCU_FLAGS_IACD) { - $url .= $int.'.'.$add.':'.$chn.'.'.$dpt.'").'.$ccuget.'()'; + $cmd = '(datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).'.$ccuget.'()'; } elsif ($flags == $HMCCU_FLAGS_NCD) { - $url .= $nam.'").DPByHssDP("'.$dpt.'").'.$ccuget.'()'; + $cmd = '(dom.GetObject(ID_CHANNELS)).Get("'.$nam.'").DPByHssDP("'.$dpt.'").'.$ccuget.'()'; ($add, $chn) = HMCCU_GetAddress ($hmccu_hash, $nam, '', ''); } - else { - return (-1, $value); - } - HMCCU_Trace ($hash, 2, $fnc, "URL=$url, param=$param, ccuget=$ccuget"); + HMCCU_Trace ($hash, 2, $fnc, "CMD=$cmd, param=$param, ccuget=$ccuget"); - my $rawresponse = GetFileFromURL ($url, $ccureqtimeout); - my $response = $rawresponse; - $response =~ m/(.*)<\/r1>/; - $value = $1; - - HMCCU_Trace ($hash, 2, $fnc, "Response = ".$rawresponse); + $value = HMCCU_HMCommand ($hmccu_hash, $cmd, 1); if (defined ($value) && $value ne '' && $value ne 'null') { $value = HMCCU_UpdateSingleDatapoint ($hash, $chn, $dpt, $value); @@ -5361,7 +5338,7 @@ sub HMCCU_GetDatapoint ($@) return (1, $value); } else { - Log3 $name, 1, "$type: Error URL = ".$url; + HMCCU_Log ($hash, 1, "Error CMD = $cmd", 0); return (-2, ''); } } @@ -5407,7 +5384,7 @@ sub HMCCU_SetDatapoint ($$$) $param = $1; } - my $cmd = 'dom.GetObject("'; + my $cmd = ''; my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $param, $HMCCU_FLAG_INTERFACE); return -1 if ($flags != $HMCCU_FLAGS_IACD && $flags != $HMCCU_FLAGS_NCD); @@ -5425,11 +5402,11 @@ sub HMCCU_SetDatapoint ($$$) } if ($flags == $HMCCU_FLAGS_IACD) { - $cmd .= $int.'.'.$add.':'.$chn.'.'.$dpt.'").State('.$value.')'; + $cmd = '(datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).State('.$value.')'; $nam = HMCCU_GetChannelName ($hmccu_hash, $add.":".$chn, ''); } elsif ($flags == $HMCCU_FLAGS_NCD) { - $cmd .= $nam.'").DPByHssDP("'.$dpt.'").State('.$value.')'; + $cmd = '(dom.GetObject(ID_CHANNELS)).Get("'.$nam.'").DPByHssDP("'.$dpt.'").State('.$value.')'; ($add, $chn) = HMCCU_GetAddress ($hmccu_hash, $nam, '', ''); } @@ -5581,12 +5558,9 @@ sub HMCCU_SetVariable ($$$$$) ); if (!defined ($vartype)) { - my $url = 'http://'.$hash->{host}.':8181/do.exe?r1=dom.GetObject("'.$varname. - '").State("'.$value.'")'; - - my $response = GetFileFromURL ($url, $ccureqtimeout); - return HMCCU_Log ($hash, 1, "URL=$url", -2) - if (!defined ($response) || $response =~ /null{iodev}; return $usage if (scalar (@$a) < 3); return "HMCCU I/O device $ioname not found" if (!exists ($defs{$ioname})); - return "Device $ioname is no HMCCU device" if ($defs{$ioname}->{TYPE} ne 'HMCCU'); + return "Device $ioname is not a HMCCU device" if ($defs{$ioname}->{TYPE} ne 'HMCCU'); $hmccu_hash = $defs{$ioname}; $hash->{host} = $hmccu_hash->{host}; $iface = $$a[2]; @@ -229,19 +230,19 @@ sub HMCCURPCPROC_Define ($$) return $usage if (scalar (@$a) < 4); $hash->{host} = $$a[2]; $iface = $$a[3]; - } - - # Find IO device - for my $d (keys %defs) { - my $dh = $defs{$d}; - next if (!exists ($dh->{TYPE}) || !exists ($dh->{NAME})); - if ($dh->{TYPE} eq 'HMCCU' && $dh->{host} eq $hash->{host}) { - $hmccu_hash = $dh; - last; + + # Find IO device + for my $d (keys %defs) { + my $dh = $defs{$d}; + next if (!exists ($dh->{TYPE}) || !exists ($dh->{NAME})); + if ($dh->{TYPE} eq 'HMCCU' && $dh->{host} eq $hash->{host}) { + $hmccu_hash = $dh; + last; + } } + return "Can't find HMCCU I/O device" if (!defined ($hmccu_hash)); } - return "Can't find HMCCU I/O device" if (!defined ($hmccu_hash)); - + # Check if interface is valid my $ifname = HMCCU_GetRPCServerInfo ($hmccu_hash, $iface, 'name'); my $ifport = HMCCU_GetRPCServerInfo ($hmccu_hash, $iface, 'port'); @@ -423,7 +424,8 @@ sub HMCCURPCPROC_Get ($@) if ($opt eq 'rpcevents') { my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO"); - my $clkey = 'CB'.$hash->{rpcport}; + my $clkey = 'CB'.$hash->{rpcport}.$hash->{rpcid}; + $result = "Event statistics for server $clkey\n"; $result .= "Average event delay = ".$hash->{hmccu}{rpc}{avgdelay}."\n" if (defined ($hash->{hmccu}{rpc}{avgdelay})); @@ -440,11 +442,11 @@ sub HMCCURPCPROC_Get ($@) return $result eq '' ? "No event statistics found" : $result; } elsif ($opt eq 'rpcstate') { - my $clkey = 'CB'.$hash->{rpcport}; - $result = "ID RPC-Process State \n"; - $result .= "-----------------------\n"; - my $sid = defined ($hash->{hmccu}{rpc}{pid}) ? sprintf ("%2d", $hash->{hmccu}{rpc}{pid}) : "N/A"; - my $sname = sprintf ("%-6s", $clkey); + my $clkey = 'CB'.$hash->{rpcport}.$hash->{rpcid}; + $result = "PID RPC-Process State \n"; + $result .= "--------------------------\n"; + my $sid = defined ($hash->{hmccu}{rpc}{pid}) ? sprintf ("%5d", $hash->{hmccu}{rpc}{pid}) : "N/A "; + my $sname = sprintf ("%-10s", $clkey); $result .= $sid." ".$sname." ".$hash->{hmccu}{rpc}{state}."\n"; return $result; } @@ -623,6 +625,7 @@ sub HMCCURPCPROC_ResetRPCState ($) $hash->{RPCPID} = "0"; $hash->{hmccu}{rpc}{pid} = undef; + $hash->{hmccu}{rpc}{clkey} = undef; $hash->{hmccu}{evtime} = 0; $hash->{hmccu}{rpcstarttime} = 0; @@ -648,7 +651,7 @@ sub HMCCURPCPROC_ProcessEvent ($$) { my ($hash, $event) = @_; my $name = $hash->{NAME}; - my $rpcname = 'CB'.$hash->{rpcport}; + my $rpcname = 'CB'.$hash->{rpcport}.$hash->{rpcid}; my $rh = \%{$hash->{hmccu}{rpc}}; # Just for code simplification my $hmccu_hash = $hash->{IODev}; @@ -886,7 +889,7 @@ sub HMCCURPCPROC_RegisterCallback ($$) my $port = $hash->{rpcport}; my $serveraddr = $hash->{host}; my $localaddr = $hash->{hmccu}{localaddr}; - my $clkey = 'CB'.$port; + my $clkey = 'CB'.$port.$hash->{rpcid}; return (0, "RPC server $clkey not in state working") if ($hash->{hmccu}{rpc}{state} ne 'working' && $force == 0); @@ -932,7 +935,7 @@ sub HMCCURPCPROC_DeRegisterCallback ($$) my $hmccu_hash = $hash->{IODev}; my $port = $hash->{rpcport}; - my $clkey = 'CB'.$port; + my $clkey = 'CB'.$port.$hash->{rpcid}; my $localaddr = $hash->{hmccu}{localaddr}; my $cburl = ''; my $clurl = ''; @@ -978,8 +981,7 @@ sub HMCCURPCPROC_DeRegisterCallback ($$) sub HMCCURPCPROC_InitRPCServer ($$$$) { - my ($name, $serverport, $callbackport, $prot) = @_; - my $clkey = 'CB'.$serverport; + my ($name, $clkey, $callbackport, $prot) = @_; my $server; # Create binary RPC server @@ -1094,7 +1096,23 @@ sub HMCCURPCPROC_StartRPCServer ($) my $rpcport = $hash->{rpcport}; my $serveraddr = HMCCU_GetRPCServerInfo ($hmccu_hash, $rpcport, 'host'); my $interface = HMCCU_GetRPCServerInfo ($hmccu_hash, $rpcport, 'name'); - + + # Detect local IP address + if ($localaddr eq '') { + my $socket = IO::Socket::INET->new (PeerAddr => $serveraddr, PeerPort => $rpcport); + return (0, "Can't connect to CCU port $rpcport") if (!$socket); + $localaddr = $socket->sockhost (); + close ($socket); + } + $hash->{hmccu}{localaddr} = $localaddr; + + # Get unique ID for RPC server: last segment of local IP address followed by 2 random digits + my @ipseg = split (/\./, $localaddr); + return (0, "Invalid local IP address $localaddr") if (scalar (@ipseg) != 4); + my $base = (time() % 10)+1; + $hash->{rpcid} = sprintf ("%03d", $ipseg[3]) . join '', map int rand ($base), 1..2; + my $clkey = 'CB'.$rpcport.$hash->{rpcid}; + # Store parameters for child process $procpar{socktimeout} = AttrVal ($name, 'rpcWriteTimeout', $HMCCURPCPROC_TIMEOUT_WRITE); $procpar{conntimeout} = AttrVal ($name, 'rpcConnTimeout', $HMCCURPCPROC_TIMEOUT_CONNECTION); @@ -1110,18 +1128,10 @@ sub HMCCURPCPROC_StartRPCServer ($) $procpar{flags} = HMCCU_GetRPCServerInfo ($hmccu_hash, $rpcport, 'flags'); $procpar{type} = HMCCU_GetRPCServerInfo ($hmccu_hash, $rpcport, 'type'); $procpar{name} = $name; + $procpar{clkey} = $clkey; my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO"); - # Detect local IP address - if ($localaddr eq '') { - my $socket = IO::Socket::INET->new (PeerAddr => $serveraddr, PeerPort => $rpcport); - return (0, "Can't connect to CCU port $rpcport") if (!$socket); - $localaddr = $socket->sockhost (); - close ($socket); - } - $hash->{hmccu}{localaddr} = $localaddr; - # Reset state of server processes $hash->{hmccu}{rpc}{state} = 'inactive'; @@ -1142,7 +1152,6 @@ sub HMCCURPCPROC_StartRPCServer ($) # Initialize RPC server my $err = ''; my %srvprocpar; - my $clkey = 'CB'.$rpcport; my $callbackport = $rpcserverport+$rpcport+($ccunum*10); # Start RPC server process @@ -1166,6 +1175,7 @@ sub HMCCURPCPROC_StartRPCServer ($) Log3 $name, 2, "HMCCURPCPROC: [$name] RPC server process started for interface $interface with PID=$rpcpid"; # Store process parameters + $hash->{hmccu}{rpc}{clkey} = $clkey; $hash->{hmccu}{rpc}{cbport} = $callbackport; $hash->{hmccu}{rpc}{pid} = $rpcpid; $hash->{hmccu}{rpc}{state} = 'initialized'; @@ -1180,12 +1190,11 @@ sub HMCCURPCPROC_StartRPCServer ($) $hash->{RPCPID} = $rpcpid; # Trigger Timer function for checking successful RPC start - # Timer will be removed if event 'IN' is reveived + # Timer will be removed before execution if event 'IN' is reveived InternalTimer (gettimeofday()+$HMCCURPCPROC_INIT_INTERVAL3, "HMCCURPCPROC_IsRPCServerRunning", $hash, 0); - HMCCURPCPROC_SetRPCState ($hash, "starting", "RPC server starting", 1); - + HMCCURPCPROC_SetRPCState ($hash, "starting", "RPC server starting", 1); DoTrigger ($name, "RPC server starting"); return (1, undef); @@ -1202,7 +1211,7 @@ sub HMCCURPCPROC_RPCServerStarted ($) my ($hash) = @_; my $name = $hash->{NAME}; my $hmccu_hash = $hash->{IODev}; - my $clkey = 'CB'.$hash->{rpcport}; + my $clkey = 'CB'.$hash->{rpcport}.$hash->{rpcid}; my $ifname = $hash->{rpcinterface}; # Check if RPC servers are running. Set overall status @@ -1231,7 +1240,7 @@ sub HMCCURPCPROC_RPCServerStopped ($) { my ($hash) = @_; my $name = $hash->{NAME}; - my $clkey = 'CB'.$hash->{rpcport}; + my $clkey = 'CB'.$hash->{rpcport}.$hash->{rpcid}; HMCCURPCPROC_CleanupProcess ($hash); HMCCURPCPROC_CleanupIO ($hash); @@ -1279,7 +1288,7 @@ sub HMCCURPCPROC_TerminateProcess ($) { my ($hash) = @_; my $name = $hash->{NAME}; - my $clkey = 'CB'.$hash->{rpcport}; + my $clkey = 'CB'.$hash->{rpcport}.$hash->{rpcid}; # return 0 if ($hash->{hmccu}{rpc}{state} eq 'inactive'); @@ -1304,7 +1313,7 @@ sub HMCCURPCPROC_CleanupProcess ($) { my ($hash) = @_; my $name = $hash->{NAME}; - my $clkey = 'CB'.$hash->{rpcport}; + my $clkey = 'CB'.$hash->{rpcport}.$hash->{rpcid}; # return 1 if ($hash->{hmccu}{rpc}{state} eq 'inactive'); @@ -1342,7 +1351,7 @@ sub HMCCURPCPROC_CleanupProcess ($) sub HMCCURPCPROC_CheckProcessState ($$) { my ($hash, $state) = @_; - my $prcname = 'CB'.$hash->{rpcport}; + my $prcname = 'CB'.$hash->{rpcport}.$hash->{rpcid}; my $pstate = $hash->{hmccu}{rpc}{state}; if ($state eq 'running' || $state eq '.*') { @@ -1404,7 +1413,7 @@ sub HMCCURPCPROC_StopRPCServer ($) { my ($hash) = @_; my $name = $hash->{NAME}; - my $clkey = 'CB'.$hash->{rpcport}; + my $clkey = 'CB'.$hash->{rpcport}.$hash->{rpcid}; if (HMCCURPCPROC_CheckProcessState ($hash, 'running')) { Log3 $name, 1, "HMCCURPCPROC: [$name] Stopping RPC server $clkey"; @@ -1435,6 +1444,7 @@ sub HMCCURPCPROC_StopRPCServer ($) ###################################################################### # Send RPC request to CCU. +# Supports XML and BINRPC requests. # Return response or undef on error. ###################################################################### @@ -1572,18 +1582,18 @@ sub HMCCURPCPROC_HandleConnection ($$$$) my $socktimeout = $procpar->{socktimeout}; my $maxsnd = $procpar->{queuesend}; my $maxioerrors = $procpar->{maxioerrors}; + my $clkey = $procpar->{clkey}; my $ioerrors = 0; my $sioerrors = 0; my $run = 1; my $pid = $$; - my $clkey = 'CB'.$port; my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO"); # Initialize RPC server Log3 $name, 2, "CCURPC: [$name] Initializing RPC server $clkey for interface $iface"; - my $rpcsrv = HMCCURPCPROC_InitRPCServer ($name, $port, $callbackport, $prot); + my $rpcsrv = HMCCURPCPROC_InitRPCServer ($name, $clkey, $callbackport, $prot); if (!defined ($rpcsrv)) { Log3 $name, 1, "CCURPC: [$name] Can't initialize RPC server $clkey for interface $iface"; return;