diff --git a/CHANGED b/CHANGED index 75acf2257..7242fac56 100644 --- a/CHANGED +++ b/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. + - update: 88_HMCCU: Homematic firmware download, advanced scripting - feature: 98_TRAFFIC: v1.3.2, stroke styles, warnings reduced, bugfix - update: 98_DOIFtools: more precise regexp for INITIALIZED event because there is more than one initialisation event now diff --git a/FHEM/88_HMCCU.pm b/FHEM/88_HMCCU.pm index dfd1de2e1..b9f3d27b2 100755 --- a/FHEM/88_HMCCU.pm +++ b/FHEM/88_HMCCU.pm @@ -4,14 +4,14 @@ # # $Id$ # -# Version 4.0 +# Version 4.0.001 # # Module for communication between FHEM and Homematic CCU2. # # Supports BidCos-RF, BidCos-Wired, HmIP-RF, virtual CCU channels, # CCU group devices, HomeGear, CUxD, Osram Lightify. # -# (c) 2017 zap (zap01 t-online de) +# (c) 2017 by zap (zap01 t-online de) # ############################################################################## # @@ -21,7 +21,7 @@ # set defaults # set execute # set importdefaults -# set hmscript +# set hmscript {|!|'['']'} [dump] [= [...]] # set rpcserver {on|off|restart} # set var [...] # @@ -34,7 +34,9 @@ # [p=] [f=] # [defattr] [= [...]]}] # get dump {devtypes|datapoints} [] +# get dutycycle # get exportdefaults {filename} +# get firmware [list] # get parfile [] # get rpcevents # get rpcstate @@ -103,7 +105,7 @@ my %HMCCU_CUST_CHN_DEFAULTS; my %HMCCU_CUST_DEV_DEFAULTS; # HMCCU version -my $HMCCU_VERSION = '4.0'; +my $HMCCU_VERSION = '4.0.001'; # RPC Ports and URL extensions my $HMCCU_RPC_PORT_DEFAULT = 2001; @@ -179,6 +181,15 @@ my $HMCCU_FLAGS_NC = $HMCCU_FLAG_NAME | $HMCCU_FLAG_CHANNEL; my $HMCCU_FLAGS_NCD = $HMCCU_FLAG_NAME | $HMCCU_FLAG_CHANNEL | $HMCCU_FLAG_DATAPOINT; +# Binary RPC data types +my $BINRPC_INTEGER = 1; +my $BINRPC_BOOL = 2; +my $BINRPC_STRING = 3; +my $BINRPC_DOUBLE = 4; +my $BINRPC_BASE64 = 17; +my $BINRPC_ARRAY = 256; +my $BINRPC_STRUCT = 257; + # Declare functions sub HMCCU_Initialize ($); sub HMCCU_Define ($$); @@ -199,7 +210,9 @@ sub HMCCU_ParseObject ($$$); sub HMCCU_FilterReading ($$$); sub HMCCU_GetReadingName ($$$$$$$); sub HMCCU_FormatReadingValue ($$); -sub HMCCU_SetError ($$); +sub HMCCU_Trace ($$$$); +sub HMCCU_Log ($$$$); +sub HMCCU_SetError ($@); sub HMCCU_SetState ($$); sub HMCCU_Substitute ($$$$$); sub HMCCU_SubstRule ($$$); @@ -221,6 +234,7 @@ sub HMCCU_IsRPCStateBlocking ($); sub HMCCU_IsRPCServerRunning ($$$); sub HMCCU_GetDeviceInfo ($$$); sub HMCCU_FormatDeviceInfo ($); +sub HMCCU_GetFirmwareVersions ($); sub HMCCU_GetDeviceList ($); sub HMCCU_GetDatapointList ($); sub HMCCU_FindDatapoint ($$$$$); @@ -255,6 +269,7 @@ sub HMCCU_ResetRPCQueue ($$); sub HMCCU_ReadRPCQueue ($); sub HMCCU_ProcessEvent ($$); sub HMCCU_HMScript ($$); +sub HMCCU_HMScriptExt ($$$); sub HMCCU_BulkUpdate ($$$$); sub HMCCU_GetDatapoint ($@); sub HMCCU_SetDatapoint ($$$); @@ -278,6 +293,11 @@ sub HMCCU_GetHMState ($$$); sub HMCCU_GetTimeSpec ($); sub HMCCU_CalculateReading ($$$); sub HMCCU_EncodeEPDisplay ($); +sub HMCCU_RefToString ($); +sub HMCCU_ExprMatch ($$$); +sub HMCCU_ExprNotMatch ($$$); +sub HMCCU_GetDutyCycle ($); +sub HMCCU_CorrectName ($); # Subprocess functions sub HMCCU_CCURPC_Write ($$); @@ -359,7 +379,7 @@ sub HMCCU_Define ($$) Log3 $name, 1, "HMCCU: Device $name. Initialized version $HMCCU_VERSION"; my ($devcnt, $chncnt) = HMCCU_GetDeviceList ($hash); - Log3 $name, 1, "HMCCU: Read $devcnt devices with $chncnt channels read from CCU".$hash->{host}; + Log3 $name, 1, "HMCCU: Read $devcnt devices with $chncnt channels read from CCU ".$hash->{host}; $hash->{hmccu}{evtime} = 0; $hash->{hmccu}{evtimeout} = 0; @@ -409,11 +429,8 @@ sub HMCCU_Attr ($@) if ($attrval =~ /(extrpc|intrpc)/) { my $rpcmode = $1; if ($ccuflags !~ /$rpcmode/) { - my @hm_pids = (); - my @ex_pids = (); - if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids)) { - return "Stop RPC server before switching between extrpc and intrpc"; - } + return "Stop RPC server before switching between extrpc and intrpc" + if (HMCCU_IsRPCServerRunning ($hash, undef, undef)); } } } @@ -492,7 +509,7 @@ sub HMCCU_AggregationRules ($$) } return if ($rulestr eq ''); - my @opts = ('name', 'filter', 'if', 'else'); + my @pars = ('name', 'filter', 'if', 'else'); # Extract aggregation rules my $cnt = 0; @@ -500,12 +517,8 @@ sub HMCCU_AggregationRules ($$) foreach my $r (@rules) { $cnt++; - # Set default rule parameters - my %opt = ( - 'read' => 'state', - prefix => 'RULE', - coll => 'NAME' - ); + # Set default rule parameters. Can be modified later + my %opt = ( 'read' => 'state', 'prefix' => 'RULE', 'coll' => 'NAME' ); # Parse aggregation rule my @specs = split (',', $r); @@ -516,11 +529,9 @@ sub HMCCU_AggregationRules ($$) } # Check if rule syntax is correct - foreach my $o (@opts) { - if (!exists ($opt{$o})) { - Log3 $name, 1, "HMCCU: Parameter $o is missing in aggregation rule $cnt."; - return 0; - } + foreach my $p (@pars) { + return HMCCU_Log ($hash, 1, "Parameter $p is missing in aggregation rule $cnt.", 0) + if (!exists ($opt{$p})); } my $fname = $opt{name}; @@ -685,9 +696,9 @@ sub HMCCU_FindDefaults ($$) return undef; } -############################################################ +###################################################################### # Set default attributes from template -############################################################ +###################################################################### sub HMCCU_SetDefaultsTemplate ($$) { @@ -701,9 +712,9 @@ sub HMCCU_SetDefaultsTemplate ($$) } } -############################################################ +###################################################################### # Set default attributes -############################################################ +###################################################################### sub HMCCU_SetDefaults ($) { @@ -722,10 +733,10 @@ sub HMCCU_SetDefaults ($) return 1; } -############################################################ +###################################################################### # List default attributes for device type (mode = 0) or all # device types (mode = 1) with default attributes available. -############################################################ +###################################################################### sub HMCCU_GetDefaults ($$) { @@ -1069,36 +1080,32 @@ sub HMCCU_Set ($@) } } elsif ($opt eq 'hmscript') { - my $scrfile = shift @$a; + my $script = shift @$a; my $dump = shift @$a; - my $script; - my $response; + my $response = ''; my %objects = (); my $objcount = 0; - - return HMCCU_SetError ($hash, "Usage: set $name hmscript {scriptfile} [dump] [parname=value [...]]") - if (!defined ($scrfile) || (defined ($dump) && $dump ne 'dump')); - - if (open (SCRFILE, "<$scrfile")) { - my @lines = ; - $script = join ("\n", @lines); - close (SCRFILE); - } - else { - return HMCCU_SetError ($hash, -16); - } - - # Replace variables - foreach my $svar (keys %{$h}) { - next if ($script !~ /\$$svar/); - $script =~ s/\$$svar/$h->{$svar}/g; + + # If no parameter is specified list available script functions + if (!defined ($script)) { + $response = "Available HomeMatic script functions:\n". + "-------------------------------------\n"; + foreach my $scr (keys %{$HMCCU_SCRIPTS}) { + $response .= "$scr ".$HMCCU_SCRIPTS->{$scr}{syntax}."\n". + $HMCCU_SCRIPTS->{$scr}{description}."\n\n"; + } } - $response = HMCCU_HMScript ($hash, $script); - return HMCCU_SetError ($hash, -2) if ($response eq ''); + $response .= "Usage: set $name hmscript {file|!function|'['code']'} [dump] [parname=value [...]]"; + + return HMCCU_SetError ($hash, $response) + if (!defined ($script) || (defined ($dump) && $dump ne 'dump')); + + $response = HMCCU_HMScriptExt ($hash, $script, $h); + return HMCCU_SetError ($hash, -2, $response) if ($response =~ /^ERROR:/); HMCCU_SetState ($hash, "OK"); - return $response if (! $ccureadings); + return $response if (! $ccureadings || defined ($dump)); foreach my $line (split /\n/, $response) { my @tokens = split /=/, $line; @@ -1115,7 +1122,10 @@ sub HMCCU_Set ($@) else { # If output is not related to a channel store reading in I/O device my $Value = HMCCU_Substitute ($tokens[1], $substitute, 0, undef, $tokens[0]); - readingsSingleUpdate ($hash, $tokens[0], $Value, 1); + my $rn = $tokens[0]; + $rn =~ s/\:/\./g; + $rn =~ s/[^A-Za-z\d_\.-]+/_/g; + readingsSingleUpdate ($hash, $rn, $Value, 1); } } @@ -1150,10 +1160,7 @@ sub HMCCU_Set ($@) } } elsif ($action eq 'restart') { - my @hm_pids; - my @ex_pids; - return "HMCCU: RPC server not running" - if (!HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids)); + return "HMCCU: No RPC server running" if (!HMCCU_IsRPCServerRunning ($hash, undef, undef)); if ($ccuflags =~ /intrpc/) { return "HMCCU: Can't stop RPC server" if (!HMCCURPC_StopRPCServer ($hash)); @@ -1209,8 +1216,8 @@ sub HMCCU_Get ($@) my ($hash, $a, $h) = @_; my $name = shift @$a; my $opt = shift @$a; - my $options = "defaults:noArg exportdefaults devicelist dump vars update updateccu parfile". - " configdesc rpcevents:noArg rpcstate:noArg deviceinfo"; + my $options = "defaults:noArg exportdefaults devicelist dump dutycycle:noArg vars update". + " updateccu parfile configdesc firmware:noArg rpcevents:noArg rpcstate:noArg deviceinfo"; my $host = $hash->{host}; if ($opt ne 'rpcstate' && HMCCU_IsRPCStateBlocking ($hash)) { @@ -1350,12 +1357,12 @@ sub HMCCU_Get ($@) } elsif ($opt eq 'rpcstate') { my @hm_pids = (); - my @ex_pids = (); - if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids)) { + my @hm_tids = (); + if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids)) { return "RPC process(es) running with pid(s) ". join (',', @hm_pids) if (scalar (@hm_pids) > 0); return "RPC thread(s) running with tid(s) ". - join (',', @ex_pids) if (scalar (@ex_pids) > 0); + join (',', @hm_tids) if (scalar (@hm_tids) > 0); } return "No RPC processes or threads are running"; } @@ -1385,10 +1392,10 @@ sub HMCCU_Get ($@) return $result; } elsif ($optcmd eq 'create') { - my $devprefix = exists ($h->{p}) ? $h->{p} : ''; + my $devprefix = exists ($h->{p}) ? $h->{p} : ''; my $devsuffix = exists ($h->{'s'}) ? $h->{'s'} : ''; - my $devtype = exists ($h->{t}) ? $h->{t} : 'dev'; - my $devformat = exists ($h->{f}) ? $h->{f} : '%n'; + my $devtype = exists ($h->{t}) ? $h->{t} : 'dev'; + my $devformat = exists ($h->{f}) ? $h->{f} : '%n'; my $devdefaults = 0; my $newcount = 0; my @devattr; @@ -1411,7 +1418,7 @@ sub HMCCU_Get ($@) my $ccuname = $hash->{hmccu}{dev}{$add}{name}; my $ccudevname = HMCCU_GetDeviceName ($hash, $add, $ccuname); next if ($devtype ne 'all' && $devtype ne $hash->{hmccu}{dev}{$add}{addtype}); - next if ($ccuname !~ /$devspec/); + next if (HMCCU_ExprNotMatch ($ccuname, $devspec, 1)); my $devname = $devformat; $devname = $devprefix.$devname.$devsuffix; $devname =~ s/%n/$ccuname/g; @@ -1441,6 +1448,32 @@ sub HMCCU_Get ($@) HMCCU_SetState ($hash, "OK"); return $result; } + elsif ($opt eq 'dutycycle') { + my $dc = HMCCU_GetDutyCycle ($hash); + return "Read $dc duty cycle values"; + } + elsif ($opt eq 'firmware') { + my $dc = HMCCU_GetFirmwareVersions ($hash); + return "Found no firmware downloads" if ($dc == 0); + $result = "Found $dc firmware downloads."; + my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef); + return $result if (scalar (@devlist) == 0); + + $result .= " Click on the new version number for download\n\n". + "Device Type Current Available Date\n". + "------------------------------------------------------------------------\n"; + foreach my $dev (@devlist) { + my $ch = $defs{$dev}; + my $ct = uc($ch->{ccutype}); + next if (!defined ($ch->{firmware})); + next if (!exists ($hash->{hmccu}{type}{$ct})); + $result .= sprintf "%-25s %-20s %-7s %-9s %-10s\n", + $ch->{NAME}, $ct, $ch->{firmware}, $hash->{hmccu}{type}{$ct}{download}, + $hash->{hmccu}{type}{$ct}{firmware}, $hash->{hmccu}{type}{$ct}{date}; + } + + return $result; + } elsif ($opt eq 'defaults') { $result = HMCCU_GetDefaults ($hash, 1); return $result; @@ -1644,7 +1677,7 @@ sub HMCCU_FilterReading ($$$) my $chnnam = HMCCU_IsChnAddr ($chn, 0) ? HMCCU_GetChannelName ($hmccu_hash, $chn, '') : $chn; - Log3 $name, 2, "HMCCU: $fnc: chn=$chn, dpt=$dpt, rules = $rf" if ($cf =~ /trace/); + HMCCU_Trace ($hash, 2, "HMCCU: $fnc: chn=$chn, dpt=$dpt, rules=$rf", $cf); my $rm = 1; my @rules = split (';', $rf); @@ -1655,24 +1688,22 @@ sub HMCCU_FilterReading ($$$) $r =~ s/^N://; } my ($c, $f) = split ("!", $r); - Log3 $name, 2, "HMCCU: rm=$rm, r=$r, dpt=$dpt chnflt=$c chnnam=$chnnam" - if ($cf =~ /trace/); + HMCCU_Trace ($hash, 2, "HMCCU: rm=$rm, r=$r, dpt=$dpt chnflt=$c chnnam=$chnnam", $cf); if (defined ($f) && $chnnam ne '') { if ($chnnam =~ /$c/) { - Log3 $name, 2, "HMCCU: $chnnam = $c" if ($cf =~/trace/); + HMCCU_Trace ($hash, 2, "HMCCU: $chnnam = $c", $cf); return $rm if (($rm && $dpt =~ /$f/) || (!$rm && $dpt =~ /$f/)); return $rm ? 0 : 1; } } else { - Log3 $name, 2, "HMCCU: check $rm = 1 AND $dpt = $r OR $rm = 0 AND $dpt = $r" - if ($cf =~ /trace/); + HMCCU_Trace ($hash, 2, "HMCCU: check $rm=1 AND $dpt=$r OR $rm=0 AND $dpt=$r", $cf); return $rm if (($rm && $dpt =~ /$r/) || (!$rm && $dpt =~ /$r/)); - Log3 $name, 2, "HMCCU: check negative" if ($cf =~ /trace/); + HMCCU_Trace ($hash, 2, "HMCCU: check negative", $cf); } } - Log3 $name, 2, "HMCCU: $fnc: return rm = $rm ? 0 : 1" if ($cf =~ /trace/); + HMCCU_Trace ($hash, 2, "HMCCU: $fnc: return rm = $rm ? 0 : 1", $cf); return $rm ? 0 : 1; } @@ -1732,8 +1763,8 @@ sub HMCCU_GetReadingName ($$$$$$$) } } - $n =~ s/\:/\./g; - $n =~ s/[^A-Za-z\d_\.-]+/_/g; + # Substitue unsupported characters in reading name + $n = HMCCU_CorrectName ($n); return '' if ($n eq ''); $rn = $n.'.'.$d; @@ -1811,12 +1842,41 @@ sub HMCCU_FormatReadingValue ($$) } ###################################################################### -# Set error state and write log file message +# Log message if trace flag is set. ###################################################################### -sub HMCCU_SetError ($$) +sub HMCCU_Trace ($$$$) { - my ($hash, $text) = @_; + my ($hash, $level, $msg, $flag) = @_; + my $name = $hash->{NAME}; + + Log3 $name, $level, $msg if ($flag =~ /trace/); +} + +###################################################################### +# Log message and return code. +###################################################################### + +sub HMCCU_Log ($$$$) +{ + my ($hash, $level, $msg, $rc) = @_; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + Log3 $name, $level, "$type: $msg"; + + return $rc; +} + +###################################################################### +# Set error state and write log file message +# Parameter text can be an error code (integer < 0) or an error text. +# Parameter addinfo is optional. +###################################################################### + +sub HMCCU_SetError ($@) +{ + my ($hash, $text, $addinfo) = @_; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; my $msg; @@ -1836,11 +1896,15 @@ sub HMCCU_SetError ($$) -13 => 'No state datapoint defined', -14 => 'No control datapoint defined', -15 => 'No state values defined', - -16 => 'Cannot open file' + -16 => 'Cannot open file', + -17 => 'Cannot detect or create external RPC device' ); $msg = exists ($errlist{$text}) ? $errlist{$text} : $text; $msg = $type.": ".$name." ". $msg; + if (defined ($addinfo) && $addinfo ne '') { + $msg .= ". $addinfo"; + } HMCCU_SetState ($hash, "Error"); Log3 $name, 1, $msg; @@ -1867,12 +1931,12 @@ sub HMCCU_SetState ($$) return ($text eq "busy") ? "HMCCU: CCU busy" : undef; } -################################################################## -# Substitute first occurrence of regular expressions or fixed -# string. Floating point values are ignored without datapoint -# specification. Integer values are compared with complete value. +###################################################################### +# Substitute first occurrence of regular expression or fixed string. +# Floating point values are ignored without datapoint specification. +# Integer values are compared with complete value. # mode: 0=Substitute regular expression, 1=Substitute text -################################################################## +###################################################################### sub HMCCU_Substitute ($$$$$) { @@ -1913,7 +1977,7 @@ sub HMCCU_Substitute ($$$$$) return $value; } -################################################################## +###################################################################### # Execute substitution list. # Syntax for single substitution: {#n-n|regexp|text}:newtext # mode=0: Substitute regular expression @@ -1923,7 +1987,7 @@ sub HMCCU_Substitute ($$$$$) # Return (status, value) # status=1: value = substituted value # status=0: value = original value -################################################################## +###################################################################### sub HMCCU_SubstRule ($$$) { @@ -1958,9 +2022,9 @@ sub HMCCU_SubstRule ($$$) return ($rc, $value); } -################################################################## +###################################################################### # Substitute datapoint variables in string by datapoint value. -################################################################## +###################################################################### sub HMCCU_SubstVariables ($$) { @@ -2269,7 +2333,7 @@ sub HMCCU_UpdateSingleDevice ($$$) my $update = AttrVal ($cn, 'ccureadings', 1); next if ($update == 0 || $disable == 1); - Log3 $ccuname, 2, "HMCCU: $fnc: Processing device $cn" if ($cf =~ /trace/); + HMCCU_Trace ($ccuhash, 2, "HMCCU: $fnc: Processing device $cn", $cf); my $crf = HMCCU_GetAttrReadingFormat ($ch, $ccuhash); my $substitute = HMCCU_GetAttrSubstitute ($ch, $ccuhash); @@ -2299,8 +2363,8 @@ sub HMCCU_UpdateSingleDevice ($$$) next if (!defined ($value)); $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{VAL} = $value; - Log3 $ccuname, 2, "HMCCU: $fnc dev=$cn, chnadd=$chnadd, dpt=$dpt, value=$value" - if ($cf =~ /trace/); + HMCCU_Trace ($ccuhash, 2, + "HMCCU: $fnc dev=$cn, chnadd=$chnadd, dpt=$dpt, value=$value", $cf); if (HMCCU_FilterReading ($ch, $chnadd, $dpt)) { my @readings = HMCCU_GetReadingName ($ch, '', $da, $chnnum, $dpt, '', $crf); @@ -2312,8 +2376,9 @@ sub HMCCU_UpdateSingleDevice ($$$) # Store the resulting value after scaling, formatting and substitution $results{$da}{$chnnum}{$dpt} = $cvalue; - Log3 $ccuname, 2, "HMCCU: $fnc device=$cltname, readings=".join(',', @readings). - ", orgvalue=$value value=$cvalue" if ($cf =~ /trace/); + HMCCU_Trace ($ccuhash, 2, + "HMCCU: $fnc device=$cltname, readings=".join(',', @readings). + ", orgvalue=$value value=$cvalue", $cf); foreach my $rn (@readings) { HMCCU_BulkUpdate ($ch, $rn, $fvalue, $cvalue) if ($rn ne ''); @@ -2495,10 +2560,7 @@ sub HMCCU_StartExtRPCServer ($) # Search RPC device. Create one if none exists my $rpcdev = HMCCU_GetRPCDevice ($hash, 1); - if ($rpcdev eq '') { - Log3 $name, 0, "HMCCU: Can't find or create HMCCURPC device"; - return 0; - } + return HMCCU_Log ($hash, 0, "Can't find or create HMCCURPC device", 0) if ($rpcdev eq ''); my ($rc, $msg) = HMCCURPC_StartRPCServer ($defs{$rpcdev}); Log3 $name, 0, "HMCCURPC: $msg" if (!$rc && defined ($msg)); @@ -2515,17 +2577,11 @@ sub HMCCU_StopExtRPCServer ($) my ($hash) = @_; my $name = $hash->{NAME}; - if (!exists ($modules{'HMCCURPC'})) { - Log3 $name, 0, "HMCCU: Module HMCCURPC not loaded"; - return 0; - } + return HMCCU_Log ($hash, 0, "Module HMCCURPC not loaded", 0) if (!exists ($modules{'HMCCURPC'})); - # Search RPC device. Create one if none exists - my $rpcdev = HMCCU_GetRPCDevice ($hash, 1); - if ($rpcdev eq '') { - Log3 $name, 0, "HMCCU: Can't find or create RPC device"; - return 0; - } + # Search RPC device + my $rpcdev = HMCCU_GetRPCDevice ($hash, 0); + return HMCCU_Log ($hash, 0, "Can't find RPC device", 0) if ($rpcdev eq ''); return HMCCURPC_StopRPCServer ($defs{$rpcdev}); } @@ -2555,23 +2611,20 @@ sub HMCCU_StartIntRPCServer ($) # Check for running RPC server processes my @hm_pids; - my @ex_pids; - HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); - if (@hm_pids > 0) { - Log3 $name, 0, "HMCCU: RPC server(s) already running with PIDs ".join (',', @hm_pids); - return scalar (@hm_pids); + my @hm_tids; + HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids); + if (scalar (@hm_pids) > 0) { + return HMCCU_Log ($hash, 0, "RPC server(s) already running with PIDs ".join (',', @hm_pids), + scalar (@hm_pids)); } - elsif (@ex_pids > 0) { - Log3 $name, 1, "HMCCU: Externally launched RPC server(s) detected. Kill process(es) manually with command kill -SIGINT pid for pid=".join (',', @ex_pids); - return 0; + elsif (scalar (@hm_tids) > 0) { + return HMCCU_Log ($hash, 1, "RPC server(s) already running with TIDs ".join (',', @hm_tids), + 0); } # Detect local IP address my $socket = IO::Socket::INET->new (PeerAddr => $serveraddr, PeerPort => $rpcportlist[0]); - if (!$socket) { - Log3 $name, 1, "HMCCU: Can't connect to CCU port".$rpcportlist[0]; - return 0; - } + return HMCCU_Log ($hash, 1, "Can't connect to CCU port".$rpcportlist[0], 0) if (!$socket); my $localaddr = $socket->sockhost (); close ($socket); @@ -2678,39 +2731,30 @@ sub HMCCU_StopRPCServer ($) # Check if processes were terminated my @hm_pids; - my @ex_pids; - HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); - if (@hm_pids > 0) { + my @hm_tids; + HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids); + if (scalar (@hm_pids) > 0) { foreach my $pid (@hm_pids) { Log3 $name, 0, "HMCCU: Stopping RPC server with PID $pid"; kill ('INT', $pid); } } - if (@ex_pids > 0) { - Log3 $name, 0, "HMCCU: Externally launched RPC server detected."; - foreach my $pid (@ex_pids) { - kill ('INT', $pid); - } - } + Log3 $name, 0, "HMCCU: Externally launched RPC server detected." if (scalar (@hm_tids) > 0); # Wait sleep (1); # Kill the rest @hm_pids = (); - @ex_pids = (); - if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids)) { - push (@hm_pids, @ex_pids); + @hm_tids = (); + if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids)) { foreach my $pid (@hm_pids) { kill ('KILL', $pid); } } - @hm_pids = (); - @ex_pids = (); - HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); - push (@hm_pids, @ex_pids); - $hash->{hmccu}{rpccount} = scalar(@hm_pids); + # Store number of running RPC servers + $hash->{hmccu}{rpccount} = HMCCU_IsRPCServerRunning ($hash, undef, undef); return $hash->{hmccu}{rpccount} > 0 ? 0 : 1; } @@ -2737,41 +2781,41 @@ sub HMCCU_IsRPCStateBlocking ($) } ###################################################################### -# Check if RPC server is running. Return list of PIDs or TIDs in -# arrays referenced by parameters hm_pids and ex_pids. -# 1 = One or more RPC servers running. -# 0 = No RPC server running. +# Check if RPC servers are running. +# Return number of running RPC servers. If paramters pids or tids are +# defined also return process or thread IDs. ###################################################################### sub HMCCU_IsRPCServerRunning ($$$) { - my ($hash, $hm_pids, $ex_pids) = @_; + my ($hash, $pids, $tids) = @_; my $name = $hash->{NAME}; + my $c = 0; my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); if ($ccuflags =~ /extrpc/) { + @$tids = () if (defined ($tids)); my $rpcdev = HMCCU_GetRPCDevice ($hash, 0); if ($rpcdev ne '') { - my $rpc_hash = $defs{$rpcdev}; - my @tids = (); - @tids = split (',', $rpc_hash->{RPCTID}) - if (exists ($rpc_hash->{RPCTID}) && $rpc_hash->{RPCTID} ne "0"); - push (@$ex_pids, @tids) if (scalar (@tids) > 0); + my ($r, $a) = HMCCURPC_CheckThreadState ($defs{$rpcdev}, 6, 'running', $tids); + $c = $r; } } else { + @$pids = () if (defined ($pids)); foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) { - if (exists ($hash->{hmccu}{rpc}{$clkey}{pid}) && - defined ($hash->{hmccu}{rpc}{$clkey}{pid}) && - $hash->{hmccu}{rpc}{$clkey}{pid} != 0) { + if (defined ($hash->{hmccu}{rpc}{$clkey}{pid})) { my $pid = $hash->{hmccu}{rpc}{$clkey}{pid}; - push (@$hm_pids, $pid) if (kill (0, $pid)); + if ($pid != 0 && kill (0, $pid)) { + push (@$pids, $pid) if (defined ($pids)); + $c++; + } } } } - return (@$hm_pids > 0 || @$ex_pids > 0) ? 1 : 0; + return $c; } ###################################################################### @@ -2834,13 +2878,15 @@ if (odev) { ###################################################################### # Make device info readable +# n=number, b=bool, f=float, i=integer, s=string, a=alarm, p=presence +# e=enumeration ###################################################################### sub HMCCU_FormatDeviceInfo ($) { my ($devinfo) = @_; - my %vtypes = (2, "b", 4, "f", 8, "n", 11, "s", 16, "i", 20, "s", 29, "e"); + my %vtypes = (0, "n", 2, "b", 4, "f", 6, "a", 8, "n", 11, "s", 16, "i", 20, "s", 23, "p", 29, "e"); my $result = ''; my $c_oaddr = ''; @@ -2857,6 +2903,59 @@ sub HMCCU_FormatDeviceInfo ($) return $result; } +###################################################################### +# Get available firmware versions from EQ-3 server. +# Firmware version, date and download link are stored in hash +# {hmccu}{type}{$type} in elements {firmware}, {date} and {download}. +# Return number of available firmware downloads. +###################################################################### + +sub HMCCU_GetFirmwareVersions ($) +{ + my ($hash) = @_; + + my $url = "http://www.eq-3.de/service/downloads.html"; + my $response = GetFileFromURL ($url, 4, "suchtext=&suche_in=&downloadart=11"); +# my @changebc = $response =~ m/href="(Downloads\/Software\/Firmware\/changelog_[^"]+)/g; +# my @changeip = $response =~ m/href="(Downloads\/Software\/Firmware\/Homematic IP\/changelog_[^"]+)/g; + my @download = $response =~ m/{hmccu}{type}{$dt}{firmware} = $fw; + $hash->{hmccu}{type}{$dt}{date} = $date; + $hash->{hmccu}{type}{$dt}{download} = $dl; + } + + return $dc; +} + ###################################################################### # Read list of CCU devices and channels via Homematic Script. # Update data of client devices if not current. @@ -3160,13 +3259,8 @@ sub HMCCU_GetValidDatapoints ($$$$$) my $ccuflags = AttrVal ($hmccu_hash->{NAME}, 'ccuflags', 'null'); return 0 if ($ccuflags =~ /dptnocheck/); - return 0 if (!exists ($hmccu_hash->{hmccu}{dp})); - - if (!defined ($chn)) { - Log3 $hash->{NAME}, 2, $hash->{NAME}.": chn undefined"; - return 0; - } + return HMCCU_Log ($hash, 2, "chn undefined", 0) if (!defined ($chn)); if ($chn >= 0) { if (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn})) { @@ -3814,13 +3908,13 @@ sub HMCCU_GetAttrSubstitute ($$) my $substdef = AttrVal ($ioname, 'ccudef-substitute', ''); my $subst = AttrVal ($clname, 'substitute', $substdef); $subst .= ";$substdef" if ($subst ne $substdef && $substdef ne ''); - Log3 $clname, 2, "HMCCU: $fnc: subst = $subst" if ($ccuflags =~ /trace/); + HMCCU_Trace ($clhash, 2, "HMCCU: $fnc: subst = $subst", $ccuflags); return $subst if ($subst !~ /\$\{.+\}/); $subst = HMCCU_SubstVariables ($clhash, $subst); - Log3 $clname, 2, "HMCCU: fnc: subst_vars = $subst" if ($ccuflags =~ /trace/); + HMCCU_Trace ($clhash, 2, "HMCCU: fnc: subst_vars = $subst", $ccuflags); return $subst; } @@ -3870,15 +3964,12 @@ sub HMCCU_ProcessEvent ($$) else { my $errtok = $t[0]; $errtok =~ s/([\x00-\xFF])/sprintf("0x%X ",ord($1))/eg; - Log3 $name, 2, "HMCCU: Received unknown event from CCU: ".$errtok; - return undef; + return HMCCU_Log ($hash, 2, "Received unknown event from CCU: ".$errtok, undef); } # Check event syntax - if (exists ($rpceventargs{$t[0]}) && ($tc-1) != $rpceventargs{$t[0]}) { - Log3 $name, 2, "HMCCU: Wrong number of parameters in event $event"; - return undef; - } + return HMCCU_Log ($hash, 2, "Wrong number of parameters in event $event", undef) + if (exists ($rpceventargs{$t[0]}) && ($tc-1) != $rpceventargs{$t[0]}); if ($t[0] eq 'EV') { # @@ -3897,10 +3988,8 @@ sub HMCCU_ProcessEvent ($$) # Output: SL, Servername, Pid # my $clkey = $t[2]; - if (!exists ($rh->{$clkey})) { - Log3 $name, 0, "HMCCU: Received SL event for unknown RPC server $clkey"; - return undef; - } + return HMCCU_Log ($hash, 0, "Received SL event for unknown RPC server $clkey", undef) + if (!exists ($rh->{$clkey})); Log3 $name, 0, "HMCCU: Received SL event. RPC server $clkey enters server loop"; $rh->{$clkey}{loop} = 1 if ($rh->{$clkey}{pid} == $t[1]); return ($t[0], $clkey, $t[1]); @@ -3916,10 +4005,8 @@ sub HMCCU_ProcessEvent ($$) my $run = 0; my $c_ok = 0; my $c_err = 0; - if (!exists ($rh->{$clkey})) { - Log3 $name, 0, "HMCCU: Received IN event for unknown RPC server $clkey"; - return undef; - } + return HMCCU_Log ($hash, 0, "Received IN event for unknown RPC server $clkey", undef) + if (!exists ($rh->{$clkey})); Log3 $name, 0, "HMCCU: Received IN event. RPC server $clkey initialized."; $rh->{$clkey}{state} = $rh->{$clkey}{pid} != 0 ? "running" : "initialized"; @@ -3949,10 +4036,8 @@ sub HMCCU_ProcessEvent ($$) # my $clkey = $t[3]; my $run = 0; - if (!exists ($rh->{$clkey})) { - Log3 $name, 0, "HMCCU: Received EX event for unknown RPC server $clkey"; - return undef; - } + return HMCCU_Log ($hash, 0, "Received EX event for unknown RPC server $clkey", undef) + if (!exists ($rh->{$clkey})); Log3 $name, 0, "HMCCU: Received EX event. RPC server $clkey terminated."; my $f = $hash->{RPCState} eq "restarting" ? 2 : 1; @@ -4139,11 +4224,11 @@ sub HMCCU_ReadRPCQueue ($) } my @hm_pids; - my @ex_pids; - HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); + my @hm_tids; + HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids); my $nhm_pids = scalar (@hm_pids); - my $nex_pids = scalar (@ex_pids); - Log3 $name, 1, "HMCCU: Externally launched RPC server(s) detected. f=$f" if ($nex_pids > 0); + my $nhm_tids = scalar (@hm_tids); + Log3 $name, 1, "HMCCU: Externally launched RPC server(s) detected. f=$f" if ($nhm_tids > 0); if ($f > 0) { # At least one RPC server has been stopped. Update PID list @@ -4207,16 +4292,83 @@ sub HMCCU_HMScript ($$) my $ua = new LWP::UserAgent (); my $response = $ua->post($url, Content => $hmscript); - if (! $response->is_success ()) { - Log3 $name, 1, "HMCCU: HMScript failed. ".$response->status_line(); - return ''; + return HMCCU_Log ($hash, 1, "HMScript failed. ".$response->status_line(), '') + if (! $response->is_success ()); + + my $output = $response->content; + $output =~ s/.*<\/xml>//; + $output =~ s/\r//g; + return $output; +} + +###################################################################### +# Execute Homematic script on CCU. +# Parameters: device-hash, script-code or script-name, parameter-hash +# If content of hmscript starts with a ! the following text is treated +# as name of an internal HomeMatic script function. +# If content of hmscript is enclosed in [] the content is treated as +# HomeMatic script code. +# Otherwise hmscript is the name of a file containing Homematic script +# code. +# Return script output or error message starting with "ERROR:". +###################################################################### + +sub HMCCU_HMScriptExt ($$$) +{ + my ($hash, $hmscript, $params) = @_; + my $name = $hash->{NAME}; + my $host = $hash->{host}; + my $code = $hmscript; + my $scrname = ''; + + # Check for internal script + if ($hmscript =~ /^!(.*)$/) { + $scrname = $1; + return "ERROR: Can't find internal script $scrname" if (!exists ($HMCCU_SCRIPTS->{$scrname})); + $code = $HMCCU_SCRIPTS->{$scrname}{code}; + } + elsif ($hmscript =~ /^\[(.*)\]$/) { + $code = $1; } else { - my $output = $response->content; - $output =~ s/.*<\/xml>//; - $output =~ s/\r//g; - return $output; + if (open (SCRFILE, "<$hmscript")) { + my @lines = ; + $code = join ("\n", @lines); + close (SCRFILE); + } + else { + return "ERROR: Can't open script file"; + } } + + # Check and replace variables + if (defined ($params)) { + my @parnames = keys %{$params}; + if ($scrname ne '') { + if (scalar (@parnames) != $HMCCU_SCRIPTS->{$scrname}{parameters}) { + return "ERROR: Wrong number of parameters. Usage: $scrname ". + $HMCCU_SCRIPTS->{$scrname}{syntax}; + } + foreach my $p (split (/[, ]+/, $HMCCU_SCRIPTS->{$scrname}{syntax})) { + return "ERROR: Missing definition of parameter $p" if (!exists ($params->{$p})); + } + } + foreach my $svar (keys %{$params}) { + next if ($code !~ /\$$svar/); + $code =~ s/\$$svar/$params->{$svar}/g; + } + } + + # Execute script on CCU + my $url = "http://".$host.":8181/tclrega.exe"; + my $ua = new LWP::UserAgent (); + my $response = $ua->post($url, Content => $code); + return "ERROR: HMScript failed. ".$response->status_line() if (! $response->is_success ()); + + my $output = $response->content; + $output =~ s/.*<\/xml>//; + $output =~ s/\r//g; + return $output; } ###################################################################### @@ -4425,7 +4577,8 @@ sub HMCCU_ScaleValue ($$$$) ###################################################################### # Get CCU system variables and update readings. -# System variable readings are stored in I/O device. +# System variable readings are stored in I/O device. Unsupported +# characters in variable names are substituted. ###################################################################### sub HMCCU_GetVariables ($$) @@ -4455,8 +4608,9 @@ foreach (ssysvarid, dom.GetObject(ID_SYSTEM_VARIABLES).EnumUsedIDs()) my @vardata = split /=/, $vardef; next if (@vardata != 3); next if ($vardata[0] !~ /$pattern/); + my $rn = HMCCU_CorrectName ($vardata[0]); my $value = HMCCU_FormatReadingValue ($hash, $vardata[2]); - readingsBulkUpdate ($hash, $vardata[0], $value) if ($ccureadings); + readingsBulkUpdate ($hash, $rn, $value) if ($ccureadings); $result .= $vardata[0].'='.$vardata[2]."\n"; $count++; } @@ -4478,10 +4632,8 @@ sub HMCCU_SetVariable ($$$) '").State("'.$value.'")'; my $response = GetFileFromURL ($url); - if (!defined ($response) || $response =~ /nullnull{NAME}; my $type = $hash->{TYPE}; + my $fnc = "RPCGetConfig"; my $method = $mode eq 'listParamset' ? 'getParamset' : $mode; my $addr; my $result = ''; + my $res = ''; my $hmccu_hash = HMCCU_GetHash ($hash); return (-3, $result) if (!defined ($hmccu_hash)); return (-4, $result) if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'deleted'); + my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my $ccureadings = AttrVal ($name, 'ccureadings', 1); my $readingformat = HMCCU_GetAttrReadingFormat ($hash, $hmccu_hash); my $substitute = HMCCU_GetAttrSubstitute ($hash, $hmccu_hash); @@ -4731,15 +4886,30 @@ sub HMCCU_RPCGetConfig ($$$$) return (-9, '') if (!exists ($HMCCU_RPC_PORT{$int})); my $port = $HMCCU_RPC_PORT{$int}; - my $url = "http://".$hmccu_hash->{host}.":".$port."/"; - $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port})); - my $client = RPC::XML::Client->new ($url); - - my $res = $client->simple_request ($method, $addr, "MASTER"); - if (! defined ($res)) { - return (-5, "Function not available"); + + if ($HMCCU_RPC_PROT{$port} eq 'B') { + # Search RPC device + my $rpcdev = HMCCU_GetRPCDevice ($hmccu_hash, 0); + return (-17, '') if ($rpcdev eq ''); + HMCCU_Trace ($hash, 2, "HMCCU: $fnc: Method=$method Addr=$addr Port=$port", $ccuflags); + $res = HMCCURPC_SendBinRequest ($defs{$rpcdev}, $port, $method, $BINRPC_STRING, $addr, + $BINRPC_STRING, "MASTER"); + if ($ccuflags =~ /trace/ && defined ($res)) { + Log3 $name, 2, "HMCCU: $fnc: Dump of binary RPC request $method $addr:"; + Log3 $name, 2, HMCCU_RefToString ($res); + } } - elsif (ref ($res)) { + else { + my $url = "http://".$hmccu_hash->{host}.":".$port."/"; + $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port})); + HMCCU_Trace ($hash, 2, "HMCCU: $fnc: Method=$method Addr=$addr Port=$port", $ccuflags); + my $client = RPC::XML::Client->new ($url); + my $res = $client->simple_request ($method, $addr, "MASTER"); + } + + return (-5, "Function not available") if (!defined ($res)); + + if (ref ($res) eq 'HASH') { my $parcount = scalar (keys %$res); if (exists ($res->{faultString})) { Log3 $name, 1, "HMCCU: ".$res->{faultString}; @@ -4806,6 +4976,7 @@ sub HMCCU_RPCSetConfig ($$$) my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my $addr; + my $res; my $hmccu_hash = HMCCU_GetHash ($hash); return -3 if (!defined ($hmccu_hash)); @@ -4819,8 +4990,6 @@ sub HMCCU_RPCSetConfig ($$$) return -9 if (!exists ($HMCCU_RPC_PORT{$int})); my $port = $HMCCU_RPC_PORT{$int}; - my $url = "http://".$hmccu_hash->{host}.":".$port."/"; - $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port})); if ($ccuflags =~ /trace/) { my $ps = ''; @@ -4830,21 +4999,39 @@ sub HMCCU_RPCSetConfig ($$$) Log3 $name, 2, "HMCCU: RPCSetConfig: addr=$addr".$ps; } - my $client = RPC::XML::Client->new ($url); - my $res = $client->simple_request ("putParamset", $addr, "MASTER", $parref); - if (! defined ($res)) { - return -5; - } - elsif (ref ($res)) { - if (exists ($res->{faultString})) { - Log3 $name, 1, "HMCCU: RPC request failed. ".$res->{faultString}; - return -2; + if ($HMCCU_RPC_PROT{$port} eq 'B') { + # Search RPC device + my $rpcdev = HMCCU_GetRPCDevice ($hmccu_hash, 0); + return -17 if ($rpcdev eq ''); + + # Rebuild parameter hash for binary encoding + my %binpar; + foreach my $e (keys %$parref) { + $binpar{$e}{T} = $BINRPC_STRING; + $binpar{$e}{V} = $parref->{$e}; } + + $res = HMCCURPC_SendBinRequest ($defs{$rpcdev}, $port, "putParamset", $BINRPC_STRING, $addr, + $BINRPC_STRING, "MASTER", $BINRPC_STRUCT, \%binpar); } + else { + my $url = "http://".$hmccu_hash->{host}.":".$port."/"; + $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port})); + my $client = RPC::XML::Client->new ($url); + $res = $client->simple_request ("putParamset", $addr, "MASTER", $parref); + } + + return -5 if (! defined ($res)); + return HMCCU_Log ($hash, 1, "HMCCU: RPC request failed. ".$res->{faultString}, -2) + if (ref ($res) && exists ($res->{faultString})); return 0; } +###################################################################### +# *** FILEQUEUE FUNCTIONS *** +###################################################################### + ###################################################################### # Open file queue ###################################################################### @@ -4978,7 +5165,7 @@ sub HMCCU_QueueDeq ($) } ###################################################################### -# HELPER FUNCTIONS +# *** HELPER FUNCTIONS *** ###################################################################### ###################################################################### @@ -5235,10 +5422,117 @@ sub HMCCU_EncodeEPDisplay ($) return '"'.$cmd.'"'; } +###################################################################### +# Convert reference to string recursively +# Supports reference to ARRAY, HASH and SCALAR and scalar values. +###################################################################### +sub HMCCU_RefToString ($) +{ + my ($r) = @_; + + my $result = ''; + + if (ref ($r) eq 'ARRAY') { + $result .= "[\n"; + foreach my $e (@$r) { + $result .= "," if ($result ne '['); + $result .= HMCCU_RefToString ($e); + } + $result .= "\n]"; + } + elsif (ref ($r) eq 'HASH') { + $result .= "{\n"; + foreach my $k (sort keys %$r) { + $result .= "," if ($result ne '{'); + $result .= "$k=".HMCCU_RefToString ($r->{$k}); + } + $result .= "\n}"; + } + elsif (ref ($r) eq 'SCALAR') { + $result .= $$r; + } + else { + $result .= $r; + } + + return $result; +} ###################################################################### -# *** Subprocess part *** +# Match string with regular expression considering illegal regular +# expressions. Return parameter e if regular expression is incorrect. +###################################################################### + +sub HMCCU_ExprMatch ($$$) +{ + my ($t, $r, $e) = @_; + + my $x = eval { $t =~ /$r/ }; + return $e if (!defined ($x)); + return "$x" eq '' ? 0 : 1; +} + +sub HMCCU_ExprNotMatch ($$$) +{ + my ($t, $r, $e) = @_; + + my $x = eval { $t !~ /$r/ }; + return $e if (!defined ($x)); + return "$x" eq '' ? 0 : 1; +} + +###################################################################### +# Read duty cycles of interfaces 2001 and 2010. +###################################################################### + +sub HMCCU_GetDutyCycle ($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + my $host = $hash->{host}; + my $dc = 0; + my @rpcports = HMCCU_GetRPCPortList ($hash); + + readingsBeginUpdate ($hash); + + foreach my $port (@rpcports) { + next if ($port != 2001 && $port != 2010); + my $url = "http://$host:$port/"; + my $rpcclient = RPC::XML::Client->new ($url); + my $response = $rpcclient->simple_request ("listBidcosInterfaces"); + next if (!defined ($response) || ref($response) ne 'ARRAY'); + foreach my $iface (@$response) { + next if (ref ($iface) ne 'HASH'); + next if (!exists ($iface->{DUTY_CYCLE})); + my $type = exists ($iface->{TYPE}) ? $iface->{TYPE} : $HMCCU_RPC_NUMPORT{$port}; + my $rn = HMCCU_CorrectName ("duty_cycle_$type"); + $dc++; + readingsBulkUpdate ($hash, lc($rn), $iface->{DUTY_CYCLE}); + } + } + + readingsEndUpdate ($hash, 1); + + return $dc; +} + +###################################################################### +# Substitute invalid characters in reading name. +# Substitution rules: ':' => '.', any other illegal character => '_' +###################################################################### + +sub HMCCU_CorrectName ($) +{ + my ($rn) = @_; + $rn =~ s/\:/\./g; + $rn =~ s/[^A-Za-z\d_\.-]+/_/g; + return $rn; +} + +###################################################################### +# *** SUBPROCESS PART *** ###################################################################### # Child process. Must be global to allow access by RPC callbacks @@ -5425,8 +5719,10 @@ sub HMCCU_CCURPC_NewDevicesCB ($$$) $msg = "C|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|null|null"; } else { + # Wired devices do not have a RX_MODE attribute + my $rx = exists ($dev->{RX_MODE}) ? $dev->{RX_MODE} : 'null'; $msg = "D|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|". - $dev->{FIRMWARE}."|".$dev->{RX_MODE}; + $dev->{FIRMWARE}."|".$rx; } HMCCU_CCURPC_Write ("ND", $msg); } @@ -5597,8 +5893,8 @@ sub HMCCU_CCURPC_ListDevicesCB ($$) Example:
set d_ccu execute PR-TEST
-
  • set <name> hmscript <script-file> [dump] [<parname>=<value> - [...]]
    +
  • set <name> hmscript {<script-file>|'!'<function>|'['<code>']'} [dump] + [<parname>=<value> [...]]
    Execute Homematic script on CCU. If script code contains parameter in format $parname they are substituted by corresponding command line parameters parname.
    If output of script contains lines in format Object=Value readings in existing @@ -5606,6 +5902,7 @@ sub HMCCU_CCURPC_ListDevicesCB ($$) variable or a valid channel and datapoint specification. Readings for system variables are set in the I/O device. Datapoint related readings are set in client devices. If option 'dump' is specified the result of script execution is displayed in FHEM web interface. + Execute command without parameters will list available script functions.

  • set <name> importdefaults <filename>
    Import default attributes from file. @@ -5668,9 +5965,17 @@ sub HMCCU_CCURPC_ListDevicesCB ($$) Dump all Homematic devicetypes or all devices including datapoints currently defined in FHEM.

  • +
  • get <name> dutycycle
    + Read duty cycle of BidCos and HM-IP interfaces from CCU and LAN gateways. The values are + stored as readings beginning with 'duty_cycle_' followed by the type of the interface. +

  • get <name> exportdefaults <filename>
    Export default attributes into file.

  • +
  • get <name> firmware
    + Get available firmware downloads from eq-3.de. List FHEM devices with current and available + firmware version. Firmware versions are only displayed after RPC server has been started. +

  • get <name> parfile [<parfile>]
    Get values of all channels / datapoints specified in parfile. The parameter parfile can also be defined as an attribute. The file must contain one channel / diff --git a/FHEM/88_HMCCUCHN.pm b/FHEM/88_HMCCUCHN.pm index 03b3f765e..307eb9f06 100644 --- a/FHEM/88_HMCCUCHN.pm +++ b/FHEM/88_HMCCUCHN.pm @@ -4,7 +4,7 @@ # # $Id$ # -# Version 4.0 +# Version 4.0.001 # # (c) 2017 zap (zap01 t-online de) # @@ -66,7 +66,6 @@ sub HMCCUCHN_Define ($@); sub HMCCUCHN_Set ($@); sub HMCCUCHN_Get ($@); sub HMCCUCHN_Attr ($@); -sub HMCCUCHN_SetError ($$); ################################################## # Initialize module @@ -286,7 +285,7 @@ sub HMCCUCHN_Set ($@) my $objname = $ccuif.'.'.$ccuaddr.'.'.$sd; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0); my $objvalue = ''; my $st = 0; @@ -487,7 +486,7 @@ sub HMCCUCHN_Get ($@) my $objname = $ccuif.'.'.$ccuaddr.'.'.$sd; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0); return $ccureadings ? undef : $result; } elsif ($opt eq 'datapoint') { @@ -500,7 +499,7 @@ sub HMCCUCHN_Get ($@) $objname = $ccuif.'.'.$ccuaddr.'.'.$objname; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0); return $ccureadings ? undef : $result; } elsif ($opt eq 'update') { @@ -536,7 +535,7 @@ sub HMCCUCHN_Get ($@) $par = '.*' if (!defined ($par)); my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset", $par); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $res) if ($rc < 0); return $ccureadings ? undef : $res; } elsif ($opt eq 'configlist') { @@ -551,7 +550,7 @@ sub HMCCUCHN_Get ($@) $par = '.*' if (!defined ($par)); my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "listParamset", $par); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $res) if ($rc < 0); return $res; } elsif ($opt eq 'configdesc') { @@ -562,7 +561,7 @@ sub HMCCUCHN_Get ($@) } my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription", undef); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $res) if ($rc < 0); return $res; } elsif ($opt eq 'defaults') { @@ -582,36 +581,6 @@ sub HMCCUCHN_Get ($@) } } -##################################### -# Set error status -##################################### - -sub HMCCUCHN_SetError ($$) -{ - my ($hash, $text) = @_; - my $name = $hash->{NAME}; - my $msg; - my %errlist = ( - -1 => 'Channel name or address invalid', - -2 => 'Execution of CCU script failed', - -3 => 'Cannot detect IO device', - -4 => 'Device deleted in CCU', - -5 => 'No response from CCU', - -6 => 'Update of readings disabled. Set attribute ccureadings first' - ); - - if (exists ($errlist{$text})) { - $msg = $errlist{$text}; - } - else { - $msg = $text; - } - - $msg = "HMCCUCHN: ".$name." ". $msg; - readingsSingleUpdate ($hash, "state", "Error", 1); - Log3 $name, 1, $msg; - return $msg; -} 1; diff --git a/FHEM/88_HMCCUDEV.pm b/FHEM/88_HMCCUDEV.pm index d72f0ea1f..d748eeb2a 100644 --- a/FHEM/88_HMCCUDEV.pm +++ b/FHEM/88_HMCCUDEV.pm @@ -4,7 +4,7 @@ # # $Id$ # -# Version 4.0 +# Version 4.0.001 # # (c) 2017 zap (zap01 t-online de) # @@ -383,7 +383,7 @@ sub HMCCUDEV_Set ($@) ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); Log3 $name, 2, "HMCCU: set toggle: GetDatapoint returned $rc, $result" if ($ccuflags =~ /trace/); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0); my $objvalue = ''; my $st = 0; @@ -588,7 +588,7 @@ sub HMCCUDEV_Get ($@) my $objname = $ccuif.'.'.$ccuaddr.':'.$sc.'.'.$sd; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0); return $ccureadings ? undef : $result; } elsif ($opt eq 'datapoint') { @@ -611,7 +611,7 @@ sub HMCCUDEV_Get ($@) $objname = $ccuif.'.'.$ccuaddr.':'.$objname; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0); HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); return $ccureadings ? undef : $result; @@ -663,7 +663,7 @@ sub HMCCUDEV_Get ($@) $par = '.*' if (!defined ($par)); my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset", $par); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $res) if ($rc < 0); HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); return $ccureadings ? undef : $res; } @@ -681,7 +681,7 @@ sub HMCCUDEV_Get ($@) $par = '.*' if (!defined ($par)); my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "listParamset", $par); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $res) if ($rc < 0); HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); return $res; } @@ -700,7 +700,7 @@ sub HMCCUDEV_Get ($@) } my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription", undef); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc, $res) if ($rc < 0); HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); return $res; } diff --git a/FHEM/88_HMCCURPC.pm b/FHEM/88_HMCCURPC.pm index 14436e227..4def6f999 100644 --- a/FHEM/88_HMCCURPC.pm +++ b/FHEM/88_HMCCURPC.pm @@ -4,7 +4,7 @@ # # $Id$ # -# Version 0.94 beta +# Version 0.95 beta # # Thread based RPC Server module for HMCCU. # @@ -40,7 +40,7 @@ use SetExtensions; ###################################################################### # HMCCURPC version -my $HMCCURPC_VERSION = '0.94 beta'; +my $HMCCURPC_VERSION = '0.95 beta'; # Maximum number of events processed per call of Read() my $HMCCURPC_MAX_EVENTS = 50; @@ -162,7 +162,7 @@ sub HMCCURPC_StartRPCServer ($); sub HMCCURPC_CleanupThreads ($$$); sub HMCCURPC_CleanupThreadIO ($); sub HMCCURPC_TerminateThreads ($$); -sub HMCCURPC_CheckThreadState ($$$); +sub HMCCURPC_CheckThreadState ($$$$); sub HMCCURPC_IsRPCServerRunning ($); sub HMCCURPC_Housekeeping ($); sub HMCCURPC_StopRPCServer ($); @@ -234,7 +234,7 @@ sub HMCCURPC_Initialize ($) $hash->{parseParams} = 1; $hash->{AttrList} = "rpcInterfaces:multiple-strict,".join(',',sort keys %HMCCURPC_RPC_PORT). - " ccuflags:multiple-strict,expert rpcMaxEvents rpcQueueSize rpcTriggerTime". + " ccuflags:multiple-strict,expert,keepThreads rpcMaxEvents rpcQueueSize rpcTriggerTime". " rpcServer:on,off rpcServerAddr rpcServerPort rpcWriteTimeout rpcAcceptTimeout". " rpcConnTimeout rpcWaitTime rpcStatistics ". $readingFnAttributes; @@ -352,7 +352,7 @@ sub HMCCURPC_Attr ($@) my $rc = 0; if ($attrname eq 'rpcInterfaces') { - my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running'); + my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running', undef); return 'Stop RPC server before modifying rpcInterfaces' if ($run > 0); } @@ -415,26 +415,26 @@ sub HMCCURPC_Set ($@) return HMCCURPC_SetError ($hash, "RPC request failed") if (!defined ($response)); - my $result = ''; - if (ref ($response) eq 'ARRAY') { - $result = join "\n", @$response; - } - elsif (ref ($response) eq 'HASH') { - foreach my $k (keys %$response) { - $result .= "$k = ".$response->{$k}."\n"; - } - } - elsif (ref ($response) eq 'SCALAR') { - $result = $$response; - } - else { - if (ref ($response)) { - $result = "Unknown response from CCU of type ".ref ($response); - } - else { - $result = ($response eq '') ? 'Request returned void' : $response; - } - } + my $result = HMCCU_RefToString ($response); +# if (ref ($response) eq 'ARRAY') { +# $result = join "\n", @$response; +# } +# elsif (ref ($response) eq 'HASH') { +# foreach my $k (keys %$response) { +# $result .= "$k = ".$response->{$k}."\n"; +# } +# } +# elsif (ref ($response) eq 'SCALAR') { +# $result = $$response; +# } +# else { +# if (ref ($response)) { +# $result = "Unknown response from CCU of type ".ref ($response); +# } +# else { +# $result = ($response eq '') ? 'Request returned void' : $response; +# } +# } return $result; } @@ -470,6 +470,8 @@ sub HMCCURPC_Get ($@) my ($hash, $a, $h) = @_; my $name = shift @$a; my $opt = shift @$a; + + my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my $options = "rpcevents:noArg rpcstate:noArg"; if ($opt ne 'rpcstate' && HMCCURPC_IsRPCStateBlocking ($hash)) { @@ -866,8 +868,8 @@ sub HMCCURPC_ProcessEvent ($$) if ($t[0] == $rh->{$clkey}{tid}) { Log3 $name, 1, "HMCCURPC: Received SL event. RPC server $clkey enters server loop"; $rh->{$clkey}{state} = $clkey eq 'DATA' ? 'running' : 'working'; - my ($run, $alld) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_DATA, "running"); - my ($work, $alls) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_SERVER, 'working'); + my ($run, $alld) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_DATA, 'running', undef); + my ($work, $alls) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_SERVER, 'working', undef); if ($work == $alls && $run == $alld) { Log3 $name, 1, "HMCCURPC: All threads working"; HMCCURPC_RegisterCallback ($hash); @@ -893,7 +895,7 @@ sub HMCCURPC_ProcessEvent ($$) $rh->{$clkey}{state} = "running"; # Set binary RPC interfaces to 'running' if all ascii interfaces are in state 'running' - my ($runa, $alla) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ASCII, 'running'); + my ($runa, $alla) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ASCII, 'running', undef); if ($runa == $alla) { foreach my $sn (keys %{$rh}) { $rh->{$sn}{state} = "running" @@ -902,7 +904,7 @@ sub HMCCURPC_ProcessEvent ($$) } # Check if all RPC servers were initialized. Set overall status - my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running'); + my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running', undef); if ($run == $all) { $hash->{hmccu}{rpcstarttime} = 0; HMCCURPC_SetRPCState ($hash, "running", "All RPC servers running"); @@ -934,17 +936,16 @@ sub HMCCURPC_ProcessEvent ($$) if ($clkey ne 'DATA') { ($stopped, $all) = HMCCURPC_CleanupThreads ($hash, $HMCCURPC_THREAD_SERVER, 'stopped'); if ($stopped == $all) { - # Terminate data processing thread + # Terminate data processing thread if all server threads stopped Log3 $name, 2, "HMCCURPC: All RPC servers stopped. Terminating data processing thread"; HMCCURPC_TerminateThreads ($hash, $HMCCURPC_THREAD_DATA); sleep (1); } } else { - # Vielleicht besser außerhalb von Read() löschen - HMCCURPC_CleanupThreadIO ($hash); ($stopped, $all) = HMCCURPC_CleanupThreads ($hash, $HMCCURPC_THREAD_DATA, '.*'); if ($stopped == $all) { + HMCCURPC_CleanupThreadIO ($hash); HMCCURPC_ResetRPCState ($hash, "OK"); RemoveInternalTimer ($hash); Log3 $name, 1, "HMCCURPC: All threads stopped"; @@ -1159,8 +1160,6 @@ sub HMCCURPC_DeRegisterCallback ($) if (exists ($rpchash->{cburl}) && $rpchash->{cburl} ne '') { Log3 $name, 1, "HMCCURPC: Deregistering RPC server ".$rpchash->{cburl}. " with ID $clkey at ".$rpchash->{clurl}; -# my $rpcclient = RPC::XML::Client->new ($rpchash->{clurl}); -# $rpcclient->send_request ("init", $rpchash->{cburl}); if (HMCCURPC_IsAscRPCPort ($rpchash->{port})) { HMCCURPC_SendRequest ($hash, $rpchash->{port}, "init", $rpchash->{cburl}); } @@ -1406,7 +1405,7 @@ sub HMCCURPC_StartRPCServer ($) sleep (1); # Cleanup if one or more threads are not initialized (ignore thread state) - my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, '.*'); + my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, '.*', undef); if ($run != $all) { Log3 $name, 0, "HMCCURPC: Only $run from $all threads are running. Cleaning up"; HMCCURPC_Housekeeping ($hash); @@ -1439,8 +1438,8 @@ sub HMCCURPC_CleanupThreadIO ($) my $pid = $$; if (exists ($selectlist{"RPC.$name.$pid"})) { Log3 $name, 2, "HMCCURPC: Stop I/O handling"; - delete $hash->{FD}; delete $selectlist{"RPC.$name.$pid"}; + delete $hash->{FD} if (defined ($hash->{FD})); } if (defined ($hash->{hmccu}{sockchild})) { Log3 $name, 2, "HMCCURPC: Close child socket"; @@ -1498,6 +1497,8 @@ sub HMCCURPC_CleanupThreads ($$$) my ($hash, $mode, $state) = @_; my $name = $hash->{NAME}; + my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); + my $count = 0; my $all = 0; @@ -1515,9 +1516,15 @@ sub HMCCURPC_CleanupThreads ($$$) " still running. Can't delete it"; next; } - Log3 $name, 2, "HMCCURPC: Thread $clkey with TID=".$thr->tid (). - " has been stopped. Deleting it"; -# undef $hash->{hmccu}{rpc}{$clkey}{child}; + if ($hash->{hmccu}{rpc}{$clkey}{state} eq 'stopped' && $ccuflags !~ /keepThreads/) { + Log3 $name, 2, "HMCCURPC: Thread $clkey with TID=".$thr->tid (). + " has been stopped. Deleting it"; + undef $hash->{hmccu}{rpc}{$clkey}{child}; + } + else { + Log3 $name, 2, "HMCCURPC: Thread $clkey with TID=".$thr->tid (). + " is in state ".$hash->{hmccu}{rpc}{$clkey}{state}.". Can't delete it"; + } # delete $hash->{hmccu}{rpc}{$clkey}; } } @@ -1531,20 +1538,21 @@ sub HMCCURPC_CleanupThreads ($$$) # Count threads in specified state. # Parameter state is a regular expression. # Parameter mode specifies which threads should be counted: -# 1 - Count data processing thread -# 2 - Count server threads -# 3 - Count all threads # If state is empty thread state is ignored and only running threads # are counted by calling thread function is_running(). # Return number of threads in specified state and total number of -# threads. +# threads. Also return IDs of running threads if parameter tids is +# defined and parameter state is 'running' or '.*'. ###################################################################### -sub HMCCURPC_CheckThreadState ($$$) +sub HMCCURPC_CheckThreadState ($$$$) { - my ($hash, $mode, $state) = @_; + my ($hash, $mode, $state, $tids) = @_; my $count = 0; my $all = 0; + + $mode = $HMCCURPC_THREAD_ALL if (!defined ($mode)); + $state = '' if (!defined ($state)); foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) { next if ($hash->{hmccu}{rpc}{$clkey}{state} eq 'inactive'); @@ -1553,8 +1561,11 @@ sub HMCCURPC_CheckThreadState ($$$) if ($state eq 'running' || $state eq '.*') { next if (!exists ($hash->{hmccu}{rpc}{$clkey}{child})); my $thr = $hash->{hmccu}{rpc}{$clkey}{child}; - $count++ if (defined ($thr) && $thr->is_running () && - $hash->{hmccu}{rpc}{$clkey}{state} =~ /$state/); + if (defined ($thr) && $thr->is_running () && + ($state eq '' || $hash->{hmccu}{rpc}{$clkey}{state} =~ /$state/)) { + $count++; + push (@$tids, $thr->tid()) if (defined ($tids)); + } } else { $count++ if ($hash->{hmccu}{rpc}{$clkey}{state} =~ /$state/); @@ -1574,7 +1585,7 @@ sub HMCCURPC_IsRPCServerRunning ($) my $name = $hash->{NAME}; Log3 $name, 2, "HMCCURPC: Checking if all threads are running"; - my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running'); + my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running', undef); if ($run != $all) { Log3 $name, 1, "HMCCURPC: Only $run of $all threads are running. Cleaning up"; HMCCURPC_Housekeeping ($hash); @@ -1596,8 +1607,11 @@ sub HMCCURPC_Housekeeping ($) my $name = $hash->{NAME}; Log3 $name, 1, "HMCCURPC: Housekeeping called. Cleaning up RPC environment"; - - # I/O Handling beenden + + # Deregister callback URLs in CCU + HMCCURPC_DeRegisterCallback ($hash); + + # Stop I/O handling HMCCURPC_CleanupThreadIO ($hash); my $count = HMCCURPC_TerminateThreads ($hash, $HMCCURPC_THREAD_ALL); @@ -1623,7 +1637,7 @@ sub HMCCURPC_StopRPCServer ($) my ($hash) = @_; my $name = $hash->{NAME}; - my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running'); + my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running', undef); if ($run > 0) { HMCCURPC_SetRPCState ($hash, "stopping", "Found $run threads. Stopping ..."); @@ -1889,6 +1903,8 @@ sub HMCCURPC_HandleConnection ($$$$) foreach my $et (@eventtypes) { Log3 $name, 4, "CCURPC: $clkey event type = $et: ".$rpcsrv->{hmccu}{rec}{$et}; } + + return; } ###################################################################### @@ -2085,9 +2101,9 @@ sub HMCCURPC_HexDump ($$) # Callback functions ###################################################################### -################################################## +###################################################################### # Callback for new devices -################################################## +###################################################################### sub HMCCURPC_NewDevicesCB ($$$) { @@ -2095,15 +2111,17 @@ sub HMCCURPC_NewDevicesCB ($$$) my $name = $server->{hmccu}{name}; my $devcount = scalar (@$a); - Log3 $name, 2, "CCURPC: $cb NewDevice received $devcount device specifications"; + Log3 $name, 2, "CCURPC: $cb NewDevice received $devcount device and channel specifications"; foreach my $dev (@$a) { my $msg = ''; if ($dev->{ADDRESS} =~ /:[0-9]{1,2}$/) { $msg = "C|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|null|null"; } else { + # Wired devices do not have a RX_MODE attribute + my $rx = exists ($dev->{RX_MODE}) ? $dev->{RX_MODE} : 'null'; $msg = "D|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|". - $dev->{FIRMWARE}."|".$dev->{RX_MODE}; + $dev->{FIRMWARE}."|".$rx; } HMCCURPC_Write ($server, "ND", $cb, $msg); } @@ -2727,6 +2745,7 @@ sub HMCCURPC_DecodeResponse ($)
  • ccuflags { expert }
    Set flags for controlling device behaviour. Meaning of flags is:
    expert - Activate expert mode
    + keepThreads - Do not delete thread objects after RPC server has been stopped

  • rpcAcceptTimeout <seconds>
    Specify timeout for accepting incoming connections. Default is 1 second. Increase this diff --git a/FHEM/HMCCUConf.pm b/FHEM/HMCCUConf.pm index fddc463b6..4ce63806b 100644 --- a/FHEM/HMCCUConf.pm +++ b/FHEM/HMCCUConf.pm @@ -4,17 +4,19 @@ # # $Id$ # -# Version 4.0 +# Version 4.0.001 # -# Configuration parameters for Homematic devices. +# Configuration parameters for HomeMatic devices. # -# (c) 2016 zap (zap01 t-online de) +# (c) 2017 by zap (zap01 t-online de) # -# Datapoints LOWBAT, LOW_BAT, UNREACH, ERROR.*, SABOTAGE and FAULT.* must -# not be specified in ccureadingfilter. They are always stored as readings. +# Datapoints LOWBAT, LOW_BAT, UNREACH, ERROR.*, SABOTAGE and FAULT.* +# must not be specified in attribute ccureadingfilter. They are always +# stored as readings. # Datapoints LOWBAT, LOW_BAT and UNREACH must not be specified in -# substitute because they are substituted by default. -# See attributes ccudef-readingname and ccudef-substitute in module HMCCU. +# attribute substitute because they are substituted by default. +# See also documentation of attributes ccudef-readingname and +# ccudef-substitute in module HMCCU. # ######################################################################### @@ -135,6 +137,14 @@ use vars qw(%HMCCU_SCRIPTS); statevals => "press:true", substitute => "PRESS_SHORT,PRESS_LONG,PRESS_CONT!(1|true):pressed,(0|false):released;PRESS_LONG_RELEASE!(0|false):no,(1|true):yes" }, + "HM-SwI-3-FM" => { + _description => "Funk-Schalterschnittstelle", + _channels => "1,2,3", + ccureadingfilter => "PRESS", + statedatapoint => "PRESS", + statevals => "press:true", + substitute => "PRESS!(1|true):pressed,(0|false):released" + }, "HM-LC-Sw1PBU-FM" => { _description => "Unterputz Schaltaktor für Markenschalter", _channels => "1", @@ -154,6 +164,14 @@ use vars qw(%HMCCU_SCRIPTS); statevals => "on:true,off:false", substitute => "STATE!(1|true):on,(0|false):off" }, + "HM-MOD-Re-8" => { + _description => "8 Kanal Empfangsmodul", + _channels => "1,2,3,4,5,6,7,8", + ccureadingfilter => "(STATE|WORKING)", + statedatapoint => "STATE", + statevals => "on:true,off:false", + substitute => "STATE!(1|true):on,(0|false):off;WORKING!(1|true):yes,(0|false):no" + }, "HM-LC-Sw1-Pl|HM-LC-Sw1-Pl-2|HM-LC-Sw1-SM|HM-LC-Sw1-FM|HM-LC-Sw1-PB-FM" => { _description => "1 Kanal Funk-Schaltaktor", _channels => "1", @@ -200,6 +218,14 @@ use vars qw(%HMCCU_SCRIPTS); statedatapoint => "TEMPERATURE", stripnumber => 1 }, + "HM-WDS100-C6-O-2" => { + _description => "Funk-Kombisensor", + _channels => "1", + ccureadingfilter => "(HUMIDITY|TEMPERATURE|WIND|RAIN|SUNSHINE|BRIGHTNESS)", + statedatapoint => "TEMPERATURE", + stripnumber => 1, + substitute => "RAINING!(1|true):yes,(0|false):no" + }, "HM-Sec-MD|HM-Sec-MDIR|HM-Sec-MDIR-2|HM-Sec-MDIR-3" => { _description => "Bewegungsmelder", _channels => "1", @@ -421,6 +447,12 @@ use vars qw(%HMCCU_SCRIPTS); ccureadingfilter => "PRESS", substitute => "PRESS_SHORT,PRESS_LONG,PRESS_CONT!(1|true):pressed,(0|false):released;PRESS_LONG_RELEASE!(0|false):no,(1|true):yes" }, + "HM-SwI-3-FM" => { + _description => "Funk-Schalterschnittstelle", + ccureadingfilter => "PRESS", + statevals => "press:true", + substitute => "PRESS!(1|true):pressed,(0|false):released" + }, "HM-LC-Sw1PBU-FM" => { _description => "Unterputz Schaltaktor für Markenschalter", ccureadingfilter => "STATE", @@ -437,6 +469,12 @@ use vars qw(%HMCCU_SCRIPTS); statevals => "on:true,off:false", substitute => "STATE!(1|true):on,(0|false):off" }, + "HM-MOD-Re-8" => { + _description => "8 Kanal Empfangsmodul", + ccureadingfilter => "(STATE|WORKING)", + statevals => "on:true,off:false", + substitute => "STATE!(1|true):on,(0|false):off;WORKING!(1|true):yes,(0|false):no" + }, "HM-LC-Bl1PBU-FM|HM-LC-Bl1-FM|HM-LC-Bl1-SM|HM-LC-BlX|HM-LC-Bl1-SM-2|HM-LC-Bl1-FM-2" => { _description => "Jalousienaktor", ccureadingfilter => "(LEVEL|INHIBIT|DIRECTION|WORKING)", @@ -508,6 +546,13 @@ use vars qw(%HMCCU_SCRIPTS); statedatapoint => "1.TEMPERATURE", stripnumber => 1 }, + "HM-WDS100-C6-O-2" => { + _description => "Funk-Kombisensor", + ccureadingfilter => "(HUMIDITY|TEMPERATURE|WIND|RAIN|SUNSHINE|BRIGHTNESS)", + statedatapoint => "1.TEMPERATURE", + stripnumber => 1, + substitute => "RAINING!(1|true):yes,(0|false):no" + }, "HM-ES-TX-WM" => { _description => "Energiezaehler Sensor", ccureadingfilter => "(ENERGY_COUNTER|POWER)" @@ -640,60 +685,259 @@ use vars qw(%HMCCU_SCRIPTS); ); ###################################################################### -# Homematic scripts +# Homematic scripts. +# Scripts can be executed via HMCCU set command 'hmscript'. Script +# name must be preceeded by a '!'. +# Example: +# set mydev hmscript !CreateStringVariable MyVar test "Test variable" ###################################################################### %HMCCU_SCRIPTS = ( - "CreateVariable" => { - _description => "Create CCU system variable of type STRING, NUMBER, BOOL or LIST", - _pardesc => "Type, Name, Unit, Init, Desc [, { Min, Max | Val1, Val2 | ValList } ]", - parameters => 6, - code => qq( + "ActivateProgram" => { + description => "Activate or deactivate a CCU program", + syntax => "name, mode", + parameters => 2, + code => qq( +object oPR = dom.GetObject("\$name"); +if (oPR) { + oPR.Active(\$mode); +} + ) + }, + "CreateStringVariable" => { + description => "Create CCU system variable of type STRING", + syntax => "name, init, desc", + parameters => 3, + code => qq( +object oSV = dom.GetObject("\$name"); +if (!oSV){ + object oSysVars = dom.GetObject(ID_SYSTEM_VARIABLES); + oSV = dom.CreateObject(OT_VARDP); + oSysVars.Add(svObj.ID()); + oSV.Name("\$name"); + oSV.ValueType(ivtString); + oSV.ValueSubType(istChar8859); + oSV.DPInfo("\$desc"); + oSV.ValueUnit(""); + oSV.State("\$init"); + oSV.Internal(false); + oSV.Visible(true); + dom.RTUpdate(false); +} +else { + oSV.State("\$init"); +} + ) + }, + "CreateNumericVariable" => { + description => "Create CCU system variable of type FLOAT", + syntax => "name, unit, init, desc, min, max", + parameters => 6, + code => qq( +object oSV = dom.GetObject("\$name"); +if (!oSV){ + object oSysVars = dom.GetObject(ID_SYSTEM_VARIABLES); + oSV = dom.CreateObject(OT_VARDP); + oSysVars.Add(svObj.ID()); + oSV.Name("\$name"); + oSV.ValueType(ivtFloat); + oSV.ValueSubType(istGeneric); + oSV.ValueMin(\$min); + oSV.ValueMax(\$max); + oSV.DPInfo("\$desc"); + oSV.ValueUnit("\$unit"); + oSV.State("\$init"); + oSV.Internal(false); + oSV.Visible(true); + dom.RTUpdate(false); +} +else { + oSV.State("\$init"); +} + ) + }, + "CreateBoolVariable" => { + description => "Create CCU system variable of type BOOL", + syntax => "name, init, desc, value1, value2", + parameters => 5, + code => qq( +object oSV = dom.GetObject("\$name"); +if (!oSV){ + object oSysVars = dom.GetObject(ID_SYSTEM_VARIABLES); + oSV = dom.CreateObject(OT_VARDP); + oSysVars.Add(svObj.ID()); + oSV.Name("\$name"); + oSV.ValueType(ivtBinary); + oSV.ValueSubType(istBool); + oSV.ValueName0("\$value1"); + oSV.ValueName1("\$value2"); + oSV.DPInfo("\$desc"); + oSV.State("\$init"); + dom.RTUpdate(false); +} +else { + oSV.State("\$init"); +} + ) + }, + "CreateListVariable" => { + description => "Create CCU system variable of type LIST", + syntax => "name, unit, init, desc, list", + parameters => 5, + code => qq( object oSV = dom.GetObject("p2"); if (!oSV){ object oSysVars = dom.GetObject(ID_SYSTEM_VARIABLES); oSV = dom.CreateObject(OT_VARDP); oSysVars.Add(svObj.ID()); - oSV.Name("p2"); - if ("p1" = "STRING") { - oSV.ValueType(ivtString); - oSV.ValueSubType(istChar8859); - } - if ("p1" = "NUMBER") { - oSV.ValueType(ivtFloat); - oSV.ValueSubType(istGeneric); - oSV.ValueMin(p6); - oSV.ValueMax(p7); - } - if ("p1" = "BOOL") { - oSV.ValueType(ivtBinary); - oSV.ValueSubType(istBool); - oSV.ValueName0("p6"); - oSV.ValueName1("p7"); - } - if ("p1" = "LIST") { - oSV.ValueType(ivtInteger); - oSV.ValueSubType(istEnum); - oSV.ValueList("p6"); - } - oSV.DPInfo("p5"); - oSV.ValueUnit("p3"); - oSV.State("p4"); - oSV.Internal(false); - oSV.Visible(true); + oSV.Name("\$name"); + oSV.ValueType(ivtInteger); + oSV.ValueSubType(istEnum); + oSV.ValueList("\$list"); + oSV.DPInfo("\$desc"); + oSV.ValueUnit("\$unit"); + oSV.State("\$init"); dom.RTUpdate(false); } - ) - }, +else { + oSV.State("\$init"); +} + ) + }, "DeleteVariable" => { - _description => "Delete CCU system variable", - parameters => 1, - code => qq( -object oSV = dom.GetObject("p1"); + description => "Delete CCU system variable", + syntax => "name", + parameters => 1, + code => qq( +object oSV = dom.GetObject("\$name"); if (oSV) { dom.DeleteObject(oSV.ID()); } ) + }, + "GetVariables" => { + description => "Query system variables", + syntax => "", + parameters => 0, + code => qq( +object osysvar; +string ssysvarid; +foreach (ssysvarid, dom.GetObject(ID_SYSTEM_VARIABLES).EnumUsedIDs()) +{ + osysvar = dom.GetObject(ssysvarid); + WriteLine (osysvar.Name() # "=" # osysvar.Variable() # "=" # osysvar.Value()); +} + ) + }, + "GetDeviceInfo" => { + description => "Query device info", + syntax => "devname, ccuget", + parameters => 2, + code => qq( +string chnid; +string sDPId; +object odev = dom.GetObject ("\$devname"); +if (odev) { + foreach (chnid, odev.Channels()) { + object ochn = dom.GetObject(chnid); + if (ochn) { + foreach(sDPId, ochn.DPs()) { + object oDP = dom.GetObject(sDPId); + if (oDP) { + integer op = oDP.Operations(); + string flags = ""; + if (OPERATION_READ & op) { flags = flags # "R"; } + if (OPERATION_WRITE & op) { flags = flags # "W"; } + if (OPERATION_EVENT & op) { flags = flags # "E"; } + WriteLine ("C;" # ochn.Address() # ";" # ochn.Name() # ";" # oDP.Name() # ";" # oDP.ValueType() # ";" # oDP.\$ccuget() # ";" # flags); + } + } + } + } +} +else { + WriteLine ("ERROR: Device not found"); +} + ) + }, + "GetDeviceList" => { + description => "Query CCU devices and channels", + syntax => "", + parameters => 0, + code => qq( +string devid; +string chnid; +foreach(devid, root.Devices().EnumUsedIDs()) { + object odev=dom.GetObject(devid); + string intid=odev.Interface(); + string intna=dom.GetObject(intid).Name(); + integer cc=0; + foreach (chnid, odev.Channels()) { + object ochn=dom.GetObject(chnid); + WriteLine("C;" # ochn.Address() # ";" # ochn.Name()); + cc=cc+1; + } + WriteLine("D;" # intna # ";" # odev.Address() # ";" # odev.Name() # ";" # odev.HssType() # ";" # cc); +} + ) + }, + "GetDatapointsByChannel" => { + description => "Query datapoints of channel list", + syntax => "list, ccuget", + parameters => 2, + code => qq( +string sDPId; +string sChnName; +string sChnList = "\$list"; +integer c = 0; +foreach (sChnName, sChnList.Split(",")) { + object oChannel = dom.GetObject (sChnName); + if (oChannel) { + foreach(sDPId, oChannel.DPs()) { + object oDP = dom.GetObject(sDPId); + if (oDP) { + if (OPERATION_READ & oDP.Operations()) { + WriteLine (sChnName # "=" # oDP.Name() # "=" # oDP.\$ccuget()); + c = c+1; + } + } + } + } +} +WriteLine (c); + ) + }, + "GetDatapointsByDevice" => { + description => "Query datapoints of device list", + syntax => "list, ccuget", + parameters => 2, + code => qq( +string chnid; +string sDPId; +string sDevName; +string sDevList = "\$list"; +integer c = 0; +foreach (sDevName, sDevList.Split(",")) { + object odev = dom.GetObject (sDevName); + if (odev) { + foreach (chnid, odev.Channels()) { + object ochn = dom.GetObject(chnid); + if (ochn) { + foreach(sDPId, ochn.DPs()) { + object oDP = dom.GetObject(sDPId); + if (oDP) { + if (OPERATION_READ & oDP.Operations()) { + WriteLine (ochn.Name() # "=" # oDP.Name() # "=" # oDP.\$ccuget()); + c = c+1; + } + } + } + } + } + } +} +WriteLine (c); + ) } );