diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm index 57f27d10a..2291477a0 100644 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm @@ -4,7 +4,7 @@ # # $Id: 88_HMCCU.pm 18745 2019-02-26 17:33:23Z zap $ # -# Version 4.4.038 +# Version 4.4.048 # # Module for communication between FHEM and Homematic CCU2/3. # @@ -33,7 +33,6 @@ use strict; use warnings; # use Data::Dumper; use IO::File; -use Fcntl 'SEEK_END', 'SEEK_SET', 'O_CREAT', 'O_RDWR'; use Encode qw(decode encode); use RPC::XML::Client; use RPC::XML::Server; @@ -43,6 +42,7 @@ use HMCCUConf; # Import configuration data my $HMCCU_STATECONTROL = \%HMCCUConf::HMCCU_STATECONTROL; +my $HMCCU_READINGS = \%HMCCUConf::HMCCU_READINGS; my $HMCCU_ROLECMDS = \%HMCCUConf::HMCCU_ROLECMDS; my $HMCCU_ATTR = \%HMCCUConf::HMCCU_ATTR; my $HMCCU_CONVERSIONS = \%HMCCUConf::HMCCU_CONVERSIONS; @@ -55,7 +55,7 @@ my %HMCCU_CUST_CHN_DEFAULTS; my %HMCCU_CUST_DEV_DEFAULTS; # HMCCU version -my $HMCCU_VERSION = '4.4.038'; +my $HMCCU_VERSION = '4.4.048'; # Timeout for CCU requests my $HMCCU_TIMEOUT_REQUEST = 4; @@ -92,11 +92,11 @@ my %HMCCU_RPC_SSL = ( ); # Default values for delayed initialization during FHEM startup -my $HMCCU_INIT_INTERVAL0 = 12; +my $HMCCU_INIT_INTERVAL0 = 12; my $HMCCU_CCU_PING_TIMEOUT = 1; -my $HMCCU_CCU_BOOT_DELAY = 180; +my $HMCCU_CCU_BOOT_DELAY = 180; my $HMCCU_CCU_DELAYED_INIT = 59; -my $HMCCU_CCU_RPC_OFFSET = 20; +my $HMCCU_CCU_RPC_OFFSET = 20; # Datapoint operations my $HMCCU_OPER_READ = 1; @@ -153,7 +153,6 @@ sub HMCCU_Shutdown ($); sub HMCCU_Set ($@); sub HMCCU_Get ($@); sub HMCCU_Attr ($@); -sub HMCCU_AttrInterfacesPorts ($$$); sub HMCCU_Notify ($$); sub HMCCU_Detail ($$$$); sub HMCCU_PostInit ($); @@ -172,13 +171,13 @@ sub HMCCU_SetDefaults ($); # Status and logging functions sub HMCCU_Trace ($$$); sub HMCCU_Log ($$$;$); +sub HMCCU_LogDisplay ($$$;$); sub HMCCU_LogError ($$$); sub HMCCU_SetError ($@); sub HMCCU_SetState ($@); sub HMCCU_SetRPCState ($@); # Filter and modify readings -sub HMCCU_CalculateScaleValue ($$$$;$$$); sub HMCCU_FilterReading ($$$;$); sub HMCCU_FormatReadingValue ($$$); sub HMCCU_GetReadingName ($$$$$$$;$); @@ -201,11 +200,11 @@ sub HMCCU_UpdateParamsetReadings ($$$;$); sub HMCCU_UpdateSingleDatapoint ($$$$); # RPC functions +sub HMCCU_CreateRPCDevice ($$$$); sub HMCCU_EventsTimedOut ($); sub HMCCU_GetRPCCallbackURL ($$$$$); sub HMCCU_GetRPCDevice ($$$); -sub HMCCU_GetRPCInterfaceList ($); -sub HMCCU_GetRPCPortList ($); +sub HMCCU_GetRPCInterfaceList ($;$); sub HMCCU_GetRPCServerInfo ($$$); sub HMCCU_IsRPCServerRunning ($;$); sub HMCCU_IsRPCType ($$$); @@ -245,7 +244,11 @@ sub HMCCU_CreateDevice ($$$$$); sub HMCCU_DeleteDevice ($); sub HMCCU_DeviceDescToStr ($$); sub HMCCU_ExecuteRoleCommand ($@); -sub HMCCU_ExecuteGetParameterCommand ($$$$); +sub HMCCU_ExecuteGetDeviceInfoCommand ($@); +sub HMCCU_ExecuteGetParameterCommand ($@); +sub HMCCU_ExecuteSetClearCommand ($@); +sub HMCCU_ExecuteSetDatapointCommand ($@); +sub HMCCU_ExecuteSetParameterCommand ($@); sub HMCCU_DisplayWeekProgram ($;$); sub HMCCU_ExistsDeviceModel ($$$;$); sub HMCCU_FindParamDef ($$$); @@ -263,6 +266,7 @@ sub HMCCU_GetDeviceDesc ($$;$); sub HMCCU_GetDeviceIdentifier ($$;$$); sub HMCCU_GetDeviceInfo ($$;$); sub HMCCU_GetDeviceInterface ($$;$); +sub HMCCU_GetInterfaceList ($); sub HMCCU_GetDeviceList ($); sub HMCCU_GetDeviceModel ($$$;$); sub HMCCU_GetDeviceName ($$;$); @@ -271,7 +275,7 @@ sub HMCCU_GetFirmwareVersions ($$); sub HMCCU_GetGroupMembers ($$); sub HMCCU_GetMatchingDevices ($$$$); sub HMCCU_GetParamDef ($$$;$); -sub HMCCU_GetParamValue ($$$$$); +sub HMCCU_GetParamValueConversion ($$$$$); sub HMCCU_GetReceivers ($$$); sub HMCCU_IsValidChannel ($$$); sub HMCCU_IsValidDevice ($$$); @@ -286,6 +290,7 @@ sub HMCCU_UpdateDevice ($$); sub HMCCU_UpdateDeviceRoles ($$;$$); sub HMCCU_UpdateDeviceTable ($$); sub HMCCU_UpdateRoleCommands ($$;$); +sub HMCCU_UpdateAdditionalCommands ($$;$$); # Handle datapoints sub HMCCU_FindDatapoint ($$$$$); @@ -293,7 +298,7 @@ sub HMCCU_GetDatapoint ($@); sub HMCCU_GetDatapointAttr ($$$$$); sub HMCCU_GetDatapointList ($;$$); sub HMCCU_GetSpecialDatapoints ($); -sub HMCCU_GetStateValues ($$;$); +sub HMCCU_GetStateValues ($;$$); sub HMCCU_GetValidDatapoints ($$$$$); sub HMCCU_IsValidDatapoint ($$$$$); sub HMCCU_SetDefaultAttributes ($;$); @@ -322,7 +327,6 @@ sub HMCCU_EncodeEPDisplay ($); sub HMCCU_ExprMatch ($$$); sub HMCCU_ExprNotMatch ($$$); sub HMCCU_FlagsToStr ($$$;$$); -# sub HMCCU_GetAdditionalCommands ($;$); sub HMCCU_GetDeviceStates ($); sub HMCCU_GetDutyCycle ($); sub HMCCU_GetHMState ($$;$); @@ -361,15 +365,13 @@ sub HMCCU_Initialize ($) $hash->{parseParams} = 1; $hash->{AttrList} = 'stripchar stripnumber ccuaggregate:textField-long'. - ' ccudefaults rpcinterfaces:multiple-strict,'.join(',',sort keys %HMCCU_RPC_PORT). + ' ccudefaults'. ' ccudef-hmstatevals:textField-long ccudef-substitute:textField-long'. - ' ccudef-readingfilter:textField-long'. ' ccudef-readingformat:name,namelc,address,addresslc,datapoint,datapointlc'. ' ccudef-stripnumber ccuReadingPrefix'. ' ccuflags:multiple-strict,procrpc,dptnocheck,logCommand,noagg,nohmstate,updGroupMembers,'. 'logEvents,noEvents,noInitialUpdate,noReadings,nonBlocking,reconnect,logPong,trace,logEnhanced'. - ' ccuReqTimeout ccuGetVars rpcinterval:2,3,5,7,10 rpcqueue rpcPingCCU'. - ' rpcport:multiple-strict,'.join(',',sort keys %HMCCU_RPC_NUMPORT). + ' ccuReqTimeout ccuGetVars rpcPingCCU'. ' rpcserver:on,off rpcserveraddr rpcserverport rpctimeout rpcevtimeout substitute'. ' ccuget:Value,State '. $readingFnAttributes; @@ -383,8 +385,9 @@ sub HMCCU_Define ($$) { my ($hash, $a, $h) = @_; my $name = $hash->{NAME}; + my $usage = "Usage: define $name HMCCU {NameOrIP} [{ccunum}] [sync] ccudelay={time} waitforccu={time} delayedinit={time}"; - return 'Specify CCU hostname or IP address as a parameter' if (scalar(@$a) < 3); + return $usage if (scalar(@$a) < 3); # Setup http or ssl connection if ($$a[2] =~ /^(https?):\/\/(.+)/) { @@ -396,50 +399,69 @@ sub HMCCU_Define ($$) $hash->{host} = $$a[2]; } - $hash->{Clients} = ':HMCCUDEV:HMCCUCHN:HMCCURPC:HMCCURPCPROC:'; - $hash->{hmccu}{ccu}{delay} = exists($h->{ccudelay}) ? $h->{ccudelay} : $HMCCU_CCU_BOOT_DELAY; - $hash->{hmccu}{ccu}{timeout} = exists($h->{waitforccu}) ? $h->{waitforccu} : $HMCCU_CCU_PING_TIMEOUT; + $hash->{Clients} = ':HMCCUDEV:HMCCUCHN:HMCCURPCPROC:'; + $hash->{hmccu}{ccu}{delay} = $h->{ccudelay} // $HMCCU_CCU_BOOT_DELAY; + $hash->{hmccu}{ccu}{timeout} = $h->{waitforccu} // $HMCCU_CCU_PING_TIMEOUT; $hash->{hmccu}{ccu}{delayed} = 0; + $hash->{hmccu}{ccu}{sync} = 0; - # Check if TCL-Rega process is running on CCU (CCU is reachable) if (exists($h->{delayedinit}) && $h->{delayedinit} > 0) { - return "Value for delayed initialization must be greater than $HMCCU_CCU_DELAYED_INIT" - if ($h->{delayedinit} <= $HMCCU_CCU_DELAYED_INIT); - $hash->{hmccu}{ccu}{delay} = $h->{delayedinit}; - $hash->{ccustate} = 'unreachable'; - HMCCU_Log ($hash, 1, 'Forced delayed initialization'); + if (!$init_done) { + # Forced delayed initialization + return "Value for delayed initialization must be greater than $HMCCU_CCU_DELAYED_INIT" + if ($h->{delayedinit} <= $HMCCU_CCU_DELAYED_INIT); + $hash->{hmccu}{ccu}{delay} = $h->{delayedinit}; + $hash->{ccustate} = 'unreachable'; + HMCCU_Log ($hash, 1, 'Forced delayed initialization'); + } + else { + HMCCU_LogDisplay ($hash, 2, 'Forced delayed initialization is done during FHEM start'); + } } else { - $hash->{ccustate} = HMCCU_TCPPing ($hash->{host}, $HMCCU_REGA_PORT{$hash->{prot}}, $hash->{hmccu}{ccu}{timeout}) ? - 'active' : 'unreachable'; - HMCCU_Log ($hash, 1, "CCU port ".$HMCCU_REGA_PORT{$hash->{prot}}.' is '.$hash->{ccustate}); + # Check if TCL-Rega process is running on CCU (CCU is reachable) + if (HMCCU_TCPPing ($hash->{host}, $HMCCU_REGA_PORT{$hash->{prot}}, $hash->{hmccu}{ccu}{timeout})) { + $hash->{ccustate} = 'active'; + HMCCU_Log ($hash, 1, "CCU port ".$HMCCU_REGA_PORT{$hash->{prot}}.' is reachable'); + } + else { + $hash->{ccustate} = 'unreachable'; + HMCCU_LogDisplay ($hash, 1, "CCU port ".$HMCCU_REGA_PORT{$hash->{prot}}.' is not reachable'); + } } # Get CCU IP address $hash->{ccuip} = HMCCU_ResolveName ($hash->{host}, 'N/A'); - # Get CCU number (if there is more than one) - if (scalar(@$a) >= 4) { - return 'CCU number must be in range 1-9' if ($$a[3] < 1 || $$a[3] > 9); - $hash->{CCUNum} = $$a[3]; + # Parse optional command line parameters + for (my $i=3; $i 9); + $hash->{CCUNum} = $$a[$i]; + } + elsif ($$a[$i] eq 'sync') { + $hash->{hmccu}{ccu}{sync} = 1; + } + else { + return $usage; + } } - else { + + # Get CCU number (if there is more than one) + if (!exists($hash->{CCUNum})) { # Count CCU devices - my $ccucount = 0; + $hash->{CCUNum} = 1; foreach my $d (keys %defs) { my $ch = $defs{$d}; - $ccucount++ if (exists($ch->{TYPE}) && $ch->{TYPE} eq 'HMCCU' && $ch != $hash); + $hash->{CCUNum}++ if (exists($ch->{TYPE}) && $ch->{TYPE} eq 'HMCCU' && $ch != $hash); } - $hash->{CCUNum} = $ccucount+1; } - $hash->{version} = $HMCCU_VERSION; - $hash->{ccutype} = 'CCU2/3'; - $hash->{RPCState} = 'inactive'; - $hash->{NOTIFYDEV} = 'global,TYPE=(HMCCU|HMCCUDEV|HMCCUCHN)'; - $hash->{hmccu}{defInterface} = $HMCCU_RPC_PRIORITY[0]; - $hash->{hmccu}{defPort} = $HMCCU_RPC_PORT{$hash->{hmccu}{defInterface}}; - $hash->{hmccu}{rpcports} = undef; + $hash->{version} = $HMCCU_VERSION; + $hash->{ccutype} = 'CCU2/3'; + $hash->{RPCState} = 'inactive'; + $hash->{NOTIFYDEV} = 'global,TYPE=(HMCCU|HMCCUDEV|HMCCUCHN)'; + $hash->{hmccu}{rpcports} = undef; HMCCU_Log ($hash, 1, "Initialized version $HMCCU_VERSION"); @@ -461,7 +483,9 @@ sub HMCCU_Define ($$) HMCCU_UpdateReadings ($hash, { 'state' => 'Initialized', 'rpcstate' => 'inactive' }); - $attr{$name}{stateFormat} = 'rpcstate/state'; + if ($init_done) { + $attr{$name}{stateFormat} = 'rpcstate/state'; + } return undef; } @@ -487,24 +511,40 @@ sub HMCCU_InitDevice ($) return HMCCU_Log ($hash, 1, "CCU port ".$HMCCU_REGA_PORT{$hash->{prot}}." is not reachable", 1); } } - + +# my $ifNum = HMCCU_GetInterfaceList ($hash); +# if ($ifNum > 0) { my ($devcnt, $chncnt, $ifcount, $prgcount, $gcount) = HMCCU_GetDeviceList ($hash); - if ($devcnt >= 0) { - return HMCCU_Log ($hash, 1, [ + if ($ifcount > 0) { + $attr{$name}{userattr} = 'rpcinterfaces:multiple-strict,'.$hash->{ccuinterfaces}; +# addToDevAttrList ($name, 'rpcinterfaces:multiple-strict,'.$hash->{ccuinterfaces}); + + HMCCU_Log ($hash, 1, [ "Read $devcnt devices with $chncnt channels from CCU $host", - "Read $ifcount interfaces from CCU $host", "Read $prgcount programs from CCU $host", "Read $gcount virtual groups from CCU $host" - ], 0); + ]); + + # Interactive device definition + if ($init_done && $hash->{hmccu}{ccu}{delayed} == 0) { + # Force sync with CCU during interactive device definition + if ($hash->{hmccu}{ccu}{sync} == 1) { + HMCCU_Log ($hash, 1, 'Reading device config from CCU. This may take a couple of seconds ...'); +# my ($devcnt, $chncnt, $ifcount, $prgcount, $gcount) = HMCCU_GetDeviceList ($hash); + my ($cDev, $cPar, $cLnk) = HMCCU_GetDeviceConfig ($hash); + HMCCU_Log ($hash, 2, "Read RPC device configuration: devices/channels=$cDev parametersets=$cPar links=$cLnk"); + } + } } else { - return HMCCU_Log ($hash, 1, "Error while reading device list from CCU $host", 2); + return HMCCU_Log ($hash, 1, "No RPC interfaces found on CCU $host", 2); } } ###################################################################### # Tasks to be executed after all devices have been defined. Executed -# as timer function. +# as timer function after FHEM has been initialized and startup is +# complete. # Read device configuration from CCU # Start RPC servers ###################################################################### @@ -512,12 +552,20 @@ sub HMCCU_InitDevice ($) sub HMCCU_PostInit ($) { my ($hash) = @_; - my $name = $hash->{NAME}; + my $host = $hash->{host}; + if ($hash->{ccustate} eq 'active') { - my $rpcServer = AttrVal ($name, 'rpcserver', 'off'); + my $rpcServer = AttrVal ($hash->{NAME}, 'rpcserver', 'off'); HMCCU_Log ($hash, 1, 'Reading device config from CCU. This may take a couple of seconds ...'); +# my ($devcnt, $chncnt, $ifcount, $prgcount, $gcount) = HMCCU_GetDeviceList ($hash); +# HMCCU_Log ($hash, 1, [ +# "Read $devcnt devices with $chncnt channels from CCU $host", +# "Read $ifcount interfaces from CCU $host", +# "Read $prgcount programs from CCU $host", +# "Read $gcount virtual groups from CCU $host" +# ]); my ($cDev, $cPar, $cLnk) = HMCCU_GetDeviceConfig ($hash); HMCCU_Log ($hash, 2, "Read device configuration: devices/channels=$cDev parametersets=$cPar links=$cLnk"); @@ -579,11 +627,8 @@ sub HMCCU_Attr ($@) elsif ($attrname eq 'rpcdevice') { return "HMCCU: Attribute rpcdevice is depricated. Please remove it"; } - elsif ($attrname eq 'rpcinterfaces' || $attrname eq 'rpcport') { - if ($hash->{hmccu}{ccu}{delayed} == 0) { - my $msg = HMCCU_AttrInterfacesPorts ($hash, $attrname, $attrval); - return $msg if ($msg ne ''); - } + elsif ($attrname eq 'rpcport') { + return 'HMCCU: Attribute rpcport is no longer supported. Use rpcinterfaces instead'; } } elsif ($cmd eq 'del') { @@ -593,60 +638,11 @@ sub HMCCU_Attr ($@) elsif ($attrname eq 'ccuGetVars') { RemoveInternalTimer ($hash, "HMCCU_UpdateVariables"); } - elsif ($attrname eq 'rpcdevice') { - delete $hash->{RPCDEV} if (exists($hash->{RPCDEV})); - } - elsif ($attrname eq 'rpcport' || $attrname eq 'rpcinterfaces') { - my ($defInterface, $defPort) = HMCCU_GetDefaultInterface ($hash); - $hash->{hmccu}{rpcports} = undef; - delete $attr{$name}{'rpcinterfaces'} if ($attrname eq 'rpcport'); - delete $attr{$name}{'rpcport'} if ($attrname eq 'rpcinterfaces'); - } } return undef; } -###################################################################### -# Set attributes rpcinterfaces and rpcport. -# Return empty string on success or error message on error. -###################################################################### - -sub HMCCU_AttrInterfacesPorts ($$$) -{ - my ($hash, $attr, $attrval) = @_; - my $name = $hash->{NAME}; - - if ($attr eq 'rpcinterfaces') { - my @ilist = split (',', $attrval); - my @plist = (); - foreach my $p (@ilist) { - my ($pn, $dc) = HMCCU_GetRPCServerInfo ($hash, $p, 'port,devcount'); - return "Illegal RPC interface $p" if (!defined($pn)); - return "No devices assigned to interface $p" if ($dc == 0); - push (@plist, $pn); - } - return 'No RPC interface specified' if (scalar(@plist) == 0); - $hash->{hmccu}{rpcports} = join (',', @plist); - $attr{$name}{'rpcport'} = $hash->{hmccu}{rpcports}; - } - elsif ($attr eq 'rpcport') { - my @plist = split (',', $attrval); - my @ilist = (); - foreach my $p (@plist) { - my ($in, $dc) = HMCCU_GetRPCServerInfo ($hash, $p, 'name,devcount'); - return "Illegal RPC port $p" if (!defined($in)); - return "No devices assigned to interface $in" if ($dc == 0); - push (@ilist, $in); - } - return 'No RPC port specified' if (scalar(@ilist) == 0); - $hash->{hmccu}{rpcports} = $attrval; - $attr{$name}{'rpcinterfaces'} = join (',', @ilist); - } - - return ''; -} - ###################################################################### # Parse aggregation rules for readings. # Syntax of aggregation rule is: @@ -670,9 +666,7 @@ sub HMCCU_AggregationRules ($$) return 0 if ($rulestr eq ''); # Delete existing aggregation rules - if (exists($hash->{hmccu}{agg})) { - delete $hash->{hmccu}{agg}; - } + if (exists($hash->{hmccu}{agg})) { delete $hash->{hmccu}{agg}; } my @pars = ('name', 'filter', 'if', 'else'); @@ -755,7 +749,7 @@ sub HMCCU_AggregationRules ($$) $hash->{hmccu}{agg}{$fname}{fcoll} = $fcoll; $hash->{hmccu}{agg}{$fname}{fdflt} = $fdflt; } - + return 1; } @@ -963,27 +957,27 @@ sub HMCCU_GetDefaults ($$) } } else { - $result = "HMCCU Channels:\n------------------------------\n"; + $result = "HMCCU Channels:\n".('-' x 30)."\n"; foreach my $deftype (sort keys %{$HMCCU_CHN_DEFAULTS}) { my $tlist = $deftype; $tlist =~ s/\|/,/g; $result .= $HMCCU_CHN_DEFAULTS->{$deftype}{_description}." ($tlist), channels ". $HMCCU_CHN_DEFAULTS->{$deftype}{_channels}."\n"; } - $result .= "\nHMCCU Devices:\n------------------------------\n"; + $result .= "\nHMCCU Devices:\n".('-' x 30)."\n"; foreach my $deftype (sort keys %{$HMCCU_DEV_DEFAULTS}) { my $tlist = $deftype; $tlist =~ s/\|/,/g; $result .= $HMCCU_DEV_DEFAULTS->{$deftype}{_description}." ($tlist)\n"; } - $result .= "\nCustom Channels:\n-----------------------------\n"; + $result .= "\nCustom Channels:\n".('-' x 30)."\n"; foreach my $deftype (sort keys %HMCCU_CUST_CHN_DEFAULTS) { my $tlist = $deftype; $tlist =~ s/\|/,/g; $result .= $HMCCU_CUST_CHN_DEFAULTS{$deftype}{_description}." ($tlist), channels ". $HMCCU_CUST_CHN_DEFAULTS{$deftype}{_channels}."\n"; } - $result .= "\nCustom Devices:\n-----------------------------\n"; + $result .= "\nCustom Devices:\n".('-' x 30)."\n"; foreach my $deftype (sort keys %HMCCU_CUST_DEV_DEFAULTS) { my $tlist = $deftype; $tlist =~ s/\|/,/g; @@ -1037,7 +1031,7 @@ sub HMCCU_Notify ($$) else { return if ($devtype ne 'HMCCUDEV' && $devtype ne 'HMCCUCHN'); my ($r, $v) = split (": ", $event); - return if (!defined($v) || HMCCU_IsFlag ($name, /noagg/)); + return if (!defined($v) || HMCCU_IsFlag ($name, 'noagg')); foreach my $rule (keys %{$hash->{hmccu}{agg}}) { my $ftype = $hash->{hmccu}{agg}{$rule}{ftype}; @@ -1112,8 +1106,8 @@ sub HMCCU_AggregateReadings ($$) my $ct = $ch->{TYPE}; my $fmatch = ''; - if ($r->{ftype} eq 'name') { $fmatch = $cn; } - elsif ($r->{ftype} eq 'type') { $fmatch = $ch->{ccutype}; } + if ($r->{ftype} eq 'name') { $fmatch = $cn // ''; } + elsif ($r->{ftype} eq 'type') { $fmatch = $ch->{ccutype} // ''; } elsif ($r->{ftype} eq 'group') { $fmatch = AttrVal ($cn, 'group', ''); } elsif ($r->{ftype} eq 'room') { $fmatch = AttrVal ($cn, 'room', ''); } elsif ($r->{ftype} eq 'alias') { $fmatch = AttrVal ($cn, 'alias', ''); } @@ -1129,23 +1123,16 @@ sub HMCCU_AggregateReadings ($$) my $f = 0; if (($r->{fcond} eq 'any' || $r->{fcond} eq 'all') && $rv =~ /$r->{ftrue}/) { - $mc++; - $f = 1; + $mc++; $f = 1; } if ($r->{fcond} eq 'max' && $rv > $resval) { - $resval = $rv; - $mc = 1; - $f = 1; + $resval = $rv; $mc = 1; $f = 1; } if ($r->{fcond} eq 'min' && $rv < $resval) { - $resval = $rv; - $mc = 1; - $f = 1; + $resval = $rv; $mc = 1; $f = 1; } if ($r->{fcond} eq 'sum' || $r->{fcond} eq 'avg') { - $resval += $rv; - $mc++; - $f = 1; + $resval += $rv; $mc++; $f = 1; } if ($r->{fcond} =~ /^(gt|lt|ge|le)$/ && (!HMCCU_IsFltNum ($rv) || !HMCCU_IsFltNum($r->{ftrue}))) { HMCCU_Log ($hash, 4, "Aggregation value $rv of reading $cn.$r or $r->{ftrue} is not numeric"); @@ -1155,8 +1142,7 @@ sub HMCCU_AggregateReadings ($$) ($r->{fcond} eq 'lt' && $rv < $r->{ftrue}) || ($r->{fcond} eq 'ge' && $rv >= $r->{ftrue}) || ($r->{fcond} eq 'le' && $rv <= $r->{ftrue})) { - $mc++; - $f = 1; + $mc++; $f = 1; } if ($f) { $rl .= ($mc > 1 ? ",$r->{fcoll}" : $r->{fcoll}); @@ -1189,10 +1175,10 @@ sub HMCCU_AggregateReadings ($$) } } - if ($r->{fcond} eq 'any') { $result = $mc > 0 ? $r->{ftrue} : $r->{felse}; } + if ($r->{fcond} eq 'any') { $result = $mc > 0 ? $r->{ftrue} : $r->{felse}; } elsif ($r->{fcond} eq 'all') { $result = $mc == $dc ? $r->{ftrue} : $r->{felse}; } - elsif ($r->{fcond} =~ /^(min|max|sum)$/) { $result = $mc > 0 ? $resval : $r->{felse}; } - elsif ($r->{fcond} eq 'avg') { $result = $mc > 0 ? $resval/$mc : $r->{felse}; } + elsif ($r->{fcond} =~ /^(min|max|sum)$/) { $result = $mc > 0 ? $resval : $r->{felse}; } + elsif ($r->{fcond} eq 'avg') { $result = $mc > 0 ? $resval/$mc : $r->{felse}; } elsif ($r->{fcond} =~ /^(gt|lt|ge|le)$/) { $result = $mc; } HMCCU_UpdateReadings ($hash, { $r->{fpref}.'state' => $result, $r->{fpref}.'match' => $mc, @@ -1216,9 +1202,10 @@ sub HMCCU_Undef ($$) # Delete reference to IO module in client devices my @keylist = keys %defs; foreach my $d (@keylist) { - if (exists ($defs{$d}) && exists($defs{$d}{IODev}) && - $defs{$d}{IODev} == $hash) { - delete $defs{$d}{IODev}; + my $ch = $defs{$d} // next; + if (exists ($ch->{TYPE}) && $ch->{TYPE} =~ /^(HMCCUDEV|HMCCUCHN|HMCCURPCPROC)$/ && + exists($ch->{IODev}) && $ch->{IODev} == $hash) { + delete $defs{$d}{IODev}; } } @@ -1256,7 +1243,6 @@ sub HMCCU_DelayedShutdown ($) sub HMCCU_Shutdown ($) { my ($hash) = @_; - my $name = $hash->{NAME}; # Shutdown RPC server if (!exists($hash->{hmccu}{delayedShutdown})) { @@ -1282,14 +1268,15 @@ sub HMCCU_Set ($@) my ($hash, $a, $h) = @_; my $name = shift @$a; my $opt = shift @$a // return 'No set command specified'; - my $options = "var clear delete execute hmscript cleardefaults:noArg datapoint defaults:noArg ". + my $options = "var clear delete execute hmscript cleardefaults:noArg datapoint ". "importdefaults rpcregister:all rpcserver:on,off ackmessages:noArg authentication ". - "prgActivate prgDeactivate"; + "prgActivate prgDeactivate on:noArg off:noArg"; $opt = lc($opt); - my @ifList = HMCCU_GetRPCInterfaceList ($hash); + my $interfaces = HMCCU_GetRPCInterfaceList ($hash, 1); + my @ifList = keys %$interfaces; if (scalar(@ifList) > 0) { - my $ifStr = join (',', @ifList); + my $ifStr = join(',', @ifList); $options =~ s/rpcregister:all/rpcregister:all,$ifStr/; } my $host = $hash->{host}; @@ -1342,7 +1329,6 @@ sub HMCCU_Set ($@) my $objvalue = shift @$a // return HMCCU_SetError ($hash, $usage); $objname =~ s/$stripchar$// if ($stripchar ne ''); - $objvalue =~ s/\\_/%20/g; $h->{name} = $objname if (!defined($h) && defined($vartype)); $result = HMCCU_SetVariable ($hash, $objname, $objvalue, $vartype, $h); @@ -1511,8 +1497,7 @@ sub HMCCU_Set ($@) # If no parameter is specified list available script functions if (!defined($script)) { - $response = "Available HomeMatic script functions:\n". - "-------------------------------------\n"; + $response = "Available HomeMatic script functions:\n".('-' x 37)."\n"; foreach my $scr (keys %{$HMCCU_SCRIPTS}) { $response .= "$scr ".$HMCCU_SCRIPTS->{$scr}{syntax}."\n". $HMCCU_SCRIPTS->{$scr}{description}."\n\n"; @@ -1554,9 +1539,9 @@ sub HMCCU_Set ($@) return defined ($dump) ? $response : undef; } elsif ($opt eq 'rpcregister') { - my $ifName = shift @$a; + my $ifName = shift @$a // 'all'; $result = ''; - @ifList = (defined($ifName) && $ifName ne 'all') ? ($ifName) : HMCCU_GetRPCInterfaceList ($hash); + @ifList = ($ifName) if ($ifName ne 'all'); foreach my $i (@ifList) { my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $i); @@ -1569,12 +1554,11 @@ sub HMCCU_Set ($@) } return HMCCU_SetState ($hash, 'OK', $result); } - elsif ($opt eq 'rpcserver') { - my $action = shift @$a; -# $action = shift @$a if ($action eq $opt); + elsif ($opt =~ /^(rpcserver|on|off)$/) { + my $action = $opt eq 'rpcserver' ? shift @$a : $opt; $usage = "Usage: set $name $opt {'on'|'off'}"; - return HMCCU_SetError ($hash, $usage) + return HMCCU_SetError ($hash, "Usage: set $name [rpcserver] {'on'|'off'}") if (!defined($action) || $action !~ /^(on|off)$/); if ($action eq 'on') { @@ -1593,11 +1577,6 @@ sub HMCCU_Set ($@) return HMCCU_SetError ($hash, -2, $response) if ($response =~ /^ERROR:/); return HMCCU_SetState ($hash, "OK", "Unreach errors in CCU cleared"); } - elsif ($opt eq 'defaults') { - my $rc = HMCCU_SetDefaults ($hash); - return HMCCU_SetError ($hash, 'HMCCU: No default attributes found') if ($rc == 0); - return HMCCU_SetState ($hash, "OK"); - } elsif ($opt eq 'cleardefaults') { %HMCCU_CUST_CHN_DEFAULTS = (); %HMCCU_CUST_DEV_DEFAULTS = (); @@ -1634,11 +1613,11 @@ sub HMCCU_Get ($@) my $opt = shift @$a // return 'No get command specified'; $opt = lc($opt); - my $options = "ccuconfig create defaults:noArg exportDefaults dump dutycycle:noArg vars update". - " updateccu deviceDesc paramsetDesc firmware rpcEvents:noArg rpcState:noArg deviceInfo". + my $options = "create defaults:noArg exportDefaults dutycycle:noArg vars update". + " updateCCU deviceDesc paramsetDesc firmware rpcEvents:noArg rpcState:noArg deviceInfo". " ccuMsg:alarm,service ccuConfig:noArg"; + my $usage = "HMCCU: Unknown argument $opt, choose one of $options"; - my $host = $hash->{host}; return undef if ($hash->{hmccu}{ccu}{delayed} || $hash->{ccustate} ne 'active'); return 'HMCCU: CCU busy, choose one of rpcstate:noArg' @@ -1647,75 +1626,36 @@ sub HMCCU_Get ($@) my $ccuflags = HMCCU_GetFlags ($name); my $ccureadings = AttrVal ($name, "ccureadings", $ccuflags =~ /noReadings/ ? 0 : 1); + my $interfaces = HMCCU_GetRPCInterfaceList ($hash, 1); + my @ifList = keys %$interfaces; + my $readname; my $readaddr; my $result = ''; my $rc; - - if ($opt eq 'dump') { - $usage = "Usage: get $name dump {'datapoints'|'devtypes'} [filter]"; - my $content = shift @$a // return HMCCU_SetError ($hash, $usage); - my $filter = shift @$a // '.*'; - - my %foper = (1, "R", 2, "W", 4, "E", 3, "RW", 5, "RE", 6, "WE", 7, "RWE"); - my %ftype = (2, "B", 4, "F", 16, "I", 20, "S"); - - if ($content eq 'devtypes') { - foreach my $devtype (sort keys %{$hash->{hmccu}{dp}}) { - $result .= $devtype."\n" if ($devtype =~ /$filter/); - } - } - elsif ($content eq 'datapoints') { - foreach my $devtype (sort keys %{$hash->{hmccu}{dp}}) { - next if ($devtype !~ /$filter/); - foreach my $chn (sort keys %{$hash->{hmccu}{dp}{$devtype}{ch}}) { - foreach my $dpt (sort keys %{$hash->{hmccu}{dp}{$devtype}{ch}{$chn}}) { - my $t = $hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dpt}{type}; - my $o = $hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dpt}{oper}; - $result .= $devtype.".".$chn.".".$dpt." [". - (exists($ftype{$t}) ? $ftype{$t} : $t)."] [". - (exists($foper{$o}) ? $foper{$o} : $o)."]\n"; - } - } - } - } - else { - return HMCCU_SetError ($hash, $usage); - } - - return HMCCU_SetState ($hash, 'OK', ($result eq '') ? 'No data found' : $result); - } - elsif ($opt eq 'vars') { - $usage = "Usage: get $name vars {regexp}[,...]"; - my $varname = shift @$a // return HMCCU_SetError ($hash, $usage); - + + if ($opt eq 'vars') { + my $varname = shift @$a // return HMCCU_SetError ($hash, "Usage: get $name vars {regexp}[,...]"); ($rc, $result) = HMCCU_GetVariables ($hash, $varname); return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0); - return HMCCU_SetState ($hash, 'OK', $ccureadings ? undef : $result); + return HMCCU_SetState ($hash, 'OK', $result); } elsif ($opt eq 'update' || $opt eq 'updateccu') { - $usage = "Usage: get $name $opt [device-expr [{'State'|'Value'}]]"; my $devexp = shift @$a // '.*'; my $ccuget = shift @$a // 'Attr'; - return HMCCU_SetError ($hash, $usage) if ($ccuget !~ /^(Attr|State|Value)$/); + return HMCCU_SetError ($hash, "Usage: get $name $opt [device-expr [{'State'|'Value'}]]") + if ($ccuget !~ /^(Attr|State|Value)$/); HMCCU_UpdateClients ($hash, $devexp, $ccuget, ($opt eq 'updateccu') ? 1 : 0); return HMCCU_SetState ($hash, 'OK'); } elsif ($opt eq 'deviceinfo') { - $usage = "Usage: get $name $opt device [{'State'|'Value'}]"; - my $device = shift @$a // return HMCCU_SetError ($hash, $usage); - my $ccuget = shift @$a // 'Attr'; - return HMCCU_SetError ($hash, $usage) if ($ccuget !~ /^(Attr|State|Value)$/); + my $device = shift @$a // return HMCCU_SetError ($hash, "Usage: get $name $opt {device}"); return HMCCU_SetError ($hash, -1) if (!HMCCU_IsValidDeviceOrChannel ($hash, $device, $HMCCU_FL_ALL)); - $result = HMCCU_GetDeviceInfo ($hash, $device, $ccuget); - return HMCCU_SetError ($hash, -2) if ($result eq '' || $result =~ /^ERROR:.*/); - HMCCU_SetState ($hash, 'OK'); - return HMCCU_FormatDeviceInfo ($result); + return HMCCU_ExecuteGetDeviceInfoCommand ($hash, $hash, $device); } elsif ($opt eq 'rpcevents') { $result = ''; - my @iflist = HMCCU_GetRPCInterfaceList ($hash); - foreach my $ifname (@iflist) { + foreach my $ifname (@ifList) { my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $ifname); if ($rpcdev eq '') { HMCCU_Log ($hash, 2, "Can't find HMCCURPCPROC device for interface $ifname"); @@ -1834,8 +1774,7 @@ sub HMCCU_Get ($@) return 'Found no firmware downloads' if ($dc == 0); $result = "Found $dc firmware downloads. Click on the new version number for download\n\n"; if ($devtype eq 'full') { - $result .= "Type Available Date\n". - "-----------------------------------------\n"; + $result .= "Type Available Date\n".('-' x 41)."\n"; foreach my $ct (keys %{$hash->{hmccu}{type}}) { $result .= sprintf "%-20s %-9s %-10s\n", $ct, $hash->{hmccu}{type}{$ct}{download}, @@ -1846,8 +1785,7 @@ sub HMCCU_Get ($@) my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)"); return $result if (scalar (@devlist) == 0); $result .= - "Device Type Current Available Date\n". - "---------------------------------------------------------------------------\n"; + "Device Type Current Available Date\n".('-' x 76)."\n"; foreach my $dev (@devlist) { my $ch = $defs{$dev}; my $ct = uc($ch->{ccutype}); @@ -1916,9 +1854,7 @@ sub HMCCU_Get ($@) return defined($result) ? $result : HMCCU_SetError ($hash, "Can't get device description"); } elsif ($opt eq 'ccumsg') { - $usage = "Usage: get $name $opt {service|alarm}"; - my $msgtype = shift @$a // return HMCCU_SetError ($hash, $usage); - + my $msgtype = shift @$a // return HMCCU_SetError ($hash, "Usage: get $name $opt {service|alarm}"); my $script = ($msgtype eq 'service') ? "!GetServiceMessages" : "!GetAlarms"; my $res = HMCCU_HMScriptExt ($hash, $script); @@ -1977,8 +1913,6 @@ sub HMCCU_ParseObject ($$$) my ($i, $a, $c, $d, $n, $f) = ('', '', '', '', '', '', 0); my $extaddr; - my ($defInterface, $defPort) = HMCCU_GetDefaultInterface ($hash); - # "ccu:" is default. Remove it. $object =~ s/^ccu://g; @@ -2085,16 +2019,16 @@ sub HMCCU_ParseObject ($$$) $f = $f | $HMCCU_FLAG_CHANNEL; } if ($flags & $HMCCU_FLAG_FULLADDR) { - ($i, $a, $c) = (HMCCU_GetDeviceInterface ($hash, $add, $defInterface), $add, $chn); - $f |= $HMCCU_FLAG_INTERFACE; + ($i, $a, $c) = (HMCCU_GetDeviceInterface ($hash, $add), $add, $chn); + $f |= $HMCCU_FLAG_INTERFACE if ($i ne ''); $f |= $HMCCU_FLAG_ADDRESS if ($add ne ''); $f |= $HMCCU_FLAG_CHANNEL if ($chn ne ''); } } elsif ($f & $HMCCU_FLAG_ADDRESS && $i eq '' && ($flags & $HMCCU_FLAG_FULLADDR || $flags & $HMCCU_FLAG_INTERFACE)) { - $i = HMCCU_GetDeviceInterface ($hash, $a, $defInterface); - $f |= $HMCCU_FLAG_INTERFACE; + $i = HMCCU_GetDeviceInterface ($hash, $a); + $f |= $HMCCU_FLAG_INTERFACE if ($i ne ''); } return ($i, $a, $c, $d, $n, $f); @@ -2253,14 +2187,29 @@ sub HMCCU_GetReadingName ($$$$$$$;$) my @rnlist; $rf //= HMCCU_GetAttrReadingFormat ($hash, $ioHash); - my $sr = '([0-9]{1,2}\.)?LEVEL$:+pct;'. - '([0-9]{1,2}\.)?SET_TEMPERATURE$:+desired-temp;'. - '([0-9]{1,2}\.)?ACTUAL_TEMPERATURE$:+measured-temp;'. - '([0-9]{1,2}\.)?SET_POINT_TEMPERATURE$:+desired-temp'. - '([0-9]{1,2}\.)?ACTUAL_HUMIDITY$:+humidity'; + my $role = HMCCU_GetChannelRole ($hash, $c); + my $sr = $role eq '' ? $HMCCU_READINGS->{DEFAULT} : $HMCCU_READINGS->{$role}; + $sr =~ s/SC#\./$hash->{hmccu}{state}{chn}\./g if (exists($hash->{hmccu}{state}{chn})); + $sr =~ s/CC#\./$hash->{hmccu}{control}{chn}\./g if (exists($hash->{hmccu}{control}{chn})); + +# my $sr = '([0-9]{1,2}\.)?LEVEL$:+pct;'. +# '([0-9]{1,2}\.)?SET_TEMPERATURE$:+desired-temp;'. +# '([0-9]{1,2}\.)?ACTUAL_TEMPERATURE$:+measured-temp;'. +# '([0-9]{1,2}\.)?SET_POINT_TEMPERATURE$:+desired-temp;'. +# '([0-9]{1,2}\.)?ACTUAL_HUMIDITY$:+humidity'; +# if (exists($hash->{hmccu}{control}{chn}) && $hash->{hmccu}{control}{chn} ne '' && +# exists($hash->{hmccu}{control}{dpt}) && $hash->{hmccu}{control}{dpt} eq $d) { +# $sr =~ s/\(\[0-9\]\{1,2\}\\\.\)\?$d/\($hash->{hmccu}{control}{chn}\\\.\)\?$d/g; +# } +# elsif (exists($hash->{hmccu}{state}{chn}) && $hash->{hmccu}{state}{chn} ne '' && +# exists($hash->{hmccu}{state}{dpt}) && $hash->{hmccu}{state}{dpt} eq $d) { +# $sr =~ s/\(\[0-9\]\{1,2\}\\\.\)\?$d/\($hash->{hmccu}{state}{chn}\\\.\)\?$d/g; +# } my $asr = AttrVal ($name, 'ccureadingname', ''); $sr .= ';'.$asr if ($asr ne ''); + HMCCU_Trace ($hash, 2, "sr=$sr"); + # Complete missing values if ($n eq '' && $a ne '') { $n = ($c ne '') ? @@ -2298,7 +2247,7 @@ sub HMCCU_GetReadingName ($$$$$$$;$) $rn = $t.'.'.$d; } elsif ($rf =~ /\%/) { - $rn = $1; + $rn = $rf; if ($a ne '') { $rn =~ s/\%a/lc($a)/ge; $rn =~ s/\%A/uc($a)/ge; } if ($n ne '') { $rn =~ s/\%n/lc($n)/ge; $rn =~ s/\%N/uc($n)/ge; } if ($c ne '') { $rn =~ s/\%c/lc($c)/ge; $rn =~ s/\%C/uc($c)/ge; } @@ -2431,14 +2380,36 @@ sub HMCCU_Trace ($$$) my $type = $hash->{TYPE}; return if (!HMCCU_IsFlag ($name, 'trace')); - + my $pid = $$; my $fnc = (caller(1))[3] // 'unknown'; + + my $traceFilter = AttrVal ($name, 'traceFilter', '.*'); + return if ($fnc !~ /$traceFilter/); + foreach my $m (split ("
", $msg)) { Log3 $name, $level, "$type: [$name : $pid] [$fnc] $m"; } } +###################################################################### +# Log message. Optionally show message on the screen, if async output +# is possible. +###################################################################### + +sub HMCCU_LogDisplay ($$$;$) +{ + my ($hash, $level, $msg, $rc) = @_; + + if (exists($hash->{CL}) && $init_done) { + my $devType = $hash->{TYPE} // ''; + my $devName = defined($hash->{NAME}) ? " [$hash->{NAME}]" : ''; + asyncOutput ($hash->{CL}, "$devType $devName $msg"); + } + + return HMCCU_Log ($hash, $level, $msg, $rc); +} + ###################################################################### # Log message with module type, device name and process id. # Return parameter rc or 0. @@ -2466,9 +2437,9 @@ sub HMCCU_Log ($$$;$) else { $name = $source // 'N/A'; } - my $hash = $defs{$name}; - my $type = exists($defs{$name}) ? $defs{$name}->{TYPE} : 'N/A'; + my $hash = $defs{$name}; + my $type = defined($hash) ? $hash->{TYPE} : 'N/A'; if (defined($hash) && HMCCU_IsFlag ($hash, 'logEnhanced')) { $type .= ":$cl"; $name .= " : $pid"; @@ -2592,7 +2563,8 @@ sub HMCCU_SetRPCState ($@) # Count number of processes in state running, error or inactive # Prepare filter for updating client devices my %stc = ('running' => 0, 'error' => 0, 'inactive' => 0); - my @iflist = HMCCU_GetRPCInterfaceList ($hash); + my $interfaces = HMCCU_GetRPCInterfaceList ($hash, 1); + my @iflist = keys %$interfaces; my $ifCount = scalar(@iflist); foreach my $i (@iflist) { my $st = $hash->{hmccu}{interfaces}{$i}{state}; @@ -2673,7 +2645,7 @@ sub HMCCU_Substitute ($$$$$;$$) } } - # Remove channel number from datapoint if specified + # Separate channel number from datapoint if specified if ($dpt =~ /^([0-9]{1,2})\.(.+)$/) { ($chn, $dpt) = ($1, $2); } @@ -3062,7 +3034,7 @@ sub HMCCU_UpdateDeviceTable ($$) # Updated or new device/channel $hash->{hmccu}{dev}{$da}{addtype} = $at; $hash->{hmccu}{dev}{$da}{valid} = 1; - + foreach my $k ('channels', 'type', 'usetype', 'interface', 'version', 'firmware', 'rxmode', 'direction', 'paramsets', 'sourceroles', 'targetroles', 'children', 'parent', 'aes') { @@ -3081,6 +3053,9 @@ sub HMCCU_UpdateDeviceTable ($$) # Device deleted, mark as invalid $hash->{hmccu}{dev}{$da}{valid} = 0; $hash->{hmccu}{adr}{$nm}{valid} = 0 if (defined ($nm)); + my $iface = $hash->{hmccu}{dev}{$da}{interface}; + $hash->{hmccu}{device}{$iface}{$da}{_valid} = 0 + if (exists($hash->{hmccu}{device}{$iface}{$da})); } elsif ($devices->{$da}{flag} eq 'R' && exists($hash->{hmccu}{dev}{$da})) { # Device replaced, change address @@ -3095,13 +3070,7 @@ sub HMCCU_UpdateDeviceTable ($$) } # Delayed initialization if CCU was not ready during FHEM start - if ($hash->{hmccu}{ccu}{delayed} == 1) { - # Initialize interface and port lists - HMCCU_AttrInterfacesPorts ($hash, 'rpcinterfaces', $attr{$name}{rpcinterfaces}) - if (exists($attr{$name}{rpcinterfaces})); - HMCCU_AttrInterfacesPorts ($hash, 'rpcport', $attr{$name}{rpcport}) - if (exists($attr{$name}{rpcport})); - + if ($hash->{hmccu}{ccu}{delayed} == 1) { # Initialize pending client devices my @cdev = HMCCU_FindClientDevices ($hash, '(HMCCUDEV|HMCCUCHN|HMCCURPCPROC)', undef, 'ccudevstate=pending'); if (scalar(@cdev) > 0) { @@ -3171,6 +3140,7 @@ sub HMCCU_UpdateDeviceTable ($$) ###################################################################### # Delete device table entries +# New version ###################################################################### sub HMCCU_ResetDeviceTables ($;$$) @@ -3197,6 +3167,8 @@ sub HMCCU_ResetDeviceTables ($;$$) # Add new CCU or FHEM device or channel # If $devName is undef, a new device or channel will be added to IO # device if it doesn't exist. +# This function is called during device definition in HMCCUDEV and +# HMCCUCHN. ###################################################################### sub HMCCU_AddDevice ($$$;$) @@ -3401,13 +3373,15 @@ sub HMCCU_GetChannelRole ($;$) sub HMCCU_GetDeviceConfig ($) { my ($ioHash) = @_; - + my ($cDev, $cPar, $cLnk) = (0, 0, 0); my $c = 0; - foreach my $iface (keys %{$ioHash->{hmccu}{interfaces}}) { - if (exists($ioHash->{hmccu}{interfaces}{$iface}{device})) { - my $rpcHash = $defs{$ioHash->{hmccu}{interfaces}{$iface}{device}}; + my $interfaces = HMCCU_GetRPCInterfaceList ($ioHash, 1); + foreach my $iface (keys %$interfaces) { + my ($rpcdev, $save) = HMCCU_GetRPCDevice ($ioHash, 1, $iface); + if ($rpcdev ne '') { + my $rpcHash = $defs{$rpcdev}; HMCCU_Log ($ioHash, 2, "Reading Device Descriptions for interface $iface"); $c = HMCCURPCPROC_GetDeviceDesc ($rpcHash); HMCCU_Log ($ioHash, 2, "Read $c Device Descriptions for interface $iface"); @@ -3438,11 +3412,14 @@ sub HMCCU_GetDeviceConfig ($) # Update FHEM devices foreach my $d (@devList) { my $clHash = $defs{$d}; - my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($clHash); HMCCU_UpdateDevice ($ioHash, $clHash); HMCCU_UpdateDeviceRoles ($ioHash, $clHash); + + my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($clHash); + HMCCU_UpdateRoleCommands ($ioHash, $clHash, $cc); + HMCCU_UpdateAdditionalCommands ($ioHash, $clHash, $cc, $cd); } return ($cDev, $cPar, $cLnk); @@ -3493,6 +3470,7 @@ sub HMCCU_AddDeviceDesc ($$$$) $hash->{hmccu}{device}{$iface}{$k}{_fw_ver} = $fw_ver."-".$desc->{VERSION}; $hash->{hmccu}{device}{$iface}{$k}{_model} = $desc->{TYPE}; $hash->{hmccu}{device}{$iface}{$k}{_name} = HMCCU_GetDeviceName ($hash, $k); + $hash->{hmccu}{device}{$iface}{$k}{_valid} = 1; } return 1; @@ -3754,6 +3732,12 @@ sub HMCCU_ExistsDeviceModel ($$$;$) } } +sub HMCCU_CloneDeviceModel ($$$$$) +{ +# if (HMCCU_ExistsDeviceModel ($hash, $type, $fw_ver)) { +# } +} + ###################################################################### # Add new device model # Parameters: @@ -3959,7 +3943,7 @@ sub HMCCU_IsValidParameter ($$$$) # Return converted or original value. ###################################################################### -sub HMCCU_GetParamValue ($$$$$) +sub HMCCU_GetParamValueConversion ($$$$$) { my ($hash, $object, $paramset, $parameter, $value) = @_; @@ -3971,21 +3955,14 @@ sub HMCCU_GetParamValue ($$$$$) return $value if (!defined($object)); $paramset = 'LINK' if ($paramset =~ /^LINK\..+$/); - my $paramDef = HMCCU_GetParamDef ($hash, $object, $paramset, $parameter); - if (defined($paramDef)) { - my $type = $paramDef->{TYPE}; - if (!defined($type)) { - my $address = ref($object) eq 'HASH' ? $object->{ADDRESS} : $object; - HMCCU_Log ($hash, 2, "Can't get type of $address:$paramset:$parameter"); - return $value; - } + my $paramDef = HMCCU_GetParamDef ($hash, $object, $paramset, $parameter) // return $value; + my $type = $paramDef->{TYPE} // return $value; - return $ct{$type}{$value} if (exists($ct{$type}) && exists($ct{$type}{$value})); + return $ct{$type}{$value} if (exists($ct{$type}) && exists($ct{$type}{$value})); - if ($type eq 'ENUM' && exists($paramDef->{VALUE_LIST})) { - my @vl = split(',', $paramDef->{VALUE_LIST}); - return $vl[$value] if ($value =~ /^[0-9]+$/ && $value < scalar(@vl)); - } + if ($type eq 'ENUM' && exists($paramDef->{VALUE_LIST})) { + my @vl = split(',', $paramDef->{VALUE_LIST}); + return $vl[$value] if ($value =~ /^[0-9]+$/ && $value < scalar(@vl)); } return $value; @@ -4217,7 +4194,7 @@ sub HMCCU_UpdateParamsetReadings ($$$;$) $sv = HMCCU_ScaleValue ($clHash, $c, $p, $v, 0); $fv = HMCCU_FormatReadingValue ($clHash, $sv, $p); $cv = HMCCU_Substitute ($fv, $clHash, 0, $c, $p, $chnType, $devDesc); - $cv = HMCCU_GetParamValue ($ioHash, $devDesc, $ps, $p, $fv) + $cv = HMCCU_GetParamValueConversion ($ioHash, $devDesc, $ps, $p, $fv) if (defined($devDesc) && "$fv" eq "$cv"); HMCCU_UpdateInternalValues ($clHash, $chKey, $ps, 'SVAL', $cv); @@ -4238,6 +4215,7 @@ sub HMCCU_UpdateParamsetReadings ($$$;$) my @rnList = HMCCU_GetReadingName ($clHash, $clInt, $a, $c, $p, '', $clRF, $ps); my $dispFlag = HMCCU_FilterReading ($clHash, $chnAddr, $p, $ps) ? '' : '.'; foreach my $rn (@rnList) { + HMCCU_Trace ($clHash, 2, "rn=$rn, dispFlag=$dispFlag, fv=$fv, cv=$cv"); HMCCU_BulkUpdate ($clHash, $dispFlag.$rn, $fv, $cv); } } @@ -4488,57 +4466,36 @@ sub HMCCU_UpdatePeers ($$$$) } ###################################################################### -# Get list of valid RPC interfaces. -# Binary interfaces are ignored if internal RPC server is used. +# Get hash with valid RPC interfaces and ports +# If $mode = 1, return only interfaces which have devices assigned. ###################################################################### -sub HMCCU_GetRPCInterfaceList ($) +sub HMCCU_GetRPCInterfaceList ($;$) { - my ($hash) = @_; + my ($hash, $mode) = @_; + $mode //= 0; my $name = $hash->{NAME}; - - my ($defInterface, $defPort) = HMCCU_GetDefaultInterface ($hash); - my $ccuflags = HMCCU_GetFlags ($name); - my @interfaces = (); - - if (defined($hash->{hmccu}{rpcports})) { - foreach my $p (split (',', $hash->{hmccu}{rpcports})) { - my ($ifname, $iftype) = HMCCU_GetRPCServerInfo ($hash, $p, 'name,type'); - push (@interfaces, $ifname) if (defined($ifname) && defined($iftype)); + my %interfaces = (); + + my $rpcInterfaces = AttrVal ($name, 'rpcinterfaces', ''); + if ($rpcInterfaces ne '') { + foreach my $in (split (',', $rpcInterfaces)) { + my ($pn, $dc) = HMCCU_GetRPCServerInfo ($hash, $in, 'port,devcount'); + if (defined($pn) && ($mode == 0 || ($mode == 1 && defined($dc) && $dc > 0))) { + $interfaces{$in} = $pn; + } } } - else { - @interfaces = ($defInterface); - } - - return @interfaces; -} - -###################################################################### -# Get list of valid RPC ports. -# Binary interfaces are ignored if internal RPC server is used. -###################################################################### - -sub HMCCU_GetRPCPortList ($) -{ - my ($hash) = @_; - my $name = $hash->{NAME}; - - my ($defInterface, $defPort) = HMCCU_GetDefaultInterface ($hash); - my $ccuflags = HMCCU_GetFlags ($name); - my @ports = (); - - if (defined($hash->{hmccu}{rpcports})) { - foreach my $p (split (',', $hash->{hmccu}{rpcports})) { - my ($ifname, $iftype) = HMCCU_GetRPCServerInfo ($hash, $p, 'name,type'); - push (@ports, $p) if (defined($ifname) && defined($iftype)); + elsif (defined($hash->{hmccu}{rpcports})) { + foreach my $pn (split (',', $hash->{hmccu}{rpcports})) { + my ($in, $it, $dc) = HMCCU_GetRPCServerInfo ($hash, $pn, 'name,type,devcount'); + if (defined($in) && defined($it) && ($mode == 0 || ($mode == 1 && defined($dc) && $dc > 0))) { + $interfaces{$in} = $pn; + } } } - else { - @ports = ($defPort); - } - return @ports; + return \%interfaces; } ###################################################################### @@ -4560,8 +4517,8 @@ sub HMCCU_EventsTimedOut ($) # Register callback for each interface my $rc = 1; - my @iflist = HMCCU_GetRPCInterfaceList ($hash); - foreach my $ifname (@iflist) { + my $interfaces = HMCCU_GetRPCInterfaceList ($hash, 1); + foreach my $ifname (keys %$interfaces) { my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $ifname); if ($rpcdev eq '') { HMCCU_Log ($hash, 0, "Can't find RPC device for interface $ifname"); @@ -4688,7 +4645,8 @@ sub HMCCU_StartExtRPCServer ($) my $c = 0; my $d = 0; my $s = 0; - my @iflist = HMCCU_GetRPCInterfaceList ($hash); + my $interfaces = HMCCU_GetRPCInterfaceList ($hash, 1); + my @iflist = keys %$interfaces; foreach my $ifname1 (@iflist) { HMCCU_Log ($hash, 2, "Get RPC device for interface $ifname1"); my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 1, $ifname1); @@ -4740,8 +4698,8 @@ sub HMCCU_StopExtRPCServer ($;$) HMCCU_SetRPCState ($hash, 'stopping'); my $rc = 1; - my @iflist = HMCCU_GetRPCInterfaceList ($hash); - foreach my $ifname (@iflist) { + my $interfaces = HMCCU_GetRPCInterfaceList ($hash, 1); + foreach my $ifname (keys %$interfaces) { my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $ifname); if ($rpcdev eq '') { HMCCU_Log ($hash, 0, "HMCCU: Can't find RPC device"); @@ -4786,8 +4744,8 @@ sub HMCCU_IsRPCServerRunning ($;$) my $ccuflags = HMCCU_GetFlags ($name); @$pids = () if (defined($pids)); - my @iflist = HMCCU_GetRPCInterfaceList ($hash); - foreach my $ifname (@iflist) { + my $interfaces = HMCCU_GetRPCInterfaceList ($hash, 1); + foreach my $ifname (keys %$interfaces) { my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $ifname); next if ($rpcdev eq ''); my $rc = HMCCURPCPROC_CheckProcessState ($defs{$rpcdev}, 'running'); @@ -5010,6 +4968,43 @@ sub HMCCU_GetDevice ($$) return ($devcount, $chncount); } +###################################################################### +# Read list of CCU interfaces via Homematic Script. +# Return number of interfaces. +# Note: Doesn't update $hash->{hmccu}{interfaces} +###################################################################### + +sub HMCCU_GetInterfaceList ($) +{ + my ($hash) = @_; + + my $ifCount = 0; + my @ifNames = (); + my @ifPorts = (); + + my $response = HMCCU_HMScriptExt ($hash, "!GetInterfaceList"); + return '' if ($response eq '' || $response =~ /^ERROR:.*/); + + foreach my $hmdef (split /[\n\r]+/,$response) { + my @hmdata = split /;/,$hmdef; + if (scalar(@hmdata) == 3 && $hmdata[2] =~ /^([^:]+):\/\/([^:]+):([0-9]+)/) { + my ($prot, $ipaddr, $port) = ($1, $2, $3); + next if (!defined ($port) || $port eq ''); + $port -= 30000 if ($port >= 10000); + push @ifNames, $hmdata[0]; + push @ifPorts, $port; + $ifCount++; + } + } + + if ($ifCount > 0) { + $hash->{hmccu}{rpcports} = join(',', @ifPorts); + $hash->{ccuinterfaces} = join(',', @ifNames); + } + + return $ifCount; +} + ###################################################################### # Read list of CCU devices, channels, interfaces, programs and groups # via Homematic Script. @@ -5158,45 +5153,16 @@ sub HMCCU_GetDeviceList ($) } } + if ($ifcount > 0) { + $hash->{ccuinterfaces} = join (',', keys %{$hash->{hmccu}{interfaces}}); + $hash->{hmccu}{rpcports} = join (',', keys %{$hash->{hmccu}{ifports}}); + } + else { + HMCCU_Log ($hash, 1, "Found no interfaces on CCU"); + return (-1, -1, -1, -1, -1); + } + if (scalar (keys %objects) > 0) { - if ($ifcount > 0) { - # Configure interfaces and RPC ports - my $defInterface = $hash->{hmccu}{defInterface}; - my $f = 0; - $hash->{ccuinterfaces} = join (',', keys %{$hash->{hmccu}{interfaces}}); - if (!exists ($hash->{hmccu}{interfaces}{$defInterface}) || - $hash->{hmccu}{interfaces}{$defInterface}{devcount} == 0) { - HMCCU_Log ($hash, 1, "Default interface $defInterface does not exist or has no devices assigned. Changing default interface."); - foreach my $i (@HMCCU_RPC_PRIORITY) { - if ("$i" ne "$defInterface" && exists ($hash->{hmccu}{interfaces}{$i}) && - $hash->{hmccu}{interfaces}{$i}{devcount} > 0) { - $hash->{hmccu}{defInterface} = $i; - $hash->{hmccu}{defPort} = $HMCCU_RPC_PORT{$i}; - $f = 1; - HMCCU_Log ($hash, 1, "Changed default interface from $defInterface to $i"); - last; - } - } - if ($f == 0) { - HMCCU_Log ($hash, 1, "None of interfaces ".join(',', @HMCCU_RPC_PRIORITY)." exist on CCU"); - return (-1, -1, -1, -1, -1); - } - } - - # Remove invalid RPC ports - if (defined ($hash->{hmccu}{rpcports})) { - my @plist = (); - foreach my $p (split (',', $hash->{hmccu}{rpcports})) { - push (@plist, $p) if (exists ($hash->{hmccu}{interfaces}{$HMCCU_RPC_NUMPORT{$p}})); - } - $hash->{hmccu}{rpcports} = join (',', @plist); - } - } - else { - HMCCU_Log ($hash, 1, "Found no interfaces on CCU"); - return (-1, -1, -1, -1, -1); - } - # Update HMCCU device tables ($devcount, $chncount) = HMCCU_UpdateDeviceTable ($hash, \%objects); @@ -5683,9 +5649,17 @@ sub HMCCU_GetDefaultInterface ($) { my ($hash) = @_; - my $ifname = exists ($hash->{hmccu}{defInterface}) ? $hash->{hmccu}{defInterface} : $HMCCU_RPC_PRIORITY[0]; + my $ifname = $HMCCU_RPC_PRIORITY[0]; my $ifport = $HMCCU_RPC_PORT{$ifname}; + foreach my $i (@HMCCU_RPC_PRIORITY) { + if (exists ($hash->{hmccu}{interfaces}{$i}) && $hash->{hmccu}{interfaces}{$i}{devcount} > 0) { + $ifname = $i; + $ifport = $HMCCU_RPC_PORT{$i}; + last; + } + } + return ($ifname, $ifport); } @@ -5965,46 +5939,55 @@ sub HMCCU_GetRPCDevice ($$$) return ($devlist[0], 0); } elsif ($devcnt > 1) { - return (HMCCU_Log ($hash, 2, "Found more than one RPC device for interface $ifname", '')); + return (HMCCU_Log ($hash, 2, "Found more than one RPC device for interface $ifname", ''), 0); } HMCCU_Log ($hash, 1, "No RPC device defined for interface $ifname"); # Create RPC device - if ($create) { - my $alias = "CCU RPC $ifname"; - my $rpccreate = ''; - $rpcdevname = 'd_rpc'; + return $create ? HMCCU_CreateRPCDevice ($hash, $ifname, $rpcprot, $rpchost) : ('', 0); +} - # Ensure unique device name by appending last 2 digits of CCU IP address - $rpcdevname .= HMCCU_GetIdFromIP ($hash->{ccuip}, '') if (exists($hash->{ccuip})); +###################################################################### +# Create a new device of type HMCCURPCPROC for a RPC interface +# Return (deviceName, 1) on success. +# Return (errorMessage, 0) on error. +###################################################################### - # Build device name and define command - $rpcdevname = makeDeviceName ($rpcdevname.$ifname); - $rpccreate = "$rpcdevname HMCCURPCPROC $rpcprot://$rpchost $ifname"; - return (HMCCU_Log ($hash, 2, "Device $rpcdevname already exists. Please delete or rename it.", '')) - if (exists($defs{"$rpcdevname"})); - - # Create RPC device - HMCCU_Log ($hash, 1, "Creating new RPC device $rpcdevname"); - my $ret = CommandDefine (undef, $rpccreate); - if (!defined($ret)) { - # RPC device created. Set/copy some attributes from HMCCU device - my %rpcdevattr = ('room' => 'copy', 'group' => 'copy', 'icon' => 'copy', - 'stateFormat' => 'rpcstate/state', 'eventMap' => '/rpcserver on:on/rpcserver off:off/', - 'verbose' => 2, 'alias' => $alias ); - foreach my $a (keys %rpcdevattr) { - my $v = $rpcdevattr{$a} eq 'copy' ? AttrVal ($name, $a, '') : $rpcdevattr{$a}; - CommandAttr (undef, "$rpcdevname $a $v") if ($v ne ''); - } - return ($rpcdevname, 1); - } - else { - HMCCU_Log ($hash, 1, "Definition of RPC device failed. $ret"); - } - } +sub HMCCU_CreateRPCDevice ($$$$) +{ + my ($hash, $ifname, $rpcprot, $rpchost) = @_; - return ('', 0); + my $alias = "CCU RPC $ifname"; + my $rpcdevname = 'd_rpc'; + + # Ensure unique device name by appending last 2 digits of CCU IP address + $rpcdevname .= HMCCU_GetIdFromIP ($hash->{ccuip}, '') if (exists($hash->{ccuip})); + + # Build device name and define command + $rpcdevname = makeDeviceName ($rpcdevname.$ifname); + my $rpccreate = "$rpcdevname HMCCURPCPROC $rpcprot://$rpchost $ifname"; + return (HMCCU_Log ($hash, 2, "Device $rpcdevname already exists. Please delete or rename it.", ''), 0) + if (exists($defs{"$rpcdevname"})); + + # Create RPC device + HMCCU_Log ($hash, 1, "Creating new RPC device $rpcdevname for interface $ifname"); + my $ret = CommandDefine (undef, $rpccreate); + if (!defined($ret)) { + # RPC device created. Set/copy some attributes from HMCCU device + my %rpcdevattr = ('room' => 'copy', 'group' => 'copy', 'icon' => 'copy', + 'stateFormat' => 'rpcstate/state', 'eventMap' => '/rpcserver on:on/rpcserver off:off/', + 'verbose' => 2, 'alias' => $alias ); + foreach my $a (keys %rpcdevattr) { + my $v = $rpcdevattr{$a} eq 'copy' ? AttrVal ($hash->{NAME}, $a, '') : $rpcdevattr{$a}; + CommandAttr (undef, "$rpcdevname $a $v") if ($v ne ''); + } + return ($rpcdevname, 1); + } + else { + HMCCU_Log ($hash, 1, "Definition of RPC device for interface $ifname failed. $ret"); + return ($ret, 0); + } } ###################################################################### @@ -6168,13 +6151,17 @@ sub HMCCU_SetDefaultAttributes ($;$) # Return '' if no state values available ###################################################################### -sub HMCCU_GetStateValues ($$;$) +sub HMCCU_GetStateValues ($;$$) { my ($clHash, $dpt, $ctrlChn) = @_; + $dpt //= ''; + $ctrlChn //= ''; + HMCCU_Trace ($clHash, 2, "dpt=$dpt, ctrlChn=$ctrlChn"); my $sv = AttrVal ($clHash->{NAME}, 'statevals', ''); - if ($sv eq '') { + if ($sv eq '' && $dpt ne '' && $ctrlChn ne '') { my $role = HMCCU_GetChannelRole ($clHash, $ctrlChn); + HMCCU_Trace ($clHash, 2, "dpt=$dpt, ctrlChn=$ctrlChn, role=$role"); if ($role ne '' && exists($HMCCU_STATECONTROL->{$role}) && $HMCCU_STATECONTROL->{$role}{C} eq $dpt) { return $HMCCU_STATECONTROL->{$role}{V}; } @@ -6349,6 +6336,22 @@ sub HMCCU_UpdateRoleCommands ($$;$) return; } +###################################################################### +# Update additional commands which depend on device state +###################################################################### + +sub HMCCU_UpdateAdditionalCommands ($$;$$) +{ + my ($ioHash, $clHash, $cc, $cd) = @_; + + # Toggle command + my $stateVals = HMCCU_GetStateValues ($clHash, $cd, $cc); + HMCCU_Trace ($clHash, 2, "stateVals=$stateVals, cd=$cd, cc=$cc"); + my %stateCmds = split (/[:,]/, $stateVals); + my @states = keys %stateCmds; + $clHash->{hmccu}{cmdlist} .= ' toggle:noArg' if (scalar(@states) > 1); +} + ###################################################################### # Execute command related to role ###################################################################### @@ -6410,8 +6413,10 @@ sub HMCCU_ExecuteRoleCommand ($@) $clHash, "Illegal value $vl. Use one of ". join(',', keys %{$cmd->{look}})); } - $value = HMCCU_Min ($value, $cmd->{max}) if (defined($cmd->{max}) && $cmd->{max} ne ''); - $value = HMCCU_Max ($value, $cmd->{min}) if (defined($cmd->{min}) && $cmd->{min} ne ''); + $value = HMCCU_Min ($value, HMCCU_ScaleValue ($clHash, $channel, $cmd->{dpt}, $cmd->{max}, 0)) + if (defined($cmd->{max}) && $cmd->{max} ne ''); + $value = HMCCU_Max ($value, HMCCU_ScaleValue ($clHash, $channel, $cmd->{dpt}, $cmd->{min}, 0)) + if (defined($cmd->{min}) && $cmd->{min} ne ''); if ($cmd->{ps} eq 'VALUES') { my $dno = sprintf ("%03d", $c); @@ -6424,6 +6429,9 @@ sub HMCCU_ExecuteRoleCommand ($@) } if (scalar(keys %dpval) > 0) { + foreach my $dpv (keys %dpval) { + HMCCU_Trace ($clHash, 2, "$dpv=$dpval{$dpv}"); + } $rc = HMCCU_SetMultipleDatapoints ($clHash, \%dpval); return HMCCU_SetError ($clHash, HMCCU_Min(0, $rc)); } @@ -6435,6 +6443,163 @@ sub HMCCU_ExecuteRoleCommand ($@) return HMCCU_SetError ($clHash, "Command $command not executed"); } +###################################################################### +# Execute set clear command +###################################################################### + +sub HMCCU_ExecuteSetClearCommand ($@) +{ + my ($clHash, $a) = @_; + + my $delPar = shift @$a // '.*'; + my $rnexp = ''; + if ($delPar eq 'reset') { + $rnexp = '.*'; + delete $clHash->{hmccu}{dp}; + } + else { + $rnexp = $delPar; + } + HMCCU_DeleteReadings ($clHash, $rnexp); + return HMCCU_SetState ($clHash, "OK"); +} + +###################################################################### +# Execute set datapoint command +###################################################################### + +sub HMCCU_ExecuteSetDatapointCommand ($@) +{ + my ($clHash, $a, $h, $cc, $cd) = @_; + + my $usage = "Usage: set $clHash->{NAME} datapoint [{channel-number}.]{datapoint} {value} [...]"; + my %dpval; + my $i = 0; + my ($devAddr, $chnNo) = HMCCU_SplitChnAddr ($clHash->{ccuaddr}); + my $stVals = HMCCU_GetStateValues ($clHash, $cd, $cc); + + push (@$a, %${h}) if (defined($h)); + while (my $dpt = shift @$a) { + my $value = shift @$a // return HMCCU_SetError ($clHash, $usage); + $i++; + + if ($clHash->{TYPE} eq 'HMCCUDEV') { + if ($dpt =~ /^([0-9]+)\..+$/) { + return HMCCU_SetError ($clHash, -7) if ($1 >= $clHash->{hmccu}{channels}); + } + else { + return HMCCU_SetError ($clHash, -12) if ($cc eq ''); + $dpt = "$cc.$dpt"; + } + } + else { + $dpt = "$chnNo.$dpt"; + } + + $value = HMCCU_Substitute ($value, $stVals, 1, undef, '') if ($stVals ne '' && $dpt eq $cd); + + my $no = sprintf ("%03d", $i); + $dpval{"$no.$clHash->{ccuif}.$devAddr:$dpt"} = $value; + } + + return HMCCU_SetError ($clHash, $usage) if (scalar(keys %dpval) < 1); + + my $rc = HMCCU_SetMultipleDatapoints ($clHash, \%dpval); + return HMCCU_SetError ($clHash, HMCCU_Min(0, $rc)); +} + +###################################################################### +# Execute set config / values / link command +# Usage of command in FHEM for HMCCUCHN devices: +# MASTER: set config ['device'] parameter=value [...] +# LINKS: set config peer-address parameter=value [...] +# VALUES: set values parameter=value [...] +# Usage of command in FHEM for HMCCUDEV devices: +# MASTER: set config [channel] parameter=value [...] +# LINKS: set config channel peer-address parameter=value [...] +# VALUES: set values channel parameter=value [...] +###################################################################### + +sub HMCCU_ExecuteSetParameterCommand ($@) +{ + my ($ioHash, $clHash, $command, $a, $h) = @_; + + my $paramset = $command eq 'config' ? 'MASTER' : 'VALUES'; + my $rc; + my $result = ''; + my $receiver = ''; + my $ccuobj = $clHash->{ccuaddr}; + + return HMCCU_SetError ($clHash, 'No parameter specified') if ((scalar keys %{$h}) < 1); + + my $p = shift @$a; + if (defined($p)) { + if ($clHash->{TYPE} eq 'HMCCUDEV') { + if ($p =~ /^([0-9]{1,2})$/) { + return HMCCU_SetError ($clHash, -7) if ($p >= $clHash->{hmccu}{channels}); + $ccuobj .= ':'.$p; + $p = shift @$a; + if (defined($p)) { + $receiver = $p; + $paramset = 'LINK'; + } + } + } + else { + if (lc($p) eq 'device') { + ($ccuobj, undef) = HMCCU_SplitChnAddr ($ccuobj); + } + else { + $receiver = $p; + $paramset = 'LINK'; + } + } + } + + my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $ccuobj, $clHash->{ccuif}) // + return HMCCU_SetError ($clHash, "Can't get device description"); + return HMCCU_SetError ($clHash, "Paramset $paramset not supported by device or channel") + if ($devDesc->{PARAMSETS} !~ /$paramset/); + if (!HMCCU_IsValidParameter ($ioHash, $devDesc, $paramset, $h)) { + my @parList = HMCCU_GetParamDef ($ioHash, $devDesc, $paramset); + return HMCCU_SetError ($clHash, 'Invalid parameter specified. Valid parameters are '. + join(',', @parList)); + } + + if ($paramset eq 'VALUES' || $paramset eq 'MASTER') { + ($rc, $result) = HMCCU_SetMultipleParameters ($clHash, $ccuobj, $h, $paramset); + } + else { + if (exists($defs{$receiver}) && defined($defs{$receiver}->{TYPE})) { + my $clRecHash = $defs{$receiver}; + if ($clRecHash->{TYPE} eq 'HMCCUDEV') { + my $chnNo = shift @$a; + return HMCCU_SetError ($clHash, 'Channel number required for link receiver') + if (!defined($chnNo) || $chnNo !~ /^[0-9]{1,2}$/); + $receiver = $clRecHash->{ccuaddr}.":$chnNo"; + } + elsif ($clRecHash->{TYPE} eq 'HMCCUCHN') { + $receiver = $clRecHash->{ccuaddr}; + } + else { + return HMCCU_SetError ($clHash, "Receiver $receiver is not a HMCCUCHN or HMCCUDEV device"); + } + } + elsif (!HMCCU_IsChnAddr ($receiver, 0)) { + my ($rcvAdd, $rcvChn) = HMCCU_GetAddress ($ioHash, $receiver); + return HMCCU_SetError ($clHash, "$receiver is not a valid CCU channel name") + if ($rcvAdd eq '' || $rcvChn eq ''); + $receiver = "$rcvAdd:$rcvChn"; + } + + return HMCCU_SetError ($clHash, "$receiver is not a link receiver of $clHash->{NAME}") + if (!HMCCU_IsValidReceiver ($ioHash, $ccuobj, $clHash->{ccuif}, $receiver)); + ($rc, $result) = HMCCU_RPCRequest ($clHash, 'putParamset', $ccuobj, $receiver, $h); + } + + return HMCCU_SetError ($clHash, HMCCU_Min(0, $rc), $result); +} + ###################################################################### # Execute toggle command ###################################################################### @@ -6475,11 +6640,33 @@ sub HMCCU_ExecuteToggleCommand ($@) return HMCCU_SetError ($clHash, HMCCU_Min(0, $rc)) } +###################################################################### +# Execute command to show device information +###################################################################### + +sub HMCCU_ExecuteGetDeviceInfoCommand ($@) +{ + my ($ioHash, $clHash, $address, $sc, $sd, $cc, $cd) = @_; + $sc //= '?'; + $sd //= '?'; + $cc //= '?'; + $cd //= '?'; + + my $result = HMCCU_GetDeviceInfo ($clHash, $address); + return HMCCU_SetError ($clHash, -2) if ($result eq ''); + my $devInfo = HMCCU_FormatDeviceInfo ($result); + $devInfo .= "
StateDatapoint = $sc.$sd
ControlDatapoint = $cc.$cd

"; + $result = HMCCU_DeviceDescToStr ($ioHash, $clHash->{TYPE} eq 'HMCCU' ? $address : $clHash); + $devInfo .= defined($result) ? $result : "Can't get device description"; + + return $devInfo; +} + ###################################################################### # Execute commands to fetch device parameters ###################################################################### -sub HMCCU_ExecuteGetParameterCommand ($$$$) +sub HMCCU_ExecuteGetParameterCommand ($@) { my ($ioHash, $clHash, $command, $addList) = @_; @@ -6507,9 +6694,8 @@ sub HMCCU_ExecuteGetParameterCommand ($$$$) } else { my ($rc, $result) = HMCCU_RPCRequest ($clHash, 'getRawParamset', $a, $ps, undef); - if ($rc >= 0) { - foreach my $p (keys %$result) { $objects{$da}{$dc}{$ps}{$p} = $result->{$p}; } - } + next if ($rc < 0); + foreach my $p (keys %$result) { $objects{$da}{$dc}{$ps}{$p} = $result->{$p}; } } } } @@ -6694,8 +6880,10 @@ sub HMCCU_GetSpecialDatapoints ($) # Detect by role, but do not override values defined as attributes if (defined($hash->{hmccu}{role}) && $hash->{hmccu}{role} ne '') { + HMCCU_Trace ($hash, 2, "hmccurole=$hash->{hmccu}{role}"); if ($type eq 'HMCCUCHN') { my $role = HMCCU_GetChannelRole ($hash); + HMCCU_Trace ($hash, 2, "role=$role"); if ($role ne '' && exists($HMCCU_STATECONTROL->{$role}) && $HMCCU_STATECONTROL->{$role}{F} & 1) { $sd = $HMCCU_STATECONTROL->{$role}{S} if ($HMCCU_STATECONTROL->{$role}{S} ne '' && $sd eq ''); $cd = $HMCCU_STATECONTROL->{$role}{C} if ($HMCCU_STATECONTROL->{$role}{C} ne '' && $cd eq ''); @@ -7202,7 +7390,7 @@ sub HMCCU_SetMultipleDatapoints ($$) { return -20 if ($int eq ''); } - if ($ccuType eq 'HM-Dis-EP-WM55' && $dpt eq 'SUBMIT') { + if ($ccuType =~ /^HM-Dis-EP-WM55/ && $dpt eq 'SUBMIT') { $v = HMCCU_EncodeEPDisplay ($v); } else { @@ -7241,7 +7429,7 @@ sub HMCCU_SetMultipleDatapoints ($$) { ###################################################################### # Scale, spread and/or shift datapoint value. -# Mode: 0 = Get/Divide, 1 = Set/Multiply +# Mode: 0 = Get/Multiply, 1 = Set/Divide # Supports reversing of value if value range is specified. Syntax for # Rule is: # [ChannelNo.]Datapoint:Factor @@ -7258,7 +7446,10 @@ sub HMCCU_ScaleValue ($$$$$) my $ccuscaleval = AttrVal ($name, 'ccuscaleval', ''); + HMCCU_Trace ($hash, 2, "chnno=$chnno, dpt=$dpt, value=$value, mode=$mode"); + if ($ccuscaleval ne '') { + HMCCU_Trace ($hash, 2, "ccuscaleval"); my @sl = split (',', $ccuscaleval); foreach my $sr (@sl) { my $f = 1.0; @@ -7307,9 +7498,12 @@ sub HMCCU_ScaleValue ($$$$$) } if ($dpt eq 'LEVEL') { - return ($mode == 0) ? HMCCU_Min($value,100.0)/100.0 : HMCCU_Min($value,1.0)*100.0; + my $rv = ($mode == 0) ? HMCCU_Min($value,1.0)*100.0 : HMCCU_Min($value,100.0)/100.0; + HMCCU_Trace ($hash, 2, "LEVEL: $rv"); + return $rv; +# return ($mode == 0) ? HMCCU_Min($value,1.0)*100.0 : HMCCU_Min($value,100.0)/100.0; } - elsif ($dpt =~ /^P[0-9]_ENDTIME/) { + elsif ($dpt =~ /^(P[0-9]_)?ENDTIME/) { if ($mode == 0) { my $hh = sprintf ("%02d", int($value/60)); my $mm = sprintf ("%02d", $value%60); @@ -7363,7 +7557,7 @@ sub HMCCU_GetVariables ($$) $count++; } - HMCCU_UpdateReadings ($hash, \%readings, 1); + HMCCU_UpdateReadings ($hash, \%readings); return ($count, $result); } @@ -7398,14 +7592,14 @@ sub HMCCU_SetVariable ($$$$$) my $ccureqtimeout = AttrVal ($name, 'ccuReqTimeout', $HMCCU_TIMEOUT_REQUEST); my %varfnc = ( - 'bool' => '!CreateBoolVariable', 'list', '!CreateListVariable', - 'number' => '!CreateNumericVariable', 'text', '!CreateStringVariable' + 'bool' => '!CreateBoolVariable', 'list' => '!CreateListVariable', + 'number' => '!CreateNumericVariable', 'text' => '!CreateStringVariable' ); if (!defined($vartype)) { my $cmd = qq(dom.GetObject("$varname").State("$value")); - my $response = HMCCU_HMCommand ($hash, $cmd, 1); - return HMCCU_Log ($hash, 1, "CMD=$cmd", -2) if (!defined($response)); + my $response = HMCCU_HMCommand ($hash, $cmd, 1) // + return HMCCU_Log ($hash, 1, "CMD=$cmd", -2); } else { return -18 if (!exists($varfnc{$vartype})); @@ -7468,7 +7662,7 @@ sub HMCCU_GetUpdate ($$$) if ($type eq 'HMCCUDEV' && (($clHash->{ccuif} eq 'VirtualDevices' && HMCCU_IsFlag ($ioHash, 'updGroupMembers'))|| $clHash->{ccuif} eq 'fhem') && exists($clHash->{ccugroup})) { - foreach my $gd (split (",", $clHash->{ccugroup})) { + foreach my $gd (split (',', $clHash->{ccugroup})) { $nam = HMCCU_GetDeviceName ($ioHash, $gd); $list .= ','.$nam if ($nam ne ''); } @@ -7540,8 +7734,7 @@ sub HMCCU_UpdateCB ($$$) } my $c_ok = HMCCU_UpdateMultipleDevices ($hash, \%events); - my $c_err = 0; - $c_err = HMCCU_Max($param->{devCount}-$c_ok, 0) if (exists($param->{devCount})); + my $c_err = exists($param->{devCount}) ? HMCCU_Max($param->{devCount}-$c_ok, 0) : 0; HMCCU_Log ($hash, 2, "Update success=$c_ok failed=$c_err") if ($logcount); } @@ -7610,7 +7803,7 @@ sub HMCCU_RPCRequest ($$$$$;$) if (!defined($pt)) { my $paramDef = HMCCU_GetParamDef ($ioHash, $addr, $paramset, $k); $pt = defined($paramDef) && defined($paramDef->{TYPE}) && $paramDef->{TYPE} ne '' ? - $paramDef->{TYPE} : "STRING"; + $paramDef->{TYPE} : 'STRING'; } $pv .= ":$pt"; push @parArray, "$k=$pv"; @@ -7622,8 +7815,8 @@ sub HMCCU_RPCRequest ($$$$$;$) } # Submit RPC request - my $reqResult = HMCCURPCPROC_SendRequest ($rpcHash, $reqMethod, @parArray); - return (-5, 'RPC function not available') if (!defined($reqResult)); + my $reqResult = HMCCURPCPROC_SendRequest ($rpcHash, $reqMethod, @parArray) // + return (-5, 'RPC function not available'); HMCCU_Trace ($clHash, 2, "Dump of RPC request $method $paramset $addr. Result type=".ref($reqResult)."
". @@ -7695,9 +7888,10 @@ sub HMCCU_SetIfEx { $_[0] = $_[1] if exists($_[1]) } sub HMCCU_DefStr ($;$$) { my ($v, $p, $d) = @_; + $p //= ''; + $d //= ''; - $p = '' if (!defined($p)); - $d = '' if (!defined($d)); + return defined($v) && $v ne '' ? $p.$v : $d; } @@ -7740,59 +7934,6 @@ sub HMCCU_IsIntNum ($) return defined($value) && $value =~ /^[+-]?[0-9]+$/ ? 1 : 0; } -###################################################################### -# Get additional commands, including state commands -# Return (CommandDefinitionList, RoleCommandHash, LookupValueHash) -# CommandDefinitionList: Command list in FHEM Set/Get format. -###################################################################### - -# sub HMCCU_GetAdditionalCommands ($;$) -# { -# my ($clHash, $cc) = @_; -# -# my $ioHash = HMCCU_GetHash ($clHash); -# return ('') if (!defined($clHash->{hmccu}{role}) || $clHash->{hmccu}{role} eq '' || -# !defined($ioHash)); -# -# my $roleCmds = HMCCU_GetRoleCommands ($clHash, $cc); -# -# my %pset = ('V' => 'VALUES', 'M' => 'MASTER', 'D' => 'MASTER'); -# my $cmdList = ''; -# my %valLookup; -# foreach my $cmd (keys %$roleCmds) { -# $cmdList .= " $cmd"; -# if ($roleCmds->{$cmd}) -# foreach my $set (split (/\s+/, $roleCmds->{$cmd}{syntax})) { -# my ($ps, $dpt, $par) = split(/:/, $set); -# my @argList = (); -# if ($par =~ /^#/) { -# my $adr = $clHash->{ccuaddr}; -# $adr =~ s/:[0-9]{1,2}$//; -# my $paramDef = HMCCU_GetParamDef ($ioHash, $adr, $pset{$ps}, $dpt); -# if (defined($paramDef) && $paramDef->{TYPE} eq 'ENUM' && defined($paramDef->{VALUE_LIST})) { -# $par = $paramDef->{VALUE_LIST}; -# $par =~ s/[ ]+/-/g; -# @argList = split (',', $par); -# while (my ($i, $e) = each(@argList)) { $valLookup{$pset{$ps}}{$dpt}{$e} = $i; } -# } -# } -# elsif ($par =~ /^\?(.+)$/) { -# my ($pn, $pv) = split('=', $1); -# $roleCmds->{$cmd}{parname} = $pn; -# $roleCmds->{$cmd}{defval} = $pv if (defined($pv)); -# } -# else { -# @argList = split (',', $par); -# } -# my $argCount = scalar(@argList); -# $par = 'noArg' if ($argCount == 1); -# $cmdList .= ":$par" if ($argCount > 0); -# } -# } -# -# return ($cmdList, $roleCmds, \%valLookup); -# } - ###################################################################### # Get device state from maintenance channel 0 # Return values for readings (devState, battery, alive) @@ -7904,7 +8045,7 @@ sub HMCCU_GetTimeSpec ($) return $ts if (HMCCU_IsFltNum ($ts, 1)); return -1 if ($ts !~ /^[0-9]{2}:[0-9]{2}$/ && $ts !~ /^[0-9]{2}:[0-9]{2}:[0-9]{2}$/); - my (undef, $h, $m, $s) = GetTimeSpec ($ts); + my (undef, $h, $m, $s) = GetTimeSpec ($ts); return -1 if (!defined($h)); $s += $h*3600+$m*60; @@ -8037,15 +8178,7 @@ sub HMCCU_CalculateReading ($$) if ($vt eq 'dewpoint' || $vt eq 'abshumidity') { # Dewpoint and absolute humidity my ($tmp, $hum) = @pars; - if ($tmp >= 0.0) { - $a = 7.5; - $b = 237.3; - } - else { - $a = 7.6; - $b = 240.7; - } - + my ($a, $b) = $tmp >= 0.0 ? (7.5, 237.3) : (7.6, 240.7); my $sdd = 6.1078*(10.0**(($a*$tmp)/($b+$tmp))); my $dd = $hum/100.0*$sdd; if ($dd != 0.0) { @@ -8143,8 +8276,8 @@ sub HMCCU_Encrypt ($) my ($istr) = @_; my $ostr = ''; - my $id = getUniqueId(); - return '' if (!defined($id) || $id eq ''); + my $id = getUniqueId() // ''; + return '' if ($id eq ''); my $key = $id; foreach my $c (split //, $istr) { @@ -8168,8 +8301,8 @@ sub HMCCU_Decrypt ($) my ($istr) = @_; my $ostr = ''; - my $id = getUniqueId(); - return '' if (!defined($id) || $id eq ''); + my $id = getUniqueId() // ''; + return '' if ($id eq ''); my $key = $id; for my $c (map { pack('C', hex($_)) } ($istr =~ /(..)/g)) { @@ -8209,11 +8342,11 @@ sub HMCCU_DeleteReadings ($$) sub HMCCU_UpdateReadings ($$;$) { my ($hash, $readings, $flag) = @_; - $flag //= 0; + $flag //= 1; my $name = $hash->{NAME}; my $ccureadings = $flag ? - AttrVal ($name, 'ccureadings', HMCCU_IsFlag ($name, 'noReadings') ? 0 : 1) : 0; + AttrVal ($name, 'ccureadings', HMCCU_IsFlag ($name, 'noReadings') ? 0 : 1) : 1; if ($ccureadings) { readingsBeginUpdate ($hash); @@ -8229,7 +8362,7 @@ sub HMCCU_UpdateReadings ($$;$) # # Parameters: # -# msg := parameter=value[,...] +# msg := parameter:value[,...] # # text1-3=Text # icon1-3=IconName @@ -8262,12 +8395,15 @@ sub HMCCU_EncodeEPDisplay ($) sig_off => '0xF0', sig_red => '0xF1', sig_green => '0xF2', sig_orange => '0xF3' ); + my %conf = ( + sound => 'snd_off', signal => 'sig_off', repeat => 1, pause => 10 + ); + # Parse command string my @text = ('', '', ''); my @icon = ('', '', ''); - my %conf = (sound => 'snd_off', signal => 'sig_off', repeat => 1, pause => 10); foreach my $tok (split (',', $msg)) { - my ($par, $val) = split ('=', $tok); + my ($par, $val) = split (':', $tok); next if (!defined($val)); if ($par =~ /^text([1-3])$/) { $text[$1-1] = substr ($val, 0, 12); } elsif ($par =~ /^icon([1-3])$/) { $icon[$1-1] = $val; } @@ -8281,7 +8417,7 @@ sub HMCCU_EncodeEPDisplay ($) $cmd .= ',0x12'; # Hex code - if ($text[$c] =~ /^0x[0-9A-F]{2}$/) { + if ($text[$c] =~ /^0x[0-9A-F]{2}$/i) { $cmd .= ','.$text[$c]; } # Predefined text code #0-9 @@ -8291,13 +8427,11 @@ sub HMCCU_EncodeEPDisplay ($) # Convert string to hex codes else { $text[$c] =~ s/\\_/ /g; - foreach my $ch (split ('', $text[$c])) { - $cmd .= sprintf (",0x%02X", ord ($ch)); - } + foreach my $ch (split ('', $text[$c])) { $cmd .= sprintf (",0x%02X", ord($ch)); } } # Icon - if ($icon[$c] ne '' && exists ($disp_icons{$icon[$c]})) { + if ($icon[$c] ne '' && exists($disp_icons{$icon[$c]})) { $cmd .= ',0x13,'.$disp_icons{$icon[$c]}; } } @@ -8307,26 +8441,16 @@ sub HMCCU_EncodeEPDisplay ($) # Sound my $snd = $disp_sounds{snd_off}; - $snd = $disp_sounds{$conf{sound}} if (exists ($disp_sounds{$conf{sound}})); + $snd = $disp_sounds{$conf{sound}} if (exists($disp_sounds{$conf{sound}})); $cmd .= ',0x14,'.$snd.',0x1C'; # Repeat - my $rep = $conf{repeat} if ($conf{repeat} >= 0 && $conf{repeat} <= 15); - $rep = 1 if ($rep < 0); - $rep = 15 if ($rep > 15); - if ($rep == 0) { - $cmd .= ',0xDF'; - } - else { - $cmd .= sprintf (",0x%02X", 0xD0+$rep-1); - } + my $rep = HMCCU_AdjustValue ($conf{repeat}, 0, 15); + $cmd .= $rep == 0 ? ',0xDF' : sprintf (",0x%02X", 0xD0+$rep-1); $cmd .= ',0x1D'; # Pause - my $pause = $conf{pause}; - $pause = 1 if ($pause < 1); - $pause = 160 if ($pause > 160); - $cmd .= sprintf (",0xE%1X,0x16", int(($pause-1)/10)); + $cmd .= sprintf (",0xE%1X,0x16", int((HMCCU_AdjustValue ($conf{pause}, 1, 160)-1)/10)); # Signal my $sig = $disp_signals{sig_off}; @@ -8345,46 +8469,56 @@ sub HMCCU_RefToString ($) { my ($r) = @_; - my $result = ''; - if (ref($r) eq 'ARRAY') { - $result .= "[\n"; + my $result = "[\n"; foreach my $e (@$r) { $result .= ',' if ($result ne '['); $result .= HMCCU_RefToString ($e); } - $result .= "\n]"; + return "$result\n]"; } elsif (ref($r) eq 'HASH') { - $result .= "{\n"; + my $result .= "{\n"; foreach my $k (sort keys %$r) { - $result .= "," if ($result ne '{'); + $result .= ',' if ($result ne '{'); $result .= "$k=".HMCCU_RefToString ($r->{$k}); } - $result .= "\n}"; + return "$result\n}"; } elsif (ref($r) eq 'SCALAR') { - $result .= $$r; + return $$r; } else { - $result .= $r; + return $r; } - - return $result; } +###################################################################### +# Convert bitmask to string +###################################################################### + sub HMCCU_BitsToStr ($$) { my ($chrMap, $bMask) = @_; my $r = ''; - foreach my $bVal (sort keys %$chrMap) { - $r .= $chrMap->{$bVal} if ($bMask & $bVal); - } - + foreach my $bVal (sort keys %$chrMap) { $r .= $chrMap->{$bVal} if ($bMask & $bVal); } return $r; } +###################################################################### +# Ensure that value is within range low-high +###################################################################### + +sub HMCCU_AdjustValue ($$$) +{ + my ($value, $low, $high); + + return $low if ($value < $low); + return $high if ($value > $high); + return $value; +} + ###################################################################### # Match string with regular expression considering illegal regular # expressions. @@ -8395,8 +8529,7 @@ sub HMCCU_ExprMatch ($$$) { my ($t, $r, $e) = @_; - my $x = eval { $t =~ /$r/ }; - return $e if (!defined($x)); + my $x = eval { $t =~ /$r/ } // $e; return "$x" eq '' ? 0 : 1; } @@ -8404,8 +8537,7 @@ sub HMCCU_ExprNotMatch ($$$) { my ($t, $r, $e) = @_; - my $x = eval { $t !~ /$r/ }; - return $e if (!defined($x)); + my $x = eval { $t !~ /$r/ } // $e; return "$x" eq '' ? 0 : 1; } @@ -8418,26 +8550,20 @@ sub HMCCU_GetDutyCycle ($) my ($hash) = @_; my $dc = 0; - my @rpcports = HMCCU_GetRPCPortList ($hash); + my $interfaces = HMCCU_GetRPCInterfaceList ($hash); my %readings; - foreach my $port (@rpcports) { + foreach my $port (values %$interfaces) { next if ($port != 2001 && $port != 2010); - my $url = HMCCU_BuildURL ($hash, $port); - next if (!defined($url)); + my $url = HMCCU_BuildURL ($hash, $port) // next; my $rpcclient = RPC::XML::Client->new ($url); - my $response = $rpcclient->simple_request ("listBidcosInterfaces"); - next if (!defined ($response) || ref($response) ne 'ARRAY'); + my $response = $rpcclient->simple_request ('listBidcosInterfaces'); + next if (!defined($response) || ref($response) ne 'ARRAY'); foreach my $iface (@$response) { - next if (ref($iface) ne 'HASH' || !exists ($iface->{DUTY_CYCLE})); + next if (ref($iface) ne 'HASH' || !exists($iface->{DUTY_CYCLE})); $dc++; - my $type; - if (exists($iface->{TYPE})) { - $type = $iface->{TYPE} - } - else { - ($type) = HMCCU_GetRPCServerInfo ($hash, $port, 'name'); - } + my ($type) = exists($iface->{TYPE}) ? + ($iface->{TYPE}) : HMCCU_GetRPCServerInfo ($hash, $port, 'name'); $readings{"iface_addr_$dc"} = $iface->{ADDRESS}; $readings{"iface_conn_$dc"} = $iface->{CONNECTED}; $readings{"iface_type_$dc"} = $type; @@ -8446,7 +8572,7 @@ sub HMCCU_GetDutyCycle ($) } HMCCU_UpdateReadings ($hash, \%readings); - + return $dc; } @@ -8462,7 +8588,7 @@ sub HMCCU_TCPPing ($$$) if ($timeout > 0) { my $t = time (); - while (time () < $t+$timeout) { + while (time() < $t+$timeout) { return 1 if (HMCCU_TCPConnect ($addr, $port) ne ''); sleep (20); } @@ -8487,7 +8613,7 @@ sub HMCCU_TCPConnect ($$) if ($socket) { my $ipaddr = $socket->sockhost (); close ($socket); - return $ipaddr if (defined ($ipaddr)); + return $ipaddr if (defined($ipaddr)); } return ''; @@ -8581,11 +8707,11 @@ sub HMCCU_MaxHashEntries ($$) Define

-
-
  • rpcinterval <Seconds>
    - Specifiy how often RPC queue is read. Default is 5 seconds. Only relevant if internal - RPC server is used (deprecated).

  • rpcPingCCU <interval>
    Send RPC ping request to CCU every interval seconds. If interval is 0 ping requests are disabled. Default value is 300 seconds. If attribut ccuflags is set to logPong a log message with level 3 is created when receiving a pong event.

  • -
  • rpcport <value[,...]>
    - Deprecated, use attribute 'rpcinterfaces' instead. Specify list of RPC ports on CCU. - Either port 2001 or 2010 (HmIP only) is default. Valid RPC ports are:

    -
      -
    • 2000 = Wired components
    • -
    • 2001 = BidCos-RF (wireless 868 MHz components with BidCos protocol)
    • -
    • 2003 = Homegear (experimental)
    • -
    • 2010 = HM-IP (wireless 868 MHz components with IPv6 protocol)
    • -
    • 7000 = HVL (Homematic Virtual Layer devices)
    • -
    • 8701 = CUxD (only supported with external RPC server HMCCURPC)
    • -
    • 9292 = CCU group devices (especially heating groups)
    • -
    -

  • -
  • rpcqueue <queue-file>
    - Specify name of RPC queue file. This parameter is only a prefix (including the - pathname) for the queue files with extension .idx and .dat. Default is - /tmp/ccuqueue. If FHEM is running on a SD card it's recommended that the queue - files are placed on a RAM disk. -

  • rpcserver {on | off}
    Specify if RPC server is automatically started on FHEM startup.

  • diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm index 368c32aef..27ccaa8b0 100644 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm @@ -4,7 +4,7 @@ # # $Id: 88_HMCCUCHN.pm 18552 2019-02-10 11:52:28Z zap $ # -# Version 4.4.021 +# Version 4.4.028 # # (c) 2020 zap (zap01 t-online de) # @@ -18,7 +18,6 @@ package main; use strict; use warnings; use SetExtensions; -# use Data::Dumper; require "$attr{global}{modpath}/FHEM/88_HMCCU.pm"; @@ -50,11 +49,11 @@ sub HMCCUCHN_Initialize ($) $hash->{AttrList} = 'IODev ccucalculate '. 'ccuflags:multiple-strict,ackState,logCommand,noReadings,trace,showMasterReadings,showLinkReadings,showDeviceReadings '. 'ccureadingfilter:textField-long '. - 'ccureadingformat:name,namelc,address,addresslc,datapoint,datapointlc '. + 'ccureadingformat:name,namelc,address,addresslc '. 'ccureadingname:textField-long ccuSetOnChange ccuReadingPrefix '. 'ccuscaleval ccuverify:0,1,2 ccuget:State,Value controldatapoint '. 'disable:0,1 hmstatevals:textField-long statedatapoint statevals substitute:textField-long '. - 'substexcl stripnumber peer:textField-long '. $readingFnAttributes; + 'substexcl stripnumber peer:textField-long traceFilter '. $readingFnAttributes; } ###################################################################### @@ -88,7 +87,7 @@ sub HMCCUCHN_Define ($@) my $n = 0; while (my $arg = shift @$a) { return $usage if ($n == 3); - if ($arg eq 'readonly') { $hash->{readonly} = "yes"; } + if ($arg eq 'readonly') { $hash->{readonly} = 'yes'; } elsif (lc($arg) eq 'nodefaults' && $init_done) { $hash->{hmccu}{nodefaults} = 1; } elsif ($arg eq 'defaults' && $init_done) { $hash->{hmccu}{nodefaults} = 0; } else { return $usage; } @@ -165,6 +164,8 @@ sub HMCCUCHN_InitDevice ($$) HMCCU_UpdateDevice ($ioHash, $devHash); HMCCU_UpdateDeviceRoles ($ioHash, $devHash); HMCCU_UpdateRoleCommands ($ioHash, $devHash); + HMCCU_UpdateAdditionalCommands ($ioHash, $devHash); + if (!exists($devHash->{hmccu}{nodefaults}) || $devHash->{hmccu}{nodefaults} == 0) { if (!HMCCU_SetDefaultAttributes ($devHash)) { HMCCU_SetDefaults ($devHash); @@ -222,6 +223,12 @@ sub HMCCUCHN_Attr ($@) elsif ($attrname eq 'statevals') { return 'Device is read only' if ($hash->{readonly} eq 'yes'); } + elsif ($attrname eq 'statedatapoint') { + $hash->{hmccu}{state}{dpt} = $attrval; + } + elsif ($attrname eq 'controldatapoint') { + $hash->{hmccu}{control}{dpt} = $attrval; + } } HMCCU_RefreshReadings ($hash) if ($init_done); @@ -252,27 +259,17 @@ sub HMCCUCHN_Set ($@) return ($opt eq '?' ? undef : 'Cannot perform set commands. CCU busy') if (HMCCU_IsRPCStateBlocking ($ioHash)); - my $ccutype = $hash->{ccutype}; - my $ccuaddr = $hash->{ccuaddr}; - my $ccuif = $hash->{ccuif}; - my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); - # Get state and control datapoints my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash); # Additional commands, including state commands my $cmdList = $hash->{hmccu}{cmdlist} // ''; - # Get state values related to control command and datapoint - my $stateVals = HMCCU_GetStateValues ($hash, $cd); - my @stateCmdList = split (/[:,]/, $stateVals); - my %stateCmds = @stateCmdList; - my @states = keys %stateCmds; - # Some commands require a control datapoint if ($opt =~ /^(control|toggle)$/) { return HMCCU_SetError ($hash, -14) if ($cd eq ''); - return HMCCU_SetError ($hash, -8) if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, $cd, 2)); + return HMCCU_SetError ($hash, -8) + if (!HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, $hash->{ccuaddr}, $cd, 2)); } my $result = ''; @@ -280,37 +277,18 @@ sub HMCCUCHN_Set ($@) # Log commands HMCCU_Log ($hash, 3, "set $name $opt ".join (' ', @$a)) - if ($opt ne '?' && $ccuflags =~ /logCommand/ || HMCCU_IsFlag ($ioName, 'logCommand')); + if ($opt ne '?' && (HMCCU_IsFlag ($name, 'logCommand') || HMCCU_IsFlag ($ioName, 'logCommand'))); if ($opt eq 'control') { my $value = shift @$a // return HMCCU_SetError ($hash, "Usage: set $name control {value}"); + my $stateVals = HMCCU_GetStateValues ($hash, $cd, $cc); $rc = HMCCU_SetMultipleDatapoints ($hash, - { "001.$ccuif.$ccuaddr.$cd" => HMCCU_Substitute ($value, $stateVals, 1, undef, '') } + { "001.$hash->{ccuif}.$hash->{ccuaddr}.$cd" => HMCCU_Substitute ($value, $stateVals, 1, undef, '') } ); return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); } elsif ($opt eq 'datapoint') { - my $usage = "Usage: set $name datapoint {{datapoint} {value} | {datapoint}={value}} [...]"; - my %dpval; - my $i = 0; - - push (@$a, %${h}) if (defined($h)); - while (my $objname = shift @$a) { - my $objvalue = shift @$a // return HMCCU_SetError ($hash, $usage); - return HMCCU_SetError ($hash, -8) - if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, $objname, 2)); - - $i++; - my $no = sprintf ("%03d", $i); - $objvalue = HMCCU_Substitute ($objvalue, $stateVals, 1, undef, '') - if ($stateVals ne '' && $objname eq $cd); - $dpval{"$no.$ccuif.$ccuaddr.$objname"} = $objvalue; - } - - return HMCCU_SetError ($hash, $usage) if (scalar(keys %dpval) < 1); - - $rc = HMCCU_SetMultipleDatapoints ($hash, \%dpval); - return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); + return HMCCU_ExecuteSetDatapointCommand ($hash, $a, $h, $cc, $cd); } elsif ($opt eq 'toggle') { return HMCCU_ExecuteToggleCommand ($hash, $cc, $cd); @@ -319,72 +297,10 @@ sub HMCCUCHN_Set ($@) return HMCCU_ExecuteRoleCommand ($ioHash, $hash, $opt, $cc, $a, $h); } elsif ($opt eq 'clear') { - my $rnexp = shift @$a; - HMCCU_DeleteReadings ($hash, $rnexp); - return HMCCU_SetState ($hash, "OK"); + return HMCCU_ExecuteSetClearCommand ($hash, $a); } elsif ($opt =~ /^(config|values)$/) { - my %parSets = ('config' => 'MASTER', 'values' => 'VALUES'); - my $paramset = $parSets{$opt}; - my $receiver = ''; - - return HMCCU_SetError ($hash, "No parameter specified") - if ((scalar keys %{$h}) < 1); - - my $ccuobj = $ccuaddr; - my $p = shift @$a; - if (defined($p)) { - if ($p eq 'device') { - ($ccuobj, undef) = HMCCU_SplitChnAddr ($ccuaddr); - } - else { - $receiver = $p; - $paramset = 'LINK'; - } - } - - my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $ccuobj, $ccuif); - return HMCCU_SetError ($hash, "Can't get device description") if (!defined($devDesc)); - return HMCCU_SetError ($hash, "Paramset $paramset not supported by device or channel") - if ($devDesc->{PARAMSETS} !~ /$paramset/); - if (!HMCCU_IsValidParameter ($ioHash, $devDesc, $paramset, $h)) { - my @parList = HMCCU_GetParamDef ($ioHash, $devDesc, $paramset); - return HMCCU_SetError ($hash, "Invalid parameter specified. Valid parameters are ". - join(',', @parList)); - } - - if ($paramset eq 'VALUES' || $paramset eq 'MASTER') { - ($rc, $result) = HMCCU_SetMultipleParameters ($hash, $ccuobj, $h, $paramset); - } - else { - if (exists($defs{$receiver}) && defined($defs{$receiver}->{TYPE})) { - my $clHash = $defs{$receiver}; - if ($clHash->{TYPE} eq 'HMCCUDEV') { - my $chnNo = shift @$a; - return HMCCU_SetError ($hash, "Channel number required for link receiver") - if (!defined($chnNo) || $chnNo !~ /^[0-9]{1,2}$/); - $receiver = $clHash->{ccuaddr}.":$chnNo"; - } - elsif ($clHash->{TYPE} eq 'HMCCUCHN') { - $receiver = $clHash->{ccuaddr}; - } - else { - return HMCCU_SetError ($hash, "Receiver $receiver is not a HMCCUCHN or HMCCUDEV device"); - } - } - elsif (!HMCCU_IsChnAddr ($receiver, 0)) { - my ($rcvAdd, $rcvChn) = HMCCU_GetAddress ($ioHash, $receiver); - return HMCCU_SetError ($hash, "$receiver is not a valid CCU channel name") - if ($rcvAdd eq '' || $rcvChn eq ''); - $receiver = "$rcvAdd:$rcvChn"; - } - - return HMCCU_SetError ($hash, "$receiver is not a link receiver of $name") - if (!HMCCU_IsValidReceiver ($ioHash, $ccuaddr, $ccuif, $receiver)); - ($rc, $result) = HMCCU_RPCRequest ($hash, "putParamset", $ccuaddr, $receiver, $h); - } - - return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); + return HMCCU_ExecuteSetParameterCommand ($ioHash, $hash, $opt, $a, $h); } elsif ($opt eq 'defaults') { my $mode = shift @$a // 'update'; @@ -398,7 +314,6 @@ sub HMCCUCHN_Set ($@) if ($hash->{readonly} ne 'yes') { $retmsg .= ' config datapoint'; $retmsg .= " $cmdList" if ($cmdList ne ''); - $retmsg .= ' toggle:noArg' if (scalar(@states) > 0); } return AttrTemplate_Set ($hash, $retmsg, $name, $opt, @$a); } @@ -450,27 +365,18 @@ sub HMCCUCHN_Get ($@) return $rc < 0 ? HMCCU_SetError ($hash, $rc, $result) : $result; } elsif ($opt eq 'deviceinfo') { - my ($a, $c) = HMCCU_SplitChnAddr ($ccuaddr); - $result = HMCCU_GetDeviceInfo ($hash, $a); - return HMCCU_SetError ($hash, -2) if ($result eq ''); - my $devInfo = HMCCU_FormatDeviceInfo ($result); - $devInfo .= "StateDatapoint = $sc.$sd\nControlDatapoint = $cc.$cd"; - return $devInfo; + my ($devAddr, undef) = HMCCU_SplitChnAddr ($ccuaddr); + return HMCCU_ExecuteGetDeviceInfoCommand ($ioHash, $hash, $devAddr, $sc, $sd, $cc, $cd); } elsif ($opt =~ /^(config|values|update)$/) { my ($devAddr, undef) = HMCCU_SplitChnAddr ($ccuaddr); - my @addList = ($devAddr, "$devAddr:0", $ccuaddr); - + my @addList = ($devAddr, "$devAddr:0", $ccuaddr); return HMCCU_ExecuteGetParameterCommand ($ioHash, $hash, $opt, \@addList); } elsif ($opt eq 'paramsetdesc') { $result = HMCCU_ParamsetDescToStr ($ioHash, $hash); return defined($result) ? $result : HMCCU_SetError ($hash, "Can't get device model"); } - elsif ($opt eq 'devicedesc') { - $result = HMCCU_DeviceDescToStr ($ioHash, $hash); - return defined($result) ? $result : HMCCU_SetError ($hash, "Can't get device description"); - } elsif ($opt eq 'defaults') { return HMCCU_GetDefaults ($hash, 0); } @@ -486,7 +392,7 @@ sub HMCCUCHN_Get ($@) my $valuecount = HMCCU_GetValidDatapoints ($hash, $hash->{ccutype}, $c, 1, \@valuelist); $retmsg .= ":".join(",",@valuelist) if ($valuecount > 0); $retmsg .= " update:noArg deviceInfo:noArg config:noArg". - " deviceDesc:noArg paramsetDesc:noArg values:noArg"; + " paramsetDesc:noArg values:noArg"; $retmsg .= ' weekProgram:all,'.join(',', sort keys %{$hash->{hmccu}{tt}}) if (exists($hash->{hmccu}{tt})); @@ -518,14 +424,14 @@ sub HMCCUCHN_Get ($@)

    If option 'readonly' is specified no set command will be available. With option 'noDefaults' no default attributes will be set during interactive device definition.
    - The define command accepts a CCU2 channel name or channel address as parameter. + The define command accepts a CCU channel name or channel address as parameter.

    Examples:
    define window_living HMCCUCHN WIN-LIV-1 readonly
    define temp_control HMCCUCHN BidCos-RF.LEQ1234567:1

    - The interface part of a channel address must not be specified. The default is 'BidCos-RF'. - Channel addresses can be found with command 'get deviceinfo <devicename>' executed + The interface part of a channel address is optional. + Channel addresses can be found with command 'get deviceinfo <CCU-DeviceName>' executed in I/O device.
    @@ -533,34 +439,41 @@ sub HMCCUCHN_Get ($@) Set

      -
    • set <name> clear [<reading-exp>]
      +
    • set <name> clear [<reading-exp>|reset]
      Delete readings matching specified reading name expression. Default expression is '.*'. - Readings 'state' and 'control' are not deleted. + Readings 'state' and 'control' are not deleted. With option 'reset' all readings + and all internally stored device parameter values are deleted.

    • -
    • set <name> config [device|<receiver>] <parameter>=<value>[:<type>]
      - Set multiple config or link parameters. If neither 'device' nor receiver is - specified, configuration parameters of current channel are set. - With option 'device' configuration parameters of the device are set.
      +
    • set <name> config [device|<receiver>] <parameter>=<value>[:<type>] [...]
      + Set multiple config (parameter set MASTER) or link (parameter set LINKS) parameters. + If neither 'device' nor receiver is specified, configuration parameters of + current channel are set. With option 'device' configuration parameters of the device + are set.
      If a receiver is specified, parameters will be set for the specified link. - Parameter receiver is the - name of a FHEM device of type HMCCUDEV or HMCCUCHN or a channel address or a CCU - channel name. For FHEM devices of type HMCCUDEV a channel number must be specified. + Parameter receiver is the name of a FHEM device of type HMCCUDEV or HMCCUCHN or + a channel address or a CCU channel name. For FHEM devices of type HMCCUDEV the number + of the linked channel must be specified.
      Parameter parameter must be a valid configuration parameter. - If type is not specified, it's taken from - parameter set definition. The default type is STRING. - Valid types are STRING, BOOL, INTEGER, FLOAT, DOUBLE. + If type is not specified, it's taken from parameter set definition. If type + cannot be determined, the default type STRING is used. + Valid types are STRING, BOOL, INTEGER, FLOAT, DOUBLE.

      + Example 1: Set device parameter AES
      + set myDev config device AES=1
      + Example 2: Set channel parameters MIN and MAX with type definition
      + set myDev config MIN=0.5:FLOAT MAX=10.0:FLOAT
      + Example 3: Set link parameter. DEV_PARTNER is a HMCCUDEV device, so channel number (3) is required
      + set myDev config DEV_PARTNER:3 MYPARAM=1

    • set <name> control <value>
      - Set value of control datapoint. This command is available for compatibility reasons. - It should not be used any more. + Set value of control datapoint. This command is available only on command line + for compatibility reasons. It should not be used any more.

    • set <name> datapoint <datapoint> <value> | <datapoint>=<value> [...]
      - Set datapoint values of a CCU channel. If parameter value contains special - character \_ it's substituted by blank. -

      + Set datapoint values of a CCU channel. If value contains blank characters it must be + enclosed in double quotes.

      Examples:
      set temp_control datapoint SET_TEMPERATURE 21
      - set temp_control datapoint AUTO_MODE 1 SET_TEMPERATURE 21 + set temp_control datapoint AUTO_MODE 1 SET_TEMPERATURE=21

    • set <name> defaults ['reset'|'update']
      Set default attributes for CCU device type. Default attributes are only available for @@ -570,89 +483,52 @@ sub HMCCUCHN_Get ($@) During update to version 4.4 it's recommended to use option 'reset'.

    • set <name> down [<value>]
      - Decrement value of datapoint LEVEL. This command is only available if channel contains - a datapoint LEVEL. Default for value is 20. -

    • -
    • set <name> link <receiver> [<channel>] <parameter>=<value>[:<type>]
      - Set multiple link parameters (parameter set LINK). Parameter receiver is the - name of a FHEM device of type HMCCUDEV or HMCCUCHN or a channel address or a CCU - channel name. For FHEM devices of type HMCCUDEV a channel number must be specified. - Parameter parameter must be a valid - link configuration parameter name. If type is not specified, it's taken from - parameter set definition. The default type is STRING. - Valid types are STRING, BOOL, INTEGER, FLOAT, DOUBLE. -

    • -
    • set <name> <statevalue>
      - Set state of a CCU device channel to StateValue. The state datapoint of a channel - must be defined by setting attribute 'statedatapoint'. The available state values must - be defined by setting attribute 'statevals'.
      - If 'statedatapoint' or 'statevals' is not set, HMCCUCHN tries to detect the parameters - depending on the device type and the available datapoints. -

      - Example: Turn switch on
      - - attr myswitch statedatapoint STATE
      - attr myswitch statevals on:true,off:false
      - set myswitch on -
      + [dimmer, blind] Decrement value of datapoint LEVEL. This command is only available + if channel contains a datapoint LEVEL. Default for value is 20.

    • set <name> on-for-timer <ontime>
      - Switch device on for specified number of seconds. This command is only available if - channel contains a datapoint ON_TIME. The attribute 'statevals' must contain at least a - value for 'on'. The attribute 'statedatapoint' must be set to a writeable datapoint. -

      + [switch] Switch device on for specified number of seconds. This command is only available if + channel contains a datapoint ON_TIME. Parameter ontime can be specified + in seconds or in format HH:MM:SS

      Example: Turn switch on for 300 seconds
      - - attr myswitch statedatapoint STATE
      - attr myswitch statevals on:true,off:false
      - set myswitch on-for-timer 300 -
      + set myswitch on-for-timer 300

    • set <name> on-till <timestamp>
      - Switch device on until timestamp. Parameter timestamp can be a time in + [switch] Switch device on until timestamp. Parameter timestamp can be a time in format HH:MM or HH:MM:SS. This command is only available if channel contains a datapoint - ON_TIME. The attribute 'statevals' must contain at least a value for 'on'. The Attribute - 'statedatapoint' must be set to a writeable datapoint. + ON_TIME.

    • set <name> pct <value> [<ontime> [<ramptime>]]
      - Set datapoint LEVEL of a channel to the specified value. Optionally a ontime + [dimmer] Set datapoint LEVEL of a channel to the specified value. Optionally a ontime and a ramptime (both in seconds) can be specified. This command is only available if channel contains at least a datapoint LEVEL and optionally datapoints ON_TIME and RAMP_TIME. The parameter ontime can be specified in seconds or as timestamp in format HH:MM or HH:MM:SS. If ontime is 0 it's ignored. This syntax can be used to - modify the ramp time only. -

      + modify the ramp time only.

      Example: Turn dimmer on for 600 second. Increase light to 100% over 10 seconds
      - - attr myswitch statedatapoint LEVEL
      - attr myswitch statevals on:100,off:0
      - set myswitch pct 100 600 10 -
      + set myswitch pct 100 600 10

    • set <name> stop
      - Set datapoint STOP of a channel to true. This command is only available, if the - channel contains a writeable datapoint STOP. + [blind] Set datapoint STOP of a channel to true. This command is only available, if the + channel contains a datapoint STOP.

    • set <name> toggle
      - Toggle state datapoint between values defined by attribute 'statevals'. This command is - only available if attribute 'statevals' is set. Toggling supports more than two state - values. -

      - Example: Toggle blind actor
      - - attr myswitch statedatapoint LEVEL
      - attr myswitch statevals up:100,down:0
      - set myswitch toggle -
      + Toggle state datapoint between values defined by attribute 'statevals'. This command is + only available if state values can be detected or are defined by using attribute + 'statevals'. Toggling supports more than two state values.

      + Example: Toggle blind actor
      + + attr myswitch statevals up:100,down:0
      + set myswitch toggle +

    • set <name> up [<value>]
      - Increment value of datapoint LEVEL. This command is only available if channel contains - a datapoint LEVEL. Default for value is 20. + [blind,dimmer] Increment value of datapoint LEVEL. This command is only available + if channel contains a datapoint LEVEL. Default for value is 20.

    • -
    • set <name> values <parameter>=<value>[:<type>]
      - Set multiple datapoint values (parameter set VALUES). - Supports attribute 'ccuscaleval'. Parameter parameter must be a valid - datapoint name. If type is not specified, it's taken from +
    • set <name> values <parameter>=<value>[:<type>] [...]
      + Set multiple datapoint values (parameter set VALUES). Parameter parameter + must be a valid datapoint name. If type is not specified, it's taken from parameter set definition. The default type is STRING. Valid types are STRING, BOOL, INTEGER, FLOAT, DOUBLE.
    • @@ -664,8 +540,8 @@ sub HMCCUCHN_Get ($@)
      • get <name> config
        Get configuration parameters of device and channel. - Values related to configuration ot link parameters are stored as readings beginning - with "R-" for MASTER parameters and "L-" for LINK parameters. + Values related to configuration or link parameters are stored as readings beginning + with "R-" for MASTER parameter set and "L-" for LINK parameter set. Prefixes can be modified with attribute 'ccuReadingPrefix'. Whether parameters are stored as readings or not, can be controlled by setting the following flags in attribute ccuflags:
        @@ -682,21 +558,22 @@ sub HMCCUCHN_Get ($@)
      • get <name> defaults
        Display default attributes for CCU device type.

      • -
      • get <name> deviceDesc
        - Display device or channel description. A channel description always includes channel 0. -

      • get <name> deviceInfo
        - Display all channels and datapoints of device with datapoint values and types. + Display information about device and channels:
        +
          +
        • all channels and datapoints of device with datapoint values and types
        • +
        • statedatapoint and controldatapoint
        • +
        • device and channel description
        • +

      • get <name> paramsetDesc
        Display description of parameter sets of channel and device.

      • -
      • get <name> update [{State | Value}]
        - Update all datapoints / readings of channel. With option 'State' the device is queried. - This request method is more accurate but slower then 'Value'. +
      • get <name> update
        + Update all readings for all parameters of all parameter sets (MASTER, LINK, VALUES).

      • get <name> values
        - Same as 'get update' but using RPC instead of ReGa. + Update all readings for all parameters of parameter set VALUES (datapoints).

      • get <name> weekProgram [<program-number>|all]
        Display week programs. This command is only available if a device supports week programs. @@ -754,20 +631,7 @@ sub HMCCUCHN_Get ($@) If channel-name or channel-number is specified the following rule applies only to this channel.
        If a rule starts with 'N:' the filter is negated which means that a reading is - stored if rule doesn't match.
        - The following table describes the dependencies between this attribute and attribute - ccudef-readingfilter in I/O device. The filtering of readings depends on which attribute - is set.
        - - - - - - -
        ccureadingfilter
        Device
        ccudef-readingfilter
        I/O Device
        Update Readings/Datapoints
        not setnot setall readings
        not setsetonly readings from ccudef-readingfilter
        setnot setonly readings from ccureadingfilter
        setsetboth readings from ccureadingfilter and ccudef-readingfilter
        - So if ccudef-readingfilter is set in I/O device one must also set ccureadingfilter to - get updates for additional, device specific readings. Please keep in mind, that readings updates - are also affected by attributes event-on-change-reading and event-on-update-reading.

        + stored if rule doesn't match.

        Examples:
        attr mydev ccureadingfilter .*
        @@ -776,12 +640,18 @@ sub HMCCUCHN_Get ($@)

      • -
      • ccureadingformat {address[lc] | name[lc] | datapoint[lc]}
        - Set format of reading names. Default for virtual device groups is 'name'. The default for all - other device types is 'datapoint'. If set to 'address' format of reading names +
      • ccureadingformat {address[lc] | name[lc] | datapoint[lc] | <format-string>}
        + Set format of reading names. Default for virtual device groups and HMCCUCHN devices is 'name'. + The default for HMCCUDEV is 'datapoint'. If set to 'address' format of reading names is channel-address.datapoint. If set to 'name' format of reading names is - channel-name.datapoint. If set to 'datapoint' format is channel-number.datapoint. With - suffix 'lc' reading names are converted to lowercase. + channel-name.datapoint. If set to 'datapoint' format is channel-number.datapoint. + For HMCCUCHN devices the channel part is ignored. With suffix 'lc' reading names are converted + to lowercase. The reading format can also contain format specifiers %a (address), + %n (name) and %c (channel). Use %A, %N, %C for conversion to upper case.

        + Example:
        + + attr mydev ccureadingformat HM_%c_%N +

      • ccureadingname <old-readingname-expr>:[+]<new-readingname>[,...];[;...]
        @@ -804,10 +674,12 @@ sub HMCCUCHN_Get ($@) attr mydev ccureadingname [1-4].PRESSED_SHORT:+pressed

      • +
      • ccuReadingPrefix <paramset>:<prefix>[,...]
        Set reading name prefix for parameter sets. The special parameter set 'peer' can be used for link readings.

      • +
      • ccuscaleval <[channelno.]datapoint>:<factor>[,...]
        ccuscaleval <[!][channelno.]datapoint>:<min>:<max>:<minn>:<maxn>[,...]
        @@ -824,6 +696,7 @@ sub HMCCUCHN_Get ($@) attr myblind ccuscale !LEVEL:0:1:0:100

      • +
      • ccuSetOnChange <expression>
        Check if datapoint value will be changed by set command before changing datapoint value. This attribute can reduce the traffic between CCU and devices. It presumes that datapoint @@ -834,18 +707,16 @@ sub HMCCUCHN_Get ($@) corresponding reading will be set to the new value directly after setting a datapoint in CCU without any verification.

      • +
      • controldatapoint <datapoint>
        - Set datapoint for device control. Can be use to realize user defined control elements for - setting control datapoint. For example if datapoint of thermostat control is - SET_TEMPERATURE one can define a slider for setting the destination temperature with - following attributes:

        - attr mydev controldatapoint SET_TEMPERATURE
        - attr mydev webCmd control
        - attr mydev widgetOverride control:slider,10,1,25 + Set datapoint for device control by commands 'set control' and 'set toggle'. + This attribute must be set if control datapoint cannot be detected automatically.

      • +
      • disable {0 | 1}
        Disable client device.

      • +
      • hmstatevals <subst-rule>[;...]
        Define building rules and substitutions for reading hmstate. Syntax of subst-rule is
        @@ -867,6 +738,7 @@ sub HMCCUCHN_Get ($@) Optionally the name of the HomeMatic state reading can be specified at the beginning of the attribute in format =<reading>;. The default reading name is 'hmstate'.

      • +
      • peer <datapoints>:<condition>: {ccu:<object>=<value>|hmccu:<object>=<value>| fhem:<command>}
        @@ -899,9 +771,12 @@ sub HMCCUCHN_Get ($@) # Set 1.STATE of device LEQ1234567 to true if current level is different from old level
        attr mydev peer 1.LEVEL:$1.LEVEL != $$1.LEVEL:ccu:LEQ1234567:1.STATE=true

      • +
      • statedatapoint <datapoint>
        - Set state datapoint used by some commands like 'set devstate'. + Set datapoint used for displaying device state. This attribute must be set, if + state datapoint cannot be detected automatically.

      • +
      • statevals <text>:<text>[,...]
        Define substitution for values of set commands. The parameters text are available as set commands. @@ -912,6 +787,7 @@ sub HMCCUCHN_Get ($@) set my_switch on

      • +
      • stripnumber [<datapoint-expr>!]{0|1|2|-n|%fmt}[;...]
        Remove trailing digits or zeroes from floating point numbers, round or format numbers. If attribute is negative (-0 is valid) floating point values are rounded @@ -928,16 +804,18 @@ sub HMCCUCHN_Get ($@) attr myDev stripnumber TEMPERATURE!%.2f degree

      • +
      • substexcl <reading-expr>
        Exclude values of readings matching reading-expr from substitution. This is helpful for reading 'control' if the reading is used for a slider widget and the corresponding datapoint is assigned to attribute statedatapoint and controldatapoint.

      • +
      • substitute <subst-rule>[;...]
        Define substitutions for datapoint/reading values. Syntax of subst-rule is

        [[<type>:][<channelno>.]<datapoint>[,...]!]<{#n1-m1|regexp}>:<text>[,...]

        - Parameter type is a valid channel type, i.e. "SHUTTER_CONTACT". + Parameter type is a valid channel type/role, i.e. "SHUTTER_CONTACT". Parameter text can contain variables in format ${varname}. The variable ${value} is substituted by the original datapoint value. All other variables must match with a valid @@ -955,7 +833,11 @@ sub HMCCUCHN_Get ($@) attr my_dim substitute LEVEL!#0-0:off,#1-100:on -
      • +
        + +
      • traceFilter <filter-expr>
        + Trace only function calls which are maching filter-expr. +

    diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm index 47e04e84a..56e3f53da 100644 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm @@ -4,7 +4,7 @@ # # $Id: 88_HMCCUDEV.pm 18552 2019-02-10 11:52:28Z zap $ # -# Version 4.4.025 +# Version 4.4.033 # # (c) 2020 zap (zap01 t-online de) # @@ -39,7 +39,7 @@ sub HMCCUDEV_Initialize ($) my ($hash) = @_; $hash->{DefFn} = 'HMCCUDEV_Define'; - $hash->{UndefFn} = 'HMCCUCHN_Undef'; + $hash->{UndefFn} = 'HMCCUDEV_Undef'; $hash->{RenameFn} = 'HMCCUDEV_Rename'; $hash->{SetFn} = 'HMCCUDEV_Set'; $hash->{GetFn} = 'HMCCUDEV_Get'; @@ -53,7 +53,7 @@ sub HMCCUDEV_Initialize ($) 'ccureadingname:textField-long ccuSetOnChange ccuReadingPrefix '. 'ccuget:State,Value ccuscaleval ccuverify:0,1,2 disable:0,1 '. 'hmstatevals:textField-long statevals substexcl substitute:textField-long statechannel '. - 'controlchannel statedatapoint controldatapoint stripnumber peer:textField-long '. + 'controlchannel statedatapoint controldatapoint stripnumber peer:textField-long traceFilter '. $readingFnAttributes; } @@ -82,16 +82,21 @@ sub HMCCUDEV_Define ($@) "Too many virtual devices", "Control channel ambiguous. Please specify control channel in device definition" ); + + my @warnmsg = ( + "OK", + "Control channel ambiguous. Please specify control channel in device definition or attribute controldatapoint" + ); my ($devname, $devtype, $devspec) = splice (@$a, 0, 3); my $ioHash = undef; # Store some definitions for delayed initialization $hash->{readonly} = 'no'; - $hash->{hmccu}{devspec} = $devspec; - $hash->{hmccu}{groupexp} = $h->{groupexp} if (exists ($h->{groupexp})); - $hash->{hmccu}{group} = $h->{group} if (exists ($h->{group})); - $hash->{hmccu}{nodefaults} = $init_done ? 0 : 1; + $hash->{hmccu}{devspec} = $devspec; + $hash->{hmccu}{groupexp} = $h->{groupexp} if (exists ($h->{groupexp})); + $hash->{hmccu}{group} = $h->{group} if (exists ($h->{group})); + $hash->{hmccu}{nodefaults} = $init_done ? 0 : 1; $hash->{hmccu}{semDefaults} = 0; if (exists($h->{address})) { @@ -142,7 +147,7 @@ sub HMCCUDEV_Define ($@) # Initialize FHEM device, set IO device my $rc = HMCCUDEV_InitDevice ($ioHash, $hash); return $errmsg[$rc] if ($rc > 0 && $rc < scalar(@errmsg)); - + HMCCU_LogDisplay ($hash, 2, $warnmsg[-$rc]) if ($init_done && $rc < 0 && -$rc < scalar(@warnmsg)); return undef; } @@ -157,7 +162,7 @@ sub HMCCUDEV_Define ($@) # 5 = Type of virtual device not defined # 6 = Device type not found # 7 = Too many virtual devices -# 8 = Control channel must be specified +# -1 = Control channel must be specified ###################################################################### sub HMCCUDEV_InitDevice ($$) @@ -219,9 +224,10 @@ sub HMCCUDEV_InitDevice ($$) HMCCU_UpdateDeviceRoles ($ioHash, $devHash); my ($sc, $sd, $cc, $cd, $sdCnt, $cdCnt) = HMCCU_GetSpecialDatapoints ($devHash); - return 8 if ($cdCnt > 2); + return -1 if ($cdCnt > 2); HMCCU_UpdateRoleCommands ($ioHash, $devHash, $attr{$devHash->{NAME}}{controlchannel}); + HMCCU_UpdateAdditionalCommands ($ioHash, $devHash, $attr{$devHash->{NAME}}{controlchannel}); if (!exists($devHash->{hmccu}{nodefaults}) || $devHash->{hmccu}{nodefaults} == 0) { if (!HMCCU_SetDefaultAttributes ($devHash, { @@ -341,6 +347,30 @@ sub HMCCUDEV_Attr ($@) elsif ($attrname eq 'statevals') { return "Device is read only" if ($hash->{readonly} eq 'yes'); } + elsif ($attrname eq 'statechannel') { + $hash->{hmccu}{state}{chn} = $attrval; + } + elsif ($attrname eq 'statedatapoint') { + if ($attrval =~ /^([0-9]{1,2})\.(.+)/) { + $hash->{hmccu}{state}{chn} = $1; + $hash->{hmccu}{state}{dpt} = $2; + } + else { + $hash->{hmccu}{state}{dpt} = $attrval; + } + } + elsif ($attrname eq 'controlchannel') { + $hash->{hmccu}{control}{chn} = $attrval; + } + elsif ($attrname eq 'controldatapoint') { + if ($attrval =~ /^([0-9]{1,2})\.(.+)/) { + $hash->{hmccu}{control}{chn} = $1; + $hash->{hmccu}{control}{dpt} = $2; + } + else { + $hash->{hmccu}{control}{dpt} = $attrval; + } + } } HMCCU_RefreshReadings ($hash) if ($init_done); @@ -370,29 +400,18 @@ sub HMCCUDEV_Set ($@) return ($opt eq '?' ? undef : 'Cannot perform set commands. CCU busy') if (HMCCU_IsRPCStateBlocking ($ioHash)); - # Get parameters of current device - my $ccutype = $hash->{ccutype}; - my $ccuaddr = $hash->{ccuaddr}; - my $ccuif = $hash->{ccuif}; - my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); - # Get state and control datapoints my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash); # Get additional commands my $cmdList = $hash->{hmccu}{cmdlist} // ''; - - # Get state values related to control command and datapoint - my $stateVals = HMCCU_GetStateValues ($hash, $cd, $cc); - my @stateCmdList = split (/[:,]/, $stateVals); - my %stateCmds = @stateCmdList; - my @states = keys %stateCmds; # Some commands require a control channel and datapoint if ($opt =~ /^(control|toggle)$/) { return HMCCU_SetError ($hash, -14) if ($cd eq ''); return HMCCU_SetError ($hash, -12) if ($cc eq ''); - return HMCCU_SetError ($hash, -8) if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $cc, $cd, 2)); + return HMCCU_SetError ($hash, -8) + if (!HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, $cc, $cd, 2)); return HMCCU_SetError ($hash, -7) if ($cc >= $hash->{hmccu}{channels}); } @@ -401,49 +420,18 @@ sub HMCCUDEV_Set ($@) # Log commands HMCCU_Log ($hash, 3, "set $name $opt ".join (' ', @$a)) - if ($opt ne '?' && $ccuflags =~ /logCommand/ || HMCCU_IsFlag ($ioName, 'logCommand')); + if ($opt ne '?' && (HMCCU_IsFlag ($name, 'logCommand') || HMCCU_IsFlag ($ioName, 'logCommand'))); if ($opt eq 'control') { my $value = shift @$a // return HMCCU_SetError ($hash, "Usage: set $name control {value}"); + my $stateVals = HMCCU_GetStateValues ($hash, $cd, $cc); $rc = HMCCU_SetMultipleDatapoints ($hash, - { "001.$ccuif.$ccuaddr:$cc.$cd" => HMCCU_Substitute ($value, $stateVals, 1, undef, '') } + { "001.$hash->{ccuif}.$hash->{ccuaddr}:$cc.$cd" => HMCCU_Substitute ($value, $stateVals, 1, undef, '') } ); return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); } elsif ($opt eq 'datapoint') { - my $usage = "Usage: set $name datapoint [{channel-number}.]{datapoint} {value} [...]"; - my %dpval; - my $i = 0; - - push (@$a, %${h}) if (defined($h)); - while (my $objname = shift @$a) { - my $value = shift @$a; - $i += 1; - - if ($ccutype eq 'HM-Dis-EP-WM55' && !defined($value)) { - $value = ''; - foreach my $t (keys %{$h}) { - $value .= $value eq '' ? $t.'='.$h->{$t} : ','.$t.'='.$h->{$t}; - } - } - - return HMCCU_SetError ($hash, $usage) if (!defined($value) || $value eq ''); - - if ($objname =~ /^([0-9]+)\..+$/) { - return HMCCU_SetError ($hash, -7) if ($1 >= $hash->{hmccu}{channels}); - } - else { - $objname = "$cc.$objname"; - } - - my $no = sprintf ("%03d", $i); - $dpval{"$no.$ccuif.$ccuaddr:$objname"} = HMCCU_Substitute ($value, $stateVals, 1, undef, ''); - } - - return HMCCU_SetError ($hash, $usage) if (scalar(keys %dpval) < 1); - - $rc = HMCCU_SetMultipleDatapoints ($hash, \%dpval); - return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); + return HMCCU_ExecuteSetDatapointCommand ($hash, $a, $h, $cc, $cd); } elsif ($opt eq 'toggle') { return HMCCU_ExecuteToggleCommand ($hash, $cc, $cd); @@ -452,73 +440,10 @@ sub HMCCUDEV_Set ($@) return HMCCU_ExecuteRoleCommand ($ioHash, $hash, $opt, $cc, $a, $h); } elsif ($opt eq 'clear') { - my $rnexp = shift @$a; - HMCCU_DeleteReadings ($hash, $rnexp); - return HMCCU_SetState ($hash, 'OK'); + return HMCCU_ExecuteSetClearCommand ($hash, $a); } elsif ($opt =~ /^(config|values)$/) { - my %parSets = ('config' => 'MASTER', 'values' => 'VALUES'); - my $paramset = $parSets{$opt}; - my $receiver = ''; - my $ccuobj = $ccuaddr; - - return HMCCU_SetError ($hash, 'No parameter specified') if ((scalar keys %{$h}) < 1); - - # Channel number is optional because parameter can be related to device or channel - my $p = shift @$a; - if (defined($p)) { - if ($p =~ /^([0-9]{1,2})$/) { - return HMCCU_SetError ($hash, -7) if ($p >= $hash->{hmccu}{channels}); - $ccuobj .= ':'.$p; - } - else { - $receiver = $p; - $paramset = 'LINK'; - } - } - - my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $ccuobj, $ccuif); - return HMCCU_SetError ($hash, "Can't get device description") if (!defined($devDesc)); - return HMCCU_SetError ($hash, "Paramset $paramset not supported by device or channel") - if ($devDesc->{PARAMSETS} !~ /$paramset/); - if (!HMCCU_IsValidParameter ($ioHash, $devDesc, $paramset, $h)) { - my @parList = HMCCU_GetParamDef ($ioHash, $devDesc, $paramset); - return HMCCU_SetError ($hash, 'Invalid parameter specified. Valid parameters are '. - join(',', @parList)); - } - - if ($paramset eq 'VALUES' || $paramset eq 'MASTER') { - ($rc, $result) = HMCCU_SetMultipleParameters ($hash, $ccuobj, $h, $paramset); - } - else { - if (exists($defs{$receiver}) && defined($defs{$receiver}->{TYPE})) { - my $clHash = $defs{$receiver}; - if ($clHash->{TYPE} eq 'HMCCUDEV') { - my $chnNo = shift @$a; - return HMCCU_SetError ($hash, 'Channel number required for link receiver') - if (!defined($chnNo) || $chnNo !~ /^[0-9]{1,2}$/); - $receiver = $clHash->{ccuaddr}.":$chnNo"; - } - elsif ($clHash->{TYPE} eq 'HMCCUCHN') { - $receiver = $clHash->{ccuaddr}; - } - else { - return HMCCU_SetError ($hash, "Receiver $receiver is not a HMCCUCHN or HMCCUDEV device"); - } - } - elsif (!HMCCU_IsChnAddr ($receiver, 0)) { - my ($rcvAdd, $rcvChn) = HMCCU_GetAddress ($ioHash, $receiver); - return HMCCU_SetError ($hash, "$receiver is not a valid CCU channel name") - if ($rcvAdd eq '' || $rcvChn eq ''); - $receiver = "$rcvAdd:$rcvChn"; - } - - return HMCCU_SetError ($hash, "$receiver is not a link receiver of $name") - if (!HMCCU_IsValidReceiver ($ioHash, $ccuaddr, $ccuif, $receiver)); - ($rc, $result) = HMCCU_RPCRequest ($hash, 'putParamset', $ccuaddr, $receiver, $h); - } - - return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); + return HMCCU_ExecuteSetParameterCommand ($ioHash, $hash, $opt, $a, $h); } elsif ($opt eq 'defaults') { my $mode = shift @$a // 'update'; @@ -533,7 +458,6 @@ sub HMCCUDEV_Set ($@) if ($hash->{readonly} ne 'yes') { $retmsg .= ' config datapoint'; $retmsg .= " $cmdList" if ($cmdList ne ''); - $retmsg .= ' toggle:noArg' if (scalar(@states) > 0); } return AttrTemplate_Set ($hash, $retmsg, $name, $opt, @$a); } @@ -602,11 +526,7 @@ sub HMCCUDEV_Get ($@) return $result; } elsif ($opt eq 'deviceinfo') { - $result = HMCCU_GetDeviceInfo ($hash, $ccuaddr); - return HMCCU_SetError ($hash, -2) if ($result eq ''); - my $devInfo = HMCCU_FormatDeviceInfo ($result); - $devInfo .= "StateDatapoint = $sc.$sd\nControlDatapoint = $cc.$cd"; - return $devInfo; + return HMCCU_ExecuteGetDeviceInfoCommand ($ioHash, $hash, $ccuaddr, $sc, $sd, $cc, $cd); } elsif ($opt =~ /^(config|values|update)$/) { my @addList = ($ccuaddr); @@ -621,10 +541,6 @@ sub HMCCUDEV_Get ($@) $result = HMCCU_ParamsetDescToStr ($ioHash, $hash); return defined($result) ? $result : HMCCU_SetError ($hash, "Can't get device model"); } - elsif ($opt eq 'devicedesc') { - $result = HMCCU_DeviceDescToStr ($ioHash, $hash); - return defined($result) ? $result : HMCCU_SetError ($hash, "Can't get device description"); - } elsif ($opt eq 'defaults') { $result = HMCCU_GetDefaults ($hash, 0); return $result; @@ -640,7 +556,7 @@ sub HMCCUDEV_Get ($@) my $valuecount = HMCCU_GetValidDatapoints ($hash, $ccutype, -1, 1, \@valuelist); $retmsg .= ':'.join(",", @valuelist) if ($valuecount > 0); $retmsg .= ' defaults:noArg update:noArg config:noArg'. - ' paramsetDesc:noArg deviceDesc:noArg deviceInfo:noArg values:noArg'; + ' paramsetDesc:noArg deviceInfo:noArg values:noArg'; $retmsg .= ' weekProgram:all,'.join(',', sort keys %{$hash->{hmccu}{tt}}) if (exists($hash->{hmccu}{tt})); @@ -674,7 +590,9 @@ sub HMCCUDEV_Get ($@) If option 'readonly' is specified no set command will be available. With option 'defaults' some default attributes depending on CCU device type will be set. Default attributes are only available for some device types. The option is ignored during FHEM start. - Parameter controlchannel corresponds to attribute 'controlchannel'.
    + Parameter controlchannel corresponds to attribute 'controlchannel'. If a device + has several identical channels, some commands need to know the channel number for + controlling the device.
    A HMCCUDEV device supports CCU group devices. The CCU devices or channels related to a group device are specified by using options 'group' or 'groupexp' followed by the names or addresses of the CCU devices or channels. By using 'groupexp' one can specify a regular @@ -813,11 +731,13 @@ sub HMCCUDEV_Get ($@)
  • get <name> defaults
    see HMCCUCHN

  • -
  • get <name> devicedesc [<channel-number>]
    - Display device description. -

  • -
  • get <name> deviceinfo [{State | Value}]
    - Display all channels and datapoints of device with datapoint values and types. +
  • get <name> deviceinfo
    + Display information about device and channels:
    +
      +
    • all channels and datapoints of device with datapoint values and types
    • +
    • statedatapoint and controldatapoint
    • +
    • device and channel description
    • +

  • get <name> update [{State | Value}]
    see HMCCUCHN @@ -898,6 +818,9 @@ sub HMCCUDEV_Get ($@)
  • substitute <subst-rule>[;...]
    see HMCCUCHN

  • +
  • traceFilter <filter-expr>
    + see HMCCUCHN +

  • diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCURPCPROC.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCURPCPROC.pm index bc1e609fc..b6b980679 100755 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCURPCPROC.pm +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCURPCPROC.pm @@ -4,7 +4,7 @@ # # $Id: 88_HMCCURPCPROC.pm 18745 2019-02-26 17:33:23Z zap $ # -# Version 4.4.010 +# Version 4.4.011 # # Subprocess based RPC Server module for HMCCU. # @@ -39,7 +39,7 @@ require "$attr{global}{modpath}/FHEM/88_HMCCU.pm"; ###################################################################### # HMCCURPC version -my $HMCCURPCPROC_VERSION = '4.4.010'; +my $HMCCURPCPROC_VERSION = '4.4.011'; # Maximum number of events processed per call of Read() my $HMCCURPCPROC_MAX_EVENTS = 100; @@ -151,7 +151,7 @@ sub HMCCURPCPROC_ProcessEvent ($$); # RPC information sub HMCCURPCPROC_GetDeviceDesc ($;$); sub HMCCURPCPROC_GetParamsetDesc ($;$); -sub HMCCURPCPROC_GetPeers ($); +sub HMCCURPCPROC_GetPeers ($;$); # RPC server control functions sub HMCCURPCPROC_CheckProcessState ($$); @@ -519,11 +519,8 @@ sub HMCCURPCPROC_Attr ($@) elsif ($attrname eq 'rpcPingCCU') { HMCCU_Log ($hash, 1, "Attribute rpcPingCCU ignored. Please set it in I/O device"); } - elsif ($attrname eq 'ccuflags' && $attrval =~ /reconnect/) { - HMCCU_Log ($hash, 1, "Flag reconnect ignored. Please set it in I/O device"); - } - elsif ($attrname eq 'ccuflags' && $attrval =~ /logPong/) { - HMCCU_Log ($hash, 1, "Flag logPong ignored. Please set it in I/O device"); + elsif ($attrname eq 'ccuflags' && $attrval =~ /(reconnect|logPong)/) { + HMCCU_Log ($hash, 1, "Flag $1 ignored. Please set it in I/O device"); } } elsif ($cmd eq 'del') { @@ -663,10 +660,7 @@ sub HMCCURPCPROC_Get ($@) $result = "Event statistics for server $clkey\n"; $result .= "Average event delay = ".$hash->{hmccu}{rpc}{avgdelay}."\n" if (defined ($hash->{hmccu}{rpc}{avgdelay})); - $result .= - "========================================\n". - "ET Sent by RPC server Received by FHEM\n". - "----------------------------------------\n"; + $result .= ('=' x 40)."\nET Sent by RPC server Received by FHEM\n".('-' x 40)."\n"; foreach my $et (@RPC_EVENT_TYPES) { my $snd = exists ($hash->{hmccu}{rpc}{snd}{$et}) ? sprintf ("%7d", $hash->{hmccu}{rpc}{snd}{$et}) : " n/a"; @@ -676,10 +670,7 @@ sub HMCCURPCPROC_Get ($@) } if ($ccuflags =~ /statistics/ && exists ($hash->{hmccu}{stats}{rcv})) { my $eh = HMCCU_MaxHashEntries ($hash->{hmccu}{stats}{rcv}, 3); - $result .= - "========================================\n". - "Top Sender\n". - "========================================\n"; + $result .= ('=' x 40)."\nTop Sender\n".('=' x 40)."\n"; for (my $i=0; $i<3; $i++) { last if (!exists ($eh->{$i})); my $dn = HMCCU_GetDeviceName ($ioHash, $eh->{$i}{k}, '?'); @@ -690,9 +681,7 @@ sub HMCCURPCPROC_Get ($@) } elsif ($opt eq 'rpcstate') { my $clkey = HMCCURPCPROC_GetKey ($hash); - $result = - "PID RPC-Process State \n". - "--------------------------------\n"; + $result = "PID RPC-Process State \n".('-' x 32)."\n"; my $sid = defined ($hash->{hmccu}{rpc}{pid}) ? sprintf ("%5d", $hash->{hmccu}{rpc}{pid}) : "N/A "; my $sname = sprintf ("%-10s", $clkey); my $cbport = defined ($hash->{hmccu}{rpc}{cbport}) ? $hash->{hmccu}{rpc}{cbport} : "N/A"; @@ -752,20 +741,14 @@ sub HMCCURPCPROC_Read ($) $hash->{ccustate} = 'active' if ($hash->{ccustate} ne 'active'); # Count events per device for statistics - if ($ccuflags =~ /statistics/) { - if (exists ($hash->{hmccu}{stats}{rcv}{$par[0]})) { - $hash->{hmccu}{stats}{rcv}{$par[0]}++; - } - else { - $hash->{hmccu}{stats}{rcv}{$par[0]} = 1; - } - } + $hash->{hmccu}{stats}{rcv}{$par[0]}++ if ($ccuflags =~ /statistics/); } elsif ($et eq 'EX') { # I/O already cleaned up. Leave Read() last; } -# elsif ($et eq 'ND') { + elsif ($et eq 'ND') { +# HMCCU_Log ($hash, 2, "ND: ".join(';', @par)); # $devices{$par[0]}{flag} = 'N'; # $devices{$par[0]}{version} = $par[3]; # $devices{$par[0]}{paramsets} = $par[6]; @@ -777,16 +760,16 @@ sub HMCCURPCPROC_Read ($) # $devices{$par[0]}{children} = $par[10]; # } # else { -# $devices{$par[0]}{addtype} = 'chn'; -# $devices{$par[0]}{usetype} = $par[2]; -# $devices{$par[0]}{sourceroles} = $par[7]; -# $devices{$par[0]}{targetroles} = $par[8]; -# $devices{$par[0]}{direction} = $par[9]; -# $devices{$par[0]}{parent} = $par[11]; -# $devices{$par[0]}{aes} = $par[12]; +# $devices{$par[0]}{addtype} = 'chn'; +# $devices{$par[0]}{usetype} = $par[2]; +# $devices{$par[0]}{sourceroles} = $par[7]; +# $devices{$par[0]}{targetroles} = $par[8]; +# $devices{$par[0]}{direction} = $par[9]; +# $devices{$par[0]}{parent} = $par[11]; +# $devices{$par[0]}{aes} = $par[12]; # } # $devcount++; -# } + } elsif ($et eq 'DD') { $devices{$par[0]}{flag} = 'D'; $devcount++; @@ -880,7 +863,7 @@ sub HMCCURPCPROC_ResetRPCState ($) { my ($hash) = @_; - $hash->{RPCPID} = "0"; + $hash->{RPCPID} = '0'; $hash->{hmccu}{rpc}{pid} = undef; $hash->{hmccu}{rpc}{clkey} = undef; $hash->{hmccu}{evtime} = 0; @@ -916,17 +899,8 @@ sub HMCCURPCPROC_ProcessEvent ($$) # Number of arguments in RPC events (without event type and clkey) my %rpceventargs = ( - 'EV', 4, - 'ND', 13, - 'DD', 1, - 'RD', 2, - 'RA', 1, - 'UD', 2, - 'IN', 2, - 'EX', 2, - 'SL', 1, - 'TO', 1, - 'ST', 11 + 'EV', 4, 'ND', 13, 'DD', 1, 'RD', 2, 'RA', 1, 'UD', 2, 'IN', 2, 'EX', 2, 'SL', 1, + 'TO', 1, 'ST', 11 ); return undef if (!defined ($event) || $event eq ''); @@ -1123,14 +1097,21 @@ sub HMCCURPCPROC_GetAttribute ($$$$) # Get links (sender and receiver) from CCU. ###################################################################### -sub HMCCURPCPROC_GetPeers ($) +sub HMCCURPCPROC_GetPeers ($;$) { - my ($hash) = @_; + my ($hash, $address) = @_; my $ioHash = $hash->{IODev}; my $c = 0; - my $rd = HMCCURPCPROC_SendRequest ($hash, 'getLinks') // - return HMCCU_Log ($hash, 2, "Can't get peers", 0); + my $rd = defined($address) ? + HMCCURPCPROC_SendRequest ($hash, 'getLinks', $address) : + HMCCURPCPROC_SendRequest ($hash, 'getLinks'); + + if (!defined($rd)) { + my $msg = defined($address) ? "Can't get peers of device $address" : + "Can't get full list of peers"; + return HMCCU_Log ($hash, 2, $msg, 0); + } if (ref($rd) eq 'HASH' && exists($rd->{faultString})) { return HMCCU_Log ($hash, 2, "Can't get peers. ".$rd->{faultString}, 0); @@ -1154,18 +1135,11 @@ sub HMCCURPCPROC_GetDeviceDesc ($;$) { my ($hash, $address) = @_; my $ioHash = $hash->{IODev}; - - my $rd; my $c = 0; - if (!defined($address)) { - # All devices - $rd = HMCCURPCPROC_SendRequest ($hash, 'listDevices'); - } - else { - # Single device (or channel) - $rd = HMCCURPCPROC_SendRequest ($hash, 'getDeviceDescription', $address); - } + my $rd = defined($address) ? + HMCCURPCPROC_SendRequest ($hash, 'getDeviceDescription', $address) : + HMCCURPCPROC_SendRequest ($hash, 'listDevices'); if (!defined($rd)) { my $msg = defined($address) ? "Can't get description of device $address" : @@ -1197,6 +1171,7 @@ sub HMCCURPCPROC_GetDeviceDesc ($;$) ###################################################################### # Get RPC device paramset descriptions from CCU +# Function is called recursively # Parameters: # $address - Device or channel address. If not specified, all # addresses known by IO device are used. @@ -1845,6 +1820,10 @@ sub HMCCURPCPROC_SendRequest ($@) } } +###################################################################### +# Send XML RPC request to CCU +###################################################################### + sub HMCCURPCPROC_SendXMLRequest ($@) { my ($hash, $ioHash, $port, $request, @param) = @_; @@ -1907,6 +1886,10 @@ sub HMCCURPCPROC_SendXMLRequest ($@) return $rc; } +###################################################################### +# Send binary RPC request to CCU +###################################################################### + sub HMCCURPCPROC_SendBINRequest ($@) { my ($hash, $ioHash, $port, $request, @param) = @_; @@ -1930,7 +1913,7 @@ sub HMCCURPCPROC_SendBINRequest ($@) } # create a connecting socket - my $socket = new IO::Socket::INET (PeerHost => $serveraddr, PeerPort => $port, Proto => 'tcp', Timeout => 3); + my $socket = IO::Socket::INET->new (PeerHost => $serveraddr, PeerPort => $port, Proto => 'tcp', Timeout => 3); return HMCCU_Log ($hash, 2, "Can't create socket for $serveraddr:$port", undef) if (!$socket); $socket->autoflush (1); @@ -2365,7 +2348,6 @@ sub HMCCURPCPROC_Write ($$$$) # Try to send events immediately. Put them in queue if send fails my $rc = 0; my $err = ''; -# if ($et ne 'ND' && $server->{hmccu}{ccuflags} !~ /queueEvents/) { if ($server->{hmccu}{ccuflags} !~ /queueEvents/) { ($rc, $err) = HMCCURPCPROC_SendData ($server->{hmccu}{sockparent}, $ev); HMCCU_Log ($name, 3, "SendData $ev $err") if ($rc == 0); @@ -2466,7 +2448,7 @@ sub HMCCURPCPROC_NewDevicesCB ($$$) # Format: # C/D|Address|Type|Version|Firmware|RxMode|Paramsets| # LinkSourceRoles|LinkTargetRoles|Direction|Children|Parent|AESActive - + foreach my $dev (@$a) { my $msg = ''; my $ps = ref($dev->{PARAMSETS}) eq 'ARRAY' ? @@ -2998,6 +2980,7 @@ sub HMCCURPCPROC_DecStruct ($$) ###################################################################### # Decode any type # Return (element, packetsize) or (undef, undef) +# element could be a scalar, array ref or hash ref. ###################################################################### sub HMCCURPCPROC_DecType ($$) diff --git a/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm b/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm index 8da01ea8c..ab9e38904 100644 --- a/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm +++ b/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm @@ -4,7 +4,7 @@ # # $Id: HMCCUConf.pm 18552 2019-02-10 11:52:28Z zap $ # -# Version 4.8.003 +# Version 4.8.006 # # Configuration parameters for HomeMatic devices. # @@ -18,6 +18,7 @@ use strict; use warnings; use vars qw(%HMCCU_STATECONTROL); +use vars qw(%HMCCU_READINGS); use vars qw(%HMCCU_ROLECMDS); use vars qw(%HMCCU_ATTR); use vars qw(%HMCCU_CONVERSIONS); @@ -94,6 +95,43 @@ use vars qw(%HMCCU_SCRIPTS); } ); +###################################################################### +# Add or rename readings +# SC# = Placeholder for state channel +# CC# = Placeholder for control channel +###################################################################### + +%HMCCU_READINGS = ( + 'BLIND' => + '(SC#\.)?LEVEL$:+pct;(CC#\.)?LEVEL$:+pct', + 'BLIND_VIRTUAL_RECEIVER' => + '(SC#\.)?LEVEL$:+pct;(CC#\.)?LEVEL$:+pct', + 'DIMMER' => + '(SC#\.)?LEVEL$:+pct;(CC#\.)?LEVEL$:+pct', + 'DIMMER_VIRTUAL_RECEIVER' => + '(SC#\.)?LEVEL$:+pct;(CC#\.)?LEVEL$:+pct', + 'WEATHER_TRANSMIT' => + '(SC#\.)?TEMPERATURE$:+measured-temp', + 'THERMALCONTROL_TRANSMIT' => + '(SC#\.)?ACTUAL_TEMPERATURE$:+measured-temp;'. + '(SC#\.)?SET_TEMPERATURE$:+desired-temp;'. + '(CC#\.)?SET_TEMPERATURE$:+desired-temp', + 'CLIMATECONTROL_RT_TRANSCEIVER' => + '(SC#\.)?ACTUAL_TEMPERATURE$:+measured-temp;'. + '(SC#\.)?SET_TEMPERATURE$:+desired-temp;'. + '(CC#\.)?SET_TEMPERATURE$:+desired-temp', + 'HEATING_CLIMATECONTROL_TRANSCEIVER' => + '(SC#\.)?ACTUAL_TEMPERATURE$:+measured-temp;'. + '(SC#\.)?SET_POINT_TEMPERATURE$:+desired-temp;'. + '(CC#\.)?SET_POINT_TEMPERATURE$:+desired-temp', + 'DEFAULT' => + '([0-9]{1,2}\.)?LEVEL$:+pct;'. + '([0-9]{1,2}\.)?SET_TEMPERATURE$:+desired-temp;'. + '([0-9]{1,2}\.)?ACTUAL_TEMPERATURE$:+measured-temp;'. + '([0-9]{1,2}\.)?SET_POINT_TEMPERATURE$:+desired-temp;'. + '([0-9]{1,2}\.)?ACTUAL_HUMIDITY$:+humidity' +); + ###################################################################### # Set commands related to channel role # Role => { Command-Definition, ... } @@ -274,11 +312,11 @@ use vars qw(%HMCCU_SCRIPTS); }, 'KEY' => { 'PRESS_SHORT' => { '1' => 'pressed', 'true' => 'pressed' }, - 'PRESS_LONG' => { '1' => 'pressed', 'true' => 'pressed' } + 'PRESS_LONG' => { '1' => 'pressed', 'true' => 'pressed' } }, 'KEY_TRANSCEIVER' => { 'PRESS_SHORT' => { '1' => 'pressed', 'true' => 'pressed' }, - 'PRESS_LONG' => { '1' => 'pressed', 'true' => 'pressed' } + 'PRESS_LONG' => { '1' => 'pressed', 'true' => 'pressed' } }, 'SHUTTER_CONTACT' => { 'STATE' => { '0' => 'closed', '1' => 'open', 'false' => 'closed', 'true' => 'open' } @@ -302,22 +340,30 @@ use vars qw(%HMCCU_SCRIPTS); 'STATE' => { '0' => 'off', 'false' => 'off', '1' => 'on', 'true' => 'on', 'off' => '0', 'on' => '1' }, }, 'BLIND' => { - 'LEVEL' => { '0' => 'closed', '100' => 'open', 'close' => '0', 'open' => '100' } + 'LEVEL' => { '0' => 'closed', '100' => 'open', 'close' => '0', 'open' => '100' }, + 'DIRECTION' => { '0' => 'none', '1' => 'up', '2' => 'down' }, + 'WORKING' => { '0' => 'no', 'false' => 'no', '1' => 'yes', 'true' => 'yes' } }, 'BLIND_VIRTUAL_RECEIVER' => { - 'LEVEL' => { '0' => 'closed', '100' => 'open', 'close' => '0', 'open' => '100' } + 'LEVEL' => { '0' => 'closed', '100' => 'open', 'close' => '0', 'open' => '100' }, + 'DIRECTION' => { '0' => 'none', '1' => 'up', '2' => 'down' }, + 'WORKING' => { '0' => 'no', 'false' => 'no', '1' => 'yes', 'true' => 'yes' } }, 'SHUTTER_VIRTUAL_RECEIVER' => { 'LEVEL' => { '0' => 'closed', '100' => 'open', 'close' => '0', 'open' => '100' } }, 'DIMMER' => { - 'LEVEL' => { '0' => 'off', '100' => 'on', 'off' => '0', 'on' => '100' } + 'LEVEL' => { '0' => 'off', '100' => 'on', 'off' => '0', 'on' => '100' }, + 'DIRECTION' => { '0' => 'none', '1' => 'up', '2' => 'down' }, + 'WORKING' => { '0' => 'no', 'false' => 'no', '1' => 'yes', 'true' => 'yes' } }, 'DIMMER_VIRTUAL_RECEIVER' => { - 'LEVEL' => { '0' => 'off', '100' => 'on', 'off' => '0', 'on' => '100' } + 'LEVEL' => { '0' => 'off', '100' => 'on', 'off' => '0', 'on' => '100' }, + 'DIRECTION' => { '0' => 'none', '1' => 'up', '2' => 'down' }, + 'WORKING' => { '0' => 'no', 'false' => 'no', '1' => 'yes', 'true' => 'yes' } }, 'THERMALCONTROL_TRANSMIT' => { - 'SET_TEMPERATURE' => { '4.5' => 'off', '30.5' => 'on' }, + 'SET_TEMPERATURE' => { '4.5' => 'off', '30.5' => 'on' }, 'WINDOW_OPEN_REPORTING' => { '0' => 'closed', '1' => 'open', 'false' => 'closed', 'true' => 'open' } }, 'CLIMATECONTROL_RT_TRANSCEIVER' => { @@ -325,13 +371,13 @@ use vars qw(%HMCCU_SCRIPTS); }, 'HEATING_CLIMATECONTROL_TRANSCEIVER' => { 'SET_POINT_TEMPERATURE' => { '4.5' => 'off', '30.5' => 'on' }, - 'WINDOW_STATE' => { '0' => 'closed', '1' => 'open', 'false' => 'closed', 'true' => 'open' } + 'WINDOW_STATE' => { '0' => 'closed', '1' => 'open', 'false' => 'closed', 'true' => 'open' } }, 'DEFAULT' => { 'AES_KEY' => { '0' => 'off', 'false' => 'off', '1' => 'on', 'true' => 'on' }, 'LOW_BAT' => { '0' => 'ok', 'false' => 'ok', '1' => 'low', 'true' => 'low' }, - 'LOWBAT' => { '0' => 'ok', 'false' => 'ok', '1' => 'low', 'true' => 'low' }, - 'STATE' => { '0' => 'false', '1' => 'true' }, + 'LOWBAT' => { '0' => 'ok', 'false' => 'ok', '1' => 'low', 'true' => 'low' }, + 'STATE' => { '0' => 'false', '1' => 'true' }, 'UNREACH' => { '0' => 'alive', 'false' => 'alive', '1' => 'dead', 'true' => 'dead' } } ); @@ -777,9 +823,8 @@ use vars qw(%HMCCU_SCRIPTS); %HMCCU_DEV_DEFAULTS = ( "CCU2" => { - _description => "HomeMatic CCU2", - "ccudef-readingformat" => 'datapoint', - "ccudef-readingname" => '^(.+\.)?BATTERY_STATE\$:batteryLevel;^(.+\.)?TEMPERATURE\$:+temperature;^(.+\.)?SET_TEMPERATURE\$:+desired-temp;^(.+\.)?HUMIDITY\$:+humidity;^(.+\.)?LEVEL\$:+pct;^(.+\.)?CONTROL_MODE\$:+controlMode' + _description => "HomeMatic CCU", + "ccudef-readingformat" => 'datapoint' }, "HM-Sec-SCo|HM-Sec-SC|HM-Sec-SC-2|HMIP-SWDO" => { _description => "Tuer/Fensterkontakt optisch und magnetisch",