diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm index ed27437c7..606a4f4bf 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.019 +# Version 4.4.034 # # Module for communication between FHEM and Homematic CCU2/3. # @@ -43,34 +43,21 @@ use HMCCUConf; # Import configuration data my $HMCCU_STATECONTROL = \%HMCCUConf::HMCCU_STATECONTROL; -my $HMCCU_ROLECMDS = \%HMCCUConf::HMCCU_ROLECMDS; -my $HMCCU_ATTR = \%HMCCUConf::HMCCU_ATTR; -my $HMCCU_CONVERSIONS = \%HMCCUConf::HMCCU_CONVERSIONS; +my $HMCCU_ROLECMDS = \%HMCCUConf::HMCCU_ROLECMDS; +my $HMCCU_ATTR = \%HMCCUConf::HMCCU_ATTR; +my $HMCCU_CONVERSIONS = \%HMCCUConf::HMCCU_CONVERSIONS; my $HMCCU_CHN_DEFAULTS = \%HMCCUConf::HMCCU_CHN_DEFAULTS; my $HMCCU_DEV_DEFAULTS = \%HMCCUConf::HMCCU_DEV_DEFAULTS; -my $HMCCU_SCRIPTS = \%HMCCUConf::HMCCU_SCRIPTS; +my $HMCCU_SCRIPTS = \%HMCCUConf::HMCCU_SCRIPTS; # Custom configuration data my %HMCCU_CUST_CHN_DEFAULTS; my %HMCCU_CUST_DEV_DEFAULTS; # HMCCU version -my $HMCCU_VERSION = '4.4.019'; +my $HMCCU_VERSION = '4.4.034'; -# Constants and default values -my $HMCCU_MAX_IOERRORS = 100; -my $HMCCU_MAX_QUEUESIZE = 500; -my $HMCCU_TIME_WAIT = 100000; -my $HMCCU_TIME_TRIGGER = 10; - -# RPC ping interval for default interface, should be smaller than HMCCU_TIMEOUT_EVENT -my $HMCCU_TIME_PING = 300; - -my $HMCCU_TIMEOUT_CONNECTION = 10; -my $HMCCU_TIMEOUT_WRITE = 0.001; -my $HMCCU_TIMEOUT_ACCEPT = 1; -my $HMCCU_TIMEOUT_EVENT = 600; -my $HMCCU_STATISTICS = 500; +# Timeout for CCU requests my $HMCCU_TIMEOUT_REQUEST = 4; # ReGa Ports @@ -183,7 +170,7 @@ sub HMCCU_GetDefaults ($$); sub HMCCU_SetDefaults ($); # Status and logging functions -sub HMCCU_Trace ($$$$); +sub HMCCU_Trace ($$$); sub HMCCU_Log ($$$;$); sub HMCCU_LogError ($$$); sub HMCCU_SetError ($@); @@ -206,13 +193,12 @@ sub HMCCU_BulkUpdate ($$$$); sub HMCCU_GetUpdate ($$$); sub HMCCU_RefreshReadings ($); sub HMCCU_UpdateCB ($$$); -sub HMCCU_UpdateClients ($$$$$$); +sub HMCCU_UpdateClients ($$$$;$$); sub HMCCU_UpdateInternalValues ($$$$$); sub HMCCU_UpdateMultipleDevices ($$); sub HMCCU_UpdatePeers ($$$$); sub HMCCU_UpdateParamsetReadings ($$$;$); sub HMCCU_UpdateSingleDatapoint ($$$$); -# sub HMCCU_UpdateSingleDevice ($$$$); # RPC functions sub HMCCU_EventsTimedOut ($); @@ -221,10 +207,9 @@ sub HMCCU_GetRPCDevice ($$$); sub HMCCU_GetRPCInterfaceList ($); sub HMCCU_GetRPCPortList ($); sub HMCCU_GetRPCServerInfo ($$$); -sub HMCCU_IsRPCServerRunning ($$$); +sub HMCCU_IsRPCServerRunning ($;$); sub HMCCU_IsRPCType ($$$); sub HMCCU_IsRPCStateBlocking ($); -sub HMCCU_ResetCounters ($); sub HMCCU_RPCRequest ($$$$$;$); sub HMCCU_StartExtRPCServer ($); sub HMCCU_StopExtRPCServer ($;$); @@ -237,9 +222,9 @@ sub HMCCU_SplitChnAddr ($); sub HMCCU_SplitDatapoint ($;$); # FHEM device handling functions -sub HMCCU_AssignIODevice ($$$); +sub HMCCU_AssignIODevice ($$;$); sub HMCCU_ExistsClientDevice ($$); -sub HMCCU_FindClientDevices ($$$$); +sub HMCCU_FindClientDevices ($$;$$); sub HMCCU_FindIODevice ($); sub HMCCU_GetHash ($@); sub HMCCU_GetAttribute ($$$$); @@ -255,29 +240,31 @@ sub HMCCU_AddDevice ($$$;$); sub HMCCU_AddDeviceDesc ($$$$); sub HMCCU_AddDeviceModel ($$$$$$); sub HMCCU_AddPeers ($$$); +sub HMCCU_CheckParameter ($$;$$$); sub HMCCU_CreateDevice ($$$$$); sub HMCCU_DeleteDevice ($); sub HMCCU_DeviceDescToStr ($$); +sub HMCCU_ExecuteRoleCommand ($@); +sub HMCCU_DisplayWeekProgram ($;$); sub HMCCU_ExistsDeviceModel ($$$;$); sub HMCCU_FindParamDef ($$$); sub HMCCU_FormatDeviceInfo ($); -sub HMCCU_GetAddress ($$$$); +sub HMCCU_GetAddress ($$;$$); sub HMCCU_GetAffectedAddresses ($); sub HMCCU_GetCCUDeviceParam ($$); -sub HMCCU_GetChannelName ($$$); +sub HMCCU_GetChannelName ($$;$); sub HMCCU_GetChannelRole ($;$); sub HMCCU_GetClientDeviceModel ($;$); sub HMCCU_GetDefaultInterface ($); sub HMCCU_GetDeviceAddresses ($;$$); -sub HMCCU_GetDeviceChannels ($$$); sub HMCCU_GetDeviceConfig ($); sub HMCCU_GetDeviceDesc ($$;$); sub HMCCU_GetDeviceIdentifier ($$;$$); sub HMCCU_GetDeviceInfo ($$;$); -sub HMCCU_GetDeviceInterface ($$$); +sub HMCCU_GetDeviceInterface ($$;$); sub HMCCU_GetDeviceList ($); sub HMCCU_GetDeviceModel ($$$;$); -sub HMCCU_GetDeviceName ($$$); +sub HMCCU_GetDeviceName ($$;$); sub HMCCU_GetDeviceType ($$$); sub HMCCU_GetFirmwareVersions ($$); sub HMCCU_GetGroupMembers ($$); @@ -297,13 +284,13 @@ sub HMCCU_ResetDeviceTables ($;$$); sub HMCCU_UpdateDevice ($$); sub HMCCU_UpdateDeviceRoles ($$;$$); sub HMCCU_UpdateDeviceTable ($$); +sub HMCCU_UpdateRoleCommands ($$;$); # Handle datapoints sub HMCCU_FindDatapoint ($$$$$); sub HMCCU_GetDatapoint ($@); sub HMCCU_GetDatapointAttr ($$$$$); -sub HMCCU_GetDatapointList ($$$); -sub HMCCU_GetSpecialCommands ($$); +sub HMCCU_GetDatapointList ($;$$); sub HMCCU_GetSpecialDatapoints ($); sub HMCCU_GetStateValues ($$;$); sub HMCCU_GetValidDatapoints ($$$$$); @@ -317,7 +304,7 @@ sub HMCCU_GetVariables ($$); sub HMCCU_HMCommand ($$$); sub HMCCU_HMCommandCB ($$$); sub HMCCU_HMCommandNB ($$$); -sub HMCCU_HMScriptExt ($$$$$); +sub HMCCU_HMScriptExt ($$;$$$); sub HMCCU_SetVariable ($$$$$); sub HMCCU_UpdateVariables ($); @@ -334,12 +321,13 @@ sub HMCCU_EncodeEPDisplay ($); sub HMCCU_ExprMatch ($$$); sub HMCCU_ExprNotMatch ($$$); sub HMCCU_FlagsToStr ($$$;$$); +# sub HMCCU_GetAdditionalCommands ($;$); sub HMCCU_GetDeviceStates ($); sub HMCCU_GetDutyCycle ($); -sub HMCCU_GetHMState ($$$); +sub HMCCU_GetHMState ($$;$); sub HMCCU_GetIdFromIP ($$); sub HMCCU_GetTimeSpec ($); -sub HMCCU_IsFltNum ($); +sub HMCCU_IsFltNum ($;$); sub HMCCU_IsIntNum ($); sub HMCCU_ISO2UTF ($); sub HMCCU_Max ($$); @@ -349,7 +337,7 @@ sub HMCCU_RefToString ($); sub HMCCU_ResolveName ($$); sub HMCCU_TCPConnect ($$); sub HMCCU_TCPPing ($$$); -sub HMCCU_UpdateReadings ($$); +sub HMCCU_UpdateReadings ($$;$); ################################################## # Initialize module @@ -359,30 +347,30 @@ sub HMCCU_Initialize ($) { my ($hash) = @_; - $hash->{DefFn} = "HMCCU_Define"; - $hash->{UndefFn} = "HMCCU_Undef"; - $hash->{SetFn} = "HMCCU_Set"; - $hash->{GetFn} = "HMCCU_Get"; - $hash->{ReadFn} = "HMCCU_Read"; - $hash->{AttrFn} = "HMCCU_Attr"; - $hash->{NotifyFn} = "HMCCU_Notify"; - $hash->{ShutdownFn} = "HMCCU_Shutdown"; - $hash->{DelayedShutdownFn} = "HMCCU_DelayedShutdown"; - $hash->{FW_detailFn} = "HMCCU_Detail"; + $hash->{DefFn} = 'HMCCU_Define'; + $hash->{UndefFn} = 'HMCCU_Undef'; + $hash->{SetFn} = 'HMCCU_Set'; + $hash->{GetFn} = 'HMCCU_Get'; + $hash->{ReadFn} = 'HMCCU_Read'; + $hash->{AttrFn} = 'HMCCU_Attr'; + $hash->{NotifyFn} = 'HMCCU_Notify'; + $hash->{ShutdownFn} = 'HMCCU_Shutdown'; + $hash->{DelayedShutdownFn} = 'HMCCU_DelayedShutdown'; + $hash->{FW_detailFn} = 'HMCCU_Detail'; $hash->{parseParams} = 1; - $hash->{AttrList} = "stripchar stripnumber ccuaggregate:textField-long". - " ccudefaults rpcinterfaces:multiple-strict,".join(',',sort keys %HMCCU_RPC_PORT). - " 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". - " ccuReqTimeout ccuGetVars rpcinterval:2,3,5,7,10 rpcqueue rpcPingCCU". - " rpcport:multiple-strict,".join(',',sort keys %HMCCU_RPC_NUMPORT). - " rpcserver:on,off rpcserveraddr rpcserverport rpctimeout rpcevtimeout substitute". - " ccuget:Value,State ". + $hash->{AttrList} = 'stripchar stripnumber ccuaggregate:textField-long'. + ' ccudefaults rpcinterfaces:multiple-strict,'.join(',',sort keys %HMCCU_RPC_PORT). + ' 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). + ' rpcserver:on,off rpcserveraddr rpcserverport rpctimeout rpcevtimeout substitute'. + ' ccuget:Value,State '. $readingFnAttributes; } @@ -408,7 +396,7 @@ sub HMCCU_Define ($$) } $hash->{Clients} = ':HMCCUDEV:HMCCUCHN:HMCCURPC:HMCCURPCPROC:'; - $hash->{hmccu}{ccu}{delay} = exists($h->{ccudelay}) ? $h->{ccudelay} : $HMCCU_CCU_BOOT_DELAY; + $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->{hmccu}{ccu}{delayed} = 0; @@ -421,19 +409,15 @@ sub HMCCU_Define ($$) HMCCU_Log ($hash, 1, 'Forced delayed initialization'); } else { - if (HMCCU_TCPPing ($hash->{host}, $HMCCU_REGA_PORT{$hash->{prot}}, $hash->{hmccu}{ccu}{timeout})) { - $hash->{ccustate} = 'active'; - } - else { - $hash->{ccustate} = 'unreachable'; - HMCCU_Log ($hash, 1, "CCU port ".$HMCCU_REGA_PORT{$hash->{prot}}." is not reachable"); - } + $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}); } # Get CCU IP address $hash->{ccuip} = HMCCU_ResolveName ($hash->{host}, 'N/A'); - # Get CCU number (if more than one) + # 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]; @@ -448,44 +432,39 @@ sub HMCCU_Define ($$) $hash->{CCUNum} = $ccucount+1; } - $hash->{version} = $HMCCU_VERSION; - $hash->{ccutype} = 'CCU2/3'; - $hash->{RPCState} = 'inactive'; - $hash->{NOTIFYDEV} = 'global,TYPE=(HMCCU|HMCCUDEV|HMCCUCHN)'; + $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->{hmccu}{defPort} = $HMCCU_RPC_PORT{$hash->{hmccu}{defInterface}}; + $hash->{hmccu}{rpcports} = undef; HMCCU_Log ($hash, 1, "Initialized version $HMCCU_VERSION"); my $rc = 0; if ($hash->{ccustate} eq 'active') { # If CCU is alive read devices, channels, interfaces and groups - HMCCU_Log ($hash, 1, "HMCCU: Initializing device"); + HMCCU_Log ($hash, 1, 'Initializing device'); $rc = HMCCU_InitDevice ($hash); } - if ($hash->{ccustate} ne 'active' || $rc > 0) { + if (($hash->{ccustate} ne 'active' || $rc > 0) && !$init_done) { # Schedule update of CCU assets if CCU is not active during FHEM startup - if (!$init_done) { - $hash->{hmccu}{ccu}{delayed} = 1; - HMCCU_Log ($hash, 1, "Scheduling delayed initialization in ".$hash->{hmccu}{ccu}{delay}." seconds"); - InternalTimer (gettimeofday()+$hash->{hmccu}{ccu}{delay}, "HMCCU_InitDevice", $hash); - } + $hash->{hmccu}{ccu}{delayed} = 1; + HMCCU_Log ($hash, 1, 'Scheduling delayed initialization in '.$hash->{hmccu}{ccu}{delay}.' seconds'); + InternalTimer (gettimeofday()+$hash->{hmccu}{ccu}{delay}, "HMCCU_InitDevice", $hash); } - $hash->{hmccu}{evtime} = 0; - $hash->{hmccu}{evtimeout} = 0; + $hash->{hmccu}{evtime} = 0; + $hash->{hmccu}{evtimeout} = 0; $hash->{hmccu}{updatetime} = 0; - $hash->{hmccu}{rpccount} = 0; + $hash->{hmccu}{rpccount} = 0; + $hash->{hmccu}{defaults} = 0; HMCCU_UpdateReadings ($hash, { 'state' => 'Initialized', 'rpcstate' => 'inactive' }); -# readingsBeginUpdate ($hash); -# readingsBulkUpdate ($hash, "state", "Initialized"); -# readingsBulkUpdate ($hash, "rpcstate", "inactive"); -# readingsEndUpdate ($hash, 1); - $attr{$name}{stateFormat} = "rpcstate/state"; + $attr{$name}{stateFormat} = 'rpcstate/state'; return undef; } @@ -502,27 +481,27 @@ sub HMCCU_InitDevice ($) { my ($hash) = @_; my $name = $hash->{NAME}; + my $host = $hash->{host}; if ($hash->{hmccu}{ccu}{delayed} == 1) { HMCCU_Log ($hash, 1, 'Initializing devices'); - if (!HMCCU_TCPPing ($hash->{host}, $HMCCU_REGA_PORT{$hash->{prot}}, $hash->{hmccu}{ccu}{timeout})) { + if (!HMCCU_TCPPing ($host, $HMCCU_REGA_PORT{$hash->{prot}}, $hash->{hmccu}{ccu}{timeout})) { $hash->{ccustate} = 'unreachable'; - HMCCU_Log ($hash, 1, "HMCCU: CCU port ".$HMCCU_REGA_PORT{$hash->{prot}}." is not reachable"); - return 1; + return HMCCU_Log ($hash, 1, "CCU port ".$HMCCU_REGA_PORT{$hash->{prot}}." is not reachable", 1); } } my ($devcnt, $chncnt, $ifcount, $prgcount, $gcount) = HMCCU_GetDeviceList ($hash); if ($devcnt >= 0) { - HMCCU_Log ($hash, 1, "Read $devcnt devices with $chncnt channels from CCU ".$hash->{host}); - HMCCU_Log ($hash, 1, "Read $ifcount interfaces from CCU ".$hash->{host}); - HMCCU_Log ($hash, 1, "Read $prgcount programs from CCU ".$hash->{host}); - HMCCU_Log ($hash, 1, "Read $gcount virtual groups from CCU ".$hash->{host}); - return 0; + return 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); } else { - HMCCU_Log ($hash, 1, "Error while reading device list from CCU ".$hash->{host}); - return 2; + return HMCCU_Log ($hash, 1, "Error while reading device list from CCU $host", 2); } } @@ -566,11 +545,8 @@ sub HMCCU_Attr ($@) if ($attrname eq 'ccudefaults') { $rc = HMCCU_ImportDefaults ($attrval); return HMCCU_SetError ($hash, -16) if ($rc == 0); - if ($rc < 0) { - $rc = -$rc; - return HMCCU_SetError ($hash, - "Syntax error in default attribute file $attrval line $rc"); - } + return HMCCU_SetError ($hash, 'Syntax error in default attribute file $attrval line '.(-$rc)) + if ($rc < 0); } elsif ($attrname eq 'ccuaggregate') { $rc = HMCCU_AggregationRules ($hash, $attrval); @@ -621,7 +597,7 @@ sub HMCCU_Attr ($@) RemoveInternalTimer ($hash, "HMCCU_UpdateVariables"); } elsif ($attrname eq 'rpcdevice') { - delete $hash->{RPCDEV} if (exists ($hash->{RPCDEV})); + delete $hash->{RPCDEV} if (exists($hash->{RPCDEV})); } elsif ($attrname eq 'rpcport' || $attrname eq 'rpcinterfaces') { my ($defInterface, $defPort) = HMCCU_GetDefaultInterface ($hash); @@ -649,13 +625,13 @@ sub HMCCU_AttrInterfacesPorts ($$$) my @plist = (); foreach my $p (@ilist) { my ($pn, $dc) = HMCCU_GetRPCServerInfo ($hash, $p, 'port,devcount'); - return "Illegal RPC interface $p" if (!defined ($pn)); + 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); + return 'No RPC interface specified' if (scalar(@plist) == 0); $hash->{hmccu}{rpcports} = join (',', @plist); - $attr{$name}{"rpcport"} = $hash->{hmccu}{rpcports}; + $attr{$name}{'rpcport'} = $hash->{hmccu}{rpcports}; } elsif ($attr eq 'rpcport') { my @plist = split (',', $attrval); @@ -666,9 +642,9 @@ sub HMCCU_AttrInterfacesPorts ($$$) return "No devices assigned to interface $in" if ($dc == 0); push (@ilist, $in); } - return "No RPC port specified" if (scalar (@ilist) == 0); + return 'No RPC port specified' if (scalar(@ilist) == 0); $hash->{hmccu}{rpcports} = $attrval; - $attr{$name}{"rpcinterfaces"} = join (',', @ilist); + $attr{$name}{'rpcinterfaces'} = join (',', @ilist); } return ''; @@ -694,26 +670,25 @@ sub HMCCU_AggregationRules ($$) my ($hash, $rulestr) = @_; my $name = $hash->{NAME}; + return 0 if ($rulestr eq ''); + # Delete existing aggregation rules - if (exists ($hash->{hmccu}{agg})) { + if (exists($hash->{hmccu}{agg})) { delete $hash->{hmccu}{agg}; } - return if ($rulestr eq ''); my @pars = ('name', 'filter', 'if', 'else'); # Extract aggregation rules my $cnt = 0; - my @rules = split (/[;\n]+/, $rulestr); - foreach my $r (@rules) { + foreach my $r (split(/[;\n]+/, $rulestr)) { $cnt++; # Set default rule parameters. Can be modified later my %opt = ('read' => 'state', 'prefix' => 'RULE', 'coll' => 'NAME'); # Parse aggregation rule - my @specs = split (',', $r); - foreach my $spec (@specs) { + foreach my $spec (split(',', $r)) { if ($spec =~ /^(name|filter|read|if|else|prefix|coll|html):(.+)$/) { $opt{$1} = $2; } @@ -721,19 +696,18 @@ sub HMCCU_AggregationRules ($$) # Check if mandatory parameters are specified foreach my $p (@pars) { - return HMCCU_Log ($hash, 1, "Parameter $p is missing in aggregation rule $cnt.") - if (!exists ($opt{$p})); + return HMCCU_Log ($hash, 1, "Parameter $p is missing in aggregation rule $cnt.", 0) + if (!exists($opt{$p})); } my $fname = $opt{name}; my ($fincl, $fexcl) = split ('!', $opt{filter}); my ($ftype, $fexpr) = split ('=', $fincl); - return 0 if (!defined ($fexpr)); - my ($fcond, $fval) = split ('=', $opt{if}); - return 0 if (!defined ($fval)); + my ($fcond, $fval) = split ('=', $opt{if}); my ($fcoll, $fdflt) = split ('!', $opt{coll}); - $fdflt = 'no match' if (!defined ($fdflt)); - my $fhtml = exists ($opt{'html'}) ? $opt{'html'} : ''; + return 0 if (!defined($fexpr) || !defined($fval)); + $fdflt //= 'no match'; + my $fhtml = exists($opt{'html'}) ? $opt{'html'} : ''; # Read HTML template (optional) if ($fhtml ne '') { @@ -746,28 +720,27 @@ sub HMCCU_AggregationRules ($$) close (TEMPLATE); } else { - return HMCCU_Log ($hash, 1, "Can't open file $fhtml."); + return HMCCU_Log ($hash, 1, "Can't open file $fhtml.", 0); } # Parse template foreach my $line (@html) { chomp $line; my ($key, $h) = split /:/, $line, 2; - next if (!defined ($h) || $key =~ /^#/); - $tdef{$key} = $h; + $tdef{$key} = $h if (defined($h) && $key !~ /^#/); } # Some syntax checks - return HMCCU_Log ($hash, 1, "Missing definition row-odd in template file.") - if (!exists ($tdef{'row-odd'})); + return HMCCU_Log ($hash, 1, 'Missing definition row-odd in template file.', 0) + if (!exists($tdef{'row-odd'})); # Set default values - $tdef{'begin-html'} = '' if (!exists ($tdef{'begin-html'})); - $tdef{'end-html'} = '' if (!exists ($tdef{'end-html'})); - $tdef{'begin-table'} = "" if (!exists ($tdef{'begin-table'})); - $tdef{'end-table'} = "
" if (!exists ($tdef{'end-table'})); - $tdef{'default'} = 'no data' if (!exists ($tdef{'default'}));; - $tdef{'row-even'} = $tdef{'row-odd'} if (!exists ($tdef{'row-even'})); + $tdef{'begin-html'} = '' if (!exists($tdef{'begin-html'})); + $tdef{'end-html'} = '' if (!exists($tdef{'end-html'})); + $tdef{'begin-table'} = "" if (!exists($tdef{'begin-table'})); + $tdef{'end-table'} = "
" if (!exists($tdef{'end-table'})); + $tdef{'default'} = 'no data' if (!exists($tdef{'default'}));; + $tdef{'row-even'} = $tdef{'row-odd'} if (!exists($tdef{'row-even'})); foreach my $t (keys %tdef) { $hash->{hmccu}{agg}{$fname}{fhtml}{$t} = $tdef{$t}; @@ -776,7 +749,7 @@ sub HMCCU_AggregationRules ($$) $hash->{hmccu}{agg}{$fname}{ftype} = $ftype; $hash->{hmccu}{agg}{$fname}{fexpr} = $fexpr; - $hash->{hmccu}{agg}{$fname}{fexcl} = (defined ($fexcl) ? $fexcl : ''); + $hash->{hmccu}{agg}{$fname}{fexcl} = (defined($fexcl) ? $fexcl : ''); $hash->{hmccu}{agg}{$fname}{fread} = $opt{'read'}; $hash->{hmccu}{agg}{$fname}{fcond} = $fcond; $hash->{hmccu}{agg}{$fname}{ftrue} = $fval; @@ -944,11 +917,13 @@ sub HMCCU_SetDefaultsTemplate ($$) my ($hash, $template) = @_; my $name = $hash->{NAME}; + $hash->{hmccu}{defaults} = 1; foreach my $a (keys %{$template}) { next if ($a =~ /^_/); my $v = $template->{$a}; CommandAttr (undef, "$name $a $v"); } + $hash->{hmccu}{defaults} = 0; } ###################################################################### @@ -958,11 +933,9 @@ sub HMCCU_SetDefaultsTemplate ($$) sub HMCCU_SetDefaults ($) { my ($hash) = @_; - my $name = $hash->{NAME}; # Set type specific attributes - my $template = HMCCU_FindDefaults ($hash, 0); - return 0 if (!defined ($template)); + my $template = HMCCU_FindDefaults ($hash, 0) // return 0; HMCCU_SetDefaultsTemplate ($hash, $template); return 1; @@ -984,7 +957,7 @@ sub HMCCU_GetDefaults ($$) if ($mode == 0) { my $template = HMCCU_FindDefaults ($hash, 0); - return ($result eq '' ? "No default attributes defined" : $result) if (!defined ($template)); + return ($result eq '' ? 'No default attributes defined' : $result) if (!defined($template)); foreach my $a (keys %{$template}) { next if ($a =~ /^_/); @@ -1035,14 +1008,11 @@ sub HMCCU_Notify ($$) my $devname = $devhash->{NAME}; my $devtype = $devhash->{TYPE}; - my $disable = AttrVal ($name, 'disable', 0); - my $ccuflags = HMCCU_GetFlags ($name); - - return if ($disable); + return if (AttrVal ($name, 'disable', 0) == 1); my $events = deviceEvents ($devhash, 1); return if (!$events); - + # Process events foreach my $event (@{$events}) { if ($devname eq 'global') { @@ -1070,18 +1040,18 @@ sub HMCCU_Notify ($$) else { return if ($devtype ne 'HMCCUDEV' && $devtype ne 'HMCCUCHN'); my ($r, $v) = split (": ", $event); - return if (!defined($v) || $ccuflags =~ /noagg/); + return if (!defined($v) || HMCCU_IsFlag ($name, /noagg/)); foreach my $rule (keys %{$hash->{hmccu}{agg}}) { my $ftype = $hash->{hmccu}{agg}{$rule}{ftype}; my $fexpr = $hash->{hmccu}{agg}{$rule}{fexpr}; my $fread = $hash->{hmccu}{agg}{$rule}{fread}; - next if ($r !~ $fread); - next if ($ftype eq 'name' && $devname !~ /$fexpr/); - next if ($ftype eq 'type' && $devhash->{ccutype} !~ /$fexpr/); - next if ($ftype eq 'group' && AttrVal ($devname, 'group', 'null') !~ /$fexpr/); - next if ($ftype eq 'room' && AttrVal ($devname, 'room', 'null') !~ /$fexpr/); - next if ($ftype eq 'alias' && AttrVal ($devname, 'alias', 'null') !~ /$fexpr/); + next if ($r !~ $fread || + ($ftype eq 'name' && $devname !~ /$fexpr/) || + ($ftype eq 'type' && $devhash->{ccutype} !~ /$fexpr/) || + ($ftype eq 'group' && AttrVal ($devname, 'group', 'null') !~ /$fexpr/) || + ($ftype eq 'room' && AttrVal ($devname, 'room', 'null') !~ /$fexpr/) || + ($ftype eq 'alias' && AttrVal ($devname, 'alias', 'null') !~ /$fexpr/)); HMCCU_AggregateReadings ($hash, $rule); } @@ -1092,7 +1062,7 @@ sub HMCCU_Notify ($$) } ###################################################################### -# Enhance device details in FHEM WEB +# Enhance device details in FHEM web view ###################################################################### sub HMCCU_Detail ($$$$) @@ -1100,7 +1070,7 @@ sub HMCCU_Detail ($$$$) my ($FW_Name, $Device, $Room, $pageHash) = @_; my $hash = $defs{$Device}; - return defined ($hash->{host}) ? qq( + return defined($hash->{host}) ? qq( CCU Administration @@ -1133,137 +1103,104 @@ sub HMCCU_AggregateReadings ($$) my $table = ''; # Get rule parameters - my $ftype = $hash->{hmccu}{agg}{$rule}{ftype}; - my $fexpr = $hash->{hmccu}{agg}{$rule}{fexpr}; - my $fexcl = $hash->{hmccu}{agg}{$rule}{fexcl}; - my $fread = $hash->{hmccu}{agg}{$rule}{fread}; - my $fcond = $hash->{hmccu}{agg}{$rule}{fcond}; - my $ftrue = $hash->{hmccu}{agg}{$rule}{ftrue}; - my $felse = $hash->{hmccu}{agg}{$rule}{felse}; - my $fpref = $hash->{hmccu}{agg}{$rule}{fpref}; - my $fhtml = exists ($hash->{hmccu}{agg}{$rule}{fhtml}) ? 1 : 0; + my $r = $hash->{hmccu}{agg}{$rule}; my $resval; - $resval = $ftrue if ($fcond =~ /^(max|min|sum|avg)$/); + $resval = $r->{ftrue} if ($r->{fcond} =~ /^(max|min|sum|avg)$/); - my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef); + my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)"); foreach my $d (@devlist) { my $ch = $defs{$d}; my $cn = $ch->{NAME}; my $ct = $ch->{TYPE}; my $fmatch = ''; - $fmatch = $cn if ($ftype eq 'name'); - $fmatch = $ch->{ccutype} if ($ftype eq 'type'); - $fmatch = AttrVal ($cn, 'group', '') if ($ftype eq 'group'); - $fmatch = AttrVal ($cn, 'room', '') if ($ftype eq 'room'); - $fmatch = AttrVal ($cn, 'alias', '') if ($ftype eq 'alias'); - next if (!defined ($fmatch) || $fmatch eq '' || $fmatch !~ /$fexpr/ || ($fexcl ne '' && $fmatch =~ /$fexcl/)); + 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', ''); } - my $fcoll = $hash->{hmccu}{agg}{$rule}{fcoll} eq 'NAME' ? - $cn : AttrVal ($cn, $hash->{hmccu}{agg}{$rule}{fcoll}, $cn); + next if ($fmatch eq '' || $fmatch !~ /$r->{fexpr}/ || ($r->{fexcl} ne '' && $fmatch =~ /$r->{fexcl}/)); + + my $fcoll = $r->{fcoll} eq 'NAME' ? $cn : AttrVal ($cn, $r->{fcoll}, $cn); # Compare readings - foreach my $r (keys %{$ch->{READINGS}}) { - next if ($r =~ /^\./ || $r !~ /$fread/); - my $rv = $ch->{READINGS}{$r}{VAL}; + foreach my $rd (keys %{$ch->{READINGS}}) { + next if ($rd =~ /^\./ || $rd !~ /$r->{fread}/); + my $rv = $ch->{READINGS}{$rd}{VAL}; my $f = 0; - if (($fcond eq 'any' || $fcond eq 'all') && $rv =~ /$ftrue/) { + if (($r->{fcond} eq 'any' || $r->{fcond} eq 'all') && $rv =~ /$r->{ftrue}/) { $mc++; $f = 1; } - if ($fcond eq 'max' && $rv > $resval) { + if ($r->{fcond} eq 'max' && $rv > $resval) { $resval = $rv; $mc = 1; $f = 1; } - if ($fcond eq 'min' && $rv < $resval) { + if ($r->{fcond} eq 'min' && $rv < $resval) { $resval = $rv; $mc = 1; $f = 1; } - if ($fcond eq 'sum' || $fcond eq 'avg') { + if ($r->{fcond} eq 'sum' || $r->{fcond} eq 'avg') { $resval += $rv; $mc++; $f = 1; } - if ($fcond =~ /^(gt|lt|ge|le)$/ && !HMCCU_IsFltNum ($rv)) { - HMCCU_Log ($hash, 4, "Aggregation value $rv of reading $cn.$r is not numeric"); + 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"); next; } - if (($fcond eq 'gt' && $rv > $ftrue) || - ($fcond eq 'lt' && $rv < $ftrue) || - ($fcond eq 'ge' && $rv >= $ftrue) || - ($fcond eq 'le' && $rv <= $ftrue)) { + if (($r->{fcond} eq 'gt' && $rv > $r->{ftrue}) || + ($r->{fcond} eq 'lt' && $rv < $r->{ftrue}) || + ($r->{fcond} eq 'ge' && $rv >= $r->{ftrue}) || + ($r->{fcond} eq 'le' && $rv <= $r->{ftrue})) { $mc++; $f = 1; } if ($f) { - $rl .= ($mc > 1 ? ",$fcoll" : $fcoll); + $rl .= ($mc > 1 ? ",$r->{fcoll}" : $r->{fcoll}); last; } } $dc++; } - $rl = $hash->{hmccu}{agg}{$rule}{fdflt} if ($rl eq ''); + $rl = $r->{fdflt} if ($rl eq ''); # HTML code generation - if ($fhtml) { + if ($r->{fhtml}) { if ($rl ne '') { - $table = $hash->{hmccu}{agg}{$rule}{fhtml}{'begin-html'}. - $hash->{hmccu}{agg}{$rule}{fhtml}{'begin-table'}; - $table .= $hash->{hmccu}{agg}{$rule}{fhtml}{'header'} - if (exists ($hash->{hmccu}{agg}{$rule}{fhtml}{'header'})); + $table = $r->{fhtml}{'begin-html'}.$r->{fhtml}{'begin-table'}; + $table .= $r->{fhtml}{'header'} if (exists($r->{fhtml}{'header'})); my $row = 1; foreach my $v (split (",", $rl)) { - my $t_row = ($row % 2) ? $hash->{hmccu}{agg}{$rule}{fhtml}{'row-odd'} : - $hash->{hmccu}{agg}{$rule}{fhtml}{'row-even'}; + my $t_row = ($row % 2) ? $r->{fhtml}{'row-odd'} : $r->{fhtml}{'row-even'}; $t_row =~ s/\/$v/; $table .= $t_row; $row++; } - $table .= $hash->{hmccu}{agg}{$rule}{fhtml}{'end-table'}. - $hash->{hmccu}{agg}{$rule}{fhtml}{'end-html'}; + $table .= $r->{fhtml}{'end-table'}.$r->{fhtml}{'end-html'}; } else { - $table = $hash->{hmccu}{agg}{$rule}{fhtml}{'begin-html'}. - $hash->{hmccu}{agg}{$rule}{fhtml}{'default'}. - $hash->{hmccu}{agg}{$rule}{fhtml}{'end-html'}; + $table = $r->{fhtml}{'begin-html'}.$r->{fhtml}{'default'}.$r->{fhtml}{'end-html'}; } } - if ($fcond eq 'any') { - $result = $mc > 0 ? $ftrue : $felse; - } - elsif ($fcond eq 'all') { - $result = $mc == $dc ? $ftrue : $felse; - } - elsif ($fcond eq 'min' || $fcond eq 'max' || $fcond eq 'sum') { - $result = $mc > 0 ? $resval : $felse; - } - elsif ($fcond eq 'avg') { - $result = $mc > 0 ? $resval/$mc : $felse; - } - elsif ($fcond =~ /^(gt|lt|ge|le)$/) { - $result = $mc; - } + 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} =~ /^(gt|lt|ge|le)$/) { $result = $mc; } - HMCCU_UpdateReadings ($hash, { $fpref.'state' => $result, $fpref.'match' => $mc, - $fpref.'count' => $dc, $fpref.'list' => $rl }); - readingsSingleUpdate ($hash, $fpref.'table', $table, 1) if ($fhtml); - - # Set readings -# readingsBeginUpdate ($hash); -# readingsBulkUpdate ($hash, $fpref.'state', $result); -# readingsBulkUpdate ($hash, $fpref.'match', $mc); -# readingsBulkUpdate ($hash, $fpref.'count', $dc); -# readingsBulkUpdate ($hash, $fpref.'list', $rl); -# readingsBulkUpdate ($hash, $fpref.'table', $table) if ($fhtml); -# readingsEndUpdate ($hash, 1); + HMCCU_UpdateReadings ($hash, { $r->{fpref}.'state' => $result, $r->{fpref}.'match' => $mc, + $r->{fpref}.'count' => $dc, $r->{fpref}.'list' => $rl }); + readingsSingleUpdate ($hash, $r->{fpref}.'table', $table, 1) if ($r->{fhtml}); return $result; } @@ -1300,16 +1237,16 @@ sub HMCCU_DelayedShutdown ($) my ($hash) = @_; my $name = $hash->{NAME}; - my $delay = HMCCU_Max (AttrVal ("global", "maxShutdownDelay", 10)-2, 0); + my $delay = HMCCU_Max (AttrVal ('global', 'maxShutdownDelay', 10)-2, 0); # Shutdown RPC server - if (!exists ($hash->{hmccu}{delayedShutdown})) { + if (!exists($hash->{hmccu}{delayedShutdown})) { $hash->{hmccu}{delayedShutdown} = $delay; HMCCU_Log ($hash, 1, "Graceful shutdown in $delay seconds"); HMCCU_StopExtRPCServer ($hash, 0); } else { - HMCCU_Log ($hash, 1, "Graceful shutdown already in progress"); + HMCCU_Log ($hash, 1, 'Graceful shutdown already in progress'); } return 1; @@ -1325,12 +1262,12 @@ sub HMCCU_Shutdown ($) my $name = $hash->{NAME}; # Shutdown RPC server - if (!exists ($hash->{hmccu}{delayedShutdown})) { - HMCCU_Log ($hash, 1, "Immediate shutdown"); + if (!exists($hash->{hmccu}{delayedShutdown})) { + HMCCU_Log ($hash, 1, 'Immediate shutdown'); HMCCU_StopExtRPCServer ($hash, 0); } else { - HMCCU_Log ($hash, 1, "Graceful shutdown"); + HMCCU_Log ($hash, 1, 'Graceful shutdown'); } # Remove existing timer functions @@ -1347,24 +1284,22 @@ sub HMCCU_Set ($@) { my ($hash, $a, $h) = @_; my $name = shift @$a; - my $opt = shift @$a; + my $opt = shift @$a // return 'No set command specified'; my $options = "var clear delete execute hmscript cleardefaults:noArg datapoint defaults:noArg ". "importdefaults rpcregister:all rpcserver:on,off,restart ackmessages:noArg authentication ". "prgActivate prgDeactivate"; - - return "No set command specified" if (!defined ($opt)); + $opt = lc($opt); my @ifList = HMCCU_GetRPCInterfaceList ($hash); - if (scalar (@ifList) > 0) { + if (scalar(@ifList) > 0) { my $ifStr = join (',', @ifList); $options =~ s/rpcregister:all/rpcregister:all,$ifStr/; } my $host = $hash->{host}; - $options = "initialize:noArg" if (exists ($hash->{hmccu}{ccu}{delayed}) && + $options = 'initialize:noArg' if (exists($hash->{hmccu}{ccu}{delayed}) && $hash->{hmccu}{ccu}{delayed} == 1 && $hash->{ccustate} eq 'unreachable'); -# return undef if ($hash->{ccustate} ne 'active'); - return "HMCCU: CCU busy, choose one of rpcserver:off" + return 'HMCCU: CCU busy, choose one of rpcserver:off' if ($opt ne 'rpcserver' && HMCCU_IsRPCStateBlocking ($hash)); my $usage = "HMCCU: Unknown argument $opt, choose one of $options"; @@ -1378,7 +1313,7 @@ sub HMCCU_Set ($@) my $result; # Add program names to command execute - if (exists ($hash->{hmccu}{prg})) { + if (exists($hash->{hmccu}{prg})) { my @progs = (); my @aprogs = (); my @iprogs = (); @@ -1403,59 +1338,56 @@ sub HMCCU_Set ($@) } if ($opt eq 'var') { - my $vartype; - $vartype = shift @$a if (scalar (@$a) == 3); - my $objname = shift @$a; - my $objvalue = shift @$a; $usage = "set $name $opt [{'bool'|'list'|'number'|'text'}] variable value [param=value [...]]"; - - return HMCCU_SetError ($hash, $usage) if (!defined ($objvalue)); + my $vartype; + $vartype = shift @$a if (scalar(@$a) == 3); + my $objname = shift @$a; + 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)); + $h->{name} = $objname if (!defined($h) && defined($vartype)); $result = HMCCU_SetVariable ($hash, $objname, $objvalue, $vartype, $h); return HMCCU_SetError ($hash, $result) if ($result < 0); - return HMCCU_SetState ($hash, "OK"); + return HMCCU_SetState ($hash, 'OK'); } elsif ($opt eq 'initialize') { - return HMCCU_SetError ($hash, "State of CCU must be unreachable") + return HMCCU_SetError ($hash, 'State of CCU must be unreachable') if ($hash->{ccustate} ne 'unreachable'); my $err = HMCCU_InitDevice ($hash); - return HMCCU_SetError ($hash, "CCU not reachable") if ($err == 1); + return HMCCU_SetError ($hash, 'CCU not reachable') if ($err == 1); return HMCCU_SetError ($hash, "Can't read device list from CCU") if ($err == 2); - return HMCCU_SetState ($hash, "OK"); + return HMCCU_SetState ($hash, 'OK'); } elsif ($opt eq 'authentication') { my $username = shift @$a; my $password = shift @$a; $usage = "set $name $opt username password"; - if (!defined ($username)) { + if (!defined($username)) { setKeyValue ($name."_username", undef); setKeyValue ($name."_password", undef); - return "Credentials for CCU authentication deleted"; - } - - return HMCCU_SetError ($hash, $usage) if (!defined ($password)); + return 'Credentials for CCU authentication deleted'; + } + return HMCCU_SetError ($hash, $usage) if (!defined($password)); my $encuser = HMCCU_Encrypt ($username); my $encpass = HMCCU_Encrypt ($password); - return HMCCU_SetError ($hash, "Encryption of credentials failed") if ($encuser eq '' || $encpass eq ''); + return HMCCU_SetError ($hash, 'Encryption of credentials failed') if ($encuser eq '' || $encpass eq ''); my $err = setKeyValue ($name."_username", $encuser); return HMCCU_SetError ($hash, "Can't store credentials. $err") if (defined ($err)); $err = setKeyValue ($name."_password", $encpass); return HMCCU_SetError ($hash, "Can't store credentials. $err") if (defined ($err)); - return "Credentials for CCU authentication stored"; + return 'Credentials for CCU authentication stored'; } elsif ($opt eq 'clear') { my $rnexp = shift @$a; HMCCU_DeleteReadings ($hash, $rnexp); - return HMCCU_SetState ($hash, "OK"); + return HMCCU_SetState ($hash, 'OK'); } elsif ($opt eq 'datapoint') { $usage = "set $name $opt [DevSpec] [Device[,...]].[Channel].Datapoint=Value [...]\n"; @@ -1470,7 +1402,7 @@ sub HMCCU_Set ($@) if (defined($devSpec)) { @devSpecList = devspec2array ($devSpec); return HMCCU_SetError ($hash, "No FHEM device matching $devSpec in command set datapoint") - if (scalar (@devSpecList) == 0); + if (scalar(@devSpecList) == 0); } foreach my $dptSpec (keys %$h) { @@ -1533,30 +1465,28 @@ sub HMCCU_Set ($@) my $rc = HMCCU_SetMultipleDatapoints ($hash, \%dpValues); return HMCCU_SetError ($hash, $rc) if ($rc < 0); - return HMCCU_SetState ($hash, "OK"); + return HMCCU_SetState ($hash, 'OK'); } elsif ($opt eq 'delete') { my $objname = shift @$a; - my $objtype = shift @$a; - $objtype = "OT_VARDP" if (!defined ($objtype)); + my $objtype = shift @$a // 'OT_VARDP'; $usage = "Usage: set $name $opt ccuobject ['OT_VARDP'|'OT_DEVICE']"; return HMCCU_SetError ($hash, $usage) - if (!defined ($objname) || $objtype !~ /^(OT_VARDP|OT_DEVICE)$/); + if (!defined($objname) || $objtype !~ /^(OT_VARDP|OT_DEVICE)$/); - $result = HMCCU_HMScriptExt ($hash, "!DeleteObject", { name => $objname, type => $objtype }, - undef, undef); + $result = HMCCU_HMScriptExt ($hash, "!DeleteObject", { name => $objname, type => $objtype }); return HMCCU_SetError ($hash, -2) if ($result =~ /^ERROR:.*/); - return HMCCU_SetState ($hash, "OK"); + return HMCCU_SetState ($hash, 'OK'); } elsif ($opt eq 'execute') { my $program = shift @$a; - $program .= ' '.join(' ', @$a) if (scalar (@$a) > 0); + $program .= ' '.join(' ', @$a) if (scalar(@$a) > 0); my $response; $usage = "Usage: set $name $opt program-name"; - return HMCCU_SetError ($hash, $usage) if (!defined ($program)); + return HMCCU_SetError ($hash, $usage) if (!defined($program)); my $cmd = qq(dom.GetObject("$program").ProgramExecute()); my $value = HMCCU_HMCommand ($hash, $cmd, 1); @@ -1565,14 +1495,11 @@ sub HMCCU_Set ($@) return HMCCU_SetError ($hash, 'Program execution error'); } elsif ($opt eq 'prgActivate' || $opt eq 'prgDeactivate') { - my $program = shift @$a; - my $mode = $opt eq 'prgActivate' ? 'true' : 'false'; $usage = "Usage: set $name $opt program-name"; + my $program = shift @$a // return HMCCU_SetError ($hash, $usage); + my $mode = $opt eq 'prgActivate' ? 'true' : 'false'; - return HMCCU_SetError ($hash, $usage) if (!defined ($program)); - - $result = HMCCU_HMScriptExt ($hash, '!ActivateProgram', { name => $program, mode => $mode }, - undef, undef); + $result = HMCCU_HMScriptExt ($hash, '!ActivateProgram', { name => $program, mode => $mode }); return HMCCU_SetError ($hash, -2) if ($result =~ /^ERROR:.*/); return HMCCU_SetState ($hash, 'OK'); @@ -1586,26 +1513,24 @@ sub HMCCU_Set ($@) $usage = "Usage: set $name $opt {file|!function|'['code']'} ['dump'] [parname=value [...]]"; # If no parameter is specified list available script functions - if (!defined ($script)) { + if (!defined($script)) { $response = "Available HomeMatic script functions:\n". "-------------------------------------\n"; foreach my $scr (keys %{$HMCCU_SCRIPTS}) { $response .= "$scr ".$HMCCU_SCRIPTS->{$scr}{syntax}."\n". $HMCCU_SCRIPTS->{$scr}{description}."\n\n"; - } - - $response .= $usage; - return $response; + } + return $response.$usage; } - return HMCCU_SetError ($hash, $usage) if (defined ($dump) && $dump ne 'dump'); + return HMCCU_SetError ($hash, $usage) if (defined($dump) && $dump ne 'dump'); # Execute script - $response = HMCCU_HMScriptExt ($hash, $script, $h, undef, undef); + $response = HMCCU_HMScriptExt ($hash, $script, $h); return HMCCU_SetError ($hash, -2, $response) if ($response =~ /^ERROR:/); HMCCU_SetState ($hash, 'OK'); - return $response if (! $ccureadings || defined ($dump)); + return $response if (! $ccureadings || defined($dump)); foreach my $line (split /[\n\r]+/, $response) { my @tokens = split /=/, $line; @@ -1613,7 +1538,7 @@ sub HMCCU_Set ($@) my $reading; my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hash, $tokens[0], $HMCCU_FLAG_INTERFACE); - ($add, $chn) = HMCCU_GetAddress ($hash, $nam, '', '') if ($flags == $HMCCU_FLAGS_NCD); + ($add, $chn) = HMCCU_GetAddress ($hash, $nam) if ($flags == $HMCCU_FLAGS_NCD); if ($flags == $HMCCU_FLAGS_IACD || $flags == $HMCCU_FLAGS_NCD) { $objects{$add}{$chn}{VALUES}{$dpt} = $tokens[1]; @@ -1634,7 +1559,7 @@ sub HMCCU_Set ($@) elsif ($opt eq 'rpcregister') { my $ifName = shift @$a; $result = ''; - @ifList = (defined ($ifName) && $ifName ne 'all') ? ($ifName) : HMCCU_GetRPCInterfaceList ($hash); + @ifList = (defined($ifName) && $ifName ne 'all') ? ($ifName) : HMCCU_GetRPCInterfaceList ($hash); foreach my $i (@ifList) { my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $i); @@ -1643,7 +1568,7 @@ sub HMCCU_Set ($@) next; } my $res = AnalyzeCommandChain (undef, "set $rpcdev register"); - $result .= $res if (defined ($res)); + $result .= $res if (defined($res)); } return HMCCU_SetState ($hash, "OK", $result); } @@ -1653,38 +1578,37 @@ sub HMCCU_Set ($@) $usage = "Usage: set $name $opt {'on'|'off'|'restart'}"; return HMCCU_SetError ($hash, $usage) - if (!defined ($action) || $action !~ /^(on|off|restart)$/); + if (!defined($action) || $action !~ /^(on|off|restart)$/); if ($action eq 'on') { - return HMCCU_SetError ($hash, "Start of RPC server failed") + return HMCCU_SetError ($hash, 'Start of RPC server failed') if (!HMCCU_StartExtRPCServer ($hash)); } elsif ($action eq 'off') { - return HMCCU_SetError ($hash, "Stop of RPC server failed") + return HMCCU_SetError ($hash, 'Stop of RPC server failed') if (!HMCCU_StopExtRPCServer ($hash)); } elsif ($action eq 'restart') { - return "HMCCU: No RPC server running" if (!HMCCU_IsRPCServerRunning ($hash, undef, undef)); - return HMCCU_SetError ($hash, "HMCCU: restart of RPC server not supported"); + return "HMCCU: No RPC server running" if (!HMCCU_IsRPCServerRunning ($hash)); + return HMCCU_SetError ($hash, 'HMCCU: restart of RPC server not supported'); } - return HMCCU_SetState ($hash, "OK"); + return HMCCU_SetState ($hash, 'OK'); } elsif ($opt eq 'ackmessages') { - my $response = HMCCU_HMScriptExt ($hash, "!ClearUnreachable", undef, undef, undef); + my $response = HMCCU_HMScriptExt ($hash, "!ClearUnreachable"); 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_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 = (); - - return HMCCU_SetState ($hash, "OK", "Default attributes deleted"); + %HMCCU_CUST_DEV_DEFAULTS = (); + return HMCCU_SetState ($hash, 'OK', 'Default attributes deleted'); } elsif ($opt eq 'importdefaults') { my $filename = shift @$a; @@ -1714,9 +1638,7 @@ sub HMCCU_Get ($@) { my ($hash, $a, $h) = @_; my $name = shift @$a; - my $opt = shift @$a; - - return "No get command specified" if (!defined($opt)); + my $opt = shift @$a // return 'No get command specified'; $opt = lc($opt); my $options = "ccuconfig create defaults:noArg exportDefaults dump dutycycle:noArg vars update". @@ -1726,7 +1648,7 @@ sub HMCCU_Get ($@) my $host = $hash->{host}; return undef if ($hash->{hmccu}{ccu}{delayed} || $hash->{ccustate} ne 'active'); - return "HMCCU: CCU busy, choose one of rpcstate:noArg" + return 'HMCCU: CCU busy, choose one of rpcstate:noArg' if ($opt ne 'rpcstate' && HMCCU_IsRPCStateBlocking ($hash)); my $ccuflags = HMCCU_GetFlags ($name); @@ -1738,16 +1660,13 @@ sub HMCCU_Get ($@) my $rc; if ($opt eq 'dump') { - my $content = shift @$a; - my $filter = shift @$a; - $filter = '.*' if (!defined ($filter)); $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"); - return HMCCU_SetError ($hash, $usage) if (!defined ($content)); - if ($content eq 'devtypes') { foreach my $devtype (sort keys %{$hash->{hmccu}{dp}}) { $result .= $devtype."\n" if ($devtype =~ /$filter/); @@ -1774,41 +1693,30 @@ sub HMCCU_Get ($@) return HMCCU_SetState ($hash, 'OK', ($result eq '') ? 'No data found' : $result); } elsif ($opt eq 'vars') { - my $varname = shift @$a; $usage = "Usage: get $name vars {regexp}[,...]"; - return HMCCU_SetError ($hash, $usage) if (!defined ($varname)); + my $varname = shift @$a // return HMCCU_SetError ($hash, $usage); ($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', $ccureadings ? undef : $result); } elsif ($opt eq 'update' || $opt eq 'updateccu') { - my $devexp = shift @$a; - $devexp = '.*' if (!defined ($devexp)); $usage = "Usage: get $name $opt [device-expr [{'State'|'Value'}]]"; - my $ccuget = shift @$a; - $ccuget = 'Attr' if (!defined ($ccuget)); + my $devexp = shift @$a // '.*'; + my $ccuget = shift @$a // 'Attr'; return HMCCU_SetError ($hash, $usage) if ($ccuget !~ /^(Attr|State|Value)$/); - my $nonBlocking = HMCCU_IsFlag ($name, 'nonBlocking') ? 1 : 0; - - HMCCU_UpdateClients ($hash, $devexp, $ccuget, ($opt eq 'updateccu') ? 1 : 0, undef, 0); - return HMCCU_SetState ($hash, "OK"); + HMCCU_UpdateClients ($hash, $devexp, $ccuget, ($opt eq 'updateccu') ? 1 : 0); + return HMCCU_SetState ($hash, 'OK'); } elsif ($opt eq 'deviceinfo') { - my $device = shift @$a; $usage = "Usage: get $name $opt device [{'State'|'Value'}]"; - - return HMCCU_SetError ($hash, $usage) if (!defined ($device)); - - my $ccuget = shift @$a; - $ccuget = 'Attr' if (!defined ($ccuget)); + my $device = shift @$a // return HMCCU_SetError ($hash, $usage); + my $ccuget = shift @$a // 'Attr'; return HMCCU_SetError ($hash, $usage) if ($ccuget !~ /^(Attr|State|Value)$/); - 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"); + HMCCU_SetState ($hash, 'OK'); return HMCCU_FormatDeviceInfo ($result); } elsif ($opt eq 'rpcevents') { @@ -1821,29 +1729,26 @@ sub HMCCU_Get ($@) next; } my $res = AnalyzeCommandChain (undef, "get $rpcdev rpcevents"); - $result .= $res if (defined ($result)); + $result .= $res if (defined($res)); } - return HMCCU_SetState ($hash, "OK", $result) if ($result ne ''); - return HMCCU_SetError ($hash, "No event statistics available"); + return HMCCU_SetState ($hash, 'OK', $result) if ($result ne ''); + return HMCCU_SetError ($hash, 'No event statistics available'); } elsif ($opt eq 'rpcstate') { my @hm_pids = (); - my @hm_tids = (); - $result = "No RPC processes or threads are running"; - - if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids)) { - $result = "RPC process(es) running with pid(s) ". - join (',', @hm_pids) if (scalar (@hm_pids) > 0); - $result = "RPC thread(s) running with tid(s) ". - join (',', @hm_tids) if (scalar (@hm_tids) > 0); + if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids)) { + $result = 'RPC process(es) running with pid(s) '. + join (',', @hm_pids) if (scalar(@hm_pids) > 0); } - - return HMCCU_SetState ($hash, "OK", $result); + else { + $result = 'No RPC processes or threads are running'; + } + return HMCCU_SetState ($hash, 'OK', $result); } elsif ($opt eq 'ccuconfig') { my ($devcount, $chncount, $ifcount, $prgcount, $gcount) = HMCCU_GetDeviceList ($hash); return HMCCU_SetError ($hash, -2) if ($devcount < 0); - return HMCCU_SetError ($hash, "No devices received from CCU") if ($devcount == 0); + return HMCCU_SetError ($hash, 'No devices received from CCU') if ($devcount == 0); HMCCU_ResetDeviceTables ($hash); my ($cDev, $cPar, $cLnk) = HMCCU_GetDeviceConfig ($hash); return "Devices: $devcount, Channels: $chncount\nDevice descriptions: $cDev\n". @@ -1863,23 +1768,23 @@ sub HMCCU_Get ($@) my $devtype = exists ($h->{t}) ? $h->{t} : 'dev'; my $devformat = exists ($h->{f}) ? $h->{f} : '%n'; return HMCCU_SetError ($hash, $usage) - if ($devtype !~ /^(dev|chn|all)$/ || !defined ($devspec)); + if ($devtype !~ /^(dev|chn|all)$/ || !defined($devspec)); foreach my $defopt (@$a) { - if ($defopt eq 'defaults') { $devdefaults = 1; } + if ($defopt eq 'defaults') { $devdefaults = 1; } elsif (lc($defopt) eq 'nodefaults') { $devdefaults = 0; } - elsif ($defopt eq 'save') { $savedef = 1; } - else { return HMCCU_SetError ($hash, $usage); } + elsif ($defopt eq 'save') { $savedef = 1; } + else { return HMCCU_SetError ($hash, $usage); } } # Get list of existing client devices - my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef); + my @devlist = HMCCU_FindClientDevices ($hash, '(HMCCUDEV|HMCCUCHN)'); foreach my $add (sort keys %{$hash->{hmccu}{dev}}) { my $defmod = $hash->{hmccu}{dev}{$add}{addtype} eq 'dev' ? 'HMCCUDEV' : 'HMCCUCHN'; my $ccuname = $hash->{hmccu}{dev}{$add}{name}; my $ccudevname = HMCCU_GetDeviceName ($hash, $add, $ccuname); - next if ($devtype ne 'all' && $devtype ne $hash->{hmccu}{dev}{$add}{addtype}); - next if (HMCCU_ExprNotMatch ($ccuname, $devspec, 1)); + next if (($devtype ne 'all' && $devtype ne $hash->{hmccu}{dev}{$add}{addtype}) || + HMCCU_ExprNotMatch ($ccuname, $devspec, 1)); # Build FHEM device name my $devname = $devformat; @@ -1890,7 +1795,7 @@ sub HMCCU_Get ($@) $devname =~ s/[^A-Za-z\d_\.]+/_/g; # Check for duplicate device definitions - next if (exists ($defs{$devname})); + next if (exists($defs{$devname})); my $devexists = 0; foreach my $exdev (@devlist) { if ($defs{$exdev}->{ccuaddr} eq $add) { @@ -1913,8 +1818,7 @@ sub HMCCU_Get ($@) foreach my $da (keys %$h) { next if ($da =~ /^[pstf]$/); $ret = CommandAttr (undef, "$devname $da ".$h->{$da}); - HMCCU_Log ($hash, 2, "Attr command failed $devname $da ".$h->{$da}.". $ret") - if ($ret); + HMCCU_Log ($hash, 2, "Attr command failed $devname $da ".$h->{$da}.". $ret") if ($ret); } HMCCU_Log ($hash, 2, "Created device $devname"); $result .= "\nCreated device $devname"; @@ -1924,24 +1828,21 @@ sub HMCCU_Get ($@) CommandSave (undef, undef) if ($newcount > 0 && $savedef); $result .= "\nCreated $newcount client devices"; - return HMCCU_SetState ($hash, "OK", $result); + return HMCCU_SetState ($hash, 'OK', $result); } elsif ($opt eq 'dutycycle') { - my $dc = HMCCU_GetDutyCycle ($hash); - return HMCCU_SetState ($hash, "OK"); + HMCCU_GetDutyCycle ($hash); + return HMCCU_SetState ($hash, 'OK'); } elsif ($opt eq 'firmware') { - my $devtype = shift @$a; - $devtype = '.*' if (!defined ($devtype)); - my $dtexp = $devtype; - $dtexp = '.*' if ($devtype eq 'full'); + my $devtype = shift @$a // '.*'; + my $dtexp = $devtype eq 'full' ? '.*' : $devtype; my $dc = HMCCU_GetFirmwareVersions ($hash, $dtexp); - return "Found no firmware downloads" if ($dc == 0); + 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". + "-----------------------------------------\n"; foreach my $ct (keys %{$hash->{hmccu}{type}}) { $result .= sprintf "%-20s %-9s %-10s\n", $ct, $hash->{hmccu}{type}{$ct}{download}, @@ -1949,7 +1850,7 @@ sub HMCCU_Get ($@) } } else { - my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef); + my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)"); return $result if (scalar (@devlist) == 0); $result .= "Device Type Current Available Date\n". @@ -1965,29 +1866,26 @@ sub HMCCU_Get ($@) } } - return HMCCU_SetState ($hash, "OK", $result); + return HMCCU_SetState ($hash, 'OK', $result); } elsif ($opt eq 'defaults') { $result = HMCCU_GetDefaults ($hash, 1); - return HMCCU_SetState ($hash, "OK", $result); + return HMCCU_SetState ($hash, 'OK', $result); } elsif ($opt eq 'exportdefaults') { - my $filename = shift @$a; $usage = "Usage: get $name $opt filename ['all']"; + my $filename = shift @$a // return HMCCU_SetError ($hash, $usage); my $all = 0; my $defopt = shift @$a; $all = 1 if (defined($defopt) && $defopt eq 'all'); - return HMCCU_SetError ($hash, $usage) if (!defined ($filename)); - my $rc = HMCCU_ExportDefaults ($filename, $all); return HMCCU_SetError ($hash, -16) if ($rc == 0); - return HMCCU_SetState ($hash, "OK", "Default attributes written to $filename"); + return HMCCU_SetState ($hash, 'OK', "Default attributes written to $filename"); } elsif ($opt eq 'aggregation') { - my $rule = shift @$a; - $usage = "Usage: get $name $opt {'all'|'rule'}"; - return HMCCU_SetError ($hash, $usage) if (!defined ($rule)); + $usage = "Usage: get $name $opt {'all'|rule}"; + my $rule = shift @$a // return HMCCU_SetError ($hash, $usage); if ($rule eq 'all') { foreach my $r (keys %{$hash->{hmccu}{agg}}) { @@ -1996,43 +1894,40 @@ sub HMCCU_Get ($@) } } else { - return HMCCU_SetError ($hash, "HMCCU: Aggregation rule does not exist") - if (!exists ($hash->{hmccu}{agg}{$rule})); + return HMCCU_SetError ($hash, "HMCCU: Aggregation rule $rule does not exist") + if (!exists($hash->{hmccu}{agg}{$rule})); $result = HMCCU_AggregateReadings ($hash, $rule); $result = "$rule = $result"; } - return HMCCU_SetState ($hash, "OK", $ccureadings ? undef : $result); + return HMCCU_SetState ($hash, 'OK', $ccureadings ? undef : $result); } elsif ($opt eq 'devicedesc') { - my $ccuobj = shift @$a; - return HMCCU_SetError ($hash, "Usage: get $name $opt {device|channel}") - if (!defined ($ccuobj)); + $usage = "Usage: get $name $opt {device|channel}"; + my $ccuobj = shift @$a // return HMCCU_SetError ($hash, $usage); my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hash, $ccuobj, $HMCCU_FLAG_FULLADDR); - return HMCCU_SetError ($hash, "Invalid device or address") + return HMCCU_SetError ($hash, 'Invalid device or address') if (!($flags & $HMCCU_FLAG_ADDRESS)); $result = HMCCU_DeviceDescToStr ($hash, $add); return defined($result) ? $result : HMCCU_SetError ($hash, "Can't get device description"); } elsif ($opt eq 'paramsetdesc') { - my $ccuobj = shift @$a; - return HMCCU_SetError ($hash, "Usage: get $name $opt {device|channel}") - if (!defined ($ccuobj)); + $usage = "Usage: get $name $opt {device|channel}"; + my $ccuobj = shift @$a // return HMCCU_SetError ($hash, $usage); my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hash, $ccuobj, $HMCCU_FLAG_FULLADDR); - return HMCCU_SetError ($hash, "Invalid device or address") + return HMCCU_SetError ($hash, 'Invalid device or address') if (!($flags & $HMCCU_FLAG_ADDRESS)); $result = HMCCU_ParamsetDescToStr ($hash, $add); return defined($result) ? $result : HMCCU_SetError ($hash, "Can't get device description"); } elsif ($opt eq 'ccumsg') { - my $msgtype = shift @$a; $usage = "Usage: get $name $opt {service|alarm}"; - return HMCCU_SetError ($hash, $usage) if (!defined ($msgtype)); + my $msgtype = shift @$a // return HMCCU_SetError ($hash, $usage); my $script = ($msgtype eq 'service') ? "!GetServiceMessages" : "!GetAlarms"; - my $res = HMCCU_HMScriptExt ($hash, $script, undef, undef, undef); + my $res = HMCCU_HMScriptExt ($hash, $script); return HMCCU_SetError ($hash, "Error") if ($res eq '' || $res =~ /^ERROR:.*/); @@ -2042,12 +1937,12 @@ sub HMCCU_Get ($@) DoTrigger ($name, $msg); } - return HMCCU_SetState ($hash, "OK", $res); + return HMCCU_SetState ($hash, 'OK', $res); } else { if (exists ($hash->{hmccu}{agg})) { my @rules = keys %{$hash->{hmccu}{agg}}; - $usage .= " aggregation:all,".join (',', @rules) if (scalar (@rules) > 0); + $usage .= " aggregation:all,".join (',', @rules) if (scalar(@rules) > 0); } return $usage; } @@ -2192,7 +2087,7 @@ sub HMCCU_ParseObject ($$$) # Check if name is a valid channel name if ($f & $HMCCU_FLAG_NAME) { - my ($add, $chn) = HMCCU_GetAddress ($hash, $n, '', ''); + my ($add, $chn) = HMCCU_GetAddress ($hash, $n); if ($chn ne '') { $f = $f | $HMCCU_FLAG_CHANNEL; } @@ -2228,10 +2123,7 @@ sub HMCCU_FilterReading ($$$;$) { my ($hash, $chn, $dpt, $ps) = @_; my $name = $hash->{NAME}; - my $fnc = "FilterReading"; - - my $ioHash = HMCCU_GetHash ($hash); - return 1 if (!defined($ioHash)); + my $ioHash = HMCCU_GetHash ($hash) // return 1; if (defined($ps)) { $ps = 'LINK' if ($ps =~ /^LINK\..+$/); @@ -2250,17 +2142,15 @@ sub HMCCU_FilterReading ($$$;$) my ($devadd, $chnnum) = HMCCU_SplitChnAddr ($chn); if ($chnnum ne 'd') { # Get channel name and channel number - $chnnam = HMCCU_GetChannelName ($ioHash, $chn, ''); + $chnnam = HMCCU_GetChannelName ($ioHash, $chn); if ($chnnam eq '') { - ($devadd, $chnnum) = HMCCU_GetAddress ($hash, $chn, '', ''); + ($devadd, $chnnum) = HMCCU_GetAddress ($hash, $chn); $chnnam = $chn; } } - HMCCU_Trace ($hash, 2, $fnc, "chn=$chn, chnnam=$chnnam chnnum=$chnnum dpt=$dpt, rules=$rf dispFlags=$dispFlags ps=$ps"); - - return 0 if ($dispFlags !~ /DEVICE/ && ($chnnum eq 'd' || $chnnum eq '0')); - return 0 if ($dispFlags !~ /$ps/); + HMCCU_Trace ($hash, 2, "chn=$chn, cName=$chnnam cNum=$chnnum dpt=$dpt, rules=$rf dispFlags=$dispFlags ps=$ps"); + return 0 if (($dispFlags !~ /DEVICE/ && ($chnnum eq 'd' || $chnnum eq '0')) || $dispFlags !~ /$ps/); foreach my $r (split (';', $rf)) { my $rm = 1; @@ -2290,7 +2180,7 @@ sub HMCCU_FilterReading ($$$;$) } } - HMCCU_Trace ($hash, 2, undef, " check rm=$rm f=$f cn=$cn c=$c"); + HMCCU_Trace ($hash, 2, " check rm=$rm f=$f cn=$cn c=$c"); # Positive filter return 1 if ( $rm && ( @@ -2310,7 +2200,7 @@ sub HMCCU_FilterReading ($$$;$) $dpt !~ /$f/ ) ); - HMCCU_Trace ($hash, 2, undef, " check result false"); + HMCCU_Trace ($hash, 2, " check result false"); } return 0; @@ -2340,55 +2230,51 @@ sub HMCCU_FilterReading ($$$;$) sub HMCCU_GetReadingName ($$$$$$$;$) { my ($hash, $i, $a, $c, $d, $n, $rf, $ps) = @_; + $c //= ''; + $i //= ''; + $ps //= 'VALUES'; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; my %prefix = ( 'MASTER' => 'R-', 'LINK' => 'L-', 'VALUES' => '', 'SERVICE' => 'S-', - 'PEER' => 'P-', 'DEVICE' => 'D-' ); + 'PEER' => 'P-', 'DEVICE' => 'R-' ); my $ioHash = HMCCU_GetHash ($hash); return () if (!defined($ioHash) || !defined($d) || $d eq ''); - $c = '' if (!defined ($c)); - $i = '' if (!defined ($i)); - my @rcv = (); - if (defined($ps)) { - if ($ps =~ /^LINK\.(.+)$/) { - @rcv = HMCCU_GetDeviceIdentifier ($ioHash, $1, undef); - $ps = 'LINK'; - } - } - else { - $ps = 'VALUES'; + if ($ps =~ /^LINK\.(.+)$/) { + @rcv = HMCCU_GetDeviceIdentifier ($ioHash, $1); + $ps = 'LINK'; } my $rn = ''; my @rnlist; -# Log3 $name, 1, "HMCCU: ChannelNo undefined: Addr=".$a if (!defined ($c)); - - $rf = HMCCU_GetAttrReadingFormat ($hash, $ioHash) if (!defined ($rf)); - my $sr = 'LEVEL$:pct;SET_TEMPERATURE$:desired-temp;ACTUAL_TEMPERATURE$measured-temp;'. - 'SET_POINT_TEMPERATURE$:desired-temp'; + $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 $asr = AttrVal ($name, 'ccureadingname', ''); $sr .= ';'.$asr if ($asr ne ''); # Complete missing values if ($n eq '' && $a ne '') { $n = ($c ne '') ? - HMCCU_GetChannelName ($ioHash, $a.':'.$c, '') : - HMCCU_GetDeviceName ($ioHash, $a, ''); + HMCCU_GetChannelName ($ioHash, $a.':'.$c) : + HMCCU_GetDeviceName ($ioHash, $a); } elsif ($n ne '' && $a eq '') { - ($a, $c) = HMCCU_GetAddress ($ioHash, $n, '', ''); + ($a, $c) = HMCCU_GetAddress ($ioHash, $n); } if ($i eq '' && $a ne '') { - $i = HMCCU_GetDeviceInterface ($ioHash, $a, ''); + $i = HMCCU_GetDeviceInterface ($ioHash, $a); } # Get reading prefix definitions - $ps = 'DEVICE' if ($c eq '0' || $c eq 'd'); + $ps = 'DEVICE' if (($c eq '0' && $ps eq 'MASTER') || $c eq 'd'); my $readingPrefix = HMCCU_GetAttribute ($ioHash, $hash, 'ccuReadingPrefix', ''); foreach my $pd (split (',', $readingPrefix)) { my ($rSet, $rPre) = split (':', $pd); @@ -2396,14 +2282,14 @@ sub HMCCU_GetReadingName ($$$$$$$;$) } my $rpf = exists($prefix{$ps}) ? $prefix{$ps} : ''; - if ($rf eq 'datapoint' || $rf =~ /^datapoint(lc|uc)$/) { - $rn = $c ne '' && $type ne 'HMCCUCHN' ? $c.'.'.$d : $d; + if ($rf =~ /^datapoint(lc|uc)?$/) { + $rn = $c ne '' && $c ne 'd' && $type ne 'HMCCUCHN' ? $c.'.'.$d : $d; } - elsif ($rf eq 'name' || $rf =~ /^name(lc|uc)$/) { + elsif ($rf =~ /^name(lc|uc)?$/) { return () if ($n eq ''); $rn = $n.'.'.$d; } - elsif ($rf eq 'address' || $rf =~ /^address(lc|uc)$/) { + elsif ($rf =~ /^address(lc|uc)?$/) { return () if ($a eq ''); my $t = $a; $t = $i.'.'.$t if ($i ne ''); @@ -2412,18 +2298,9 @@ sub HMCCU_GetReadingName ($$$$$$$;$) } elsif ($rf =~ /\%/) { $rn = $1; - 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; - } + 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; } $rn =~ s/\%d/lc($d)/ge; $rn =~ s/\%D/uc($d)/ge; } @@ -2481,10 +2358,9 @@ sub HMCCU_FormatReadingValue ($$$) { my ($hash, $value, $dpt) = @_; my $name = $hash->{NAME}; - my $fnc = "FormatReadingValue"; if (!defined($value)) { - HMCCU_Trace ($hash, 2, $fnc, "Value undefined for datapoint $dpt"); + HMCCU_Trace ($hash, 2, "Value undefined for datapoint $dpt"); return $value; } @@ -2505,11 +2381,11 @@ sub HMCCU_FormatReadingValue ($$$) return HMCCU_StripNumber ($value, $s, $isint | 1); } - HMCCU_Trace ($hash, 2, $fnc, "sn = $stripnumber, dpt=$dpt, isint=$isint, value $value not changed"); + HMCCU_Trace ($hash, 2, "sn = $stripnumber, dpt=$dpt, isint=$isint, value $value not changed"); } else { my $h = uc(unpack "H*", $value); - HMCCU_Trace ($hash, 2, $fnc, "sn = $stripnumber, Value $value 0x$h not changed"); + HMCCU_Trace ($hash, 2, "sn = $stripnumber, Value $value 0x$h not changed"); } return $value; @@ -2547,17 +2423,18 @@ sub HMCCU_StripNumber ($$;$) # by
###################################################################### -sub HMCCU_Trace ($$$$) +sub HMCCU_Trace ($$$) { - my ($hash, $level, $fnc, $msg) = @_; + my ($hash, $level, $msg) = @_; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - return if (!HMCCU_IsFlag ($name, "trace")); + return if (!HMCCU_IsFlag ($name, 'trace')); + my $pid = $$; + my $fnc = (caller(1))[3] // 'unknown'; foreach my $m (split ("
", $msg)) { - $m = "[$name] $fnc: $m" if (defined ($fnc) && $fnc ne ''); - Log3 $name, $level, "$type: $m"; + Log3 $name, $level, "$type: [$name : $pid] [$fnc] $m"; } } @@ -2566,6 +2443,7 @@ sub HMCCU_Trace ($$$$) # Return parameter rc or 0. # Parameter source can be a device hash reference, a string reference # or a string. +# Parameter msg can be an array reference or a string. ###################################################################### sub HMCCU_Log ($$$;$) @@ -2573,26 +2451,37 @@ sub HMCCU_Log ($$$;$) my ($source, $level, $msg, $rc) = @_; $rc //= 0; - my $r = ref($source); + my ($cf, $cp, $cl) = caller; + my $r = defined($source) ? ref($source) : 'N/A'; my $pid = $$; - my $name = "n/a"; - my $type = "n/a"; - + my $name = 'N/A'; + if ($r eq 'HASH') { - $name = $source->{NAME} if (exists ($source->{NAME})); - $type = $source->{TYPE} if (exists ($source->{TYPE})); + $name = $source->{NAME} // 'N/A'; } elsif ($r eq 'SCALAR') { $name = $$source; - $type = $defs{$name}->{TYPE} if (exists ($defs{$name})); } else { - $name = $source; - $type = $defs{$name}->{TYPE} if (exists ($defs{$name})); + $name = $source // 'N/A'; } + my $hash = $defs{$name}; - Log3 $name, $level, "$type: [$name : $pid] $msg"; - + my $type = exists($defs{$name}) ? $defs{$name}->{TYPE} : 'N/A'; + if (defined($hash) && HMCCU_IsFlag ($hash, 'logEnhanced')) { + $type .= ":$cl"; + $name .= " : $pid"; + } + my $logname = exists($defs{$name}) ? $name : undef; + + if (ref($msg) eq 'ARRAY') { + foreach my $m (@$msg) { + Log3 $logname, $level, "$type [$name] $m"; + } + } + else { + Log3 $logname, $level, "$type [$name] $msg"; + } return $rc; } @@ -2604,9 +2493,7 @@ sub HMCCU_LogError ($$$) { my ($hash, $level, $msg) = @_; - HMCCU_Log ($hash, $level, $msg, undef); - - return "ERROR: $msg"; + return HMCCU_Log ($hash, $level, $msg, "ERROR: $msg"); } ###################################################################### @@ -2620,6 +2507,7 @@ sub HMCCU_LogError ($$$) sub HMCCU_SetError ($@) { my ($hash, $text, $addinfo) = @_; + $addinfo //= ''; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; my $msg; @@ -2648,17 +2536,14 @@ sub HMCCU_SetError ($@) ); if ($text ne 'OK' && $text ne '0') { - $msg = exists ($errlist{$text}) ? $errlist{$text} : $text; - $msg = $type.": ".$name." ". $msg; - if (defined ($addinfo) && $addinfo ne '') { - $msg .= ". $addinfo"; - } + $msg = exists($errlist{$text}) ? $errlist{$text} : $text; + $msg = "$type: $name $msg"; + $msg .= ". $addinfo" if ($addinfo ne ''); HMCCU_Log ($hash, 1, $msg); - return HMCCU_SetState ($hash, "Error", $msg); - } - else { - return HMCCU_SetState ($hash, "OK"); + return HMCCU_SetState ($hash, 'Error', $msg); } + + return HMCCU_SetState ($hash, 'OK'); } ###################################################################### @@ -2674,7 +2559,7 @@ sub HMCCU_SetState ($@) my $ccuflags = HMCCU_GetFlags ($name); $ccuflags .= ',ackState' if ($hash->{TYPE} eq 'HMCCU' && $ccuflags !~ /ackState/); - if (defined ($hash) && defined ($text) && $ccuflags =~ /ackState/) { + if (defined($hash) && defined($text) && $ccuflags =~ /ackState/) { readingsSingleUpdate ($hash, 'state', $text, 1) if (ReadingsVal ($name, 'state', '') ne $text); } @@ -2698,7 +2583,7 @@ sub HMCCU_SetRPCState ($@) my $filter; my $rpcstate = $state; - if (defined ($iface)) { + if (defined($iface)) { # Set interface state my ($ifname) = HMCCU_GetRPCServerInfo ($hash, $iface, 'name'); $hash->{hmccu}{interfaces}{$ifname}{state} = $state if (defined ($ifname)); @@ -2707,6 +2592,7 @@ sub HMCCU_SetRPCState ($@) # Prepare filter for updating client devices my %stc = ('running' => 0, 'error' => 0, 'inactive' => 0); my @iflist = HMCCU_GetRPCInterfaceList ($hash); + my $ifCount = scalar(@iflist); foreach my $i (@iflist) { my $st = $hash->{hmccu}{interfaces}{$i}{state}; $stc{$st}++ if (exists ($stc{$st})); @@ -2719,24 +2605,24 @@ sub HMCCU_SetRPCState ($@) } # Determine overall process state - my $rpcstate = 'null'; - $rpcstate = 'running' if ($stc{"running"} == scalar (@iflist)); - $rpcstate = 'inactive' if ($stc{"inactive"} == scalar (@iflist)); - $rpcstate = 'error' if ($stc{"error"} == scalar (@iflist)); - - if ($rpcstate =~ /^(running|inactive|error)$/) { - if ($rpcstate ne $hash->{RPCState}) { - $hash->{RPCState} = $rpcstate; - readingsSingleUpdate ($hash, "rpcstate", $rpcstate, 1); - HMCCU_Log ($hash, 4, "Set rpcstate to $rpcstate"); - HMCCU_Log ($hash, 1, $msg, undef) if (defined ($msg)); - HMCCU_Log ($hash, 1, "All RPC servers $rpcstate"); - DoTrigger ($name, "RPC server $rpcstate"); - if ($rpcstate eq 'running' && defined ($filter)) { - HMCCU_UpdateClients ($hash, '.*', 'Value', 0, $filter, 1); - } + $rpcstate = 'null'; + foreach my $rpcst (keys %stc) { + if ($stc{$rpcst} == $ifCount) { + $rpcstate = $rpcst; + last; } } + + if ($rpcstate ne 'null' && $rpcstate ne $hash->{RPCState}) { + $hash->{RPCState} = $rpcstate; + readingsSingleUpdate ($hash, "rpcstate", $rpcstate, 1); + HMCCU_Log ($hash, 4, "Set rpcstate to $rpcstate"); + HMCCU_Log ($hash, 1, $msg) if (defined($msg)); + HMCCU_Log ($hash, 1, "All RPC servers $rpcstate"); + DoTrigger ($name, "RPC server $rpcstate"); + HMCCU_UpdateClients ($hash, '.*', 'Value', 0, $filter, 1) + if ($rpcstate eq 'running' && defined($filter)); + } } # Set I/O device state @@ -2883,7 +2769,7 @@ sub HMCCU_SubstRule ($$$) my @sub_list = split /[, ]/,$substitutes; foreach my $s (@sub_list) { my ($regexp, $text) = split /:/,$s,2; - next if (!defined ($regexp) || !defined($text)); + next if (!defined($regexp) || !defined($text)); if ($regexp =~ /^#([+-]?\d*\.?\d+?)\-([+-]?\d*\.?\d+?)$/) { my ($mi, $ma) = ($1, $2); if ($value =~ /^\d*\.?\d+?$/ && $value >= $mi && $value <= $ma) { @@ -2893,12 +2779,12 @@ sub HMCCU_SubstRule ($$$) } elsif ($mode == 0 && $value =~ /$regexp/ && $value !~ /^[+-]?\d+$/) { my $x = eval { $value =~ s/$regexp/$text/ }; - $rc = 1 if (defined ($x)); + $rc = 1 if (defined($x)); last; } elsif (($mode == 1 || $value =~ /^[+-]?\d+$/) && $value =~ /^$regexp$/) { my $x = eval { $value =~ s/^$regexp$/$text/ }; - $rc = 1 if (defined ($x)); + $rc = 1 if (defined($x)); last; } } @@ -2923,17 +2809,16 @@ sub HMCCU_SubstRule ($$$) sub HMCCU_SubstVariables ($$$) { my ($clhash, $text, $dplist) = @_; - my $fnc = 'HMCCU_SubstVariables'; my @varlist = defined($dplist) ? split (',', $dplist) : keys %{$clhash->{hmccu}{dp}}; - HMCCU_Trace ($clhash, 2, $fnc, "text=$text"); + HMCCU_Trace ($clhash, 2, "text=$text"); # Substitute datapoint variables by value foreach my $dp (@varlist) { my ($chn, $dpt) = split (/\./, $dp); - HMCCU_Trace ($clhash, 2, $fnc, "var=$dp"); + HMCCU_Trace ($clhash, 2, "var=$dp"); if (defined ($clhash->{hmccu}{dp}{$dp}{VALUES}{OSVAL})) { $text =~ s/\$\$\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{VALUES}{OSVAL}/g; @@ -2954,7 +2839,7 @@ sub HMCCU_SubstVariables ($$$) } } - HMCCU_Trace ($clhash, 2, $fnc, "text=$text"); + HMCCU_Trace ($clhash, 2, "text=$text"); return $text; } @@ -2968,14 +2853,15 @@ sub HMCCU_SubstVariables ($$$) # only devices belonging to interface ifname are updated. ###################################################################### -sub HMCCU_UpdateClients ($$$$$$) +sub HMCCU_UpdateClients ($$$$;$$) { my ($hash, $devexp, $ccuget, $fromccu, $ifname, $nonBlock) = @_; my $fhname = $hash->{NAME}; + $nonBlock //= HMCCU_IsFlag ($fhname, 'nonBlocking'); my $c = 0; my $dc = 0; - my $filter = "ccudevstate=active"; - $filter .= ",ccuif=$ifname" if (defined ($ifname)); + my $filter = 'ccudevstate=active'; + $filter .= ",ccuif=$ifname" if (defined($ifname)); $ccuget = AttrVal ($fhname, 'ccuget', 'Value') if ($ccuget eq 'Attr'); my $list = ''; @@ -2987,11 +2873,8 @@ sub HMCCU_UpdateClients ($$$$$$) $dc += scalar(@devlist); foreach my $d (@devlist) { my $ch = $defs{$d}; - next if ( - !defined ($ch->{IODev}) || - !defined ($ch->{ccuaddr}) || - $ch->{ccuaddr} ne $hash->{hmccu}{adr}{$name}{address} || - $ch->{ccuif} eq 'fhem' || + next if (!defined($ch->{IODev}) || !defined($ch->{ccuaddr}) || + $ch->{ccuaddr} ne $hash->{hmccu}{adr}{$name}{address} || $ch->{ccuif} eq 'fhem' || !HMCCU_IsValidDeviceOrChannel ($hash, $ch->{ccuaddr}, $HMCCU_FL_ADDRESS)); $list .= ($list eq '') ? $name : ",$name"; $c++; @@ -2999,33 +2882,30 @@ sub HMCCU_UpdateClients ($$$$$$) } } else { - my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", $devexp, $filter); + my @devlist = HMCCU_FindClientDevices ($hash, '(HMCCUDEV|HMCCUCHN)', $devexp, $filter); $dc = scalar(@devlist); foreach my $d (@devlist) { my $ch = $defs{$d}; - next if ( - !defined ($ch->{IODev}) || - !defined ($ch->{ccuaddr}) || - $ch->{ccuif} eq 'fhem' || + next if (!defined($ch->{IODev}) || !defined($ch->{ccuaddr}) || $ch->{ccuif} eq 'fhem' || !HMCCU_IsValidDeviceOrChannel ($hash, $ch->{ccuaddr}, $HMCCU_FL_ADDRESS)); - my $name = HMCCU_GetDeviceName ($hash, $ch->{ccuaddr}, ''); + my $name = HMCCU_GetDeviceName ($hash, $ch->{ccuaddr}); next if ($name eq ''); $list .= ($list eq '') ? $name : ",$name"; $c++; } } - return HMCCU_Log ($hash, 2, "HMCCU: Found no devices to update") if ($c == 0); + return HMCCU_Log ($hash, 2, 'Found no devices to update') if ($c == 0); HMCCU_Log ($hash, 2, "Updating $c of $dc client devices matching devexp=$devexp filter=$filter"); - if (HMCCU_IsFlag ($fhname, 'nonBlocking') || $nonBlock) { + if ($nonBlock) { HMCCU_HMScriptExt ($hash, '!GetDatapointsByDevice', { list => $list, ccuget => $ccuget }, \&HMCCU_UpdateCB, { logCount => 1, devCount => $c }); return 1; } else { my $response = HMCCU_HMScriptExt ($hash, '!GetDatapointsByDevice', - { list => $list, ccuget => $ccuget }, undef, undef); + { list => $list, ccuget => $ccuget }); return -2 if ($response eq '' || $response =~ /^ERROR:.*/); HMCCU_UpdateCB ({ ioHash => $hash, logCount => 1, devCount => $c }, undef, $response); @@ -3047,25 +2927,29 @@ sub HMCCU_CreateDevice ($$$$$) my %object; - if (!defined ($sourceAddr)) { + if (!defined($sourceAddr)) { # Search for type in device table - return 1 if (!defined ($newType)); + return 1 if (!defined($newType)); foreach my $da (keys %{$hash->{hmccu}{dev}}) { if ($hash->{hmccu}{dev}{$da}{type} eq $newType) { $sourceAddr = $da; last; } } - return 2 if (!defined ($sourceAddr)); + return 2 if (!defined($sourceAddr)); } else { $newType = $hash->{hmccu}{dev}{$sourceAddr}{type}; } + my $chnCount = exists($hash->{hmccu}{dev}{$sourceAddr}{channels}) ? + $hash->{hmccu}{dev}{$sourceAddr}{channels} : 0; + HMCCU_Log ($hash, 2, "Found no channels for $sourceAddr") if ($chnCount == 0); + # Device attributes $object{$newAddr}{flag} = 'N'; $object{$newAddr}{addtype} = 'dev'; - $object{$newAddr}{channels} = $hash->{hmccu}{dev}{$sourceAddr}{channels}; + $object{$newAddr}{channels} = $chnCount; $object{$newAddr}{name} = $newName; $object{$newAddr}{type} = $newType; $object{$newAddr}{interface} = 'fhem'; @@ -3074,13 +2958,13 @@ sub HMCCU_CreateDevice ($$$$$) # Channel attributes for (my $chn=0; $chn<$object{$newAddr}{channels}; $chn++) { my $ca = "$newAddr:$chn"; - $object{$ca}{flag} = 'N'; - $object{$ca}{addtype} = 'chn'; - $object{$ca}{channels} = 1; - $object{$ca}{name} = "$newName:$chn"; + $object{$ca}{flag} = 'N'; + $object{$ca}{addtype} = 'chn'; + $object{$ca}{channels} = 1; + $object{$ca}{name} = "$newName:$chn"; $object{$ca}{direction} = $hash->{hmccu}{dev}{"$sourceAddr:$chn"}{direction}; - $object{$ca}{rxmode} = $hash->{hmccu}{dev}{"$sourceAddr:$chn"}{rxmode}; - $object{$ca}{usetype} = $hash->{hmccu}{dev}{"$sourceAddr:$chn"}{usetype}; + $object{$ca}{rxmode} = $hash->{hmccu}{dev}{"$sourceAddr:$chn"}{rxmode}; + $object{$ca}{usetype} = $hash->{hmccu}{dev}{"$sourceAddr:$chn"}{usetype}; } HMCCU_UpdateDeviceTable ($hash, \%object); @@ -3097,13 +2981,13 @@ sub HMCCU_DeleteDevice ($) my ($clthash) = @_; my $name = $clthash->{NAME}; - return if (!exists ($clthash->{IODev}) || !exists ($clthash->{ccuif}) || - !exists ($clthash->{ccuaddr})); + return if (!exists($clthash->{IODev}) || !exists($clthash->{ccuif}) || + !exists($clthash->{ccuaddr})); return if ($clthash->{ccuif} ne 'fhem'); my $hmccu_hash = $clthash->{IODev}; my $devaddr = $clthash->{ccuaddr}; - my $channels = exists ($clthash->{hmccu}{channels}) ? $clthash->{hmccu}{channels} : 0; + my $channels = exists($clthash->{hmccu}{channels}) ? $clthash->{hmccu}{channels} : 0; # Delete device address entries if (exists ($hmccu_hash->{hmccu}{dev}{$devaddr})) { @@ -3112,16 +2996,16 @@ sub HMCCU_DeleteDevice ($) # Delete channel address and name entries for (my $chn=0; $chn<=$channels; $chn++) { - if (exists ($hmccu_hash->{hmccu}{dev}{"$devaddr:$chn"})) { + if (exists($hmccu_hash->{hmccu}{dev}{"$devaddr:$chn"})) { delete $hmccu_hash->{hmccu}{dev}{"$devaddr:$chn"}; } - if (exists ($hmccu_hash->{hmccu}{adr}{"$name:$chn"})) { + if (exists($hmccu_hash->{hmccu}{adr}{"$name:$chn"})) { delete $hmccu_hash->{hmccu}{adr}{"$name:$chn"}; } } # Delete device name entries - if (exists ($hmccu_hash->{hmccu}{adr}{$name})) { + if (exists($hmccu_hash->{hmccu}{adr}{$name})) { delete $hmccu_hash->{hmccu}{adr}{$name}; } } @@ -3163,9 +3047,9 @@ sub HMCCU_UpdateDeviceTable ($$) my $nm = $hash->{hmccu}{dev}{$da}{name} if (defined ($hash->{hmccu}{dev}{$da}{name})); $nm = $devices->{$da}{name} if (defined ($devices->{$da}{name})); - if ($devices->{$da}{flag} eq 'N' && defined ($nm)) { + if ($devices->{$da}{flag} eq 'N' && defined($nm)) { my $at = ''; - if (defined ($devices->{$da}{addtype})) { + if (defined($devices->{$da}{addtype})) { $at = $devices->{$da}{addtype}; } else { @@ -3177,32 +3061,32 @@ sub HMCCU_UpdateDeviceTable ($$) next; } HMCCU_Log ($hash, 2, "Duplicate name for device/channel $nm address=$da in CCU.") - if (exists ($hash->{hmccu}{adr}{$nm}) && $at ne $hash->{hmccu}{adr}{$nm}{addtype}); + if (exists($hash->{hmccu}{adr}{$nm}) && $at ne $hash->{hmccu}{adr}{$nm}{addtype}); # Updated or new device/channel - $hash->{hmccu}{dev}{$da}{addtype} = $at; - $hash->{hmccu}{dev}{$da}{valid} = 1; + $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') { $hash->{hmccu}{dev}{$da}{$k} = $devices->{$da}{$k} - if (defined ($devices->{$da}{$k})); + if (defined($devices->{$da}{$k})); } - if (defined ($nm)) { - $hash->{hmccu}{dev}{$da}{name} = $nm; - $hash->{hmccu}{adr}{$nm}{address} = $da; - $hash->{hmccu}{adr}{$nm}{addtype} = $hash->{hmccu}{dev}{$da}{addtype}; - $hash->{hmccu}{adr}{$nm}{valid} = 1; + if (defined($nm)) { + $hash->{hmccu}{dev}{$da}{name} = $nm; + $hash->{hmccu}{adr}{$nm}{address} = $da; + $hash->{hmccu}{adr}{$nm}{addtype} = $hash->{hmccu}{dev}{$da}{addtype}; + $hash->{hmccu}{adr}{$nm}{valid} = 1; } } - elsif ($devices->{$da}{flag} eq 'D' && exists ($hash->{hmccu}{dev}{$da})) { + elsif ($devices->{$da}{flag} eq 'D' && exists($hash->{hmccu}{dev}{$da})) { # Device deleted, mark as invalid $hash->{hmccu}{dev}{$da}{valid} = 0; $hash->{hmccu}{adr}{$nm}{valid} = 0 if (defined ($nm)); } - elsif ($devices->{$da}{flag} eq 'R' && exists ($hash->{hmccu}{dev}{$da})) { + elsif ($devices->{$da}{flag} eq 'R' && exists($hash->{hmccu}{dev}{$da})) { # Device replaced, change address my $na = $devices->{hmccu}{newaddr}; # Copy device entries and delete old device entries @@ -3218,27 +3102,21 @@ sub HMCCU_UpdateDeviceTable ($$) if ($hash->{hmccu}{ccu}{delayed} == 1) { # Initialize interface and port lists HMCCU_AttrInterfacesPorts ($hash, 'rpcinterfaces', $attr{$name}{rpcinterfaces}) - if (exists ($attr{$name}{rpcinterfaces})); + if (exists($attr{$name}{rpcinterfaces})); HMCCU_AttrInterfacesPorts ($hash, 'rpcport', $attr{$name}{rpcport}) - if (exists ($attr{$name}{rpcport})); + if (exists($attr{$name}{rpcport})); # Initialize pending client devices my @cdev = HMCCU_FindClientDevices ($hash, '(HMCCUDEV|HMCCUCHN|HMCCURPCPROC)', undef, 'ccudevstate=pending'); - if (scalar (@cdev) > 0) { + if (scalar(@cdev) > 0) { HMCCU_Log ($hash, 2, "Initializing ".scalar(@cdev)." client devices in state 'pending'"); foreach my $cd (@cdev) { my $ch = $defs{$cd}; my $ct = $ch->{TYPE}; my $rc = 0; - if ($ct eq 'HMCCUDEV') { - $rc = HMCCUDEV_InitDevice ($hash, $ch); - } - elsif ($ct eq 'HMCCUCHN') { - $rc = HMCCUCHN_InitDevice ($hash, $ch); - } - elsif ($ct eq 'HMCCURPCPROC') { - $rc = HMCCURPCPROC_InitDevice ($hash, $ch); - } + if ($ct eq 'HMCCUDEV') { $rc = HMCCUDEV_InitDevice ($hash, $ch); } + elsif ($ct eq 'HMCCUCHN') { $rc = HMCCUCHN_InitDevice ($hash, $ch); } + elsif ($ct eq 'HMCCURPCPROC') { $rc = HMCCURPCPROC_InitDevice ($hash, $ch); } HMCCU_Log ($hash, 3, "Can't initialize client device ".$ch->{NAME}) if ($rc > 0); } } @@ -3247,37 +3125,30 @@ sub HMCCU_UpdateDeviceTable ($$) } # Update client devices - my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef); + my @devlist = HMCCU_FindClientDevices ($hash, '(HMCCUDEV|HMCCUCHN)'); foreach my $d (@devlist) { my $ch = $defs{$d}; my $ct = $ch->{TYPE}; - next if (!exists ($ch->{ccuaddr})); + next if (!exists($ch->{ccuaddr})); my $ca = $ch->{ccuaddr}; - next if (!exists ($devices->{$ca})); + next if (!exists($devices->{$ca})); if ($devices->{$ca}{flag} eq 'N') { # New device or new device information $ch->{ccudevstate} = 'active'; if ($ct eq 'HMCCUDEV') { - $ch->{ccutype} = $hash->{hmccu}{dev}{$ca}{type} - if (defined ($hash->{hmccu}{dev}{$ca}{type})); - $ch->{firmware} = $devices->{$ca}{firmware} - if (defined ($devices->{$ca}{firmware})); + $ch->{ccutype} = $hash->{hmccu}{dev}{$ca}{type} if (defined($hash->{hmccu}{dev}{$ca}{type})); + $ch->{firmware} = $devices->{$ca}{firmware} if (defined($devices->{$ca}{firmware})); } else { - $ch->{chntype} = $devices->{$ca}{usetype} - if (defined ($devices->{$ca}{usetype})); my ($add, $chn) = HMCCU_SplitChnAddr ($ca); - $ch->{ccutype} = $devices->{$add}{type} - if (defined ($devices->{$add}{type})); - $ch->{firmware} = $devices->{$add}{firmware} - if (defined ($devices->{$add}{firmware})); + $ch->{chntype} = $devices->{$ca}{usetype} if (defined($devices->{$ca}{usetype})); + $ch->{ccutype} = $devices->{$add}{type} if (defined($devices->{$add}{type})); + $ch->{firmware} = $devices->{$add}{firmware} if (defined($devices->{$add}{firmware})); } - $ch->{ccuname} = $hash->{hmccu}{dev}{$ca}{name} - if (defined ($hash->{hmccu}{dev}{$ca}{name})); - $ch->{ccuif} = $hash->{hmccu}{dev}{$ca}{interface} - if (defined ($devices->{$ca}{interface})); + $ch->{ccuname} = $hash->{hmccu}{dev}{$ca}{name} if (defined($hash->{hmccu}{dev}{$ca}{name})); + $ch->{ccuif} = $hash->{hmccu}{dev}{$ca}{interface} if (defined($devices->{$ca}{interface})); $ch->{hmccu}{channels} = $hash->{hmccu}{dev}{$ca}{channels} - if (defined ($hash->{hmccu}{dev}{$ca}{channels})); + if (defined($hash->{hmccu}{dev}{$ca}{channels})); } elsif ($devices->{$ca}{flag} eq 'D') { # Deleted device @@ -3441,25 +3312,23 @@ sub HMCCU_UpdateDeviceRoles ($$;$$) my $clType = $clHash->{TYPE}; - $iface = $clHash->{ccuif} if (!defined($iface)); - $address = $clHash->{ccuaddr} if (!defined($address)); + $iface //= $clHash->{ccuif}; + $address //= $clHash->{ccuaddr}; return if (!defined($address)); - my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $address, $iface); - if (defined($devDesc)) { - if ($clType eq 'HMCCUCHN' && defined($devDesc->{TYPE})) { - $clHash->{hmccu}{role} = $devDesc->{TYPE}; - } - elsif ($clType eq 'HMCCUDEV' && defined($devDesc->{CHILDREN})) { - my @roles = (); - foreach my $c (split(',', $devDesc->{CHILDREN})) { - my $childDevDesc = HMCCU_GetDeviceDesc ($ioHash, $c, $iface); - if (defined($childDevDesc) && defined($childDevDesc->{TYPE})) { - push @roles, $childDevDesc->{INDEX}.':'.$childDevDesc->{TYPE}; - } + my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $address, $iface) // return; + if ($clType eq 'HMCCUCHN' && defined($devDesc->{TYPE})) { + $clHash->{hmccu}{role} = $devDesc->{INDEX}.':'.$devDesc->{TYPE}; + } + elsif ($clType eq 'HMCCUDEV' && defined($devDesc->{CHILDREN})) { + my @roles = (); + foreach my $c (split(',', $devDesc->{CHILDREN})) { + my $childDevDesc = HMCCU_GetDeviceDesc ($ioHash, $c, $iface); + if (defined($childDevDesc) && defined($childDevDesc->{TYPE})) { + push @roles, $childDevDesc->{INDEX}.':'.$childDevDesc->{TYPE}; } - $clHash->{hmccu}{role} = join(',', @roles) if (scalar(@roles) > 0); } + $clHash->{hmccu}{role} = join(',', @roles) if (scalar(@roles) > 0); } } @@ -3497,27 +3366,31 @@ sub HMCCU_RenameDevice ($$$) ###################################################################### # Return role of a channel as stored in device hash +# Parameter chnNo is ignored for HMCCUCHN devices. If chnNo is not +# specified for a HMCCUDEV device, the control channel is used. +# Returns role name or empty string if role cannot be detected. ###################################################################### sub HMCCU_GetChannelRole ($;$) { my ($clHash, $chnNo) = @_; - return '' if (!defined($clHash->{hmccu}{role})); + return '' if (!defined($clHash->{hmccu}{role}) || $clHash->{hmccu}{role} eq ''); - if ($clHash->{TYPE} eq 'HMCCUCHN') { - return $clHash->{hmccu}{role}; - } - elsif ($clHash->{TYPE} eq 'HMCCUDEV') { - if (!defined($chnNo)) { - my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($clHash); + if (!defined($chnNo)) { + if ($clHash->{TYPE} eq 'HMCCUCHN') { + my ($ad, $cc) = HMCCU_SplitChnAddr ($clHash->{ccuaddr}); $chnNo = $cc; } - if ($chnNo ne '') { - foreach my $role (split(',', $clHash->{hmccu}{role})) { - my ($c, $r) = split(':', $role); - return $r if (defined($r) && "$c" eq "$chnNo"); - } + else { + my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($clHash); + $chnNo = $cc; + } + } + if (defined($chnNo) && $chnNo ne '') { + foreach my $role (split(',', $clHash->{hmccu}{role})) { + my ($c, $r) = split(':', $role); + return $r if (defined($r) && "$c" eq "$chnNo"); } } @@ -3536,30 +3409,38 @@ sub HMCCU_GetDeviceConfig ($) my ($cDev, $cPar, $cLnk) = (0, 0, 0); foreach my $iface (keys %{$ioHash->{hmccu}{interfaces}}) { - next if ($ioHash->{hmccu}{interfaces}{$iface}{type} eq 'B'); +# next if ($ioHash->{hmccu}{interfaces}{$iface}{type} eq 'B'); if (exists($ioHash->{hmccu}{interfaces}{$iface}{device})) { my $rpcHash = $defs{$ioHash->{hmccu}{interfaces}{$iface}{device}}; + HMCCU_Log ($ioHash, 2, "Reading Device Descriptions for interface $iface"); $cDev += HMCCURPCPROC_GetDeviceDesc ($rpcHash); + HMCCU_Log ($ioHash, 2, "Reading Paramset Descriptions for interface $iface"); $cPar += HMCCURPCPROC_GetParamsetDesc ($rpcHash); + HMCCU_Log ($ioHash, 2, "Reading Peer Descriptions for interface $iface"); $cLnk += HMCCURPCPROC_GetPeers ($rpcHash); } + else { + HMCCU_Log ($ioHash, 2, "No RPC device found for interface $iface. Can't read device config."); + } } # Get defined FHEM devices - my @devList = HMCCU_FindClientDevices ($ioHash, "(HMCCUDEV|HMCCUCHN)", undef, undef); + my @devList = HMCCU_FindClientDevices ($ioHash, "(HMCCUDEV|HMCCUCHN)"); foreach my $d (@devList) { - my $ch = $defs{$d}; - if (exists($ch->{ccuaddr}) && exists($ch->{ccuif})) { - my $i = $ch->{ccuif}; - my $a = $ch->{ccuaddr}; - HMCCU_AddDevice ($ioHash, $i, $a, $d); + my $clHash = $defs{$d}; + if (exists($clHash->{ccuaddr}) && exists($clHash->{ccuif})) { + HMCCU_AddDevice ($ioHash, $clHash->{ccuif}, $clHash->{ccuaddr}, $d); } } # Update FHEM devices foreach my $d (@devList) { - HMCCU_UpdateDevice ($ioHash, $defs{$d}); - HMCCU_UpdateDeviceRoles ($ioHash, $defs{$d}); + my $clHash = $defs{$d}; + my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($clHash); + + HMCCU_UpdateDevice ($ioHash, $clHash); + HMCCU_UpdateDeviceRoles ($ioHash, $clHash); + HMCCU_UpdateRoleCommands ($ioHash, $clHash, $cc); } return ($cDev, $cPar, $cLnk); @@ -3581,7 +3462,7 @@ sub HMCCU_AddDeviceDesc ($$$$) { my ($hash, $desc, $key, $iface) = @_; - return 0 if (!exists ($desc->{$key})); + return 0 if (!exists($desc->{$key})); my $k = $desc->{$key}; @@ -3601,7 +3482,7 @@ sub HMCCU_AddDeviceDesc ($$$$) $hash->{hmccu}{device}{$iface}{$k}{_addtype} = 'chn'; $hash->{hmccu}{device}{$iface}{$k}{_fw_ver} = $hash->{hmccu}{device}{$iface}{$desc->{PARENT}}{_fw_ver}; $hash->{hmccu}{device}{$iface}{$k}{_model} = $hash->{hmccu}{device}{$iface}{$desc->{PARENT}}{_model}; - $hash->{hmccu}{device}{$iface}{$k}{_name} = HMCCU_GetChannelName ($hash, $k, ''); + $hash->{hmccu}{device}{$iface}{$k}{_name} = HMCCU_GetChannelName ($hash, $k); } else { $hash->{hmccu}{device}{$iface}{$k}{_addtype} = 'dev'; @@ -3609,7 +3490,7 @@ sub HMCCU_AddDeviceDesc ($$$$) $fw_ver =~ s/[-\.]/_/g; $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}{_name} = HMCCU_GetDeviceName ($hash, $k); } return 1; @@ -4043,8 +3924,7 @@ sub HMCCU_IsValidParameter ($$$$) { my ($clHash, $object, $ps, $parameter) = @_; - my $ioHash = HMCCU_GetHash ($clHash); - return 0 if (!defined ($ioHash)); + my $ioHash = HMCCU_GetHash ($clHash) // return 0; my $devDesc = ref($object) eq 'HASH' ? $object : HMCCU_GetDeviceDesc ($ioHash, $object); @@ -4092,6 +3972,11 @@ sub HMCCU_GetParamValue ($$$$$) 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; + } return $ct{$type}{$value} if (exists($ct{$type}) && exists($ct{$type}{$value})); @@ -4171,31 +4056,31 @@ sub HMCCU_FlagsToStr ($$$;$$) { my ($set, $flag, $value, $sep, $default) = @_; - $value = 0 if (!defined($value)); - $default = '' if (!defined($default)); - $sep = '' if (!defined($sep)); + $value //= 0; + $default //= ''; + $sep //= ''; my %bitmasks = ( 'device' => { - 'DIRECTION' => { 1 => "SENDER", 2 => "RECEIVER" }, - 'FLAGS' => { 1 => "Visible", 2 => "Internal", 8 => "DontDelete" }, - 'RX_MODE' => { 1 => "ALWAYS", 2 => "BURST", 4 => "CONFIG", 8 => "WAKEUP", 16 => "LAZY_CONFIG" } + 'DIRECTION' => { 1 => 'SENDER', 2 => 'RECEIVER' }, + 'FLAGS' => { 1 => 'Visible', 2 => 'Internal', 8 => 'DontDelete' }, + 'RX_MODE' => { 1 => 'ALWAYS', 2 => 'BURST', 4 => 'CONFIG', 8 => 'WAKEUP', 16 => 'LAZY_CONFIG' } }, 'model' => { - 'FLAGS' => { 1 => "Visible", 2 => "Internal", 4 => "Transform", 8 => "Service", 16 => "Sticky" }, + 'FLAGS' => { 1 => 'Visible', 2 => 'Internal', 4 => 'Transform', 8 => 'Service', 16 => 'Sticky' }, 'OPERATIONS' => { 1 => 'R', 2 => 'W', 4 => 'E' } }, 'peer' => { - 'FLAGS' => { 1 => "SENDER_BROKEN", 2 => "RECEIVER_BROKEN" } + 'FLAGS' => { 1 => 'SENDER_BROKEN', 2 => 'RECEIVER_BROKEN' } } ); my %mappings = ( 'device' => { - 'DIRECTION' => { 0 => "NONE" } + 'DIRECTION' => { 0 => 'NONE' } }, 'peer' => { - 'FLAGS' => { 0 => "OK" } + 'FLAGS' => { 0 => 'OK' } } ); @@ -4226,9 +4111,7 @@ sub HMCCU_UpdateSingleDatapoint ($$$$) { my ($clHash, $chn, $dpt, $value) = @_; - my $ioHash = HMCCU_GetHash ($clHash); - return $value if (!defined($ioHash)); - + my $ioHash = HMCCU_GetHash ($clHash) // $value; my %objects; my $ccuaddr = $clHash->{ccuaddr}; @@ -4285,7 +4168,7 @@ sub HMCCU_UpdateParamsetReadings ($$$;$) my $clRF = HMCCU_GetAttrReadingFormat ($clHash, $ioHash); my $peer = AttrVal ($clName, 'peer', 'null'); my $clInt = $clHash->{ccuif}; - my ($sc, $sd, $cc, $cd, $ss, $cs) = HMCCU_GetSpecialDatapoints ($clHash); + my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($clHash); readingsBeginUpdate ($clHash); @@ -4376,8 +4259,8 @@ sub HMCCU_UpdateParamsetReadings ($$$;$) # Calculate and update HomeMatic state if ($ccuflags !~ /nohmstate/) { - my ($hms_read, $hms_chn, $hms_dpt, $hms_val) = HMCCU_GetHMState ($clName, $ioName, undef); - HMCCU_BulkUpdate ($clHash, $hms_read, $hms_val, $hms_val) if (defined ($hms_val)); + my ($hms_read, $hms_chn, $hms_dpt, $hms_val) = HMCCU_GetHMState ($clName, $ioName); + HMCCU_BulkUpdate ($clHash, $hms_read, $hms_val, $hms_val) if (defined($hms_val)); } readingsEndUpdate ($clHash, 1); @@ -4393,8 +4276,9 @@ sub HMCCU_RefreshReadings ($) { my ($clHash) = @_; - my $ioHash = HMCCU_GetHash ($clHash); - return if (!defined($ioHash)); + return if ($clHash->{hmccu}{defaults} == 1); + + my $ioHash = HMCCU_GetHash ($clHash) // return; HMCCU_DeleteReadings ($clHash, '.*'); @@ -4420,7 +4304,7 @@ sub HMCCU_RefreshReadings ($) # Parameter type is VAL or SVAL. # Structure: # {hmccu}{dp}{channel.datapoint}{paramset}{VAL|OVAL|SVAL|OSVAL} -# {hmccu}{tt}{} +# {hmccu}{tt}{program}{section}{daynum}{entrynum} ###################################################################### sub HMCCU_UpdateInternalValues ($$$$$) @@ -4435,7 +4319,7 @@ sub HMCCU_UpdateInternalValues ($$$$$) if ($type eq 'SVAL' && $chkey =~ /^[0-9d]+\.P([0-9])_([A-Z]+)_([A-Z]+)_([0-9]+)$/) { my ($prog, $valName, $day, $time) = ($1, $2, $3, $4); if (exists($weekDay{$day})) { - $ch->{hmccu}{tt}{$prog}{$valName}{$day}{$time} = $value; + $ch->{hmccu}{tt}{$prog}{$valName}{$weekDay{$day}}{$time} = $value; } } @@ -4474,7 +4358,7 @@ sub HMCCU_UpdateMultipleDevices ($$) 'ccudevstate=active'); foreach my $d (@devlist) { my $clHash = $defs{$d}; - if (!defined ($clHash)) { + if (!defined($clHash)) { HMCCU_Log ($name, 2, "Can't find hash for device $d"); next; } @@ -4540,27 +4424,25 @@ sub HMCCU_GetAffectedAddresses ($) sub HMCCU_UpdatePeers ($$$$) { my ($clt_hash, $chndpt, $val, $peerattr) = @_; - my $fnc = "UpdatePeers"; my $io_hash = HMCCU_GetHash ($clt_hash); - HMCCU_Trace ($clt_hash, 2, $fnc, "chndpt=$chndpt val=$val peer=$peerattr"); + HMCCU_Trace ($clt_hash, 2, "chndpt=$chndpt val=$val peer=$peerattr"); - my @rules = split (/[;\n]+/, $peerattr); - foreach my $r (@rules) { - HMCCU_Trace ($clt_hash, 2, $fnc, "rule=$r"); + foreach my $r (split (/[;\n]+/, $peerattr)) { + HMCCU_Trace ($clt_hash, 2, "rule=$r"); my ($vars, $cond, $type, $act) = split (/:/, $r, 4); next if (!defined ($act)); - HMCCU_Trace ($clt_hash, 2, $fnc, "vars=$vars, cond=$cond, type=$type, act=$act"); + HMCCU_Trace ($clt_hash, 2, "vars=$vars, cond=$cond, type=$type, act=$act"); next if ($cond !~ /$chndpt/); # Check if rule is affected by datapoint update my $ex = 0; foreach my $dpt (split (",", $vars)) { - HMCCU_Trace ($clt_hash, 2, $fnc, "dpt=$dpt"); + HMCCU_Trace ($clt_hash, 2, "dpt=$dpt"); $ex = 1 if ($ex == 0 && $dpt eq $chndpt); if (!exists ($clt_hash->{hmccu}{dp}{$dpt})) { - HMCCU_Trace ($clt_hash, 2, $fnc, "Datapoint $dpt does not exist on hash"); + HMCCU_Trace ($clt_hash, 2, "Datapoint $dpt does not exist on hash"); } last if ($ex == 1); } @@ -4569,17 +4451,17 @@ sub HMCCU_UpdatePeers ($$$$) # Substitute variables and evaluate condition $cond = HMCCU_SubstVariables ($clt_hash, $cond, $vars); my $e = eval "$cond"; - HMCCU_Trace ($clt_hash, 2, $fnc, "eval $cond = $e") if (defined ($e)); - HMCCU_Trace ($clt_hash, 2, $fnc, "Error in eval $cond") if (!defined ($e)); - HMCCU_Trace ($clt_hash, 2, $fnc, "NoMatch in eval $cond") if (defined ($e) && $e eq ''); - next if (!defined ($e) || $e eq ''); + HMCCU_Trace ($clt_hash, 2, "eval $cond = $e") if (defined($e)); + HMCCU_Trace ($clt_hash, 2, "Error in eval $cond") if (!defined($e)); + HMCCU_Trace ($clt_hash, 2, "NoMatch in eval $cond") if (defined($e) && $e eq ''); + next if (!defined($e) || $e eq ''); # Substitute variables and execute action if ($type eq 'ccu' || $type eq 'hmccu') { my ($aobj, $aexp) = split (/=/, $act); $aexp =~ s/\$value/$val/g; $aexp = HMCCU_SubstVariables ($clt_hash, $aexp, $vars); - HMCCU_Trace ($clt_hash, 2, $fnc, "set $aobj to $aexp"); + HMCCU_Trace ($clt_hash, 2, "set $aobj to $aexp"); my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($io_hash, "$type:$aobj", $HMCCU_FLAG_INTERFACE); next if ($flags != $HMCCU_FLAGS_IACD && $flags != $HMCCU_FLAGS_NCD); @@ -4588,7 +4470,7 @@ sub HMCCU_UpdatePeers ($$$$) elsif ($type eq 'fhem') { $act =~ s/\$value/$val/g; $act = HMCCU_SubstVariables ($clt_hash, $act, $vars); - HMCCU_Trace ($clt_hash, 2, $fnc, "Execute command $act"); + HMCCU_Trace ($clt_hash, 2, "Execute command $act"); AnalyzeCommandChain (undef, $act); } } @@ -4765,25 +4647,6 @@ sub HMCCU_IsRPCType ($$$) return $rpctype eq $type ? 1 : 0; } -###################################################################### -# Initialize statistic counters -###################################################################### - -sub HMCCU_ResetCounters ($) -{ - my ($hash) = @_; - my @counters = ('total', 'EV', 'ND', 'IN', 'DD', 'RA', 'RD', 'UD', 'EX', 'SL', 'ST'); - - foreach my $cnt (@counters) { - $hash->{hmccu}{ev}{$cnt} = 0; - } - delete $hash->{hmccu}{evs}; - delete $hash->{hmccu}{evr}; - - $hash->{hmccu}{evtimeout} = 0; - $hash->{hmccu}{evtime} = 0; -} - ###################################################################### # Start external RPC server via RPC device. # Return number of RPC servers or 0 on error. @@ -4806,8 +4669,7 @@ sub HMCCU_StartExtRPCServer ($) # Disable existing devices of type HMCCURPC foreach my $d (keys %defs) { my $ch = $defs{$d}; - next if (!exists ($ch->{TYPE}) || !exists ($ch->{NAME})); - next if ($ch->{TYPE} ne 'HMCCURPC'); + next if (!exists ($ch->{TYPE}) || !exists ($ch->{NAME}) || $ch->{TYPE} ne 'HMCCURPC'); CommandAttr (undef, $ch->{NAME}." disable 1") if (IsDisabled ($ch->{NAME}) != 1); } } @@ -4826,11 +4688,11 @@ sub HMCCU_StartExtRPCServer ($) # Save FHEM config if new RPC devices were defined or attribute has changed if ($s > 0 || $attrset) { - HMCCU_Log ($hash, 1, "Saving FHEM config"); + HMCCU_Log ($hash, 1, 'Saving FHEM config'); CommandSave (undef, undef); } - if ($d == scalar (@iflist)) { + if ($d == scalar(@iflist)) { foreach my $ifname2 (@iflist) { my $dh = $defs{$hash->{hmccu}{interfaces}{$ifname2}{device}}; $hash->{hmccu}{interfaces}{$ifname2}{manager} = 'HMCCU'; @@ -4846,7 +4708,7 @@ sub HMCCU_StartExtRPCServer ($) return $c; } else { - HMCCU_Log ($hash, 0, "Definition of some RPC devices failed"); + HMCCU_Log ($hash, 0, 'Definition of some RPC devices failed'); } return 0; @@ -4861,7 +4723,9 @@ sub HMCCU_StopExtRPCServer ($;$) my ($hash, $wait) = @_; my $name = $hash->{NAME}; - return HMCCU_Log ($hash, 0, "Module HMCCURPCPROC not loaded") if (!exists ($modules{'HMCCURPCPROC'})); + return HMCCU_Log ($hash, 0, 'Module HMCCURPCPROC not loaded', 0) + if (!exists($modules{'HMCCURPCPROC'})); + HMCCU_SetRPCState ($hash, 'stopping'); my $rc = 1; @@ -4890,9 +4754,9 @@ sub HMCCU_IsRPCStateBlocking ($) { my ($hash) = @_; - return ($hash->{RPCState} eq "starting" || - $hash->{RPCState} eq "restarting" || - $hash->{RPCState} eq "stopping" + return ($hash->{RPCState} eq 'starting' || + $hash->{RPCState} eq 'restarting' || + $hash->{RPCState} eq 'stopping' ) ? 1 : 0; } @@ -4902,22 +4766,22 @@ sub HMCCU_IsRPCStateBlocking ($) # defined also return process or thread IDs. ###################################################################### -sub HMCCU_IsRPCServerRunning ($$$) +sub HMCCU_IsRPCServerRunning ($;$) { - my ($hash, $pids, $tids) = @_; + my ($hash, $pids) = @_; my $name = $hash->{NAME}; my $c = 0; my $ccuflags = HMCCU_GetFlags ($name); - @$pids = () if (defined ($pids)); + @$pids = () if (defined($pids)); my @iflist = HMCCU_GetRPCInterfaceList ($hash); foreach my $ifname (@iflist) { my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $ifname); next if ($rpcdev eq ''); my $rc = HMCCURPCPROC_CheckProcessState ($defs{$rpcdev}, 'running'); if ($rc < 0 || $rc > 1) { - push (@$pids, $rc); + push (@$pids, $rc) if (defined($pids)); $c++; } } @@ -4937,8 +4801,7 @@ sub HMCCU_GetDeviceInfo ($$;$) my $devname = ''; my $response = ''; - my $ioHash = HMCCU_GetHash ($hash); - return '' if (!defined($ioHash)); + my $ioHash = HMCCU_GetHash ($hash) // return ''; my @devlist; if ($hash->{ccuif} eq 'fhem' && exists ($hash->{ccugroup})) { @@ -4954,7 +4817,7 @@ sub HMCCU_GetDeviceInfo ($$;$) foreach my $dev (@devlist) { my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($ioHash, $dev, 0); if ($flags == $HMCCU_FLAG_ADDRESS) { - $devname = HMCCU_GetDeviceName ($ioHash, $add, ''); + $devname = HMCCU_GetDeviceName ($ioHash, $add); return '' if ($devname eq ''); } else { @@ -4962,8 +4825,8 @@ sub HMCCU_GetDeviceInfo ($$;$) } $response .= HMCCU_HMScriptExt ($ioHash, "!GetDeviceInfo", - { devname => $devname, ccuget => $ccuget }, undef, undef); - HMCCU_Trace ($hash, 2, undef, + { devname => $devname, ccuget => $ccuget }); + HMCCU_Trace ($hash, 2, "Device=$devname Devname=$devname
". "Script response = \n".$response."
". "Script = GetDeviceInfo"); @@ -4982,7 +4845,7 @@ sub HMCCU_FormatDeviceInfo ($) { my ($devinfo) = @_; - my %vtypes = (0, "n", 2, "b", 4, "f", 6, "a", 8, "n", 11, "s", 16, "i", 20, "s", 23, "p", 29, "e"); + my %vtypes = (0, 'n', 2, 'b', 4, 'f', 6, 'a', 8, 'n', 11, 's', 16, 'i', 20, 's', 23, 'p', 29, 'e'); my $result = ''; my $c_oaddr = ''; @@ -4992,7 +4855,7 @@ sub HMCCU_FormatDeviceInfo ($) $result .= "CHN $c_addr $c_name\n"; $c_oaddr = $c_addr; } - my $t = exists ($vtypes{$d_type}) ? $vtypes{$d_type} : $d_type; + my $t = exists($vtypes{$d_type}) ? $vtypes{$d_type} : $d_type; $result .= " DPT {$t} $d_name = $d_value [$d_flags]\n"; } @@ -5012,9 +4875,9 @@ sub HMCCU_GetFirmwareVersions ($$) { my ($hash, $type) = @_; my $name = $hash->{NAME}; - my $ccureqtimeout = AttrVal ($name, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST); + my $ccureqtimeout = AttrVal ($name, 'ccuReqTimeout', $HMCCU_TIMEOUT_REQUEST); - my $url = "http://www.eq-3.de/service/downloads.html"; + my $url = 'http://www.eq-3.de/service/downloads.html'; my $response = GetFileFromURL ($url, $ccureqtimeout, "suchtext=&suche_in=&downloadart=11"); my @download = $response =~ m/ $name }, undef, undef); + my $response = HMCCU_HMScriptExt ($hash, '!GetDevice', { name => $name }); return (-1, -1) if ($response eq '' || $response =~ /^ERROR:.*/); my @scrlines = split /[\n\r]+/,$response; @@ -5125,7 +4988,7 @@ sub HMCCU_GetDevice ($$) } } - if (scalar (keys %objects) > 0) { + if (scalar(keys %objects) > 0) { # Update HMCCU device tables ($devcount, $chncount) = HMCCU_UpdateDeviceTable ($hash, \%objects); @@ -5157,9 +5020,9 @@ sub HMCCU_GetDeviceList ($) my %objects = (); # Read devices, channels, interfaces and groups from CCU - my $response = HMCCU_HMScriptExt ($hash, "!GetDeviceList", undef, undef, undef); + my $response = HMCCU_HMScriptExt ($hash, "!GetDeviceList"); return (-1, -1, -1, -1, -1) if ($response eq '' || $response =~ /^ERROR:.*/); - my $groups = HMCCU_HMScriptExt ($hash, "!GetGroupDevices", undef, undef, undef); + my $groups = HMCCU_HMScriptExt ($hash, "!GetGroupDevices"); # CCU is reachable $hash->{ccustate} = 'active'; @@ -5329,7 +5192,7 @@ sub HMCCU_GetDeviceList ($) # Read available datapoints for each device type # This will lead to problems if some devices have different firmware versions # or links to system variables ! - HMCCU_GetDatapointList ($hash, undef, undef); + HMCCU_GetDatapointList ($hash); } # Store group configurations @@ -5358,13 +5221,6 @@ sub HMCCU_GetDeviceList ($) HMCCU_UpdateReadings ($hash, { "count_devices" => $devcount, "count_channels" => $chncount, "count_interfaces" => $ifcount, "count_programs" => $prgcount, "count_groups" => $gcount }); -# readingsBeginUpdate ($hash); -# readingsBulkUpdate ($hash, "count_devices", $devcount); -# readingsBulkUpdate ($hash, "count_channels", $chncount); -# readingsBulkUpdate ($hash, "count_interfaces", $ifcount); -# readingsBulkUpdate ($hash, "count_programs", $prgcount); -# readingsBulkUpdate ($hash, "count_groups", $gcount); -# readingsEndUpdate ($hash, 1); return ($devcount, $chncount, $ifcount, $prgcount, $gcount); } @@ -5375,7 +5231,7 @@ sub HMCCU_GetDeviceList ($) # Return number of datapoints read. ###################################################################### -sub HMCCU_GetDatapointList ($$$) +sub HMCCU_GetDatapointList ($;$$) { my ($hash, $devname, $devtype) = @_; my $name = $hash->{NAME}; @@ -5412,7 +5268,7 @@ sub HMCCU_GetDatapointList ($$$) if (scalar(@devunique) == 0); my $devlist = join (',', @devunique); - my $response = HMCCU_HMScriptExt ($hash, '!GetDatapointList', { list => $devlist }, undef, undef); + my $response = HMCCU_HMScriptExt ($hash, '!GetDatapointList', { list => $devlist }); return HMCCU_Log ($hash, 2, "Cannot get datapoint list", 0) if ($response eq '' || $response =~ /^ERROR:.*/); @@ -5475,23 +5331,15 @@ sub HMCCU_IsValidDevice ($$$) elsif (HMCCU_IsDevAddr ($param, 0)) { $a = $param; } -# else { -# HMCCU_Log ($hash, 3, "$param is not a valid address", 0); -# } if (exists ($hash->{hmccu}{dev}{$a})) { return $hash->{hmccu}{dev}{$a}{valid}; } -# else { -# HMCCU_Log ($hash, 3, "Address $param not found", 0); -# } # Special address for Non-Homematic devices if (($mode & $HMCCU_FL_EXADDRESS) && exists ($hash->{hmccu}{dev}{$param})) { return $hash->{hmccu}{dev}{$param}{valid} && $hash->{hmccu}{dev}{$param}{addtype} eq 'dev' ? 1 : 0; } - -# HMCCU_Log ($hash, 3, "Invalid address $param", 0); } # Name @@ -5499,9 +5347,6 @@ sub HMCCU_IsValidDevice ($$$) if (exists ($hash->{hmccu}{adr}{$param})) { return $hash->{hmccu}{adr}{$param}{valid} && $hash->{hmccu}{adr}{$param}{addtype} eq 'dev' ? 1 : 0; } -# else { -# HMCCU_Log ($hash, 3, "Device $param not found", 0); -# } } return 0; @@ -5580,9 +5425,9 @@ sub HMCCU_GetCCUDeviceParam ($$) } } - return (undef, undef, undef, undef) if (!defined ($add)); + return (undef, undef, undef, undef) if (!defined($add)); ($devadd, $chn) = split (':', $add); - return (undef, undef, undef, undef) if (!defined ($devadd) || + return (undef, undef, undef, undef) if (!defined($devadd) || !exists ($hash->{hmccu}{dev}{$devadd}) || $hash->{hmccu}{dev}{$devadd}{valid} == 0); return ($hash->{hmccu}{dev}{$devadd}{interface}, $add, $hash->{hmccu}{dev}{$add}{name}, @@ -5606,7 +5451,7 @@ sub HMCCU_GetValidDatapoints ($$$$$) my $ioHash = HMCCU_GetHash ($hash); return 0 if (HMCCU_IsFlag ($ioHash->{NAME}, 'dptnocheck') || !exists($ioHash->{hmccu}{dp})); - return HMCCU_Log ($hash, 2, 'Parameter chn undefined') if (!defined($chn)); + return HMCCU_Log ($hash, 2, 'Parameter chn undefined', 0) if (!defined($chn)); if ($chn >= 0) { if (exists($ioHash->{hmccu}{dp}{$devtype}{ch}{$chn})) { @@ -5703,7 +5548,6 @@ sub HMCCU_FindDatapoint ($$$$$) sub HMCCU_IsValidDatapoint ($$$$$) { my ($hash, $devtype, $chn, $dpt, $oper) = @_; - my $fnc = 'IsValidDatapoint'; my $ioHash = HMCCU_GetHash ($hash); return 0 if (!defined($ioHash)); @@ -5715,7 +5559,7 @@ sub HMCCU_IsValidDatapoint ($$$$$) return 1 if (HMCCU_IsFlag ($ioHash->{NAME}, "dptnocheck") || !exists($ioHash->{hmccu}{dp})); my $chnno; - if (defined ($chn) && $chn ne '') { + if (defined($chn) && $chn ne '') { if ($chn =~ /^[0-9]{1,2}$/) { $chnno = $chn; } @@ -5724,8 +5568,8 @@ sub HMCCU_IsValidDatapoint ($$$$$) $chnno = $c; } else { - HMCCU_Trace ($hash, 2, $fnc, "$chn is not a valid channel address or number"); - HMCCU_Trace ($hash, 2, $fnc, stacktraceAsString(undef)); + HMCCU_Trace ($hash, 2, "$chn is not a valid channel address or number"); + HMCCU_Trace ($hash, 2, stacktraceAsString(undef)); return 0; } } @@ -5734,13 +5578,13 @@ sub HMCCU_IsValidDatapoint ($$$$$) $dpt = $2; } else { - HMCCU_Trace ($hash, 2, $fnc, "channel number missing in datapoint $dpt"); + HMCCU_Trace ($hash, 2, "channel number missing in datapoint $dpt"); return 0; } my $v = (exists($ioHash->{hmccu}{dp}{$devtype}{ch}{$chnno}{$dpt}) && ($ioHash->{hmccu}{dp}{$devtype}{ch}{$chnno}{$dpt}{oper} & $oper)) ? 1 : 0; - HMCCU_Trace ($hash, 2, $fnc, "devtype=$devtype, chnno=$chnno, dpt=$dpt, valid=$v"); + HMCCU_Trace ($hash, 2, "devtype=$devtype, chnno=$chnno, dpt=$dpt, valid=$v"); return $v; } @@ -5774,10 +5618,11 @@ sub HMCCU_GetMatchingDevices ($$$$) # Channel number will be removed if specified. ###################################################################### -sub HMCCU_GetDeviceName ($$$) +sub HMCCU_GetDeviceName ($$;$) { my ($hash, $addr, $default) = @_; - + $default //= ''; + if (HMCCU_IsValidDeviceOrChannel ($hash, $addr, $HMCCU_FL_ADDRESS)) { $addr =~ s/:[0-9]+$//; return $hash->{hmccu}{dev}{$addr}{name}; @@ -5790,10 +5635,11 @@ sub HMCCU_GetDeviceName ($$$) # Get name of a CCU device channel by address. ###################################################################### -sub HMCCU_GetChannelName ($$$) +sub HMCCU_GetChannelName ($$;$) { my ($hash, $addr, $default) = @_; - + $default //= ''; + if (HMCCU_IsValidChannel ($hash, $addr, $HMCCU_FL_ADDRESS)) { return $hash->{hmccu}{dev}{$addr}{name}; } @@ -5818,24 +5664,6 @@ sub HMCCU_GetDeviceType ($$$) return $default; } - -###################################################################### -# Get number of channels of a CCU device. -# Channel number will be removed if specified. -###################################################################### - -sub HMCCU_GetDeviceChannels ($$$) -{ - my ($hash, $addr, $default) = @_; - - if (HMCCU_IsValidDeviceOrChannel ($hash, $addr, $HMCCU_FL_ADDRESS)) { - $addr =~ s/:[0-9]+$//; - return $hash->{hmccu}{dev}{$addr}{channels}; - } - - return 0; -} - ###################################################################### # Get default RPC interface and port ###################################################################### @@ -5855,9 +5683,10 @@ sub HMCCU_GetDefaultInterface ($) # Channel number will be removed if specified. ###################################################################### -sub HMCCU_GetDeviceInterface ($$$) +sub HMCCU_GetDeviceInterface ($$;$) { my ($hash, $addr, $default) = @_; + $default //= ''; if (HMCCU_IsValidDeviceOrChannel ($hash, $addr, $HMCCU_FL_ADDRESS)) { $addr =~ s/:[0-9]+$//; @@ -5876,9 +5705,11 @@ sub HMCCU_GetDeviceInterface ($$$) # returned. ###################################################################### -sub HMCCU_GetAddress ($$$$) +sub HMCCU_GetAddress ($$;$$) { my ($hash, $name, $defadd, $defchn) = @_; + $defadd //= ''; + $defchn //= ''; my $add = $defadd; my $chn = $defchn; my $chnno = $defchn; @@ -6004,6 +5835,11 @@ sub HMCCU_SplitChnAddr ($) { my ($addr) = @_; + if (!defined($addr)) { + HMCCU_Log ('HMCCU', 2, stacktraceAsString(undef)); + return ('', ''); + } + my ($dev, $chn) = split (':', $addr); $chn = '' if (!defined ($chn)); @@ -6029,7 +5865,7 @@ sub HMCCU_SplitDatapoint ($;$) # returned. ###################################################################### -sub HMCCU_FindClientDevices ($$$$) +sub HMCCU_FindClientDevices ($$;$$) { my ($hash, $modexp, $namexp, $internal) = @_; my @devlist = (); @@ -6100,8 +5936,8 @@ sub HMCCU_GetRPCDevice ($$$) return (HMCCU_Log ($hash, 1, 'Interface not defined for RPC server of type HMCCURPCPROC', '')) if (!defined($ifname)); ($rpcdevname, $rpchost) = HMCCU_GetRPCServerInfo ($hash, $ifname, 'device,host'); - return ($rpcdevname, 0) if (defined ($rpcdevname)); - return ('', 0) if (!defined ($rpchost)); + return ($rpcdevname, 0) if (defined($rpcdevname)); + return ('', 0) if (!defined($rpchost)); # Search for RPC devices associated with I/O device my @devlist; @@ -6169,7 +6005,7 @@ sub HMCCU_GetRPCDevice ($$$) # Return 1 on success or 0 on error. ###################################################################### -sub HMCCU_AssignIODevice ($$$) +sub HMCCU_AssignIODevice ($$;$) { my ($hash, $ioName, $ifName) = @_; my $type = $hash->{TYPE}; @@ -6178,11 +6014,11 @@ sub HMCCU_AssignIODevice ($$$) AssignIoPort ($hash, $ioName); - $ioHash = $hash->{IODev} if (exists ($hash->{IODev})); + $ioHash = $hash->{IODev} if (exists($hash->{IODev})); return HMCCU_Log ($hash, 1, "Can't assign I/O device", 0) if (!defined($ioHash) || !exists($ioHash->{TYPE}) || $ioHash->{TYPE} ne 'HMCCU'); - if ($type eq 'HMCCURPCPROC' && defined ($ifName) && exists($ioHash->{hmccu}{interfaces}{$ifName})) { + if ($type eq 'HMCCURPCPROC' && defined($ifName) && exists($ioHash->{hmccu}{interfaces}{$ifName})) { # Register RPC device $ioHash->{hmccu}{interfaces}{$ifName}{device} = $name; } @@ -6202,12 +6038,10 @@ sub HMCCU_FindIODevice ($) foreach my $dn (sort keys %defs) { my $ch = $defs{$dn}; - next if (!exists ($ch->{TYPE})); - next if ($ch->{TYPE} ne 'HMCCU'); my $disabled = AttrVal ($ch->{NAME}, 'disable', 0); - next if ($disabled); + next if (!exists($ch->{TYPE}) || $ch->{TYPE} ne 'HMCCU' || $disabled); - return $ch if (!defined ($param)); + return $ch if (!defined($param)); return $ch if (HMCCU_IsValidDeviceOrChannel ($ch, $param, $HMCCU_FL_ALL)); } @@ -6226,9 +6060,8 @@ sub HMCCU_IODeviceStates () # Search for first HMCCU device foreach my $dn (sort keys %defs) { my $ch = $defs{$dn}; - next if (!exists ($ch->{TYPE})); - next if ($ch->{TYPE} ne 'HMCCU'); - if (exists ($ch->{ccustate}) && $ch->{ccustate} eq 'active') { + next if (!exists($ch->{TYPE}) || $ch->{TYPE} ne 'HMCCU'); + if (exists($ch->{ccustate}) && $ch->{ccustate} eq 'active') { $active++; } else { @@ -6252,7 +6085,7 @@ sub HMCCU_GetHash ($@) if (defined($hash) && $hash != 0) { if ($hash->{TYPE} eq 'HMCCUDEV' || $hash->{TYPE} eq 'HMCCUCHN') { - return $hash->{IODev} if (exists ($hash->{IODev})); + return $hash->{IODev} if (exists($hash->{IODev})); return HMCCU_FindIODevice ($hash->{ccuaddr}) if (exists($hash->{ccuaddr})); } elsif ($hash->{TYPE} eq 'HMCCU') { @@ -6263,7 +6096,7 @@ sub HMCCU_GetHash ($@) # Search for first HMCCU device foreach my $dn (sort keys %defs) { my $ch = $defs{$dn}; - next if (!exists ($ch->{TYPE})); + next if (!exists($ch->{TYPE})); return $ch if ($ch->{TYPE} eq 'HMCCU'); } @@ -6290,16 +6123,27 @@ sub HMCCU_GetAttribute ($$$$) sub HMCCU_SetDefaultAttributes ($;$) { - my ($clHash, $ctrlChn) = @_; + my ($clHash, $parRef) = @_; my $clName = $clHash->{NAME}; + + $parRef //= { mode => 'update', role => undef, ctrlChn => '' }; + my $role = $parRef->{role} // HMCCU_GetChannelRole ($clHash, $parRef->{ctrlChn}); - my $role = HMCCU_GetChannelRole ($clHash, $ctrlChn); - HMCCU_Log ($clHash, 2, "SetDefaultAttributes: role=$role"); if ($role ne '' && exists($HMCCU_ATTR->{$role})) { - HMCCU_Log ($clHash, 2, "SetDefaultAttributes: attributes found"); + HMCCU_Log ($clHash, 2, "Default attributes found for role $role"); + $clHash->{hmccu}{defaults} = 1; + if ($parRef->{mode} eq 'reset') { + my @removeAttr = ('ccureadingname', 'ccuscaleval', 'eventMap', 'substexcl', + 'substitute', 'webCmd', 'widgetOverride' + ); + foreach my $a (@removeAttr) { + CommandDeleteAttr (undef, "$clName $a") if (exists($attr{$clName}{$a})); + } + } foreach my $a (keys %{$HMCCU_ATTR->{$role}}) { CommandAttr (undef, "$clName $a ".$HMCCU_ATTR->{$role}{$a}); } + $clHash->{hmccu}{defaults} = 0; return 1; } else { @@ -6329,16 +6173,332 @@ sub HMCCU_GetStateValues ($$;$) ###################################################################### # Return additional commands depending on the channel role. +# +# Command-Defintion: +# 'Datapoint-Definition [...]' +# Datapoint-Definition: +# Paramset:Datapoint:[+|-]Value +# Paramset:Datapoint:?Parameter +# Paramset:Datapoint:?Parameter=Default-Value +# Paramset:Datapoint:#Parameter=[Value1[,...]] +# Paramset: +# V=VALUES, M=MASTER (channel), D=MASTER (device) +# If Parameter is preceded by ? any value is accepted. +# If Parameter is preceded by #, Datapoint must have type ENUM or +# a list of values must be specified. +# If Default-Value is preceeded by + or -, value is added to or +# subtracted from current datapoint value. +# +# Output format: +# {'cmd'}{syntax} - Command syntax (input definition) +# {'cmd'}{channel} - Channel number +# {'cmd'}{role} - Channel role +# {'cmd'}{usage} - Usage string +# {'cmd'}{cmdlist} - Set command definition +# {'cmd'}{subcount} - Number of sub commands +# {'cmd'}{subcmd}{'nnn'}{ps} - Parameter set name +# {'cmd'}{subcmd}{'nnn'}{dpt} - Datapoint name +# {'cmd'}{subcmd}{'nnn'}{type} - Datapoint type +# {'cmd'}{subcmd}{'nnn'}{parname} - Parameter name (default=datapoint) +# {'cmd'}{subcmd}{'nnn'}{partype} - Parameter type (s. below) +# {'cmd'}{subcmd}{'nnn'}{args} - Comma separated list of valid values +# or default value or fix value or '' +# {'cmd'}{subcmd}{'nnn'}{min} - Minimum value +# {'cmd'}{subcmd}{'nnn'}{max} - Maximum value +# {'cmd'}{subcmd}{'nnn'}{unit} - Unit of parameter value +# +# Datapoint types: BOOL, INTEGER, ENUM, ACTION, STRING +# +# Parameter types: +# 1 = argument required, list of valid parameters defined +# 2 = argument required, default value may be available +# 3 = fix value, no argument required ###################################################################### -sub HMCCU_GetSpecialCommands ($$) +sub HMCCU_UpdateRoleCommands ($$;$) { - my ($clHash, $ctrlChn) = @_; + my ($ioHash, $clHash, $chnNo) = @_; + + my %pset = ('V' => 'VALUES', 'M' => 'MASTER', 'D' => 'MASTER'); + my @cmdList = (); + return if (!defined($clHash->{hmccu}{role}) || $clHash->{hmccu}{role} eq ''); - my $role = HMCCU_GetChannelRole ($clHash, $ctrlChn); - return $HMCCU_ROLECMDS->{$role} if ($role ne '' && exists($HMCCU_ROLECMDS->{$role})); + delete $clHash->{hmccu}{roleCmds} if (exists($clHash->{hmccu}{roleCmds})); - return undef; + foreach my $chnRole (split(',', $clHash->{hmccu}{role})) { + my ($channel, $role) = split(':', $chnRole); + next if (!defined($role) || !exists($HMCCU_ROLECMDS->{$role})); + + foreach my $cmd (keys %{$HMCCU_ROLECMDS->{$role}}) { + next if (defined($chnNo) && $chnNo ne '' && $chnNo != $channel && $chnNo ne 'd'); + my $cmdChn = $channel; + + $clHash->{hmccu}{roleCmds}{$cmd}{syntax} = $HMCCU_ROLECMDS->{$role}{$cmd}; + $clHash->{hmccu}{roleCmds}{$cmd}{role} = $role; + + my $cnt = 0; + my $usage = $cmd; + my $cmdArgList = ''; + my @parTypes = (0, 0, 0); + + foreach my $subCmd (split(/\s+/, $HMCCU_ROLECMDS->{$role}{$cmd})) { + my $pt; + my $scn = sprintf ("%03d", $cnt); + my ($ps, $dpt, $par) = split(/:/, $subCmd); + my ($addr, undef) = HMCCU_SplitChnAddr ($clHash->{ccuaddr}); + $cmdChn = 'd' if ($ps eq 'D'); + my $paramDef = HMCCU_GetParamDef ($ioHash, "$addr:$cmdChn", $pset{$ps}, $dpt); + if (!defined($paramDef)) { + HMCCU_Log ($ioHash, 2, "Can't get paramdef of $addr:$cmdChn.$dpt"); + next; + } + + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{ps} = $pset{$ps}; + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{dpt} = $dpt; + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{min} = $paramDef->{MIN}; + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{max} = $paramDef->{MAX}; + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{unit} = $paramDef->{UNIT}; + + if ($par =~ /^#(.+)$/) { + my $argList = ''; + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{parname} = $1; + $pt = 1; + + if ($paramDef->{TYPE} eq 'ENUM' && defined($paramDef->{VALUE_LIST})) { + $par = $paramDef->{VALUE_LIST}; + $par =~ s/[ ]+/-/g; + $argList = $par; + my @el = split(',', $par); + + while (my ($i, $e) = each @el) { + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{look}{$e} = $i; + } + } + else { + my ($pn, $pv) = split('=', $par); + $argList = $pv // ''; + foreach my $e (split(',', $argList)) { + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{look}{$e} = $e; + } + } + + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{args} = $argList; + $cmdArgList = $argList; + $usage .= " {$argList}"; + } + elsif ($par =~ /^\?(.+)$/) { + # User must specify a parameter (default value possible) + my ($pn, $pv) = split('=', $1); + $pt = 2; + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{parname} = $pn; + if (defined($pv)) { + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{args} = "$pv"; + $usage .= " [$pn]"; + } + else { + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{args} = $paramDef->{DEFAULT} // ''; + $usage .= " $pn"; + } + } + else { + # Fix value. Command has no argument + $pt = 3; + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{parname} = $dpt; + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{args} = $par; + } + + $clHash->{hmccu}{roleCmds}{$cmd}{subcmd}{$scn}{partype} = $pt; + $parTypes[$pt-1]++; + $cnt++; + } + + my $cmdDef = $cmd; + if ($parTypes[0] == 1 && $parTypes[1] == 0 && $cmdArgList ne '') { + $cmdDef .= ":$cmdArgList"; + } + elsif ($parTypes[0] == 0 && $parTypes[1] == 0) { + $cmdDef .= ':noArg'; + } + + $clHash->{hmccu}{roleCmds}{$cmd}{channel} = $cmdChn; + $clHash->{hmccu}{roleCmds}{$cmd}{usage} = $usage; + $clHash->{hmccu}{roleCmds}{$cmd}{subcount} = $cnt; + push @cmdList, $cmdDef; + } + } + + $clHash->{hmccu}{cmdlist} = join(' ', @cmdList); + + return; +} + +###################################################################### +# Execute command related to role +###################################################################### + +sub HMCCU_ExecuteRoleCommand ($@) +{ + my ($ioHash, $clHash, $command, $a, $h) = @_; + + my $rc; + my %dpval; + my %cfval; + my ($devAddr, undef) = HMCCU_SplitChnAddr ($clHash->{ccuaddr}); + my $usage = $clHash->{hmccu}{roleCmds}{$command}{usage}; + my $channel = $clHash->{hmccu}{roleCmds}{$command}{channel}; + my $chnAddr = "$devAddr:$channel"; + my $c = 0; + + foreach my $cmdNo (sort keys %{$clHash->{hmccu}{roleCmds}{$command}{subcmd}}) { + my $cmd = $clHash->{hmccu}{roleCmds}{$command}{subcmd}{$cmdNo}; + my $value; + + if (!HMCCU_IsValidParameter ($clHash, $chnAddr, $cmd->{ps}, $cmd->{dpt})) { + HMCCU_Trace ($clHash, 2, "Invalid parameter $cmd->{ps} $cmd->{dpt} for command $command"); + return HMCCU_SetError ($clHash, -8); + } + + if ($cmd->{partype} == 3) { + # Fix value + if ($cmd->{args} =~ /^[+-](.+)$/) { + return HMCCU_SetError ($clHash, "Current value of $channel.$cmd->{dpt} not available") + if (!defined($clHash->{hmccu}{dp}{"$channel.$cmd->{dpt}"}{$cmd->{ps}}{SVAL})); + $value = $clHash->{hmccu}{dp}{"$channel.$cmd->{dpt}"}{$cmd->{ps}}{SVAL}+int($cmd->{args}); + } + else { + $value = $cmd->{args}; + } + } + elsif ($cmd->{partype} == 2) { + # Normal value + $value = shift @$a // $cmd->{args}; + return HMCCU_SetError ($clHash, "Missing parameter $cmd->{parname}. Usage: $usage") + if ($value eq ''); + if ($cmd->{unit} eq 's') { + $value = HMCCU_GetTimeSpec ($value); + return HMCCU_SetError ($clHash, 'Wrong time format. Use seconds or HH:MM[:SS]') + if ($value < 0); + } + } + else { + # Set of valid values + my $vl = shift @$a // return HMCCU_SetError ( + $clHash, "Missing parameter $cmd->{parname}. Usage: $usage"); + $value = $cmd->{look}{$vl} // return HMCCU_SetError ( + $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 ''); + + if ($cmd->{ps} eq 'VALUES') { + my $dno = sprintf ("%03d", $c); + $dpval{"$dno.$clHash->{ccuif}.$chnAddr.$cmd->{dpt}"} = $value; + $c++; + } + else { + $cfval{$cmd->{dpt}} = $value; + } + } + + if (scalar(keys %dpval) > 0) { + $rc = HMCCU_SetMultipleDatapoints ($clHash, \%dpval); + return HMCCU_SetError ($clHash, HMCCU_Min(0, $rc)); + } + if (scalar(keys %cfval) > 0) { + ($rc, undef) = HMCCU_SetMultipleParameters ($clHash, $chnAddr, \%cfval, 'MASTER'); + return HMCCU_SetError ($clHash, HMCCU_Min(0, $rc)); + } + + return HMCCU_SetError ($clHash, "Command $command not executed"); +} + +###################################################################### +# Get week program(s) as html table +###################################################################### + +sub HMCCU_DisplayWeekProgram ($;$) +{ + my ($hash, $program) = @_; + + my @weekDay = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'); + + return "No data available for week program(s)" if (!exists($hash->{hmccu}{tt})); + + my $s = ''; + foreach my $w (sort keys %{$hash->{hmccu}{tt}}) { + next if (defined($program) && "$w" ne "$program" && "$program" ne 'all'); + my $p = $hash->{hmccu}{tt}{$w}; + $s .= '

Week Program '.$w.'


'; + foreach my $d (sort keys %{$p->{ENDTIME}}) { + $s .= ''; + my $h24 = 0; + foreach my $h (sort { $a <=> $b } keys %{$p->{ENDTIME}{$d}}) { + $s .= ''; + $h24 = 1 if ($h24 == 0 && $p->{ENDTIME}{$d}{$h} eq '24:00'); + } + $s .= ''; + } + $s .= '
'.$weekDay[$d].''.($h24 == 0 ? $p->{ENDTIME}{$d}{$h}.' / '.$p->{TEMPERATURE}{$d}{$h} : ' ').'

'; + } + $s .= ''; + + return $s; +} + +###################################################################### +# Check if value matches parameter definition +# Parameter t can be a datapoint type or a hash reference to a role +# command parameter definition. +# Parameter list can be a comma separated list of valid values. +###################################################################### + +sub HMCCU_CheckParameter ($$;$$$) +{ + my ($v, $t, $min, $max, $list) = @_; + + return 0 if (!defined($v) || !defined($t)); + + my $type; + + if (ref($t) eq 'HASH') { + $type = $t->{type}; + if ($type eq 'ENUM') { + return exists($t->{look}) && exists($t->{look}{$v}) || + (HMCCU_IsIntNum ($v) && (!defined($min) || $v >= $min) && (!defined($max) || $v <= $max)) ? 1 : 0; + } + $min = $t->{min}; + $max = $t->{max}; + } + else { + $type = $t; + } + + if ($type eq 'BOOL') { + return $v =~ /^(true|false|1|0)$/ ? 1 : 0; + } + elsif ($type eq 'INTEGER') { + return HMCCU_IsIntNum ($v) && (!defined($min) || $v >= $min) && (!defined($max) || $v <= $max); + } + elsif ($type eq 'FLOAT' || $type eq 'DOUBLE') { + return HMCCU_IsFltNum ($v) && (!defined($min) || $v >= $min) && (!defined($max) || $v <= $max); + } + elsif ($type eq 'ENUM') { + if (HMCCU_IsIntNum ($v)) { + return (!defined($min) || $v >= $min) && (!defined($max) || $v <= $max) ? 1 : 0; + } + elsif (defined($list)) { + foreach my $le (split(',', $list)) { return 1 if ($v eq $le); } + } + } + elsif ($type eq 'ACTION') { + return $v =~ /^[01]$/ ? 1 : 0; + } + elsif ($type eq 'STRING') { + return 1; + } + + return 0; } ###################################################################### @@ -6360,20 +6520,23 @@ sub HMCCU_GetSpecialDatapoints ($) my $type = $hash->{TYPE}; my $ccutype = $hash->{ccutype}; - my ($sc, $sd, $cc, $cd) = ('', '', '', ''); + my $da; + my $dc; + if (exists($hash->{ccuaddr})) { + ($da, $dc) = HMCCU_SplitChnAddr ($hash->{ccuaddr}); + } + else { + HMCCU_Log ($hash, 2, "No CCU address defined"); + } + my ($sc, $sd, $cc, $cd) = ($dc // '', '', $dc // '', ''); my $statedatapoint = AttrVal ($name, 'statedatapoint', ''); my $controldatapoint = AttrVal ($name, 'controldatapoint', ''); - my ($da, $dc) = HMCCU_SplitChnAddr ($hash->{ccuaddr}); # Attributes controlchannel and statechannel are only valid for HMCCUDEV devices if ($type eq 'HMCCUDEV') { $sc = AttrVal ($name, 'statechannel', ''); $cc = AttrVal ($name, 'controlchannel', ''); } - else { - $sc = $dc; - $cc = $dc; - } # If attribute statedatapoint is specified, use it. Attribute statechannel overrides # channel specification in statedatapoint @@ -6384,8 +6547,8 @@ sub HMCCU_GetSpecialDatapoints ($) else { $sd = $statedatapoint; if ($sc eq '') { - # Try to find state channel - my $c = HMCCU_FindDatapoint ($hash, $type, -1, $sd, 3); + # Try to find state channel (datapoint must be readable or provide events) + my $c = HMCCU_FindDatapoint ($hash, $type, -1, $sd, 5); $sc = $c if ($c >= 0); } } @@ -6400,26 +6563,26 @@ sub HMCCU_GetSpecialDatapoints ($) else { $cd = $controldatapoint; if ($cc eq '') { - # Try to find control channel - my $c = HMCCU_FindDatapoint ($hash, $type, -1, $cd, 3); + # Try to find control channel (datapoint must be writeable) + my $c = HMCCU_FindDatapoint ($hash, $type, -1, $cd, 4); $cc = $c if ($c >= 0); } } } # Detect by role, but do not override values defined as attributes - if (exists($hash->{hmccu}{role})) { - my $ccuRole = $hash->{hmccu}{role}; + if (defined($hash->{hmccu}{role}) && $hash->{hmccu}{role} ne '') { if ($type eq 'HMCCUCHN') { - if (exists($HMCCU_STATECONTROL->{$ccuRole}) && $HMCCU_STATECONTROL->{$ccuRole}{F} & 1) { - $sd = $HMCCU_STATECONTROL->{$ccuRole}{S} if ($HMCCU_STATECONTROL->{$ccuRole}{S} ne '' && $sd eq ''); - $cd = $HMCCU_STATECONTROL->{$ccuRole}{C} if ($HMCCU_STATECONTROL->{$ccuRole}{C} ne '' && $cd eq ''); + my $role = HMCCU_GetChannelRole ($hash); + 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 ''); } } elsif ($type eq 'HMCCUDEV') { my ($rsdCnt, $rcdCnt) = (0, 0); my ($rsc, $rsd, $rcc, $rcd) = ('', '', '', ''); - foreach my $roleDef (split(',', $ccuRole)) { + foreach my $roleDef (split(',', $hash->{hmccu}{role})) { my ($rc, $role) = split(':', $roleDef); if (defined($role) && exists($HMCCU_STATECONTROL->{$role}) && $HMCCU_STATECONTROL->{$role}{F} & 2) { @@ -6498,20 +6661,18 @@ sub HMCCU_IsFlag ($$) sub HMCCU_GetAttrReadingFormat ($$) { - my ($clhash, $iohash) = @_; + my ($clHash, $ioHash) = @_; - my $clname = $clhash->{NAME}; - my $ioname = $iohash->{NAME}; - my $rfdef = ''; - - if (exists($clhash->{ccutype}) && $clhash->{ccutype} =~ /^HM-CC-VG/) { + my $rfdef; + + if (HMCCU_IsFlag ($ioHash, 'updGroupMembers') && exists($clHash->{ccutype}) && $clHash->{ccutype} =~ /^HM-CC-VG/) { $rfdef = 'name'; } else { - $rfdef = AttrVal ($ioname, 'ccudef-readingformat', 'datapoint'); + $rfdef = AttrVal ($ioHash->{NAME}, 'ccudef-readingformat', 'datapoint'); } - - return AttrVal ($clname, 'ccureadingformat', $rfdef); + + return AttrVal ($clHash->{NAME}, 'ccureadingformat', $rfdef); } ###################################################################### @@ -6522,67 +6683,50 @@ sub HMCCU_GetAttrReadingFormat ($$) sub HMCCU_GetAttrStripNumber ($) { my ($hash) = @_; - my $fnc = "GetAttrStripNumber"; - my $type = $hash->{TYPE}; my %strip = ( 'BLIND' => '0', 'DIMMER' => '0' ); - - my $snDef = '1'; - + my $ioHash = HMCCU_GetHash ($hash); - if (defined($ioHash)) { - $snDef = AttrVal ($ioHash->{NAME}, 'ccudef-stripnumber', $snDef); - } + my $snDef = defined($ioHash) ? AttrVal ($ioHash->{NAME}, 'ccudef-stripnumber', '1') : '1'; - if (exists($hash->{hmccu}{role})) { - if ($type eq 'HMCCUDEV') { - foreach my $cr (split(',', $hash->{hmccu}{role})) { - my ($c, $r) = split(':', $cr); - if (exists($strip{$r})) { - $snDef = $strip{$r}; - last; - } + if (defined($hash->{hmccu}{role}) && $hash->{hmccu}{role} ne '') { + foreach my $cr (split(',', $hash->{hmccu}{role})) { + my ($c, $r) = split(':', $cr); + if (exists($strip{$r})) { + $snDef = $strip{$r}; + last; } } - elsif ($type eq 'HMCCUCHN') { - $snDef = $strip{$hash->{hmccu}{role}} if (exists($strip{$hash->{hmccu}{role}})); - } } - my $stripnumber = AttrVal ($hash->{NAME}, 'stripnumber', $snDef); - - HMCCU_Trace ($hash, 2, $fnc, "stripnumber = $stripnumber"); - - return $stripnumber; + return AttrVal ($hash->{NAME}, 'stripnumber', $snDef); } ###################################################################### -# Get attributes substitute and substexcl considering default -# attribute ccudef-substitute defined in I/O device. +# Get attribute substitute considering default attribute +# ccudef-substitute defined in I/O device. # Substitute ${xxx} by datapoint value. ###################################################################### sub HMCCU_GetAttrSubstitute ($$) { my ($clhash, $iohash) = @_; - my $fnc = 'GetAttrSubstitute'; - my $clname = $clhash->{NAME}; my $ioname = $iohash->{NAME}; my $substdef = AttrVal ($ioname, 'ccudef-substitute', ''); my $subst = AttrVal ($clname, 'substitute', $substdef); $subst .= ";$substdef" if ($subst ne $substdef && $substdef ne ''); - HMCCU_Trace ($clhash, 2, $fnc, "subst = $subst"); + HMCCU_Trace ($clhash, 2, "subst = $subst"); return $subst if ($subst !~ /\$\{.+\}/); $subst = HMCCU_SubstVariables ($clhash, $subst, undef); - HMCCU_Trace ($clhash, 2, $fnc, "subst_vars = $subst"); + HMCCU_Trace ($clhash, 2, "subst_vars = $subst"); return $subst; } @@ -6597,14 +6741,13 @@ sub HMCCU_HMCommand ($$$) { my ($cl_hash, $cmd, $mode) = @_; my $cl_name = $cl_hash->{NAME}; - my $fnc = 'HMCommand'; my $io_hash = HMCCU_GetHash ($cl_hash); - my $ccureqtimeout = AttrVal ($io_hash->{NAME}, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST); + my $ccureqtimeout = AttrVal ($io_hash->{NAME}, 'ccuReqTimeout', $HMCCU_TIMEOUT_REQUEST); my $url = HMCCU_BuildURL ($io_hash, 'rega'); my $value; - HMCCU_Trace ($cl_hash, 2, $fnc, "URL=$url, cmd=$cmd"); + HMCCU_Trace ($cl_hash, 2, "URL=$url, cmd=$cmd"); my $param = { url => $url, timeout => $ccureqtimeout, data => $cmd, method => "POST" }; $param->{sslargs} = { SSL_verify_mode => 0 }; @@ -6612,13 +6755,15 @@ sub HMCCU_HMCommand ($$$) if ($err eq '') { $value = $response; - $value =~ s/(.*)<\/xml>//; - $value =~ s/\r//g; - HMCCU_Trace ($cl_hash, 2, $fnc, "Response=$response, Value=".(defined ($value) ? $value : "undef")); + if (defined($value)) { + $value =~ s/(.*)<\/xml>//; + $value =~ s/\r//g; + } + HMCCU_Trace ($cl_hash, 2, "Response=$response, Value=".(defined($value) ? $value : "undef")); } else { HMCCU_Log ($io_hash, 2, "Error during HTTP request: $err"); - HMCCU_Trace ($cl_hash, 2, $fnc, "Response=".(defined($response) ? $response : 'undef')); + HMCCU_Trace ($cl_hash, 2, "Response=".(defined($response) ? $response : 'undef')); return undef; } @@ -6638,13 +6783,12 @@ sub HMCCU_HMCommandNB ($$$) { my ($clHash, $cmd, $cbFunc) = @_; my $clName = $clHash->{NAME}; - my $fnc = 'HMCommandNB'; my $ioHash = HMCCU_GetHash ($clHash); my $ccureqtimeout = AttrVal ($ioHash->{NAME}, 'ccuReqTimeout', $HMCCU_TIMEOUT_REQUEST); my $url = HMCCU_BuildURL ($ioHash, 'rega'); - HMCCU_Trace ($clHash, 2, $fnc, "URL=$url"); + HMCCU_Trace ($clHash, 2, "URL=$url"); if (defined($cbFunc)) { my $param = { url => $url, timeout => $ccureqtimeout, data => $cmd, method => "POST", @@ -6668,10 +6812,9 @@ sub HMCCU_HMCommandCB ($$$) { my ($param, $err, $data) = @_; my $hash = $param->{devhash}; - my $fnc = 'HMCommandCB'; HMCCU_Log ($hash, 2, "Error during CCU request. $err") if ($err ne ''); - HMCCU_Trace ($hash, 2, $fnc, "URL=".$param->{url}."
Response=$data"); + HMCCU_Trace ($hash, 2, "URL=".$param->{url}."
Response=$data"); } ###################################################################### @@ -6687,7 +6830,7 @@ sub HMCCU_HMCommandCB ($$$) # Return script output or error message starting with "ERROR:". ###################################################################### -sub HMCCU_HMScriptExt ($$$$$) +sub HMCCU_HMScriptExt ($$;$$$) { my ($hash, $hmscript, $params, $cbFunc, $cbParam) = @_; my $name = $hash->{NAME}; @@ -6698,15 +6841,15 @@ sub HMCCU_HMScriptExt ($$$$$) HMCCU_Log ($hash, 2, stacktraceAsString(undef)); } - return HMCCU_LogError ($hash, 2, "CCU host name not defined") if (!exists ($hash->{host})); + return HMCCU_LogError ($hash, 2, 'CCU host name not defined') if (!exists($hash->{host})); my $host = $hash->{host}; - my $ccureqtimeout = AttrVal ($hash->{NAME}, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST); + my $ccureqtimeout = AttrVal ($hash->{NAME}, 'ccuReqTimeout', $HMCCU_TIMEOUT_REQUEST); if ($hmscript =~ /^!(.*)$/) { # Internal script $scrname = $1; - return "ERROR: Can't find internal script $scrname" if (!exists ($HMCCU_SCRIPTS->{$scrname})); + return "ERROR: Can't find internal script $scrname" if (!exists($HMCCU_SCRIPTS->{$scrname})); $code = $HMCCU_SCRIPTS->{$scrname}{code}; } elsif ($hmscript =~ /^\[(.*)\]$/) { @@ -6749,18 +6892,16 @@ sub HMCCU_HMScriptExt ($$$$$) } } - HMCCU_Trace ($hash, 2, "HMScriptEx", $code); + HMCCU_Trace ($hash, 2, $code); # Execute script on CCU my $url = HMCCU_BuildURL ($hash, 'rega'); - if (defined ($cbFunc)) { + if (defined($cbFunc)) { # Non blocking my $param = { url => $url, timeout => $ccureqtimeout, data => $code, method => "POST", callback => $cbFunc, ioHash => $hash }; - if (defined ($cbParam)) { - foreach my $p (keys %{$cbParam}) { - $param->{$p} = $cbParam->{$p}; - } + if (defined($cbParam)) { + foreach my $p (keys %{$cbParam}) { $param->{$p} = $cbParam->{$p}; } } $param->{sslargs} = { SSL_verify_mode => 0 }; HttpUtils_NonblockingGet ($param); @@ -6772,14 +6913,12 @@ sub HMCCU_HMScriptExt ($$$$$) $param->{sslargs} = { SSL_verify_mode => 0 }; my ($err, $response) = HttpUtils_BlockingGet ($param); if ($err eq '') { - my $output = $response; - $output =~ s/.*<\/xml>//; - $output =~ s/\r//g; - return $output; + $response =~ s/.*<\/xml>//; + $response =~ s/\r//g; + return $response; } else { - HMCCU_Log ($hash, 2, "HMScript failed. $err"); - return "ERROR: HMScript failed. $err"; + return HMCCU_LogError ($hash, 2, "HMScript failed. $err"); } } @@ -6790,9 +6929,8 @@ sub HMCCU_HMScriptExt ($$$$$) sub HMCCU_BulkUpdate ($$$$) { my ($hash, $reading, $orgval, $subval) = @_; - my $name = $hash->{NAME}; - my $excl = AttrVal ($name, 'substexcl', ''); + my $excl = AttrVal ($hash->{NAME}, 'substexcl', 'control|pct'); readingsBulkUpdate ($hash, $reading, ($excl ne '' && $reading =~ /$excl/ ? $orgval : $subval)); } @@ -6806,11 +6944,9 @@ sub HMCCU_GetDatapoint ($@) { my ($cl_hash, $param, $noupd) = @_; my $cl_name = $cl_hash->{NAME}; - my $fnc = 'GetDatapoint'; my $value = ''; - my $io_hash = HMCCU_GetHash ($cl_hash); - return (-3, $value) if (!defined ($io_hash)); + my $io_hash = HMCCU_GetHash ($cl_hash) // return (-3, $value); return (-4, $value) if ($cl_hash->{TYPE} ne 'HMCCU' && $cl_hash->{ccudevstate} eq 'deleted'); my $readingformat = HMCCU_GetAttrReadingFormat ($cl_hash, $io_hash); @@ -6829,22 +6965,22 @@ sub HMCCU_GetDatapoint ($@) } elsif ($flags == $HMCCU_FLAGS_NCD) { $cmd = 'Write((dom.GetObject(ID_CHANNELS)).Get("'.$nam.'").DPByHssDP("'.$dpt.'").'.$ccuget.'())'; - ($add, $chn) = HMCCU_GetAddress ($io_hash, $nam, '', ''); + ($add, $chn) = HMCCU_GetAddress ($io_hash, $nam); } - HMCCU_Trace ($cl_hash, 2, $fnc, "CMD=$cmd, param=$param, ccuget=$ccuget"); + HMCCU_Trace ($cl_hash, 2, "CMD=$cmd, param=$param, ccuget=$ccuget"); $value = HMCCU_HMCommand ($cl_hash, $cmd, 1); - if (defined ($value) && $value ne '' && $value ne 'null') { - if (!defined ($noupd) || $noupd == 0) { + if (defined($value) && $value ne '' && $value ne 'null') { + if (!defined($noupd) || $noupd == 0) { $value = HMCCU_UpdateSingleDatapoint ($cl_hash, $chn, $dpt, $value); } else { my $svalue = HMCCU_ScaleValue ($cl_hash, $chn, $dpt, $value, 0); $value = HMCCU_Substitute ($svalue, $substitute, 0, $chn, $dpt); } - HMCCU_Trace ($cl_hash, 2, $fnc, "Value of $chn.$dpt = $value"); + HMCCU_Trace ($cl_hash, 2, "Value of $chn.$dpt = $value"); return (1, $value); } else { @@ -6864,12 +7000,13 @@ sub HMCCU_SetMultipleParameters ($$$;$) { my ($clHash, $address, $params, $paramSet) = @_; $paramSet //= 'VALUES'; - + $address =~ s/:d$//; + my ($add, $chn) = HMCCU_SplitChnAddr ($address); - return -1 if ($paramSet eq 'VALUES' && !defined($chn)); + return (-1, undef) if ($paramSet eq 'VALUES' && !defined($chn)); foreach my $p (sort keys %$params) { - return -8 if ( + return (-8, undef) if ( ($paramSet eq 'VALUES' && !HMCCU_IsValidDatapoint ($clHash, $clHash->{ccutype}, $chn, $p, 2)) || ($paramSet eq 'MASTER' && !HMCCU_IsValidParameter ($clHash, $address, $paramSet, $p)) ); @@ -6885,11 +7022,11 @@ sub HMCCU_SetMultipleParameters ($$$;$) # datapoint specifications in format: # no.interface.{address|fhemdev}:channelno.datapoint # Parameter no defines the command order. +# Return value < 0 on error. ###################################################################### sub HMCCU_SetMultipleDatapoints ($$) { my ($clHash, $params) = @_; - my $fnc = "SetMultipleDatapoints"; my $mdFlag = $clHash->{TYPE} eq 'HMCCU' ? 1 : 0; my $ioHash; @@ -6897,8 +7034,7 @@ sub HMCCU_SetMultipleDatapoints ($$) { $ioHash = $clHash; } else { - $ioHash = HMCCU_GetHash ($clHash); - return -3 if (!defined($ioHash)); + $ioHash = HMCCU_GetHash ($clHash) // return -3; } my $ioName = $ioHash->{NAME}; @@ -6912,30 +7048,29 @@ sub HMCCU_SetMultipleDatapoints ($$) { # Check address. dev is either a device address or a FHEM device name my ($no, $int, $addchn, $dpt) = split (/\./, $p); - return -1 if (!defined ($dpt)); + return -1 if (!defined($dpt)); my ($dev, $chn) = split (':', $addchn); - return -1 if (!defined ($chn)); + return -1 if (!defined($chn)); my $add = $dev; # Get hash of FHEM device if ($mdFlag) { - return -1 if (!exists ($defs{$dev})); - $clHash = $defs{$dev}; + $clHash = $defs{$dev} // return -1; ($add, undef) = HMCCU_SplitChnAddr ($clHash->{ccuaddr}); } # Device has been deleted or is disabled - return -4 if (exists ($clHash->{ccudevstate}) && $clHash->{ccudevstate} eq 'deleted'); + return -4 if (exists($clHash->{ccudevstate}) && $clHash->{ccudevstate} eq 'deleted'); return -21 if (IsDisabled ($clHash->{NAME})); - HMCCU_Trace ($clHash, 2, $fnc, "dpt=$p, value=$v"); + HMCCU_Trace ($clHash, 2, "dpt=$p, value=$v"); # Check client device type and datapoint my $clType = $clHash->{TYPE}; my $ccuType = $clHash->{ccutype}; return -1 if ($clType ne 'HMCCUCHN' && $clType ne 'HMCCUDEV'); if (!HMCCU_IsValidDatapoint ($clHash, $ccuType, $chn, $dpt, 2)) { - HMCCU_Trace ($clHash, 2, $fnc, "Invalid datapoint $chn $dpt"); + HMCCU_Trace ($clHash, 2, "Invalid datapoint $chn $dpt"); return -8; } @@ -6944,13 +7079,13 @@ sub HMCCU_SetMultipleDatapoints ($$) { # Build device address list considering group devices my @addrList = $clHash->{ccuif} eq 'fhem' ? split (',', $clHash->{ccugroup}) : ($add); - return -1 if (scalar (@addrList) < 1); + return -1 if (scalar(@addrList) < 1); foreach my $a (@addrList) { # Override address and interface of group device with address of group members if ($clHash->{ccuif} eq 'fhem') { ($add, undef) = HMCCU_SplitChnAddr ($a); - $int = HMCCU_GetDeviceInterface ($ioHash, $a, ''); + $int = HMCCU_GetDeviceInterface ($ioHash, $a); return -20 if ($int eq ''); } @@ -6967,7 +7102,7 @@ sub HMCCU_SetMultipleDatapoints ($$) { } my $dptType = HMCCU_GetDatapointAttr ($ioHash, $ccuType, $chn, $dpt, 'type'); - $v = "'".$v."'" if (defined ($dptType) && $dptType == $HMCCU_TYPE_STRING); + $v = "'".$v."'" if (defined($dptType) && $dptType == $HMCCU_TYPE_STRING); my $c = '(datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).State('.$v.");\n"; if ($dpt =~ /$ccuChange/) { @@ -7098,28 +7233,24 @@ sub HMCCU_ScaleValue ($$$$$) sub HMCCU_GetVariables ($$) { my ($hash, $pattern) = @_; - my $name = $hash->{NAME}; - my $count = 0; - my $result = ''; - my $ccureadings = AttrVal ($name, 'ccureadings', HMCCU_IsFlag ($name, 'noReadings') ? 0 : 1); - - my $response = HMCCU_HMScriptExt ($hash, "!GetVariables", undef, undef, undef); + my $response = HMCCU_HMScriptExt ($hash, '!GetVariables'); return (-2, $response) if ($response eq '' || $response =~ /^ERROR:.*/); - readingsBeginUpdate ($hash) if ($ccureadings); + my %readings; + my $count = 0; + my $result = ''; foreach my $vardef (split /[\n\r]+/, $response) { my @vardata = split /=/, $vardef; next if (@vardata != 3 || $vardata[0] !~ /$pattern/); my $rn = HMCCU_CorrectName ($vardata[0]); - my $value = HMCCU_FormatReadingValue ($hash, $vardata[2], $vardata[0]); - readingsBulkUpdate ($hash, $rn, $value) if ($ccureadings); + $readings{$rn} = HMCCU_FormatReadingValue ($hash, $vardata[2], $vardata[0]); $result .= $vardata[0].'='.$vardata[2]."\n"; $count++; } - - readingsEndUpdate ($hash, 1) if ($ccureadings); + + HMCCU_UpdateReadings ($hash, \%readings, 1); return ($count, $result); } @@ -7151,7 +7282,7 @@ sub HMCCU_SetVariable ($$$$$) my ($hash, $varname, $value, $vartype, $params) = @_; my $name = $hash->{NAME}; - my $ccureqtimeout = AttrVal ($name, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST); + my $ccureqtimeout = AttrVal ($name, 'ccuReqTimeout', $HMCCU_TIMEOUT_REQUEST); my %varfnc = ( 'bool' => '!CreateBoolVariable', 'list', '!CreateListVariable', @@ -7177,7 +7308,7 @@ sub HMCCU_SetVariable ($$$$$) $params->{valtrue} = 'ist wahr' if ($vartype eq 'bool' && !exists ($params->{valtrue})); $params->{valfalse} = 'ist falsch' if ($vartype eq 'bool' && !exists ($params->{valfalse})); - my $rc = HMCCU_HMScriptExt ($hash, $varfnc{$vartype}, $params, undef, undef); + my $rc = HMCCU_HMScriptExt ($hash, $varfnc{$vartype}, $params); return HMCCU_Log ($hash, 1, $rc, -2) if ($rc =~ /^ERROR:.*/); } @@ -7196,11 +7327,9 @@ sub HMCCU_GetUpdate ($$$) my ($clHash, $addr, $ccuget) = @_; my $name = $clHash->{NAME}; my $type = $clHash->{TYPE}; - my $fnc = 'GetUpdate'; return 1 if (AttrVal ($name, 'disable', 0) == 1); - my $ioHash = HMCCU_GetHash ($clHash); - return -3 if (!defined($ioHash)); + my $ioHash = HMCCU_GetHash ($clHash) // return -3; return -4 if ($type ne 'HMCCU' && $clHash->{ccudevstate} eq 'deleted'); my $nam = ''; @@ -7209,25 +7338,25 @@ sub HMCCU_GetUpdate ($$$) $ccuget = HMCCU_GetAttribute ($ioHash, $clHash, 'ccuget', 'Value') if ($ccuget eq 'Attr'); if (HMCCU_IsValidChannel ($ioHash, $addr, $HMCCU_FL_ADDRESS)) { - $nam = HMCCU_GetChannelName ($ioHash, $addr, ''); + $nam = HMCCU_GetChannelName ($ioHash, $addr); return -1 if ($nam eq ''); my ($stadd, $stchn) = split (':', $addr); - my $stnam = HMCCU_GetChannelName ($ioHash, "$stadd:0", ''); + my $stnam = HMCCU_GetChannelName ($ioHash, "$stadd:0"); $list = $stnam eq '' ? $nam : $stnam . "," . $nam; - $script = "!GetDatapointsByChannel"; + $script = '!GetDatapointsByChannel'; } elsif (HMCCU_IsValidDevice ($ioHash, $addr, $HMCCU_FL_ADDRESS)) { - $nam = HMCCU_GetDeviceName ($ioHash, $addr, ''); + $nam = HMCCU_GetDeviceName ($ioHash, $addr); return -1 if ($nam eq ''); $list = $nam if ($clHash->{ccuif} ne 'fhem'); - $script = "!GetDatapointsByDevice"; + $script = '!GetDatapointsByDevice'; # Consider members of group device 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})) { - $nam = HMCCU_GetDeviceName ($ioHash, $gd, ''); + $nam = HMCCU_GetDeviceName ($ioHash, $gd); $list .= ','.$nam if ($nam ne ''); } } @@ -7239,14 +7368,14 @@ sub HMCCU_GetUpdate ($$$) if (HMCCU_IsFlag ($ioHash->{NAME}, 'nonBlocking')) { # Non blocking request HMCCU_HMScriptExt ($ioHash, $script, { list => $list, ccuget => $ccuget }, - \&HMCCU_UpdateCB, undef); + \&HMCCU_UpdateCB); return 1; } # Blocking request my $response = HMCCU_HMScriptExt ($ioHash, $script, - { list => $list, ccuget => $ccuget }, undef, undef); - HMCCU_Trace ($clHash, 2, $fnc, "Addr=$addr Name=$nam Script=$script
". + { list => $list, ccuget => $ccuget }); + HMCCU_Trace ($clHash, 2, "Addr=$addr Name=$nam Script=$script
". "Script response = \n".$response); return -2 if ($response eq '' || $response =~ /^ERROR:.*/); @@ -7266,14 +7395,13 @@ sub HMCCU_UpdateCB ($$$) { my ($param, $err, $data) = @_; - if (!exists ($param->{ioHash})) { - Log3 1, undef, "HMCCU: Missing parameter ioHash in update callback"; + if (!exists($param->{ioHash})) { + Log3 1, undef, 'HMCCU: Missing parameter ioHash in update callback'; return; } my $hash = $param->{ioHash}; - my $logcount = 0; - $logcount = 1 if (exists ($param->{logCount}) && $param->{logCount} == 1); + my $logcount = exists($param->{logCount}) && $param->{logCount} == 1 ? 1 : 0; my $count = 0; my @dpdef = split /[\n\r]+/, $data; @@ -7289,7 +7417,7 @@ sub HMCCU_UpdateCB ($$$) next if (!defined($dpt)); my ($add, $chn) = ('', ''); if ($iface eq 'sysvar' && $chnadd eq 'link') { - ($add, $chn) = HMCCU_GetAddress ($hash, $chnname, '', ''); + ($add, $chn) = HMCCU_GetAddress ($hash, $chnname); } else { ($add, $chn) = HMCCU_SplitChnAddr ($chnadd); @@ -7300,7 +7428,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})); + $c_err = HMCCU_Max($param->{devCount}-$c_ok, 0) if (exists($param->{devCount})); HMCCU_Log ($hash, 2, "Update success=$c_ok failed=$c_err") if ($logcount); } @@ -7326,15 +7454,13 @@ sub HMCCU_RPCRequest ($$$$$;$) $filter //= '.*'; my $name = $clHash->{NAME}; my $type = $clHash->{TYPE}; - my $fnc = "RPCRequest"; my $reqMethod = $method eq 'listParamset' || $method eq 'listRawParamset' || $method eq 'getRawParamset' ? 'getParamset' : $method; my $addr = ''; my $result = ''; - my $ioHash = HMCCU_GetHash ($clHash); - return (-3, $result) if (!defined($ioHash)); + my $ioHash = HMCCU_GetHash ($clHash) // return (-3, $result); return (-4, $result) if ($type ne 'HMCCU' && $clHash->{ccudevstate} eq 'deleted'); # Get flags and attributes @@ -7386,14 +7512,15 @@ sub HMCCU_RPCRequest ($$$$$;$) my $reqResult = HMCCURPCPROC_SendRequest ($rpcHash, $reqMethod, @parArray); return (-5, 'RPC function not available') if (!defined($reqResult)); - HMCCU_Trace ($clHash, 2, $fnc, - "Dump of RPC request $method $addr. Result type=".ref($reqResult)."
". + HMCCU_Trace ($clHash, 2, + "Dump of RPC request $method $paramset $addr. Result type=".ref($reqResult)."
". HMCCU_RefToString ($reqResult)); my $parCount = 0; if (ref($reqResult) eq 'HASH') { if (exists($reqResult->{faultString})) { - HMCCU_Log ($rpcHash, 1, $reqResult->{faultString}); + HMCCU_Log ($rpcHash, 1, "Error in request $reqMethod ".join(' ', @parArray).': '. + $reqResult->{faultString}); return (-2, $reqResult->{faultString}); } else { @@ -7434,27 +7561,6 @@ sub HMCCU_RPCRequest ($$$$$;$) " UNIT=".$reqResult->{$_}->{UNIT} } sort keys %$reqResult); } - elsif ($method eq 'getParamset') { - readingsBeginUpdate ($clHash) if ($ccureadings); - - foreach my $k (sort keys %$reqResult) { - next if ($k !~ /$filter/); - my $value = $reqResult->{$k}; - $result .= "$k=$value\n"; - if ($ccureadings) { - $value = HMCCU_FormatReadingValue ($clHash, $value, $k); - $value = HMCCU_Substitute ($value, $substitute, 0, $chn, $k); - my @readings = HMCCU_GetReadingName ($clHash, $int, $add, $chn, $k, $nam, $readingformat); - foreach my $rn (@readings) { - next if ($rn eq ''); - $rn = "R-".$rn; - readingsBulkUpdate ($clHash, $rn, $value); - } - } - } - - readingsEndUpdate ($clHash, 1) if ($ccureadings); - } else { $result = $reqResult; } @@ -7466,6 +7572,9 @@ sub HMCCU_RPCRequest ($$$$$;$) # *** HELPER FUNCTIONS *** ###################################################################### +sub HMCCU_SetIfDef { $_[0] = $_[1] if defined($_[1]) } +sub HMCCU_SetIfEx { $_[0] = $_[1] if exists($_[1]) } + ###################################################################### # Return Prefix.Value if value is defined. Otherwise default. ###################################################################### @@ -7494,11 +7603,17 @@ sub HMCCU_ISO2UTF ($) # Check for floating point number ###################################################################### -sub HMCCU_IsFltNum ($) +sub HMCCU_IsFltNum ($;$) { - my ($value) = @_; - - return defined($value) && $value =~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/ ? 1 : 0; + my ($value, $flag) = @_; + $flag //= 0; + + if ($flag) { + return defined($value) && $value =~ /^[+-]?\d*\.?\d+?$/ ? 1 : 0; + } + else { + return defined($value) && $value =~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/ ? 1 : 0; + } } ###################################################################### @@ -7512,6 +7627,59 @@ 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) @@ -7564,11 +7732,10 @@ sub HMCCU_GetDeviceStates ($) # Return (reading, channel, datapoint, value) ###################################################################### -sub HMCCU_GetHMState ($$$) +sub HMCCU_GetHMState ($$;$) { my ($name, $ioname, $defval) = @_; my @hmstate = ('hmstate', undef, undef, $defval); - my $fnc = "GetHMState"; my $clhash = $defs{$name}; my $cltype = $clhash->{TYPE}; @@ -7621,6 +7788,7 @@ sub HMCCU_GetTimeSpec ($) { my ($ts) = @_; + 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); @@ -7671,22 +7839,22 @@ sub HMCCU_BuildURL ($$) my $url = ''; my $username = ''; my $password = ''; - my ($erruser, $encuser) = getKeyValue ($name."_username"); - my ($errpass, $encpass) = getKeyValue ($name."_password"); - if (!defined ($erruser) && !defined ($errpass) && defined ($encuser) && defined ($encpass)) { + my ($erruser, $encuser) = getKeyValue ($name.'_username'); + my ($errpass, $encpass) = getKeyValue ($name.'_password'); + if (!defined($erruser) && !defined($errpass) && defined($encuser) && defined($encpass)) { $username = HMCCU_Decrypt ($encuser); $password = HMCCU_Decrypt ($encpass); } my $auth = ($username ne '' && $password ne '') ? "$username:$password".'@' : ''; if ($backend eq 'rega') { - $url = $hash->{prot}."://$auth".$hash->{host}.":". - $HMCCU_REGA_PORT{$hash->{prot}}."/tclrega.exe"; + $url = $hash->{prot}."://$auth".$hash->{host}.':'. + $HMCCU_REGA_PORT{$hash->{prot}}.'/tclrega.exe'; } else { ($url) = HMCCU_GetRPCServerInfo ($hash, $backend, 'url'); - if (defined ($url)) { - if (exists ($HMCCU_RPC_SSL{$backend})) { + if (defined($url)) { + if (exists($HMCCU_RPC_SSL{$backend})) { my $p = $hash->{prot} eq 'https' ? '4' : ''; $url =~ s/^http:\/\//$hash->{prot}:\/\/$auth/; $url =~ s/:([0-9]+)/:$p$1/; @@ -7713,26 +7881,27 @@ sub HMCCU_CalculateReading ($$) { my ($cl_hash, $chkeys) = @_; my $name = $cl_hash->{NAME}; - my $fnc = "HMCCU_CalculateReading"; + my %parCount = ('dewpoint' => 2, 'abshumidity' => 2, 'equ' => 1, + 'max' => 1, 'min' => 1, 'inc' => 1, 'dec' => 1, 'avg' => 1, + 'sum' => 1, 'or' => 1, 'and' => 1, 'set' => 1); my @result = (); my $ccucalculate = AttrVal ($name, 'ccucalculate', ''); return @result if ($ccucalculate eq ''); - my @calclist = split (/[;\n]+/, $ccucalculate); - foreach my $calculation (@calclist) { + foreach my $calculation (split (/[;\n]+/, $ccucalculate)) { my ($vt, $rn, $dpts) = split (':', $calculation, 3); - next if (!defined ($dpts)); + if (!defined($dpts) || !exists($parCount{$vt})) { + HMCCU_Log ($cl_hash, 2, "Error in reading calculation expression $calculation. Ignored."); + next; + } my $tmpdpts = ",$dpts,"; $tmpdpts =~ s/[\$\%\{\}]+//g; - HMCCU_Trace ($cl_hash, 2, $fnc, "vt=$vt, rn=$rn, dpts=$dpts, tmpdpts=$tmpdpts"); + HMCCU_Trace ($cl_hash, 2, "vt=$vt, rn=$rn, dpts=$dpts, tmpdpts=$tmpdpts"); my $f = 0; foreach my $chkey (@$chkeys) { - if ($tmpdpts =~ /,$chkey,/) { - $f = 1; - last; - } + if ($tmpdpts =~ /,$chkey,/) { $f = 1; last; } } next if ($f == 0); my @dplist = split (',', $dpts); @@ -7741,20 +7910,19 @@ sub HMCCU_CalculateReading ($$) my $newdpts = HMCCU_SubstVariables ($cl_hash, $dpts, undef); my @pars = split (',', $newdpts); my $pc = scalar (@pars); - next if ($pc != scalar(@dplist)); + if ($pc != scalar(@dplist) || $pc < $parCount{$vt}) { + HMCCU_Log ($cl_hash, 2, "Wrong number of parameters in reading calculation expression $calculation"); + next; + } $f = 0; for (my $i=0; $i<$pc; $i++) { $pars[$i] =~ s/^#//; - if ($pars[$i] eq $dplist[$i]) { - $f = 1; - last; - } + if ($pars[$i] eq $dplist[$i]) { $f = 1; last; } } next if ($f); if ($vt eq 'dewpoint' || $vt eq 'abshumidity') { # Dewpoint and absolute humidity - next if ($pc < 2); my ($tmp, $hum) = @pars; if ($tmp >= 0.0) { $a = 7.5; @@ -7781,7 +7949,6 @@ sub HMCCU_CalculateReading ($$) } elsif ($vt eq 'equ') { # Set reading to value if all variables have the same value - next if ($pc < 1); my $curval = shift @pars; my $f = 1; foreach my $newval (@pars) { @@ -7791,7 +7958,6 @@ sub HMCCU_CalculateReading ($$) } elsif ($vt eq 'min' || $vt eq 'max') { # Minimum or maximum values - next if ($pc < 1); my $curval = $pc > 1 ? shift @pars : ReadingsVal ($name, $rn, 0); foreach my $newval (@pars) { $curval = $newval if ($vt eq 'min' && $newval < $curval); @@ -7801,7 +7967,6 @@ sub HMCCU_CalculateReading ($$) } elsif ($vt eq 'inc' || $vt eq 'dec') { # Increasing or decreasing values without reset - next if ($pc < 1); my $newval = shift @pars; my $oldval = ReadingsVal ($name, $rn."_old", 0); my $curval = ReadingsVal ($name, $rn, 0); @@ -7814,7 +7979,6 @@ sub HMCCU_CalculateReading ($$) } elsif ($vt eq 'avg') { # Average value - next if ($pc < 1); if ($pc == 1) { my $newval = shift @pars; my $cnt = ReadingsVal ($name, $rn."_cnt", 0); @@ -7832,34 +7996,24 @@ sub HMCCU_CalculateReading ($$) } elsif ($vt eq 'sum') { # Sum of values - next if ($pc < 1); my $curval = $pc > 1 ? 0 : ReadingsVal ($name, $rn, 0); - foreach my $newval (@pars) { - $curval += $newval; - } + foreach my $newval (@pars) { $curval += $newval; } push (@result, $rn, $curval); } elsif ($vt eq 'or') { # Logical OR - next if ($pc < 1); my $curval = $pc > 1 ? 0 : ReadingsVal ($name, $rn, 0); - foreach my $newval (@pars) { - $curval |= $newval; - } + foreach my $newval (@pars) { $curval |= $newval; } push (@result, $rn, $curval); } elsif ($vt eq 'and') { # Logical AND - next if ($pc < 1); my $curval = $pc > 1 ? 1 : ReadingsVal ($name, $rn, 1); - foreach my $newval (@pars) { - $curval &= $newval; - } + foreach my $newval (@pars) { $curval &= $newval; } push (@result, $rn, $curval); } elsif ($vt eq 'set') { # Set reading to value - next if ($pc < 1); push (@result, $rn, join('', @pars)); } } @@ -7926,8 +8080,8 @@ sub HMCCU_Decrypt ($) sub HMCCU_DeleteReadings ($$) { my ($hash, $rnexp) = @_; - - $rnexp = '.*' if (!defined ($rnexp)); + $rnexp //= '.*'; + my @readlist = keys %{$hash->{READINGS}}; foreach my $rd (@readlist) { readingsDelete ($hash, $rd) if ($rd ne 'state' && $rd ne 'control' && $rd =~ /$rnexp/); @@ -7936,17 +8090,25 @@ sub HMCCU_DeleteReadings ($$) ###################################################################### # Update readings from hash +# If flag = 1, consider reading update attributes ###################################################################### -sub HMCCU_UpdateReadings ($$) +sub HMCCU_UpdateReadings ($$;$) { - my ($hash, $readings) = @_; + my ($hash, $readings, $flag) = @_; + $flag //= 0; + my $name = $hash->{NAME}; + + my $ccureadings = $flag ? + AttrVal ($name, 'ccureadings', HMCCU_IsFlag ($name, 'noReadings') ? 0 : 1) : 0; - readingsBeginUpdate ($hash); - foreach my $rn (keys %{$readings}) { - readingsBulkUpdate ($hash, $rn, $readings->{$rn}); + if ($ccureadings) { + readingsBeginUpdate ($hash); + foreach my $rn (keys %{$readings}) { + readingsBulkUpdate ($hash, $rn, $readings->{$rn}); + } + readingsEndUpdate ($hash, 1); } - readingsEndUpdate ($hash, 1); } ###################################################################### @@ -7994,15 +8156,9 @@ sub HMCCU_EncodeEPDisplay ($) foreach my $tok (split (',', $msg)) { 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; - } - elsif ($par =~ /^(sound|pause|repeat|signal)$/) { - $conf{$1} = $val; - } + if ($par =~ /^text([1-3])$/) { $text[$1-1] = substr ($val, 0, 12); } + elsif ($par =~ /^icon([1-3])$/) { $icon[$1-1] = $val; } + elsif ($par =~ /^(sound|pause|repeat|signal)$/) { $conf{$1} = $val; } } my $cmd = '0x02,0x0A'; @@ -8061,7 +8217,7 @@ sub HMCCU_EncodeEPDisplay ($) # Signal my $sig = $disp_signals{sig_off}; - $sig = $disp_signals{$conf{signal}} if(exists ($disp_signals{$conf{signal}})); + $sig = $disp_signals{$conf{signal}} if(exists($disp_signals{$conf{signal}})); $cmd .= ','.$sig.',0x03'; return $cmd; @@ -8081,7 +8237,7 @@ sub HMCCU_RefToString ($) if (ref($r) eq 'ARRAY') { $result .= "[\n"; foreach my $e (@$r) { - $result .= "," if ($result ne '['); + $result .= ',' if ($result ne '['); $result .= HMCCU_RefToString ($e); } $result .= "\n]"; @@ -8150,8 +8306,7 @@ sub HMCCU_GetDutyCycle ($) my $dc = 0; my @rpcports = HMCCU_GetRPCPortList ($hash); - - readingsBeginUpdate ($hash); + my %readings; foreach my $port (@rpcports) { next if ($port != 2001 && $port != 2010); @@ -8170,15 +8325,15 @@ sub HMCCU_GetDutyCycle ($) else { ($type) = HMCCU_GetRPCServerInfo ($hash, $port, 'name'); } - readingsBulkUpdate ($hash, "iface_addr_$dc", $iface->{ADDRESS}); - readingsBulkUpdate ($hash, "iface_conn_$dc", $iface->{CONNECTED}); - readingsBulkUpdate ($hash, "iface_type_$dc", $type); - readingsBulkUpdate ($hash, "iface_ducy_$dc", $iface->{DUTY_CYCLE}); + $readings{"iface_addr_$dc"} = $iface->{ADDRESS}; + $readings{"iface_conn_$dc"} = $iface->{CONNECTED}; + $readings{"iface_type_$dc"} = $type; + $readings{"iface_ducy_$dc"} = $iface->{DUTY_CYCLE}; } } - readingsEndUpdate ($hash, 1); - + HMCCU_UpdateReadings ($hash, \%readings); + return $dc; } @@ -8258,6 +8413,7 @@ sub HMCCU_ResolveName ($$) sub HMCCU_CorrectName ($) { my ($rn) = @_; + $rn =~ s/\:/\./g; $rn =~ s/[^A-Za-z\d_\.-]+/_/g; return $rn; @@ -8594,6 +8750,7 @@ sub HMCCU_MaxHashEntries ($$) intrpc - No longer supported.
extrpc - No longer supported.
logCommand - Write all set and get commands of all devices to log file with verbose level 3.
+ logEnhanced - Messages in FHEM logfile will contain line number and process ID.
logEvents - Write events from CCU into FHEM logfile
logPong - Write log message when receiving pong event if verbose level is at least 3.
noEvents - Ignore events / device updates sent by CCU. No readings will be updated!
diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm index 81eb24b84..6bcac7cf0 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.016 +# Version 4.4.019 # # (c) 2020 zap (zap01 t-online de) # @@ -39,22 +39,22 @@ sub HMCCUCHN_Initialize ($) { my ($hash) = @_; - $hash->{DefFn} = "HMCCUCHN_Define"; - $hash->{UndefFn} = "HMCCUCHN_Undef"; - $hash->{RenameFn} = "HMCCUCHN_Rename"; - $hash->{SetFn} = "HMCCUCHN_Set"; - $hash->{GetFn} = "HMCCUCHN_Get"; - $hash->{AttrFn} = "HMCCUCHN_Attr"; + $hash->{DefFn} = 'HMCCUCHN_Define'; + $hash->{UndefFn} = 'HMCCUCHN_Undef'; + $hash->{RenameFn} = 'HMCCUCHN_Rename'; + $hash->{SetFn} = 'HMCCUCHN_Set'; + $hash->{GetFn} = 'HMCCUCHN_Get'; + $hash->{AttrFn} = 'HMCCUCHN_Attr'; $hash->{parseParams} = 1; - $hash->{AttrList} = "IODev ccucalculate ". - "ccuflags:multiple-strict,ackState,logCommand,noReadings,trace,showMasterReadings,showLinkReadings,showDeviceReadings ". - "ccureadingfilter ". - "ccureadingformat:name,namelc,address,addresslc,datapoint,datapointlc ". - "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; + $hash->{AttrList} = 'IODev ccucalculate '. + 'ccuflags:multiple-strict,ackState,logCommand,noReadings,trace,showMasterReadings,showLinkReadings,showDeviceReadings '. + 'ccureadingfilter:textField-long '. + 'ccureadingformat:name,namelc,address,addresslc,datapoint,datapointlc '. + '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; } ###################################################################### @@ -69,9 +69,7 @@ sub HMCCUCHN_Define ($@) my $usage = "Usage: define $name HMCCUCHN {device} ['readonly'] ['noDefaults'|'defaults'] [iodev={iodevname}]"; return $usage if (@$a < 3); - my $devname = shift @$a; - my $devtype = shift @$a; - my $devspec = shift @$a; + my ($devname, $devtype, $devspec) = splice (@$a, 0, 3); my $ioHash; my $existDev = HMCCU_ExistsClientDevice ($devspec, $devtype); @@ -81,25 +79,25 @@ sub HMCCUCHN_Define ($@) $hash->{hmccu}{devspec} = $devspec; # Defaults - $hash->{readonly} = "no"; + $hash->{readonly} = 'no'; $hash->{hmccu}{channels} = 1; + $hash->{hmccu}{defaults} = 0; # Parse optional command line parameters 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; } + elsif ($arg eq 'defaults' && $init_done) { $hash->{hmccu}{nodefaults} = 0; } + else { return $usage; } $n++; } # IO device can be set by command line parameter iodev, otherwise try to detect IO device - if (exists ($h->{iodev})) { - return "Specified IO Device ".$h->{iodev}." does not exist" if (!exists($defs{$h->{iodev}})); - return "Specified IO Device ".$h->{iodev}." is not a HMCCU device" - if ($defs{$h->{iodev}}->{TYPE} ne 'HMCCU'); + if (exists($h->{iodev})) { + return "Device $h->{iodev} does not exist" if (!exists($defs{$h->{iodev}})); + return "Type of device $h->{iodev} is not HMCCU" if ($defs{$h->{iodev}}->{TYPE} ne 'HMCCU'); $ioHash = $defs{$h->{iodev}}; } else { @@ -111,18 +109,15 @@ sub HMCCUCHN_Define ($@) # Interactive define command while CCU not ready or no IO device defined if (!defined($ioHash)) { my ($ccuactive, $ccuinactive) = HMCCU_IODeviceStates (); - if ($ccuinactive > 0) { - return "CCU and/or IO device not ready. Please try again later"; - } - else { - return "Cannot detect IO device"; - } + return $ccuinactive > 0 ? + 'CCU and/or IO device not ready. Please try again later' : + 'Cannot detect IO device'; } } else { # CCU not ready during FHEM start - if (!defined ($ioHash) || $ioHash->{ccustate} ne 'active') { - Log3 $name, 2, "HMCCUCHN: [$devname] Cannot detect IO device, maybe CCU not ready. Trying later ..."; + if (!defined($ioHash) || $ioHash->{ccustate} ne 'active') { + HMCCU_Log ($hash, 2, 'Cannot detect IO device, maybe CCU not ready. Trying later ...'); $hash->{ccudevstate} = 'pending'; return undef; } @@ -130,8 +125,8 @@ sub HMCCUCHN_Define ($@) # Initialize FHEM device, set IO device my $rc = HMCCUCHN_InitDevice ($ioHash, $hash); - return "Invalid or unknown CCU channel name or address" if ($rc == 1); - return "Can't assign I/O device ".$ioHash->{NAME} if ($rc == 2); + return 'Invalid or unknown CCU channel name or address' if ($rc == 1); + return "Can't assign I/O device $ioHash->{NAME}" if ($rc == 2); return undef; } @@ -152,22 +147,23 @@ sub HMCCUCHN_InitDevice ($$) return 1 if (!HMCCU_IsValidChannel ($ioHash, $devspec, 7)); my ($di, $da, $dn, $dt, $dc) = HMCCU_GetCCUDeviceParam ($ioHash, $devspec); - return 1 if (!defined ($da)); + return 1 if (!defined($da)); # Inform HMCCU device about client device - return 2 if (!HMCCU_AssignIODevice ($devHash, $ioHash->{NAME}, undef)); + return 2 if (!HMCCU_AssignIODevice ($devHash, $ioHash->{NAME})); - $devHash->{ccuif} = $di; - $devHash->{ccuaddr} = $da; - $devHash->{ccuname} = $dn; - $devHash->{ccutype} = $dt; - $devHash->{ccudevstate} = 'active'; + $devHash->{ccuif} = $di; + $devHash->{ccuaddr} = $da; + $devHash->{ccuname} = $dn; + $devHash->{ccutype} = $dt; + $devHash->{ccudevstate} = 'active'; if ($init_done) { # Interactive device definition HMCCU_AddDevice ($ioHash, $di, $da, $devHash->{NAME}); HMCCU_UpdateDevice ($ioHash, $devHash); HMCCU_UpdateDeviceRoles ($ioHash, $devHash); + HMCCU_UpdateRoleCommands ($ioHash, $devHash); if (!exists($devHash->{hmccu}{nodefaults}) || $devHash->{hmccu}{nodefaults} == 0) { if (!HMCCU_SetDefaultAttributes ($devHash)) { HMCCU_SetDefaults ($devHash); @@ -217,19 +213,17 @@ sub HMCCUCHN_Attr ($@) my ($cmd, $name, $attrname, $attrval) = @_; my $hash = $defs{$name}; - if ($cmd eq "set") { - return "Missing attribute value" if (!defined ($attrval)); + if ($cmd eq 'set') { + return 'Missing attribute value' if (!defined($attrval)); if ($attrname eq 'IODev') { $hash->{IODev} = $defs{$attrval}; } elsif ($attrname eq 'statevals') { - return "Device is read only" if ($hash->{readonly} eq 'yes'); + return 'Device is read only' if ($hash->{readonly} eq 'yes'); } } - if ($init_done) { - HMCCU_RefreshReadings ($hash); - } + HMCCU_RefreshReadings ($hash) if ($init_done); return undef; } @@ -242,24 +236,20 @@ sub HMCCUCHN_Set ($@) { my ($hash, $a, $h) = @_; my $name = shift @$a; - my $opt = shift @$a; - - return 'No set command specified' if (!defined($opt)); + my $opt = shift @$a // return 'No set command specified'; $opt = lc($opt); # Check device state - return undef if (!defined($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' || - !defined($hash->{IODev})); - return undef if ($hash->{readonly} eq 'yes' && $opt ne '?' && - $opt !~ /^(clear|config|defaults)$/); - return undef if (AttrVal ($name, 'disable', 0) == 1); + return "Device state doesn't allow set commands" + if (!defined($hash->{ccudevstate}) || + $hash->{ccudevstate} eq 'pending' || !defined($hash->{IODev}) || + ($hash->{readonly} eq 'yes' && $opt !~ /^(\?|clear|config|defaults)$/) || + AttrVal ($name, 'disable', 0) == 1); my $ioHash = $hash->{IODev}; my $ioName = $ioHash->{NAME}; - if (HMCCU_IsRPCStateBlocking ($ioHash)) { - return undef if ($opt eq '?'); - return 'HMCCUCHN: CCU busy'; - } + return ($opt eq '?' ? undef : 'Cannot perform set commands. CCU busy') + if (HMCCU_IsRPCStateBlocking ($ioHash)); my $ccutype = $hash->{ccutype}; my $ccuaddr = $hash->{ccuaddr}; @@ -269,58 +259,31 @@ sub HMCCUCHN_Set ($@) # Get state and control datapoints my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash); - # Get additional commands (including state commands) - my $roleCmds = HMCCU_GetSpecialCommands ($hash, $cc); - - my %pset = ('V' => 'VALUES', 'M' => 'MASTER', 'D' => 'MASTER'); - my $cmdList = ''; - my %valLookup; - foreach my $cmd (keys %$roleCmds) { - $cmdList .= " $cmd"; - my @setList = split (/\s+/, $roleCmds->{$cmd}); - foreach my $set (@setList) { - my ($ps, $dpt, $par) = split(/:/, $set); - my @argList = (); - if ($par =~ /^#/) { - my $adr = $ccuaddr; - $adr =~ s/:[0-9]{1,2}$//; - my $paramDef = HMCCU_GetParamDef ($ioHash, $adr, $pset{$ps}, $dpt); - if (defined($paramDef)) { - if ($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 !~ /^\?/) { - @argList = split (',', $par); - } - $cmdList .= scalar(@argList) > 1 ? ":$par" : ":noArg"; - } - } - + # Get 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|on-for-timer|on-till)$/) { + return HMCCU_SetError ($hash, -14) if ($cd eq ''); + return HMCCU_SetError ($hash, -8) if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, $cd, 2)); + } + my $result = ''; my $rc; # Log commands HMCCU_Log ($hash, 3, "set $name $opt ".join (' ', @$a)) if ($opt ne '?' && $ccuflags =~ /logCommand/ || HMCCU_IsFlag ($ioName, 'logCommand')); - + if ($opt eq 'control') { - return HMCCU_SetError ($hash, -14) if ($cd eq ''); - my $value = shift @$a; - return HMCCU_SetError ($hash, "Usage: set $name control {value}") if (!defined($value)); - + my $value = shift @$a // return HMCCU_SetError ($hash, "Usage: set $name control {value}"); $value =~ s/\\_/%20/g; - $rc = HMCCU_SetMultipleDatapoints ($hash, { "001.$ccuif.$ccuaddr.$cd" => HMCCU_Substitute ($value, $stateVals, 1, undef, '') } ); @@ -331,14 +294,13 @@ sub HMCCUCHN_Set ($@) my %dpval; my $i = 0; + push (@$a, %${h}) if (defined($h)); while (my $objname = shift @$a) { - my $objvalue = shift @$a; - $i++; - - return HMCCU_SetError ($hash, $usage) if (!defined ($objvalue)); + 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 =~ s/\\_/%20/g; $objvalue = HMCCU_Substitute ($objvalue, $stateVals, 1, undef, '') @@ -346,36 +308,17 @@ sub HMCCUCHN_Set ($@) $dpval{"$no.$ccuif.$ccuaddr.$objname"} = $objvalue; } - if (defined($h)) { - foreach my $objname (keys %$h) { - my $objvalue = $h->{$objname}; - $i++; - my $no = sprintf ("%03d", $i); - return HMCCU_SetError ($hash, -8) - if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, $objname, 2)); - $objvalue =~ s/\\_/%20/g; - $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); + return HMCCU_SetError ($hash, $usage) if (scalar(keys %dpval) < 1); $rc = HMCCU_SetMultipleDatapoints ($hash, \%dpval); return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); } elsif ($opt eq 'toggle') { - return HMCCU_SetError ($hash, -15) if ($stateVals eq ''); - return HMCCU_SetError ($hash, -12) if ($cc eq ''); - return HMCCU_SetError ($hash, -14) if ($cd eq ''); - return HMCCU_SetError ($hash, -8) - if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, $cd, 2)); - my $stc = scalar (@states); + return HMCCU_SetError ($hash, -15) if ($stc == 0); + my $curState = defined($hash->{hmccu}{dp}{"$cc.$cd"}{VALUES}{SVAL}) ? $hash->{hmccu}{dp}{"$cc.$cd"}{VALUES}{SVAL} : $states[0]; - my $newState = ''; my $st = 0; while ($st < $stc) { @@ -383,9 +326,7 @@ sub HMCCUCHN_Set ($@) $newState = ($st == $stc-1) ? $states[0] : $states[$st+1]; last; } - else { - $st++; - } + $st++; } return HMCCU_SetError ($hash, "Current device state doesn't match any state value") @@ -396,122 +337,8 @@ sub HMCCUCHN_Set ($@) ); return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); } - elsif (defined($roleCmds) && exists($roleCmds->{$opt})) { - my $value; - my %dpval; - my %cfval; - - my @setList = split (/\s+/, $roleCmds->{$opt}); - my $i = 0; - foreach my $set (@setList) { - my ($ps, $dpt, $par) = split(/:/, $set); - - return HMCCU_SetError ($hash, "Syntax error in definition of command $opt") - if (!defined($par)); - if (!HMCCU_IsValidParameter ($hash, $ccuaddr, $pset{$ps}, $dpt)) { - HMCCU_Trace ($hash, 2, "Set", "Invalid parameter $ps $dpt"); - return HMCCU_SetError ($hash, -8); - } - - if ($par =~ /^\?(.+)$/) { - $par = $1; - my ($parName, $parDef) = split ('=', $par); - $value = shift @$a; - if (!defined($value) && defined($parDef)) { - if ($parDef =~ /^[+-][0-9]+$/) { - return HMCCU_SetError ($hash, "Current value of $cc.$dpt not available") - if (!defined($hash->{hmccu}{dp}{"$cc.$dpt"}{$pset{$ps}}{SVAL})); - $value = $hash->{hmccu}{dp}{"$cc.$dpt"}{$pset{$ps}}{SVAL}+int($parDef); - } - else { - $value = $parDef; - } - } - - return HMCCU_SetError ($hash, "Missing parameter $parName") - if (!defined($value)); - } - else { - if (exists($valLookup{$ps}{$dpt})) { - return HMCCU_SetError ($hash, "Illegal value $par. Use one of ".join(',', keys %{$valLookup{$ps}{$dpt}})) - if (!exists($valLookup{$ps}{$dpt}{$par})); - $value = $valLookup{$ps}{$dpt}{$par}; - } - else { - $value = $par; - } - } - - if ($opt eq 'pct' || $opt eq 'level') { - my $timespec = shift @$a; - my $ramptime = shift @$a; - - # Set on time - if (defined ($timespec)) { - return HMCCU_SetError ($hash, "Can't find ON_TIME datapoint for device type $ccutype") - if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, "ON_TIME", 2)); - if ($timespec =~ /^[0-9]{2}:[0-9]{2}/) { - $timespec = HMCCU_GetTimeSpec ($timespec); - return HMCCU_SetError ($hash, "Wrong time format. Use HH:MM[:SS]") if ($timespec < 0); - } - $dpval{"001.$ccuif.$ccuaddr.ON_TIME"} = $timespec if ($timespec > 0); - } - - # Set ramp time - if (defined($ramptime)) { - return HMCCU_SetError ($hash, "Can't find RAMP_TIME datapoint for device type $ccutype") - if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, "RAMP_TIME", 2)); - $dpval{"002.$ccuif.$ccuaddr.RAMP_TIME"} = $ramptime if (defined ($ramptime)); - } - - $dpval{"003.$ccuif.$ccuaddr.$dpt"} = $value; - last; - } - else { - if ($ps eq 'V') { - my $no = sprintf ("%03d", $i); - $dpval{"$i.$ccuif.$ccuaddr.$dpt"} = $value; - $i++; - } - else { - $cfval{$dpt} = $value; - } - } - } - - if (scalar(keys %dpval) > 0) { - $rc = HMCCU_SetMultipleDatapoints ($hash, \%dpval); - return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); - } - if (scalar(keys %cfval) > 0) { - ($rc, $result) = HMCCU_SetMultipleParameters ($hash, $ccuaddr, $h, 'MASTER'); - return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); - } - } - elsif ($opt eq 'on-for-timer' || $opt eq 'on-till') { - return HMCCU_SetError ($hash, -15) if ($stateVals eq ''); - return HMCCU_SetError ($hash, "No state value for 'on' defined") - if (!exists($stateCmds{"on"})); - 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, "Can't find ON_TIME datapoint for device type") - if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, "ON_TIME", 2)); - - my $timespec = shift @$a; - return HMCCU_SetError ($hash, "Usage: set $name $opt {ontime-spec}") - if (!defined ($timespec)); - - if ($opt eq 'on-till') { - $timespec = HMCCU_GetTimeSpec ($timespec); - return HMCCU_SetError ($hash, "Wrong time format. Use HH:MM[:SS]") if ($timespec < 0); - } - - $rc = HMCCU_SetMultipleDatapoints ($hash, { - "001.$ccuif.$ccuaddr.ON_TIME" => $timespec, - "002.$ccuif.$ccuaddr.$cd" => $stateCmds{"on"} - }); - return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); + elsif (exists($hash->{hmccu}{roleCmds}{$opt})) { + return HMCCU_ExecuteRoleCommand ($ioHash, $hash, $opt, $a, $h); } elsif ($opt eq 'clear') { my $rnexp = shift @$a; @@ -569,7 +396,7 @@ sub HMCCUCHN_Set ($@) } } elsif (!HMCCU_IsChnAddr ($receiver, 0)) { - my ($rcvAdd, $rcvChn) = HMCCU_GetAddress ($ioHash, $receiver, '', ''); + 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"; @@ -583,18 +410,18 @@ sub HMCCUCHN_Set ($@) return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); } elsif ($opt eq 'defaults') { - $rc = HMCCU_SetDefaultAttributes ($hash); + my $mode = shift @$a // 'update'; + $rc = HMCCU_SetDefaultAttributes ($hash, { mode => $mode, role => undef, ctrlChn => $cc }); $rc = HMCCU_SetDefaults ($hash) if (!$rc); + HMCCU_RefreshReadings ($hash) if ($rc); return HMCCU_SetError ($hash, $rc == 0 ? "No default attributes found" : "OK"); } else { - my $retmsg = "clear defaults:noArg"; + my $retmsg = "clear defaults:reset,update"; if ($hash->{readonly} ne 'yes') { - $retmsg .= " config datapoint".$cmdList; -# $retmsg .= ':'.join(',', @states) if (scalar(@states) > 0); + $retmsg .= ' config datapoint'; + $retmsg .= " $cmdList" if ($cmdList ne ''); $retmsg .= ' toggle:noArg' if (scalar(@states) > 0); - $retmsg .= " on-for-timer on-till" - if ($cc ne '' && HMCCU_IsValidDatapoint ($hash, $ccutype, $cc, "ON_TIME", 2)); } return AttrTemplate_Set ($hash, $retmsg, $name, $opt, @$a); } @@ -608,9 +435,7 @@ sub HMCCUCHN_Get ($@) { my ($hash, $a, $h) = @_; my $name = shift @$a; - my $opt = shift @$a; - - return "No get command specified" if (!defined ($opt)); + my $opt = shift @$a // return 'No get command specified'; $opt = lc($opt); return undef if (!defined ($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' || @@ -621,15 +446,15 @@ sub HMCCUCHN_Get ($@) my $ioHash = $hash->{IODev}; my $ioName = $ioHash->{NAME}; - if (HMCCU_IsRPCStateBlocking ($ioHash)) { - return undef if ($opt eq '?'); - return "HMCCUCHN: CCU busy"; - } + + return $opt eq '?' ? undef : 'Cannot perform get command. CCU busy' + if (HMCCU_IsRPCStateBlocking ($ioHash)); my $ccutype = $hash->{ccutype}; my $ccuaddr = $hash->{ccuaddr}; my $ccuif = $hash->{ccuif}; my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); + my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash); my $result = ''; my $rc; @@ -639,33 +464,20 @@ sub HMCCUCHN_Get ($@) if ($opt ne '?' && $ccuflags =~ /logCommand/ || HMCCU_IsFlag ($ioName, 'logCommand')); if ($opt eq 'datapoint') { - my $objname = shift @$a; - - return HMCCU_SetError ($hash, "Usage: get $name datapoint {datapoint}") - if (!defined ($objname)); + my $objname = shift @$a // return HMCCU_SetError ($hash, "Usage: get $name datapoint {datapoint}"); return HMCCU_SetError ($hash, -8) if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, $objname, 1)); $objname = $ccuif.'.'.$ccuaddr.'.'.$objname; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname, 0); - return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0); - return $result; + return $rc < 0 ? HMCCU_SetError ($hash, $rc, $result) : $result; } -# elsif ($opt eq 'update') { -# my $ccuget = shift @$a; -# $ccuget = 'Attr' if (!defined ($ccuget)); -# if ($ccuget !~ /^(Attr|State|Value)$/) { -# return HMCCU_SetError ($hash, "Usage: get $name update [{'State'|'Value'}]"); -# } -# $rc = HMCCU_GetUpdate ($hash, $ccuaddr, $ccuget); -# return HMCCU_SetError ($hash, $rc) if ($rc < 0); -# return undef; -# } elsif ($opt eq 'deviceinfo') { my ($a, $c) = HMCCU_SplitChnAddr ($ccuaddr); $result = HMCCU_GetDeviceInfo ($hash, $a); - return HMCCU_SetError ($hash, -2) if ($result eq ''); + return HMCCU_SetError ($hash, -2) if ($result eq ''); my $devInfo = HMCCU_FormatDeviceInfo ($result); + $devInfo .= "StateDatapoint = $sc.$sd\nControlDatapoint = $cc.$cd"; return $devInfo; } elsif ($opt =~ /^(config|values|update)$/) { @@ -711,10 +523,10 @@ sub HMCCUCHN_Get ($@) $res .= "Device $da\n"; foreach my $dc (sort keys %{$convRes->{$da}}) { foreach my $ps (sort keys %{$convRes->{$da}{$dc}}) { - $res .= " Channel $dc [$ps]\n"; - $res .= join ("\n", map { - " ".$_.' = '.$convRes->{$da}{$dc}{$ps}{$_} - } sort keys %{$convRes->{$da}{$dc}{$ps}})."\n"; + $res .= " Channel $dc [$ps]\n". + join ("\n", map { + " ".$_.' = '.$convRes->{$da}{$dc}{$ps}{$_} + } sort keys %{$convRes->{$da}{$dc}{$ps}})."\n"; } } } @@ -734,6 +546,10 @@ sub HMCCUCHN_Get ($@) elsif ($opt eq 'defaults') { return HMCCU_GetDefaults ($hash, 0); } + elsif ($opt eq 'weekprogram') { + my $program = shift @$a; + return HMCCU_DisplayWeekProgram ($hash, $program); + } else { my $retmsg = "HMCCUCHN: Unknown argument $opt, choose one of defaults:noArg datapoint"; @@ -743,6 +559,8 @@ sub HMCCUCHN_Get ($@) $retmsg .= ":".join(",",@valuelist) if ($valuecount > 0); $retmsg .= " update:noArg deviceInfo:noArg config:noArg". " deviceDesc:noArg paramsetDesc:noArg values:noArg"; + $retmsg .= ' weekProgram:all,'.join(',', sort keys %{$hash->{hmccu}{tt}}) + if (exists($hash->{hmccu}{tt})); return $retmsg; } @@ -753,7 +571,7 @@ sub HMCCUCHN_Get ($@) =pod =item device -=item summary controls HMCCU client devices for Homematic CCU2 - FHEM integration +=item summary controls HMCCU client devices for Homematic CCU2/3 - FHEM integration =begin html @@ -816,9 +634,12 @@ sub HMCCUCHN_Get ($@) set temp_control datapoint SET_TEMPERATURE 21
set temp_control datapoint AUTO_MODE 1 SET_TEMPERATURE 21
-
  • set <name> defaults
    +
  • set <name> defaults ['reset'|'update']
    Set default attributes for CCU device type. Default attributes are only available for - some device types and for some channels of a device type. + some device types and for some channels of a device type. If option 'reset' is specified, + the following attributes are deleted before the new attributes are set: + 'ccureadingname', 'ccuscaleval', 'eventMap', 'substexcl', 'webCmd', 'widgetOverride'. + 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 @@ -948,6 +769,9 @@ sub HMCCUCHN_Get ($@)

  • get <name> values
    Same as 'get update' but using RPC instead of ReGa. +

  • +
  • get <name> weekProgram [<program-number>|all]
    + Display week programs. This command is only available if a device supports week programs.

  • diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm index 42635453a..08e4d2bdc 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.016 +# Version 4.4.022 # # (c) 2020 zap (zap01 t-online de) # @@ -38,21 +38,22 @@ sub HMCCUDEV_Initialize ($) { my ($hash) = @_; - $hash->{DefFn} = "HMCCUDEV_Define"; - $hash->{UndefFn} = "HMCCUCHN_Undef"; - $hash->{RenameFn} = "HMCCUDEV_Rename"; - $hash->{SetFn} = "HMCCUDEV_Set"; - $hash->{GetFn} = "HMCCUDEV_Get"; - $hash->{AttrFn} = "HMCCUDEV_Attr"; + $hash->{DefFn} = 'HMCCUDEV_Define'; + $hash->{UndefFn} = 'HMCCUCHN_Undef'; + $hash->{RenameFn} = 'HMCCUDEV_Rename'; + $hash->{SetFn} = 'HMCCUDEV_Set'; + $hash->{GetFn} = 'HMCCUDEV_Get'; + $hash->{AttrFn} = 'HMCCUDEV_Attr'; $hash->{parseParams} = 1; - $hash->{AttrList} = "IODev ccuaggregate:textField-long ccucalculate:textField-long ". - "ccuflags:multiple-strict,ackState,logCommand,nochn0,noReadings,trace ccureadingfilter:textField-long ". - "ccureadingformat:name,namelc,address,addresslc,datapoint,datapointlc ". - "ccureadingname:textField-long ". - "ccuget:State,Value ccuscaleval ccuSetOnChange ccuverify:0,1,2 disable:0,1 ". - "hmstatevals:textField-long statevals substexcl substitute:textField-long statechannel ". - "controlchannel statedatapoint controldatapoint stripnumber peer:textField-long ". + $hash->{AttrList} = 'IODev ccuaggregate:textField-long ccucalculate:textField-long '. + 'ccuflags:multiple-strict,ackState,logCommand,noReadings,trace,showMasterReadings,showLinkReadings,showDeviceReadings '. + 'ccureadingfilter:textField-long '. + 'ccureadingformat:name,namelc,address,addresslc,datapoint,datapointlc '. + '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 '. $readingFnAttributes; } @@ -68,7 +69,7 @@ sub HMCCUDEV_Define ($@) my $usage = "Usage: define $name HMCCUDEV {device|'virtual'} [control-channel] ". "['readonly'] ['noDefaults'|'defaults'] [iodev={iodev-name}] [address={virtual-device-no}]". "[{groupexp=regexp|group={device|channel}[,...]]"; - return $usage if (scalar (@$a) < 3); + return $usage if (scalar(@$a) < 3); my @errmsg = ( "OK", @@ -81,47 +82,37 @@ sub HMCCUDEV_Define ($@) "Too many virtual devices" ); - my $devname = shift @$a; - my $devtype = shift @$a; - my $devspec = shift @$a; - + my ($devname, $devtype, $devspec) = splice (@$a, 0, 3); my $ioHash = undef; - -# my $existDev = HMCCU_ExistsClientDevice ($devspec, $devtype); -# return "FHEM device $existDev for CCU device $devspec already exists" if (defined($existDev)); # 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}{defaults} = 0; - if (exists ($h->{address})) { - if ($init_done || $devspec ne 'virtual') { - return "Option address not allowed"; - } - else { - $hash->{hmccu}{address} = $h->{address}; - } + if (exists($h->{address})) { + return 'Option address not allowed' if ($init_done || $devspec ne 'virtual'); + $hash->{hmccu}{address} = $h->{address}; } else { - return "Option address not specified" if (!$init_done && $devspec eq 'virtual'); + return 'Option address not specified' if (!$init_done && $devspec eq 'virtual'); } # Parse optional command line parameters foreach my $arg (@$a) { - 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; } - elsif ($arg =~ /^[0-9]+$/) { $attr{$name}{controlchannel} = $arg; } - else { return $usage; } + elsif ($arg eq 'defaults' && $init_done) { $hash->{hmccu}{nodefaults} = 0; } + elsif ($arg =~ /^[0-9]+$/) { $attr{$name}{controlchannel} = $arg; } + else { return $usage; } } # IO device can be set by command line parameter iodev, otherwise try to detect IO device - if (exists ($h->{iodev})) { - return "Specified IO Device ".$h->{iodev}." does not exist" if (!exists ($defs{$h->{iodev}})); - return "Specified IO Device ".$h->{iodev}." is not a HMCCU device" - if ($defs{$h->{iodev}}->{TYPE} ne 'HMCCU'); + if (exists($h->{iodev})) { + return "IO device $h->{iodev} does not exist" if (!exists($defs{$h->{iodev}})); + return "Type of device $h->{iodev} is not HMCCU" if ($defs{$h->{iodev}}->{TYPE} ne 'HMCCU'); $ioHash = $defs{$h->{iodev}}; } else { @@ -133,18 +124,14 @@ sub HMCCUDEV_Define ($@) # Interactive define command while CCU not ready if (!defined($ioHash)) { my ($ccuactive, $ccuinactive) = HMCCU_IODeviceStates (); - if ($ccuinactive > 0) { - return "CCU and/or IO device not ready. Please try again later"; - } - else { - return "Cannot detect IO device"; - } + return $ccuinactive > 0 ? 'CCU and/or IO device not ready. Please try again later' : + 'Cannot detect IO device'; } } else { # CCU not ready during FHEM start if (!defined($ioHash) || $ioHash->{ccustate} ne 'active') { - Log3 $name, 2, "HMCCUDEV: [$devname] Cannot detect IO device, maybe CCU not ready. Trying later ..."; + HMCCU_Log ($hash, 2, 'Cannot detect IO device, maybe CCU not ready. Trying later ...'); $hash->{ccudevstate} = 'pending'; return undef; } @@ -152,7 +139,7 @@ sub HMCCUDEV_Define ($@) # Initialize FHEM device, set IO device my $rc = HMCCUDEV_InitDevice ($ioHash, $hash); - return $errmsg[$rc] if ($rc > 0); + return $errmsg[$rc] if ($rc > 0 && $rc < scalar(@errmsg)); return undef; } @@ -180,7 +167,7 @@ sub HMCCUDEV_InitDevice ($$) if ($devspec eq 'virtual') { my $no = 0; - if (exists ($devHash->{hmccu}{address})) { + if (exists($devHash->{hmccu}{address})) { # Only true during FHEM start $no = $devHash->{hmccu}{address}; } @@ -198,11 +185,11 @@ sub HMCCUDEV_InitDevice ($$) } # Inform HMCCU device about client device - return 2 if (!HMCCU_AssignIODevice ($devHash, $ioHash->{NAME}, undef)); + return 2 if (!HMCCU_AssignIODevice ($devHash, $ioHash->{NAME})); - $devHash->{ccuif} = 'fhem'; - $devHash->{ccuaddr} = sprintf ("VIR%07d", $no); - $devHash->{ccuname} = $name; + $devHash->{ccuif} = 'fhem'; + $devHash->{ccuaddr} = sprintf ("VIR%07d", $no); + $devHash->{ccuname} = $name; $devHash->{ccudevstate} = 'active'; } else { @@ -213,13 +200,13 @@ sub HMCCUDEV_InitDevice ($$) $gdname = $dn; # Inform HMCCU device about client device - return 2 if (!HMCCU_AssignIODevice ($devHash, $ioHash->{NAME}, undef)); + return 2 if (!HMCCU_AssignIODevice ($devHash, $ioHash->{NAME})); - $devHash->{ccuif} = $di; - $devHash->{ccuaddr} = $da; - $devHash->{ccuname} = $dn; - $devHash->{ccutype} = $dt; - $devHash->{ccudevstate} = 'active'; + $devHash->{ccuif} = $di; + $devHash->{ccuaddr} = $da; + $devHash->{ccuname} = $dn; + $devHash->{ccutype} = $dt; + $devHash->{ccudevstate} = 'active'; $devHash->{hmccu}{channels} = $dc; if ($init_done) { @@ -227,6 +214,7 @@ sub HMCCUDEV_InitDevice ($$) HMCCU_AddDevice ($ioHash, $di, $da, $devHash->{NAME}); HMCCU_UpdateDevice ($ioHash, $devHash); HMCCU_UpdateDeviceRoles ($ioHash, $devHash); + HMCCU_UpdateRoleCommands ($ioHash, $devHash, $attr{$devHash->{NAME}}{controlchannel}); if (!exists($devHash->{hmccu}{nodefaults}) || $devHash->{hmccu}{nodefaults} == 0) { if (!HMCCU_SetDefaultAttributes ($devHash)) { HMCCU_SetDefaults ($devHash); @@ -246,17 +234,12 @@ sub HMCCUDEV_InitDevice ($$) } elsif (exists ($devHash->{hmccu}{group})) { # Group devices specified by comma separated name list - my @gdevlist = split (",", $devHash->{hmccu}{group}); - $devHash->{ccugroup} = '' if (@gdevlist > 0); + my @gdevlist = split (',', $devHash->{hmccu}{group}); + $devHash->{ccugroup} = '' if (scalar(@gdevlist) > 0); foreach my $gd (@gdevlist) { - my ($gda, $gdc, $gdo) = ('', '', '', ''); - return 1 if (!HMCCU_IsValidDevice ($ioHash, $gd, 7)); - - ($gda, $gdc) = HMCCU_GetAddress ($ioHash, $gd, '', ''); - $gdo = $gda; - $gdo .= ':'.$gdc if ($gdc ne ''); - push @devlist, $gdo; + my ($gda, $gdc) = HMCCU_GetAddress ($ioHash, $gd); + push @devlist, $gdc eq '' ? "$gda:$gdc" : $gda; $gdcount++; } } @@ -310,9 +293,7 @@ sub HMCCUDEV_Undef ($$) if ($hash->{IODev}) { HMCCU_RemoveDevice ($hash->{IODev}, $hash->{ccuif}, $hash->{ccuaddr}, $hash->{NAME}); - if ($hash->{ccuif} eq 'fhem') { - HMCCU_DeleteDevice ($hash->{IODev}); - } + HMCCU_DeleteDevice ($hash->{IODev}) if ($hash->{ccuif} eq 'fhem'); } return undef; @@ -351,9 +332,7 @@ sub HMCCUDEV_Attr ($@) } } - if ($init_done) { - HMCCU_RefreshReadings ($hash); - } + HMCCU_RefreshReadings ($hash) if ($init_done); return; } @@ -366,24 +345,19 @@ sub HMCCUDEV_Set ($@) { my ($hash, $a, $h) = @_; my $name = shift @$a; - my $opt = shift @$a; - - return 'No set command specified' if (!defined($opt)); + my $opt = shift @$a // return 'No set command specified'; + $opt = lc($opt); # Check device state - return undef if (!defined($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' || - !defined($hash->{IODev})); - return undef if ($hash->{readonly} eq 'yes' && $opt ne '?' && $opt !~ /^(clear|config|defaults)$/); - return undef if (AttrVal ($name, 'disable', 0) == 1); + return "Device state doesn't allow set commands" + if (!defined($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' || !defined($hash->{IODev}) || + ($hash->{readonly} eq 'yes' && $opt !~ /^(\?|clear|config|defaults)$/) || + AttrVal ($name, 'disable', 0) == 1); my $ioHash = $hash->{IODev}; - my $hmccu_name = $ioHash->{NAME}; - - # Check if CCU is busy - if (HMCCU_IsRPCStateBlocking ($ioHash)) { - return undef if ($opt eq '?'); - return 'HMCCUDEV: CCU busy'; - } + my $ioName = $ioHash->{NAME}; + return ($opt eq '?' ? undef : 'Cannot perform set commands. CCU busy') + if (HMCCU_IsRPCStateBlocking ($ioHash)); # Get parameters of current device my $ccutype = $hash->{ccutype}; @@ -393,36 +367,9 @@ sub HMCCUDEV_Set ($@) # Get state and control datapoints my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash); - - # Get additional commands (including state commands) - my $roleCmds = HMCCU_GetSpecialCommands ($hash, $cc); - my %pset = ('V' => 'VALUES', 'M' => 'MASTER', 'D' => 'MASTER'); - my $cmdList = ''; - my %valLookup; - foreach my $cmd (keys %$roleCmds) { - $cmdList .= " $cmd"; - my @setList = split (/\s+/, $roleCmds->{$cmd}); - foreach my $set (@setList) { - my ($ps, $dpt, $par) = split(/:/, $set); - my @argList = (); - if ($par =~ /^#/) { - my $paramDef = HMCCU_GetParamDef ($ioHash, $ccuaddr, $pset{$ps}, $dpt); - if (defined($paramDef)) { - if ($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 !~ /^\?/) { - @argList = split (',', $par); - } - $cmdList .= scalar(@argList) > 1 ? ":$par" : ":noArg"; - } - } + # Get additional commands + my $cmdList = $hash->{hmccu}{cmdlist} // ''; # Get state values related to control command and datapoint my $stateVals = HMCCU_GetStateValues ($hash, $cd, $cc); @@ -430,23 +377,24 @@ sub HMCCUDEV_Set ($@) my %stateCmds = @stateCmdList; my @states = keys %stateCmds; + # Some commands require a control channel and datapoint + if ($opt =~ /^(control|toggle|on-for-timer|on-till)$/) { + 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, -7) if ($cc >= $hash->{hmccu}{channels}); + } + my $result = ''; my $rc; # Log commands HMCCU_Log ($hash, 3, "set $name $opt ".join (' ', @$a)) - if ($opt ne '?' && $ccuflags =~ /logCommand/ || HMCCU_IsFlag ($hmccu_name, 'logCommand')); + if ($opt ne '?' && $ccuflags =~ /logCommand/ || HMCCU_IsFlag ($ioName, 'logCommand')); if ($opt eq 'control') { - return HMCCU_SetError ($hash, -12) if ($cc eq ''); - return HMCCU_SetError ($hash, -14) if ($cd eq ''); - return HMCCU_SetError ($hash, -7) if ($cc >= $hash->{hmccu}{channels}); - - my $value = shift @$a; - return HMCCU_SetError ($hash, "Usage: set $name control {value}") if (!defined($value)); - + my $value = shift @$a // return HMCCU_SetError ($hash, "Usage: set $name control {value}"); $value =~ s/\\_/%20/g; - $rc = HMCCU_SetMultipleDatapoints ($hash, { "001.$ccuif.$ccuaddr:$cc.$cd" => HMCCU_Substitute ($value, $stateVals, 1, undef, '') } ); @@ -457,6 +405,7 @@ sub HMCCUDEV_Set ($@) my %dpval; my $i = 0; + push (@$a, %${h}) if (defined($h)); while (my $objname = shift @$a) { my $value = shift @$a; $i += 1; @@ -464,24 +413,17 @@ sub HMCCUDEV_Set ($@) if ($ccutype eq 'HM-Dis-EP-WM55' && !defined($value)) { $value = ''; foreach my $t (keys %{$h}) { - if ($value eq '') { - $value = $t.'='.$h->{$t}; - } - else { - $value .= ','.$t.'='.$h->{$t}; - } + $value .= $value eq '' ? $t.'='.$h->{$t} : ','.$t.'='.$h->{$t}; } } - return HMCCU_SetError ($hash, $usage) if (!defined ($value) || $value eq ''); + return HMCCU_SetError ($hash, $usage) if (!defined($value) || $value eq ''); if ($objname =~ /^([0-9]+)\..+$/) { - my $chn = $1; - return HMCCU_SetError ($hash, -7) if ($chn >= $hash->{hmccu}{channels}); + return HMCCU_SetError ($hash, -7) if ($1 >= $hash->{hmccu}{channels}); } else { - return HMCCU_SetError ($hash, -11) if ($cc eq ''); - $objname = $cc.'.'.$objname; + $objname = "$cc.$objname"; } my $no = sprintf ("%03d", $i); @@ -489,19 +431,15 @@ sub HMCCUDEV_Set ($@) $dpval{"$no.$ccuif.$ccuaddr:$objname"} = HMCCU_Substitute ($value, $stateVals, 1, undef, ''); } - return HMCCU_SetError ($hash, $usage) if (scalar (keys %dpval) < 1); + return HMCCU_SetError ($hash, $usage) if (scalar(keys %dpval) < 1); $rc = HMCCU_SetMultipleDatapoints ($hash, \%dpval); return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); } elsif ($opt eq 'toggle') { - return HMCCU_SetError ($hash, -12) if ($cc eq ''); - return HMCCU_SetError ($hash, -14) if ($cd eq ''); - return HMCCU_SetError ($hash, -15) if ($stateVals eq ''); - return HMCCU_SetError ($hash, -8) - if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $cc, $cd, 2)); + my $stc = scalar(@states); + return HMCCU_SetError ($hash, -15) if ($stc == 0); - my $stc = scalar (@states); my $curState = defined($hash->{hmccu}{dp}{"$cc.$cd"}{VALUES}{SVAL}) ? $hash->{hmccu}{dp}{"$cc.$cd"}{VALUES}{SVAL} : $states[0]; @@ -512,9 +450,7 @@ sub HMCCUDEV_Set ($@) $newState = ($st == $stc-1) ? $states[0] : $states[$st+1]; last; } - else { - $st++; - } + $st++; } return HMCCU_SetError ($hash, "Current device state doesn't match any state value") @@ -525,126 +461,8 @@ sub HMCCUDEV_Set ($@) ); return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); } - elsif (defined($roleCmds) && exists($roleCmds->{$opt})) { - return HMCCU_SetError ($hash, -12) if ($cc eq ''); - return HMCCU_SetError ($hash, -14) if ($cd eq ''); - - my $value; - my %dpval; - my %cfval; - - my @setList = split (/\s+/, $roleCmds->{$opt}); - my $i = 0; - foreach my $set (@setList) { - my ($ps, $dpt, $par) = split(/:/, $set); - - return HMCCU_SetError ($hash, "Syntax error in definition of command $opt") - if (!defined($par)); - if (!HMCCU_IsValidParameter ($hash, $ccuaddr, $pset{$ps}, $dpt)) { - HMCCU_Trace ($hash, 2, "Set", "Invalid parameter $ps $dpt"); - return HMCCU_SetError ($hash, -8); - } - - # Check if parameter is required - if ($par =~ /^\?(.+)$/) { - $par = $1; - # Consider default value for parameter - my ($parName, $parDef) = split ('=', $par); - $value = shift @$a; - if (!defined($value) && defined($parDef)) { - if ($parDef =~ /^[+-][0-9]+$/) { - return HMCCU_SetError ($hash, "Current value of $cc.$dpt not available") - if (!defined($hash->{hmccu}{dp}{"$cc.$dpt"}{$pset{$ps}}{SVAL})); - $value = $hash->{hmccu}{dp}{"$cc.$dpt"}{$pset{$ps}}{SVAL}+int($parDef); - } - else { - $value = $parDef; - } - } - - return HMCCU_SetError ($hash, "Missing parameter $parName") - if (!defined($value)); - } - else { - if (exists($valLookup{$ps}{$dpt})) { - return HMCCU_SetError ($hash, "Illegal value $par. Use one of ".join(',', keys %{$valLookup{$ps}{$dpt}})) - if (!exists($valLookup{$ps}{$dpt}{$par})); - $value = $valLookup{$ps}{$dpt}{$par}; - } - else { - $value = $par; - } - } - - if ($opt eq 'pct' || $opt eq 'level') { - my $timespec = shift @$a; - my $ramptime = shift @$a; - - # Set on time - if (defined ($timespec)) { - return HMCCU_SetError ($hash, "Can't find ON_TIME datapoint for device type $ccutype") - if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, "ON_TIME", 2)); - if ($timespec =~ /^[0-9]{2}:[0-9]{2}/) { - $timespec = HMCCU_GetTimeSpec ($timespec); - return HMCCU_SetError ($hash, "Wrong time format. Use HH:MM[:SS]") if ($timespec < 0); - } - $dpval{"001.$ccuif.$ccuaddr.$cc.ON_TIME"} = $timespec if ($timespec > 0); - } - - # Set ramp time - if (defined($ramptime)) { - return HMCCU_SetError ($hash, "Can't find RAMP_TIME datapoint for device type $ccutype") - if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, "RAMP_TIME", 2)); - $dpval{"002.$ccuif.$ccuaddr.$cc.RAMP_TIME"} = $ramptime if (defined ($ramptime)); - } - - $dpval{"003.$ccuif.$ccuaddr.$cc.$dpt"} = $value; - last; - } - else { - if ($ps eq 'V') { - my $no = sprintf ("%03d", $i); - $dpval{"$i.$ccuif.$ccuaddr.$cc.$dpt"} = $value; - $i++; - } - else { - $cfval{$dpt} = $value; - } - } - } - - if (scalar(keys %dpval) > 0) { - $rc = HMCCU_SetMultipleDatapoints ($hash, \%dpval); - return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); - } - if (scalar(keys %cfval) > 0) { - ($rc, $result) = HMCCU_SetMultipleParameters ($hash, $ccuaddr, $h, 'MASTER'); - return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); - } - } - elsif ($opt eq 'on-for-timer' || $opt eq 'on-till') { - return HMCCU_SetError ($hash, -15) if ($stateVals eq '' || !exists($hash->{hmccu}{statevals})); - return HMCCU_SetError ($hash, "No state value for 'on' defined") - if ("on" !~ /($hash->{hmccu}{statevals})/); - return HMCCU_SetError ($hash, -12) if ($cc eq ''); - return HMCCU_SetError ($hash, -14) if ($cd eq ''); - return HMCCU_SetError ($hash, "Can't find ON_TIME datapoint for device type") - if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $cc, 'ON_TIME', 2)); - - my $timespec = shift @$a; - return HMCCU_SetError ($hash, "Usage: set $name $opt {ontime-spec}") - if (!defined ($timespec)); - - if ($opt eq 'on-till') { - $timespec = HMCCU_GetTimeSpec ($timespec); - return HMCCU_SetError ($hash, 'Wrong time format. Use HH:MM[:SS]') if ($timespec < 0); - } - - $rc = HMCCU_SetMultipleDatapoints ($hash, { - "001.$ccuif.$ccuaddr:$cc.ON_TIME" => $timespec, - "002.$ccuif.$ccuaddr:$cc.$cd" => HMCCU_Substitute ('on', $stateVals, 1, undef, '') - }); - return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); + elsif (exists($hash->{hmccu}{roleCmds}{$opt})) { + return HMCCU_ExecuteRoleCommand ($ioHash, $hash, $opt, $a, $h); } elsif ($opt eq 'clear') { my $rnexp = shift @$a; @@ -673,13 +491,12 @@ sub HMCCUDEV_Set ($@) } my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $ccuobj, $ccuif); - return HMCCU_SetError ($hash, "Can't get device description") - if (!defined($devDesc)); + 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 ". + return HMCCU_SetError ($hash, 'Invalid parameter specified. Valid parameters are '. join(',', @parList)); } @@ -691,7 +508,7 @@ sub HMCCUDEV_Set ($@) my $clHash = $defs{$receiver}; if ($clHash->{TYPE} eq 'HMCCUDEV') { my $chnNo = shift @$a; - return HMCCU_SetError ($hash, "Channel number required for link receiver") + return HMCCU_SetError ($hash, 'Channel number required for link receiver') if (!defined($chnNo) || $chnNo !~ /^[0-9]{1,2}$/); $receiver = $clHash->{ccuaddr}.":$chnNo"; } @@ -703,7 +520,7 @@ sub HMCCUDEV_Set ($@) } } elsif (!HMCCU_IsChnAddr ($receiver, 0)) { - my ($rcvAdd, $rcvChn) = HMCCU_GetAddress ($ioHash, $receiver, '', ''); + 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"; @@ -711,27 +528,25 @@ sub HMCCUDEV_Set ($@) 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); + ($rc, $result) = HMCCU_RPCRequest ($hash, 'putParamset', $ccuaddr, $receiver, $h); } return HMCCU_SetError ($hash, HMCCU_Min(0, $rc)); } elsif ($opt eq 'defaults') { - $rc = HMCCU_SetDefaultAttributes ($hash, $cc); + my $mode = shift @$a // 'update'; + $rc = HMCCU_SetDefaultAttributes ($hash, { mode => $mode, role => undef, ctrlChn => $cc }); $rc = HMCCU_SetDefaults ($hash) if (!$rc); - return HMCCU_SetError ($hash, $rc == 0 ? "No default attributes found" : "OK"); + HMCCU_RefreshReadings ($hash) if ($rc); + return HMCCU_SetError ($hash, $rc == 0 ? 'No default attributes found' : 'OK'); } else { - my $retmsg = "clear defaults:noArg"; + my $retmsg = 'clear defaults:reset,update'; if ($hash->{readonly} ne 'yes') { - $retmsg .= " datapoint rpcparameter"; - if ($sc ne '') { - $retmsg .= " config datapoint".$cmdList; - $retmsg .= " toggle:noArg" if (scalar(@states) > 0); - $retmsg .= " on-for-timer on-till" - if ($cc ne '' && HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, $cc, "ON_TIME", 2)); - } + $retmsg .= ' config datapoint'; + $retmsg .= " $cmdList" if ($cmdList ne ''); + $retmsg .= ' toggle:noArg' if (scalar(@states) > 0); } return AttrTemplate_Set ($hash, $retmsg, $name, $opt, @$a); } @@ -745,27 +560,20 @@ sub HMCCUDEV_Get ($@) { my ($hash, $a, $h) = @_; my $name = shift @$a; - my $opt = shift @$a; - - return "No get command specified" if (!defined ($opt)); + my $opt = shift @$a // return 'No get command specified'; $opt = lc($opt); # Get I/O device - return undef if (!defined ($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' || - !defined ($hash->{IODev})); + return "Device state doesn't allow set commands" + if (!defined ($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' || + !defined ($hash->{IODev}) || AttrVal ($name, "disable", 0) == 1); my $ioHash = $hash->{IODev}; - my $hmccu_name = $ioHash->{NAME}; - - # Handle disabled devices - my $disable = AttrVal ($name, "disable", 0); - return undef if ($disable == 1); + my $ioName = $ioHash->{NAME}; # Check if CCU is busy - if (HMCCU_IsRPCStateBlocking ($ioHash)) { - return undef if ($opt eq '?'); - return "HMCCUDEV: CCU busy"; - } - + return $opt eq '?' ? undef : 'Cannot perform get commands. CCU busy' + if (HMCCU_IsRPCStateBlocking ($ioHash)); + # Get parameters of current device my $ccutype = $hash->{ccutype}; my $ccuaddr = $hash->{ccuaddr}; @@ -773,22 +581,19 @@ sub HMCCUDEV_Get ($@) my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my ($sc, $sd, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash); + # Virtual devices only support command get update + return "HMCCUDEV: Unknown argument $opt, choose one of update:noArg" + if ($ccuif eq 'fhem' && $opt ne 'update'); + my $result = ''; my $rc; - # Virtual devices only support command get update - if ($ccuif eq 'fhem' && $opt ne 'update') { - return "HMCCUDEV: Unknown argument $opt, choose one of update:noArg"; - } - # Log commands HMCCU_Log ($hash, 3, "get $name $opt ".join (' ', @$a)) - if ($opt ne '?' && $ccuflags =~ /logCommand/ || HMCCU_IsFlag ($hmccu_name, 'logCommand')); + if ($opt ne '?' && $ccuflags =~ /logCommand/ || HMCCU_IsFlag ($ioName, 'logCommand')); if ($opt eq 'datapoint') { - my $objname = shift @$a; - return HMCCU_SetError ($hash, "Usage: get $name datapoint [{channel-number}.]{datapoint}") - if (!defined ($objname)); + my $objname = shift @$a // return HMCCU_SetError ($hash, "Usage: get $name datapoint [{channel-number}.]{datapoint}"); if ($objname =~ /^([0-9]+)\..+$/) { my $chn = $1; @@ -809,35 +614,8 @@ sub HMCCUDEV_Get ($@) HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); return $result; } -# elsif ($opt eq 'update') { -# my $ccuget = shift @$a; -# $ccuget = 'Attr' if (!defined ($ccuget)); -# if ($ccuget !~ /^(Attr|State|Value)$/) { -# return HMCCU_SetError ($hash, "Usage: get $name update [{'State'|'Value'}]"); -# } -# -# if ($hash->{ccuif} ne 'fhem') { -# $rc = HMCCU_GetUpdate ($hash, $ccuaddr, $ccuget); -# return HMCCU_SetError ($hash, $rc) if ($rc < 0); -# } -# else { -# # Update all devices belonging to group -# my @vdevs = split (",", $hash->{ccugroup}); -# foreach my $vd (@vdevs) { -# $rc = HMCCU_GetUpdate ($hash, $vd, $ccuget); -# return HMCCU_SetError ($hash, $rc) if ($rc < 0); -# } -# } -# -# return undef; -# } elsif ($opt eq 'deviceinfo') { - my $ccuget = shift @$a; - $ccuget = 'Attr' if (!defined ($ccuget)); - if ($ccuget !~ /^(Attr|State|Value)$/) { - return HMCCU_SetError ($hash, "Usage: get $name deviceinfo [{'State'|'Value'}]"); - } - $result = HMCCU_GetDeviceInfo ($hash, $ccuaddr, $ccuget); + $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"; @@ -874,9 +652,12 @@ sub HMCCUDEV_Get ($@) } } else { + # The following request could fail if device description or parameter set + # description is not correct. ($rc, $result) = HMCCU_RPCRequest ($hash, 'getRawParamset', $a, $ps, undef); - return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0); - foreach my $p (keys %$result) { $objects{$da}{$dc}{$ps}{$p} = $result->{$p}; } + if ($rc >= 0) { + foreach my $p (keys %$result) { $objects{$da}{$dc}{$ps}{$p} = $result->{$p}; } + } } } } @@ -889,10 +670,10 @@ sub HMCCUDEV_Get ($@) $res .= "Device $da\n"; foreach my $dc (sort keys %{$convRes->{$da}}) { foreach my $ps (sort keys %{$convRes->{$da}{$dc}}) { - $res .= " Channel $dc [$ps]\n"; - $res .= join ("\n", map { - " ".$_.' = '.$convRes->{$da}{$dc}{$ps}{$_} - } sort keys %{$convRes->{$da}{$dc}{$ps}})."\n"; + $res .= " Channel $dc [$ps]\n". + join ("\n", map { + " ".$_.' = '.$convRes->{$da}{$dc}{$ps}{$_} + } sort keys %{$convRes->{$da}{$dc}{$ps}})."\n"; } } } @@ -913,6 +694,10 @@ sub HMCCUDEV_Get ($@) $result = HMCCU_GetDefaults ($hash, 0); return $result; } + elsif ($opt eq 'weekprogram') { + my $program = shift @$a; + return HMCCU_DisplayWeekProgram ($hash, $program); + } else { my $retmsg = "HMCCUDEV: Unknown argument $opt, choose one of datapoint"; @@ -921,12 +706,13 @@ sub HMCCUDEV_Get ($@) $retmsg .= ':'.join(",", @valuelist) if ($valuecount > 0); $retmsg .= ' defaults:noArg update:noArg config'. ' paramsetDesc:noArg deviceDesc:noArg deviceInfo:noArg values:noArg'; + $retmsg .= ' weekProgram:all,'.join(',', sort keys %{$hash->{hmccu}{tt}}) + if (exists($hash->{hmccu}{tt})); return $retmsg; } } - 1; =pod @@ -1002,9 +788,12 @@ sub HMCCUDEV_Get ($@) set temp_control datapoint 2.SET_TEMPERATURE 21
    set temp_control datapoint 2.AUTO_MODE 1 2.SET_TEMPERATURE 21
    -
  • set <name> defaults
    +
  • set <name> defaults ['reset'|'update']
    Set default attributes for CCU device type. Default attributes are only available for - some device types. + some device types and for some channels of a device type. If option 'reset' is specified, + the following attributes are deleted before the new attributes are set: + 'ccureadingname', 'ccuscaleval', 'eventMap', 'substexcl', 'webCmd', 'widgetOverride'. + During update to version 4.4 it's recommended to use option 'reset'.

  • set <name> down [<value>]
    see HMCCUCHN @@ -1018,13 +807,6 @@ sub HMCCUDEV_Get ($@)
  • set <name> pct <value;> [<ontime> [<ramptime>]]
    see HMCCUCHN

  • -
  • set <name> rpcparameter [<channel>] { VALUES | MASTER | LINK } <parameter>=<value> [...]
    - Set multiple datapoints or config parameters by using RPC interface instead of ReGa. - Supports attribute 'ccuscaleval' for datapoints. Methods VALUES (setting datapoints) - and LINK require a channel number. For method MASTER (setting parameters) a channel number - is optional (setting device parameters). Parameter parameter must be a valid - datapoint or config parameter name. -

  • set <name> <statevalue>
    State datapoint of a CCU device channel is set to 'statevalue'. State channel and state datapoint must be defined as attribute 'statedatapoint'. Values for statevalue @@ -1105,6 +887,9 @@ sub HMCCUDEV_Get ($@)
  • get <name> update [{State | Value}]
    see HMCCUCHN

  • +
  • get <name> weekProgram [<program-number>|all]
    + Display week programs. This command is only available if a device supports week programs. +

  • diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCURPCPROC.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCURPCPROC.pm index f64d346c2..a6b944fa5 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.005 +# Version 4.4.010 # # 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.005'; +my $HMCCURPCPROC_VERSION = '4.4.010'; # Maximum number of events processed per call of Read() my $HMCCURPCPROC_MAX_EVENTS = 100; @@ -65,6 +65,9 @@ my $HMCCURPCPROC_TIMEOUT_CONNECTION = 1; # Timeout for TriggerIO() in seconds my $HMCCURPCPROC_TIMEOUT_WRITE = 0.001; +# Timeout for reading from Socket +my $HMCCURPCPROC_TIMEOUT_READ = 0.25; + # Timeout for accepting incoming connections in seconds (0 = default) my $HMCCURPCPROC_TIMEOUT_ACCEPT = 1; @@ -103,25 +106,29 @@ my $BINRPC_ERROR = 0x42696EFF; # BinRPC datatype mapping my %BINRPC_TYPE_MAPPING = ( - "BOOL" => $BINRPC_BOOL, - "INTEGER" => $BINRPC_INTEGER, - "STRING" => $BINRPC_STRING, - "FLOAT" => $BINRPC_DOUBLE, - "DOUBLE" => $BINRPC_DOUBLE, - "BASE64" => $BINRPC_BASE64, - "ARRAY" => $BINRPC_ARRAY, - "STRUCT" => $BINRPC_STRUCT + 'BOOL' => $BINRPC_BOOL, + 'INTEGER' => $BINRPC_INTEGER, + 'STRING' => $BINRPC_STRING, + 'FLOAT' => $BINRPC_DOUBLE, + 'DOUBLE' => $BINRPC_DOUBLE, + 'BASE64' => $BINRPC_BASE64, + 'ARRAY' => $BINRPC_ARRAY, + 'STRUCT' => $BINRPC_STRUCT ); # Read/Write flags for RPC methods (0=Read, 1=Write) my %RPC_METHODS = ( - 'putParamset' => 1, - 'getParamset' => 0, + 'putParamset' => 1, + 'getParamset' => 0, 'getParamsetDescription' => 0, - 'setValue' => 1, - 'getValue' => 0 + 'setValue' => 1, + 'getValue' => 0 ); +# RPC event types +my @RPC_EVENT_TYPES = ('EV', 'ND', 'DD', 'RD', 'RA', 'UD', 'IN', 'EX', 'SL', 'TO'); + + ###################################################################### # Functions ###################################################################### @@ -162,6 +169,8 @@ sub HMCCURPCPROC_RPCPing ($); sub HMCCURPCPROC_RPCServerStarted ($); sub HMCCURPCPROC_RPCServerStopped ($); sub HMCCURPCPROC_SendRequest ($@); +sub HMCCURPCPROC_SendXMLRequest ($@); +sub HMCCURPCPROC_SendBINRequest ($@); sub HMCCURPCPROC_SetRPCState ($$$$); sub HMCCURPCPROC_StartRPCServer ($); sub HMCCURPCPROC_StopRPCServer ($$); @@ -177,6 +186,10 @@ sub HMCCURPCPROC_ProcessRequest ($$); sub HMCCURPCPROC_HandleConnection ($$$$); sub HMCCURPCPROC_SendQueue ($$$$); sub HMCCURPCPROC_SendData ($$); +sub HMCCURPCPROC_ReceiveData ($$); +sub HMCCURPCPROC_ReadFromSocket ($$$); +sub HMCCURPCPROC_DataAvailableOnSocket ($$); +sub HMCCURPCPROC_WriteToSocket ($$$); sub HMCCURPCPROC_Write ($$$$); sub HMCCURPCPROC_WriteStats ($$); sub HMCCURPCPROC_NewDevicesCB ($$$); @@ -222,21 +235,21 @@ sub HMCCURPCPROC_Initialize ($) { my ($hash) = @_; - $hash->{DefFn} = "HMCCURPCPROC_Define"; - $hash->{UndefFn} = "HMCCURPCPROC_Undef"; - $hash->{SetFn} = "HMCCURPCPROC_Set"; - $hash->{GetFn} = "HMCCURPCPROC_Get"; - $hash->{ReadFn} = "HMCCURPCPROC_Read"; - $hash->{AttrFn} = "HMCCURPCPROC_Attr"; - $hash->{ShutdownFn} = "HMCCURPCPROC_Shutdown"; - $hash->{DelayedShutdownFn} = "HMCCURPCPROC_DelayedShutdown"; + $hash->{DefFn} = 'HMCCURPCPROC_Define'; + $hash->{UndefFn} = 'HMCCURPCPROC_Undef'; + $hash->{SetFn} = 'HMCCURPCPROC_Set'; + $hash->{GetFn} = 'HMCCURPCPROC_Get'; + $hash->{ReadFn} = 'HMCCURPCPROC_Read'; + $hash->{AttrFn} = 'HMCCURPCPROC_Attr'; + $hash->{ShutdownFn} = 'HMCCURPCPROC_Shutdown'; + $hash->{DelayedShutdownFn} = 'HMCCURPCPROC_DelayedShutdown'; $hash->{parseParams} = 1; - $hash->{AttrList} = "ccuflags:multiple-strict,expert,logEvents,ccuInit,queueEvents,noEvents,noInitialUpdate,statistics". - " rpcMaxEvents rpcQueueSend rpcQueueSize rpcMaxIOErrors". - " rpcServerAddr rpcServerPort rpcWriteTimeout rpcAcceptTimeout". - " rpcConnTimeout rpcStatistics rpcEventTimeout rpcPingCCU ". + $hash->{AttrList} = 'ccuflags:multiple-strict,expert,logEvents,ccuInit,queueEvents,noEvents,noInitialUpdate,statistics'. + ' rpcMaxEvents rpcQueueSend rpcQueueSize rpcMaxIOErrors'. + ' rpcServerAddr rpcServerPort rpcReadTimeout rpcWriteTimeout rpcAcceptTimeout'. + ' rpcConnTimeout rpcStatistics rpcEventTimeout rpcPingCCU '. $readingFnAttributes; } @@ -262,7 +275,7 @@ sub HMCCURPCPROC_Define ($$) return "HMCCU I/O device $ioname not found" if (!exists($defs{$ioname})); return "Device $ioname is not a HMCCU device" if ($defs{$ioname}->{TYPE} ne 'HMCCU'); $ioHash = $defs{$ioname}; - if (scalar (@$a) < 4) { + if (scalar(@$a) < 4) { $hash->{host} = $ioHash->{host}; $hash->{prot} = $ioHash->{prot}; $iface = $$a[2]; @@ -281,7 +294,7 @@ sub HMCCURPCPROC_Define ($$) $rpcip = HMCCU_ResolveName ($hash->{host}, 'N/A'); } else { - return $usage if (scalar (@$a) < 4); + return $usage if (scalar(@$a) < 4); if ($$a[2] =~ /^(https?):\/\/(.+)/) { $hash->{prot} = $1; $hash->{host} = $2; @@ -296,12 +309,8 @@ sub HMCCURPCPROC_Define ($$) # Find IO device foreach my $d (keys %defs) { my $dh = $defs{$d}; - next if (!exists ($dh->{TYPE}) || !exists ($dh->{NAME})); - next if ($dh->{TYPE} ne 'HMCCU'); - if ($dh->{ccuip} eq $rpcip) { - $ioHash = $dh; - last; - } + next if (!exists ($dh->{TYPE}) || !exists ($dh->{NAME}) || $dh->{TYPE} ne 'HMCCU'); + if ($dh->{ccuip} eq $rpcip) { $ioHash = $dh; last; } } } @@ -313,12 +322,9 @@ sub HMCCURPCPROC_Define ($$) # Interactive define command while CCU not ready or no IO device defined if (!defined($ioHash)) { my ($ccuactive, $ccuinactive) = HMCCU_IODeviceStates (); - if ($ccuinactive > 0) { - return "CCU and/or IO device not ready. Please try again later"; - } - else { - return "Cannot detect IO device"; - } + return $ccuinactive > 0 ? + 'CCU and/or IO device not ready. Please try again later' : + 'Cannot detect IO device'; } } else { @@ -354,7 +360,8 @@ sub HMCCURPCPROC_Define ($$) # 5 = Cannot connect to CCU ###################################################################### -sub HMCCURPCPROC_InitDevice ($$) { +sub HMCCURPCPROC_InitDevice ($$) +{ my ($ioHash, $dev_hash) = @_; my $name = $dev_hash->{NAME}; my $iface = $dev_hash->{hmccu}{devspec}; @@ -402,7 +409,7 @@ sub HMCCURPCPROC_InitDevice ($$) { # Set some attributes if ($init_done) { - $attr{$name}{stateFormat} = "rpcstate/state"; + $attr{$name}{stateFormat} = 'rpcstate/state'; $attr{$name}{verbose} = 2; } @@ -447,7 +454,7 @@ sub HMCCURPCPROC_DelayedShutdown ($) my $ioHash = $hash->{IODev}; my $ifname = $hash->{rpcinterface}; - my $delay = HMCCU_Max (AttrVal ("global", "maxShutdownDelay", 10)-2, 0); + my $delay = HMCCU_Max (AttrVal ('global', 'maxShutdownDelay', 10)-2, 0); # Shutdown RPC server if (defined($ioHash) && exists($ioHash->{hmccu}{interfaces}{$ifname}{manager}) && @@ -458,7 +465,7 @@ sub HMCCURPCPROC_DelayedShutdown ($) HMCCURPCPROC_StopRPCServer ($hash, $delay); } else { - HMCCU_Log ($hash, 1, "Graceful shutdown already in progress"); + HMCCU_Log ($hash, 1, 'Graceful shutdown already in progress'); } } @@ -479,11 +486,11 @@ sub HMCCURPCPROC_Shutdown ($) if (defined($ioHash) && exists($ioHash->{hmccu}{interfaces}{$ifname}{manager}) && $ioHash->{hmccu}{interfaces}{$ifname}{manager} eq 'HMCCURPCPROC') { if (!exists ($hash->{hmccu}{delayedShutdown})) { - HMCCU_Log ($hash, 1, "Immediate shutdown"); + HMCCU_Log ($hash, 1, 'Immediate shutdown'); HMCCURPCPROC_StopRPCServer ($hash, 0); } else { - HMCCU_Log ($hash, 1, "Graceful shutdown"); + HMCCU_Log ($hash, 1, 'Graceful shutdown'); } } @@ -537,14 +544,12 @@ sub HMCCURPCPROC_Set ($@) my ($hash, $a, $h) = @_; my $ioHash = $hash->{IODev}; my $name = shift @$a; - my $opt = shift @$a; - - return 'No set command specified' if (!defined($opt)); + my $opt = shift @$a // return 'No set command specified'; my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my $options = $ccuflags =~ /expert/ ? - "cleanup:noArg deregister:noArg register:noArg rpcrequest rpcserver:on,off" : ""; - my $busyoptions = $ccuflags =~ /expert/ ? "rpcserver:off" : ""; + 'cleanup:noArg deregister:noArg register:noArg rpcrequest rpcserver:on,off' : ''; + my $busyoptions = $ccuflags =~ /expert/ ? 'rpcserver:off' : ''; return "HMCCURPCPROC: CCU busy, choose one of $busyoptions" if ($opt ne 'rpcserver' && HMCCURPCPROC_IsRPCStateBlocking ($hash)); @@ -574,9 +579,8 @@ sub HMCCURPCPROC_Set ($@) return HMCCURPCPROC_SetState ($hash, "OK"); } elsif ($opt eq 'rpcrequest') { - my $request = shift @$a; - return HMCCURPCPROC_SetError ($hash, "Usage: set $name rpcrequest {request} [{parameter} ...]", 2) - if (!defined($request)); + my $request = shift @$a // return HMCCURPCPROC_SetError ( + $hash, "Usage: set $name rpcrequest {request} [{parameter} ...]", 2); my $response = HMCCURPCPROC_SendRequest ($hash, $request, @$a); return HMCCURPCPROC_SetError ($hash, 'RPC request failed', 2) if (!defined($response)); @@ -584,7 +588,6 @@ sub HMCCURPCPROC_Set ($@) } elsif ($opt eq 'rpcserver') { my $action = shift @$a; - return HMCCURPCPROC_SetError ($hash, "Usage: set $name rpcserver {on|off}", 2) if (!defined($action) || $action !~ /^(on|off)$/); @@ -605,9 +608,8 @@ sub HMCCURPCPROC_Set ($@) return undef; } - else { - return "HMCCURPCPROC: Unknown argument $opt, choose one of ".$options; - } + + return "HMCCURPCPROC: Unknown argument $opt, choose one of $options"; } ###################################################################### @@ -619,14 +621,12 @@ sub HMCCURPCPROC_Get ($@) my ($hash, $a, $h) = @_; my $ioHash = $hash->{IODev}; my $name = shift @$a; - my $opt = shift @$a; - - return 'No get command specified' if (!defined($opt)); + my $opt = shift @$a // return 'No get command specified'; my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); - my $options = "deviceDesc rpcevents:noArg rpcstate:noArg peers:noArg"; + my $options = 'deviceDesc rpcevents:noArg rpcstate:noArg peers:noArg'; - return "HMCCURPCPROC: CCU busy, choose one of rpcstate:noArg" + return 'HMCCURPCPROC: CCU busy, choose one of rpcstate:noArg' if ($opt ne 'rpcstate' && HMCCURPCPROC_IsRPCStateBlocking ($hash)); my $result = 'Command not implemented'; @@ -658,16 +658,16 @@ sub HMCCURPCPROC_Get ($@) return "Read $cp links from CCU"; } elsif ($opt eq 'rpcevents') { - my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO"); my $clkey = HMCCURPCPROC_GetKey ($hash); $result = "Event statistics for server $clkey\n"; $result .= "Average event delay = ".$hash->{hmccu}{rpc}{avgdelay}."\n" if (defined ($hash->{hmccu}{rpc}{avgdelay})); - $result .= "========================================\n"; - $result .= "ET Sent by RPC server Received by FHEM\n"; - $result .= "----------------------------------------\n"; - foreach my $et (@eventtypes) { + $result .= + "========================================\n". + "ET Sent by RPC server Received by FHEM\n". + "----------------------------------------\n"; + foreach my $et (@RPC_EVENT_TYPES) { my $snd = exists ($hash->{hmccu}{rpc}{snd}{$et}) ? sprintf ("%7d", $hash->{hmccu}{rpc}{snd}{$et}) : " n/a"; my $rec = exists ($hash->{hmccu}{rpc}{rec}{$et}) ? @@ -676,33 +676,34 @@ sub HMCCURPCPROC_Get ($@) } if ($ccuflags =~ /statistics/ && exists ($hash->{hmccu}{stats}{rcv})) { my $eh = HMCCU_MaxHashEntries ($hash->{hmccu}{stats}{rcv}, 3); - $result .= "========================================\n"; - $result .= "Top Sender\n"; - $result .= "========================================\n"; + $result .= + "========================================\n". + "Top Sender\n". + "========================================\n"; for (my $i=0; $i<3; $i++) { last if (!exists ($eh->{$i})); my $dn = HMCCU_GetDeviceName ($ioHash, $eh->{$i}{k}, '?'); $result .= "$eh->{$i}{k} / $dn : $eh->{$i}{v}\n"; } } - return $result eq '' ? "No event statistics found" : $result; + return $result eq '' ? 'No event statistics found' : $result; } elsif ($opt eq 'rpcstate') { my $clkey = HMCCURPCPROC_GetKey ($hash); - $result = "PID RPC-Process State \n"; - $result .= "--------------------------------\n"; + $result = + "PID RPC-Process State \n". + "--------------------------------\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"; my $addr = defined ($hash->{hmccu}{localaddr}) ? $hash->{hmccu}{localaddr} : "N/A"; - $result .= $sid." ".$sname." ".$hash->{hmccu}{rpc}{state}."\n\n"; - $result .= "Local address = $addr\n"; - $result .= "Callback port = $cbport\n"; + $result .= $sid." ".$sname." ".$hash->{hmccu}{rpc}{state}."\n\n". + "Local address = $addr\n". + "Callback port = $cbport\n"; return $result; } - else { - return "HMCCURPCPROC: Unknown argument $opt, choose one of ".$options; - } + + return "HMCCURPCPROC: Unknown argument $opt, choose one of $options"; } ###################################################################### @@ -721,8 +722,6 @@ sub HMCCURPCPROC_Read ($) my %events = (); my %devices = (); - HMCCU_Log ($hash, 4, "Read called"); - # Check if child socket exists if (!defined ($hash->{hmccu}{sockchild})) { HMCCU_Log ($hash, 2, 'Child socket does not exist'); @@ -745,7 +744,7 @@ sub HMCCURPCPROC_Read ($) HMCCU_Log ($hash, 4, "read $item from queue") if ($ccuflags =~ /logEvents/); my ($et, $clkey, @par) = HMCCURPCPROC_ProcessEvent ($hash, $item); - next if (!defined ($et)); + next if (!defined($et)); if ($et eq 'EV') { $events{$par[0]}{$par[1]}{VALUES}{$par[2]} = $par[3]; @@ -809,8 +808,6 @@ sub HMCCURPCPROC_Read ($) HMCCU_UpdateDeviceTable ($ioHash, \%devices) if ($devcount > 0); HMCCU_UpdateMultipleDevices ($ioHash, \%events) if ($evcount > 0 && $ccuflags !~ /noEvents/ && $hmccuflags !~ /noEvents/); - - HMCCU_Log ($hash, 4, "Read finished"); } ###################################################################### @@ -837,7 +834,7 @@ sub HMCCURPCPROC_SetState ($$) { my ($hash, $state) = @_; - if (defined ($state)) { + if (defined($state)) { readingsSingleUpdate ($hash, 'state', $state, 1); HMCCU_Log ($hash, 4, "Set state to $state"); } @@ -856,17 +853,17 @@ sub HMCCURPCPROC_SetRPCState ($$$$) my $name = $hash->{NAME}; my $ioHash = $hash->{IODev}; - return undef if (exists ($hash->{RPCState}) && $hash->{RPCState} eq $state); + return undef if (exists($hash->{RPCState}) && $hash->{RPCState} eq $state); $hash->{hmccu}{rpc}{state} = $state; $hash->{RPCState} = $state; - readingsSingleUpdate ($hash, "rpcstate", $state, 1); + readingsSingleUpdate ($hash, 'rpcstate', $state, 1); HMCCURPCPROC_SetState ($hash, 'busy') if ($state ne 'running' && $state ne 'inactive' && $state ne 'error' && ReadingsVal ($name, 'state', '') ne 'busy'); - HMCCU_Log ($hash, (defined($level) ? $level : 1), $msg) if (defined ($msg)); + HMCCU_Log ($hash, (defined($level) ? $level : 1), $msg) if (defined($msg)); HMCCU_Log ($hash, 4, "Set rpcstate to $state"); # Set state of interface in I/O device @@ -883,8 +880,6 @@ sub HMCCURPCPROC_ResetRPCState ($) { my ($hash) = @_; - HMCCU_Log ($hash, 4, 'Reset RPC state'); - $hash->{RPCPID} = "0"; $hash->{hmccu}{rpc}{pid} = undef; $hash->{hmccu}{rpc}{clkey} = undef; @@ -903,7 +898,7 @@ sub HMCCURPCPROC_IsRPCStateBlocking ($) my ($hash) = @_; return (exists($hash->{RPCState}) && - ($hash->{RPCState} eq "running" || $hash->{RPCState} eq "inactive")) ? 0 : 1; + ($hash->{RPCState} eq 'running' || $hash->{RPCState} eq 'inactive')) ? 0 : 1; } ###################################################################### @@ -921,59 +916,51 @@ 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 ''); + my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my $ping = AttrVal ($ioHash->{NAME}, 'rpcPingCCU', $HMCCURPCPROC_TIME_PING); my $evttimeout = ($ping > 0 && $hash->{rpcinterface} eq $defInterface) ? $ping*2 : - HMCCURPCPROC_GetAttribute ($hash, 'rpcEventTimeout', 'rpcevtimeout', $HMCCURPCPROC_TIMEOUT_EVENT); - - return undef if (!defined ($event) || $event eq ''); + HMCCURPCPROC_GetAttribute ($hash, 'rpcEventTimeout', 'rpcevtimeout', $HMCCURPCPROC_TIMEOUT_EVENT); # Log event HMCCU_Log ($hash, 2, "CCUEvent = $event") if ($ccuflags =~ /logEvents/); # Detect event type and clkey my ($et, $clkey, $evdata) = split (/\|/, $event, 3); - if (!defined ($evdata)) { - HMCCU_Log ($hash, 2, "Syntax error in RPC event data $event"); - return undef; - } + return HMCCU_Log ($hash, 2, "Syntax error in RPC event data $event", undef) + if (!defined($evdata)); # Check for valid server - if ($clkey ne $rpcname) { - HMCCU_Log ($hash, 2, "Received $et event for unknown RPC server $clkey"); - return undef; - } + return HMCCU_Log ($hash, 2, "Received $et event for unknown RPC server $clkey", undef) + if ($clkey ne $rpcname); # Check event type if (!exists ($rpceventargs{$et})) { $et =~ s/([\x00-\xFF])/sprintf("0x%X ",ord($1))/eg; - HMCCU_Log ($hash, 2, "Received unknown event from CCU: ".$et); - return undef; + return HMCCU_Log ($hash, 2, "Received unknown event from CCU: $et", undef); } # Parse event my @t = split (/\|/, $evdata, $rpceventargs{$et}); - my $tc = scalar (@t); + my $tc = scalar(@t); # Check event parameters - if ($tc != $rpceventargs{$et}) { - HMCCU_Log ($hash, 2, "Wrong number of $tc parameters in event $event. Expected ". - $rpceventargs{$et}); - return undef; - } + return HMCCU_Log ($hash, 2, "Wrong number of $tc parameters in event $event. Expected ". + $rpceventargs{$et}, undef) if ($tc != $rpceventargs{$et}); # Update statistic counters $rh->{rec}{$et}++; @@ -992,7 +979,7 @@ sub HMCCURPCPROC_ProcessEvent ($$) HMCCU_Log ($hash, 3, "Received CENTRAL event from $clkey. ".$t[2]."=".$t[3]) if ($t[1] eq 'CENTRAL' && $t[3] eq $rpcname && HMCCU_IsFlag ($ioHash->{NAME}, 'logPong')); my ($add, $chn) = split (/:/, $t[1]); - return defined ($chn) ? ($et, $clkey, $add, $chn, @t[2,3]) : undef; + return defined($chn) ? ($et, $clkey, $add, $chn, @t[2,3]) : undef; } elsif ($et eq 'SL') { # @@ -1014,8 +1001,7 @@ sub HMCCURPCPROC_ProcessEvent ($$) return ($et, $clkey, ($srun == 0 ? 1 : 0), $srun); } else { - HMCCU_Log ($hash, 0, "Received SL event. Wrong PID=".$t[0]." for RPC server $clkey"); - return undef; + return HMCCU_Log ($hash, 0, "Received SL event. Wrong PID=".$t[0]." for RPC server $clkey", undef); } } elsif ($et eq 'IN') { @@ -1083,9 +1069,8 @@ sub HMCCURPCPROC_ProcessEvent ($$) my @res = ($et, $clkey); push (@res, @t); my $total = shift @t; - my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO"); - for (my $i=0; $i{hmccu}{rpc}{snd}{$eventtypes[$i]} += $t[$i]; + for (my $i=0; $i{hmccu}{rpc}{snd}{$RPC_EVENT_TYPES[$i]} += $t[$i]; } return @res; } @@ -1142,11 +1127,10 @@ sub HMCCURPCPROC_GetPeers ($) { my ($hash) = @_; my $ioHash = $hash->{IODev}; - my $c = 0; - my $rd = HMCCURPCPROC_SendRequest ($hash, "getLinks"); - return HMCCU_Log ($hash, 2, "Can't get peers", 0) if (!defined($rd)); + my $rd = HMCCURPCPROC_SendRequest ($hash, 'getLinks') // + return HMCCU_Log ($hash, 2, "Can't get peers", 0); if (ref($rd) eq 'HASH' && exists($rd->{faultString})) { return HMCCU_Log ($hash, 2, "Can't get peers. ".$rd->{faultString}, 0); @@ -1155,7 +1139,7 @@ sub HMCCURPCPROC_GetPeers ($) $c = HMCCU_AddPeers ($ioHash, $rd, $hash->{rpcinterface}); } else { - return HMCCU_Log ($hash, 2, "Unexpected response from getLinks", 0); + return HMCCU_Log ($hash, 2, 'Unexpected response from getLinks', 0); } return $c; @@ -1176,14 +1160,18 @@ sub HMCCURPCPROC_GetDeviceDesc ($;$) if (!defined($address)) { # All devices - $rd = HMCCURPCPROC_SendRequest ($hash, "listDevices"); + $rd = HMCCURPCPROC_SendRequest ($hash, 'listDevices'); } else { # Single device (or channel) - $rd = HMCCURPCPROC_SendRequest ($hash, "getDeviceDescription", $address); + $rd = HMCCURPCPROC_SendRequest ($hash, 'getDeviceDescription', $address); } - return HMCCU_Log ($hash, 2, "Can't get device description", 0) if (!defined($rd)); + if (!defined($rd)) { + my $msg = defined($address) ? "Can't get description of device $address" : + "Can't get full list of device descriptions"; + return HMCCU_Log ($hash, 2, $msg); + } if (ref($rd) eq 'HASH') { return HMCCU_Log ($hash, 2, "Can't get device description. ".$rd->{faultString}, 0) @@ -1287,10 +1275,8 @@ sub HMCCURPCPROC_RegisterCallback ($$) return (0, "RPC server $clkey not in state working") if ($hash->{hmccu}{rpc}{state} ne 'working' && $force == 0); - - if ($force == 2) { - return (0, "CCU port $port not reachable") if (!HMCCU_TCPConnect ($hash->{host}, $port)); - } + return (0, "CCU port $port not reachable") + if ($force == 2 && !HMCCU_TCPConnect ($hash->{host}, $port)); my $cburl = HMCCU_GetRPCCallbackURL ($ioHash, $localaddr, $hash->{hmccu}{rpc}{cbport}, $clkey, $port); my $clurl = HMCCU_BuildURL ($ioHash, $port); @@ -1343,7 +1329,7 @@ sub HMCCURPCPROC_DeRegisterCallback ($$) # Deregister up to 2 times for (my $i=0; $i<2; $i++) { - my $rc = HMCCURPCPROC_SendRequest ($hash, "init", "$cburl:STRING"); + my $rc = HMCCURPCPROC_SendRequest ($hash, "init", "$cburl:STRING". ''); if (defined ($rc)) { HMCCURPCPROC_SetRPCState ($hash, $force == 0 ? 'deregistered' : $rpchash->{state}, @@ -1381,10 +1367,8 @@ sub HMCCURPCPROC_InitRPCServer ($$$$) # Create XML RPC server $server = RPC::XML::Server->new (port => $cbPort); - if (!ref($server)) { - HMCCU_Log ($name, 1, "Can't create RPC callback server $clkey. Port $cbPort in use?"); - return undef; - } + return HMCCU_Log ($name, 1, "Can't create RPC callback server $clkey. Port $cbPort in use?", undef) + if (!ref($server)); HMCCU_Log ($name, 2, "Callback server $clkey created. Listening on port $cbPort"); # Callback for events @@ -1472,7 +1456,7 @@ sub HMCCURPCPROC_StartRPCServer ($) if (!exists($hash->{hmccu}{localaddr}) || !exists($hash->{rpcid})); # Check if RPC server is already running - return (0, "RPC server already running") if (HMCCURPCPROC_CheckProcessState ($hash, 'running')); + return (0, 'RPC server already running') if (HMCCURPCPROC_CheckProcessState ($hash, 'running')); # Get parameters and attributes my $ping = AttrVal ($ioHash->{NAME}, 'rpcPingCCU', $HMCCURPCPROC_TIME_PING); @@ -1503,8 +1487,6 @@ sub HMCCURPCPROC_StartRPCServer ($) $procpar{name} = $name; $procpar{clkey} = $clkey; - my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO"); - # Reset state of server processes $hash->{hmccu}{rpc}{state} = 'inactive'; @@ -1555,7 +1537,7 @@ sub HMCCURPCPROC_StartRPCServer ($) $hash->{hmccu}{rpc}{state} = 'initialized'; # Reset statistic counter - foreach my $et (@eventtypes) { + foreach my $et (@RPC_EVENT_TYPES) { $hash->{hmccu}{rpc}{rec}{$et} = 0; $hash->{hmccu}{rpc}{snd}{$et} = 0; } @@ -1568,7 +1550,7 @@ sub HMCCURPCPROC_StartRPCServer ($) InternalTimer (gettimeofday()+$HMCCURPCPROC_INIT_INTERVAL3, "HMCCURPCPROC_IsRPCServerRunning", $hash, 0); - HMCCURPCPROC_SetRPCState ($hash, "starting", "RPC server starting", 1); + HMCCURPCPROC_SetRPCState ($hash, 'starting', 'RPC server starting', 1); DoTrigger ($name, 'RPC server starting'); return (1, undef); @@ -1605,7 +1587,7 @@ sub HMCCURPCPROC_RPCServerStarted ($) # Activate heartbeat if interface is default interface and rpcPingCCU > 0 if ($ping > 0 && $ifname eq $defInterface) { - HMCCU_Log ($hash, 1, "Scheduled CCU ping every $ping seconds", undef); + HMCCU_Log ($hash, 1, "Scheduled CCU ping every $ping seconds"); InternalTimer (gettimeofday()+$ping, "HMCCURPCPROC_RPCPing", $hash, 0); } @@ -1637,7 +1619,7 @@ sub HMCCURPCPROC_RPCServerStopped ($) # Inform FHEM that instance can be shut down HMCCU_Log ($hash, 2, 'RPC server stopped. Cancel delayed shutdown.'); - CancelDelayedShutdown ($name) if (exists ($hash->{hmccu}{delayedShutdown})); + CancelDelayedShutdown ($name) if (exists($hash->{hmccu}{delayedShutdown})); } ###################################################################### @@ -1757,8 +1739,7 @@ sub HMCCURPCPROC_IsRPCServerRunning ($) return 0; } - HMCCU_Log ($hash, 2, 'RPC server process running', undef); - return 1; + return HMCCU_Log ($hash, 2, 'RPC server process running', 1); } ###################################################################### @@ -1793,8 +1774,6 @@ sub HMCCURPCPROC_StopRPCServer ($$) $wait //= $HMCCURPCPROC_INIT_INTERVAL2; my $clkey = HMCCURPCPROC_GetKey ($hash); - HMCCU_Log ($hash, 3, 'StopRPCServer()'); - if (HMCCURPCPROC_CheckProcessState ($hash, 'running')) { HMCCU_Log ($hash, 1, "Stopping RPC server $clkey"); HMCCURPCPROC_SetState ($hash, "busy"); @@ -1857,132 +1836,138 @@ sub HMCCURPCPROC_StopRPCServer ($$) sub HMCCURPCPROC_SendRequest ($@) { my ($hash, $request, @param) = @_; - my $name = $hash->{NAME}; - my $ioHash = $hash->{IODev}; my $port = $hash->{rpcport}; - my $rc; - - return HMCCU_Log ($hash, 2, 'I/O device not found', undef) if (!defined($ioHash)); - - my $re = ':('.join('|', keys(%BINRPC_TYPE_MAPPING)).')'; + my $ioHash = $hash->{IODev} // + return HMCCU_Log ($hash, 2, 'I/O device not found', undef); if (HMCCU_IsRPCType ($ioHash, $port, 'A')) { - # Use XMLRPC - my $clurl = HMCCU_BuildURL ($ioHash, $port); - return HMCCU_Log ($hash, 2, "Can't get client URL for port $port", undef) - if (!defined ($clurl)); - - HMCCU_Log ($hash, 4, "Send ASCII RPC request $request to $clurl", undef); - my $rpcclient = RPC::XML::Client->new ($clurl, useragent => [ - ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0 } ]); - - if (exists($RPC_METHODS{$request})) { - # Read or write parameter sets - my $address = shift @param; - my $key = shift @param; - return HMCCU_Log ($hash, 2, "Missing address or key in RPC request $request", undef) - if (!defined ($key)); - - my %hparam; - - # Write requests have at least one parameter - if ($RPC_METHODS{$request} == 1) { - # Build a parameter hash - while (my $p = shift @param) { - my $pt; - if ($p =~ /${re}/) { - $pt = $1; - $p =~ s/${re}//; - } - my ($pn, $pv) = split ('=', $p, 2); - next if (!defined($pv)); - $hparam{$pn} = HMCCURPCPROC_EncValue ($pv, $pt); - } - - return HMCCU_Log ($hash, 2, "Missing parameter in RPC request $request", undef) - if (!keys %hparam); - - # Submit write paramset request - $rc = $rpcclient->simple_request ($request, $address, $key, \%hparam); - } - else { - # Submit read paramset request - $rc = $rpcclient->simple_request ($request, $address, $key); - } - } - else { - # RPC commands - my @aparam = (); - - # Build a parameter array - while (my $p = shift @param) { - my $pt; - if ($p =~ /${re}/) { - $pt = $1; - $p =~ s/${re}//; - } - push (@aparam, HMCCURPCPROC_EncValue ($p, $pt)); - } - - # Submit RPC command - $rc = $rpcclient->simple_request ($request, @aparam); - } - - HMCCU_Log ($hash, 2, "RPC request error ".$RPC::XML::ERROR, undef) if (!defined ($rc)); + return HMCCURPCPROC_SendXMLRequest ($hash, $ioHash, $port, $request, @param); } elsif (HMCCU_IsRPCType ($ioHash, $port, 'B')) { - # Use BINRPC - my ($serveraddr) = HMCCU_GetRPCServerInfo ($ioHash, $port, 'host'); - return HMCCU_Log ($ioHash, 2, "Can't get server address for port $port", undef) - if (!defined($serveraddr)); - - my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); - my $verbose = GetVerbose ($name); - - HMCCU_Log ($hash, 4, "Send binary RPC request $request to $serveraddr:$port", undef); - my $encreq = HMCCURPCPROC_EncodeRequest ($request, \@param); - return HMCCU_Log ($hash, 2, 'Error encoding binary request', undef) if ($encreq eq ''); - - # auto-flush on socket - $| = 1; - - # create a connecting socket - my $socket = new IO::Socket::INET (PeerHost => $serveraddr, PeerPort => $port, Proto => 'tcp'); - return HMCCU_Log ($hash, 2, "Can't create socket for $serveraddr:$port", undef) if (!$socket); - - my $size = $socket->send ($encreq); - if (defined($size)) { - my $encresp = ''; - while (my $readData = <$socket>) { - $encresp .= $readData; - } - $socket->close (); - - if (defined($encresp) && $encresp ne '') { - if ($ccuflags =~ /logEvents/ && $verbose >= 4) { - HMCCU_Log ($hash, 4, 'Response', undef); - HMCCURPCPROC_HexDump ($name, $encresp); - } - my ($response, $err) = HMCCURPCPROC_DecodeResponse ($encresp); - HMCCU_Log ($hash, 4, 'Error while decoding BIN RPC response') - if (defined($err) && $err == 0); - return $response; - } - else { - return ''; - } - } - - $socket->close (); + return HMCCURPCPROC_SendBINRequest ($hash, $ioHash, $port, $request, @param); } else { - HMCCU_Log ($hash, 2, 'Unknown RPC server type', undef); + return HMCCU_Log ($hash, 2, 'Unknown RPC server type', undef); + } +} + +sub HMCCURPCPROC_SendXMLRequest ($@) +{ + my ($hash, $ioHash, $port, $request, @param) = @_; + my $name = $hash->{NAME}; + + my $rc; + my $re = ':('.join('|', keys(%BINRPC_TYPE_MAPPING)).')'; + my $clurl = HMCCU_BuildURL ($ioHash, $port) // + return HMCCU_Log ($hash, 2, "Can't get client URL for port $port", undef); + + HMCCU_Log ($hash, 4, "Send ASCII RPC request $request to $clurl"); + my $rpcclient = RPC::XML::Client->new ($clurl, useragent => [ + ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0 } ]); + + if (exists($RPC_METHODS{$request})) { + # Read or write parameter sets + my $address = shift @param // + return HMCCU_Log ($hash, 2, "Missing address in RPC request $request", undef); + my $key = shift @param // + return HMCCU_Log ($hash, 2, "Missing key in RPC request $request", undef); + my %hparam; + + # Write requests have at least one parameter + if ($RPC_METHODS{$request} == 1) { + # Build a parameter hash + while (my $p = shift @param) { + my $pt; + if ($p =~ /${re}/) { $pt = $1; $p =~ s/${re}//; } + my ($pn, $pv) = split ('=', $p, 2); + $hparam{$pn} = HMCCURPCPROC_EncValue ($pv, $pt) if (defined($pv)); + } + + return HMCCU_Log ($hash, 2, "Missing parameter in RPC request $request", undef) + if (!keys %hparam); + + # Submit write paramset request + $rc = $rpcclient->simple_request ($request, $address, $key, \%hparam); + } + else { + # Submit read paramset request + $rc = $rpcclient->simple_request ($request, $address, $key); + } + } + else { + # RPC commands + my @aparam = (); + + # Build a parameter array + while (my $p = shift @param) { + my $pt; + if ($p =~ /${re}/) { $pt = $1; $p =~ s/${re}//; } + push (@aparam, HMCCURPCPROC_EncValue ($p, $pt)); + } + + # Submit RPC command + $rc = $rpcclient->simple_request ($request, @aparam); } + HMCCU_Log ($hash, 2, "RPC request error ".$RPC::XML::ERROR) if (!defined($rc)); return $rc; } +sub HMCCURPCPROC_SendBINRequest ($@) +{ + my ($hash, $ioHash, $port, $request, @param) = @_; + my $name = $hash->{NAME}; + + my ($serveraddr) = HMCCU_GetRPCServerInfo ($ioHash, $port, 'host'); + return HMCCU_Log ($ioHash, 2, "Can't get server address for port $port", undef) + if (!defined($serveraddr)); + + my $timeoutRead = AttrVal ($name, 'rpcReadTimeout', $HMCCURPCPROC_TIMEOUT_READ); + my $timeoutWrite = AttrVal ($name, 'rpcWriteTimeout', $HMCCURPCPROC_TIMEOUT_WRITE); + my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); + my $verbose = GetVerbose ($name); + + my $encreq = HMCCURPCPROC_EncodeRequest ($request, \@param); + return HMCCU_Log ($hash, 2, 'Error while encoding binary request', undef) if ($encreq eq ''); + + if ($ccuflags =~ /logEvents/) { + HMCCU_Log ($hash, 4, 'Binary RPC request'); + HMCCURPCPROC_HexDump ($name, $encreq); + } + + # create a connecting socket + my $socket = new IO::Socket::INET (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); + $socket->timeout (1); + + my ($bytesWritten, $errmsg) = HMCCURPCPROC_WriteToSocket ($socket, $encreq, $timeoutWrite); + if ($bytesWritten > 0) { + my ($bytesRead, $encresp) = HMCCURPCPROC_ReadFromSocket ($hash, $socket, $timeoutRead); + $socket->close (); + + if ($bytesRead > 0) { + if ($ccuflags =~ /logEvents/) { + HMCCU_Log ($hash, 4, 'Binary RPC response'); + HMCCURPCPROC_HexDump ($name, $encresp); + } + my ($response, $err) = HMCCURPCPROC_DecodeResponse ($encresp); + return HMCCU_Log ($hash, 2, 'Error while decoding binary response', undef) + if (!defined($err) || $err == 0); + return $response; + } + else { + return HMCCU_Log ($hash, 2, "Error while reading response for command $request: $encresp", ''); + } + } + else { + $socket->close (); + return HMCCU_Log ($hash, 2, "No data sent for request $request: $errmsg", undef); + } +} + ###################################################################### # Timer function for RPC Ping ###################################################################### @@ -1992,19 +1977,15 @@ sub HMCCURPCPROC_RPCPing ($) my ($hash) = @_; my $ioHash = $hash->{IODev}; my $ping = AttrVal ($ioHash->{NAME}, 'rpcPingCCU', $HMCCURPCPROC_TIME_PING); - my ($defInterface, $defPort) = HMCCU_GetDefaultInterface ($ioHash); + return HMCCU_Log ($hash, 1, 'CCU ping disabled') if ($ping == 0); + my ($defInterface, $defPort) = HMCCU_GetDefaultInterface ($ioHash); if ($hash->{rpcinterface} eq $defInterface) { - if ($ping > 0) { - if ($init_done && HMCCURPCPROC_CheckProcessState ($hash, 'running')) { - my $clkey = HMCCURPCPROC_GetKey ($hash); - HMCCURPCPROC_SendRequest ($hash, 'ping', "$clkey:STRING"); - } - InternalTimer (gettimeofday()+$ping, "HMCCURPCPROC_RPCPing", $hash, 0); - } - else { - HMCCU_Log ($hash, 1, 'CCU ping disabled'); + if ($init_done && HMCCURPCPROC_CheckProcessState ($hash, 'running')) { + my $clkey = HMCCURPCPROC_GetKey ($hash); + HMCCURPCPROC_SendRequest ($hash, 'ping', "$clkey:STRING"); } + InternalTimer (gettimeofday()+$ping, "HMCCURPCPROC_RPCPing", $hash, 0); } } @@ -2017,7 +1998,7 @@ sub HMCCURPCPROC_ProcessRequest ($$) my ($server, $connection) = @_; my $name = $server->{hmccu}{name}; my $clkey = $server->{hmccu}{clkey}; - my @methodlist = ('listDevices', 'listMethods', 'system.multicall'); + my @methodlist = ('listDevices', 'listMethods', 'system.listMethods', 'system.multicall'); my $verbose = GetVerbose ($name); # Read request @@ -2025,7 +2006,7 @@ sub HMCCURPCPROC_ProcessRequest ($$) while (my $packet = <$connection>) { $request .= $packet; } - return if (!defined($request) || $request eq ''); + return if ($request eq ''); if ($server->{hmccu}{ccuflags} =~ /logEvents/ && $verbose >= 4) { HMCCU_Log ($name, 4, "$clkey raw request:"); @@ -2035,9 +2016,10 @@ sub HMCCURPCPROC_ProcessRequest ($$) # Decode request my ($method, $params) = HMCCURPCPROC_DecodeRequest ($request); return if (!defined($method)); + $method = lc($method); HMCCU_Log ($name, 4, "Request method = $method"); - if ($method eq 'listmethods') { + if ($method eq 'listmethods' || $method eq 'system.listmethods') { $connection->send (HMCCURPCPROC_EncodeResponse ($BINRPC_ARRAY, \@methodlist)); } elsif ($method eq 'listdevices') { @@ -2045,11 +2027,11 @@ sub HMCCURPCPROC_ProcessRequest ($$) $connection->send (HMCCURPCPROC_EncodeResponse ($BINRPC_ARRAY, undef)); } elsif ($method eq 'system.multicall') { - return if (ref ($params) ne 'ARRAY'); + return if (ref($params) ne 'ARRAY'); my $a = $$params[0]; foreach my $s (@$a) { next if (!exists($s->{methodName}) || !exists($s->{params}) || - $s->{methodName} ne 'event' || scalar (@{$s->{params}}) < 4); + $s->{methodName} ne 'event' || scalar(@{$s->{params}}) < 4); HMCCURPCPROC_EventCB ($server, $clkey, ${$s->{params}}[1], ${$s->{params}}[2], ${$s->{params}}[3]); HMCCU_Log ($name, 4, 'Event '.${$s->{params}}[1].' '.${$s->{params}}[2].' ' @@ -2082,19 +2064,13 @@ sub HMCCURPCPROC_HandleConnection ($$$$) my $run = 1; my $pid = $$; - my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO"); - # Initialize RPC server HMCCU_Log ($name, 2, "Initializing RPC server $clkey for interface $iface"); my $rpcsrv = HMCCURPCPROC_InitRPCServer ($name, $clkey, $callbackport, $prot); - if (!defined($rpcsrv)) { - HMCCU_Log ($name, 1, "Can't initialize RPC server $clkey for interface $iface"); - return; - } - if (!($rpcsrv->{__daemon})) { - HMCCU_Log ($name, 1, "Server socket not found for port $port"); - return; - } + return HMCCU_Log ($name, 1, "Can't initialize RPC server $clkey for interface $iface") + if (!defined($rpcsrv)); + return HMCCU_Log ($name, 1, "Server socket not found for port $port") + if (!($rpcsrv->{__daemon})); # Event queue my @queue = (); @@ -2111,7 +2087,7 @@ sub HMCCURPCPROC_HandleConnection ($$$$) $rpcsrv->{hmccu}{evttime} = time (); # Initialize statistic counters - foreach my $et (@eventtypes) { + foreach my $et (@RPC_EVENT_TYPES) { $rpcsrv->{hmccu}{rec}{$et} = 0; $rpcsrv->{hmccu}{snd}{$et} = 0; } @@ -2174,7 +2150,7 @@ sub HMCCURPCPROC_HandleConnection ($$$$) HMCCURPCPROC_WriteStats ($rpcsrv, $clkey); # Send exit information - HMCCURPCPROC_Write ($rpcsrv, "EX", $clkey, "SHUTDOWN|$pid"); + HMCCURPCPROC_Write ($rpcsrv, 'EX', $clkey, "SHUTDOWN|$pid"); # Send queue entries to parent process. Resend on error to ensure that EX event is sent my ($c, $m) = HMCCURPCPROC_SendQueue ($sockparent, $socktimeout, \@queue, 0); @@ -2186,7 +2162,7 @@ sub HMCCURPCPROC_HandleConnection ($$$$) } # Log statistic counters - foreach my $et (@eventtypes) { + foreach my $et (@RPC_EVENT_TYPES) { HMCCU_Log ($name, 4, "$clkey event type = $et: ".$rpcsrv->{hmccu}{rec}{$et}); } HMCCU_Log ($name, 2, "Number of I/O errors = $sioerrors"); @@ -2251,7 +2227,7 @@ sub HMCCURPCPROC_SendData ($$) $bytes = 0; } elsif ($bytes != length($msg)) { - $err = 'Sent incomplete data'; + $err = 'send: incomplete data'; } return ($bytes, $err); @@ -2259,28 +2235,20 @@ sub HMCCURPCPROC_SendData ($$) ###################################################################### # Check if file descriptor is readable and read data. -# Return data and error message. +# Return (data, '') on success +# Return (undef, errmsg) on error ###################################################################### sub HMCCURPCPROC_ReceiveData ($$) { - my ($fh, $socktimeout) = @_; + my ($fh, $timeout) = @_; my $header; my $data; my $err = ''; - # Check if data is available - my $fd = fileno ($fh); - my $rin = ''; - vec ($rin, $fd, 1) = 1; - my $nfound = select ($rin, undef, undef, $socktimeout); - if ($nfound < 0) { - return (undef, $!); - } - elsif ($nfound == 0) { - return (undef, 'read: no data'); - } + my ($st, $msg) = HMCCURPCPROC_DataAvailableOnSocket ($fh, $timeout); + return (undef, $msg) if ($st <= 0); # Read datagram size my $sbytes = sysread ($fh, $header, 4); @@ -2288,7 +2256,7 @@ sub HMCCURPCPROC_ReceiveData ($$) return (undef, $!); } elsif ($sbytes != 4) { - return (undef, 'read: short header'); + return (undef, 'receive: short header'); } # Read datagram @@ -2298,12 +2266,90 @@ sub HMCCURPCPROC_ReceiveData ($$) return (undef, $!); } elsif ($bytes != $size) { - return (undef, 'read: incomplete data'); + return (undef, 'receive: incomplete data'); } return ($data, $err); } +###################################################################### +# Read data from socket +# Return (-1, ErrorStr) on error. +# Return (0, 'read: no data') if no data available. +# Return (BytesRead, Data) on success. +###################################################################### + +sub HMCCURPCPROC_ReadFromSocket ($$$) +{ + my ($hash, $socket, $timeout) = @_; + + my $data = ''; + my $totalBytes = 0; + + my ($st, $msg) = HMCCURPCPROC_DataAvailableOnSocket ($socket, $timeout); + while ($st > 0) { + my $buffer; + my $bytes = sysread ($socket, $buffer, 10000); + return (-1, $!) if (!defined($bytes)); + last if ($bytes == 0); + $data .= $buffer; + $totalBytes += $bytes; + ($st, $msg) = HMCCURPCPROC_DataAvailableOnSocket ($socket, $timeout); + } + + return $st < 0 ? ($st, $msg) : ($totalBytes, $data); +} + +###################################################################### +# Check if data is available for reading from socket +###################################################################### + +sub HMCCURPCPROC_DataAvailableOnSocket ($$) +{ + my ($socket, $timeout) = @_; + + my $fd = fileno ($socket); + my $rin = ''; + vec ($rin, $fd, 1) = 1; + + my $nfound = select ($rin, undef, undef, $timeout); + if ($nfound < 0) { + return (-1, $!); + } + elsif ($nfound == 0) { + return (0, 'read: no data'); + } + + return (1, ''); +} + +###################################################################### +# Write data to socket +# Return (-1, ErrorStr) on error. +# Return (0, 'write: no reader') if no reading process on remote host. +# Return (BytesWritten, 'OK') on success. +###################################################################### + +sub HMCCURPCPROC_WriteToSocket ($$$) +{ + my ($socket, $data, $timeout) = @_; + + my $fd = fileno ($socket); + my $win = ''; + vec ($win, $fd, 1) = 1; + my $nfound = select (undef, $win, undef, $timeout); + if ($nfound < 0) { + return (-1, $!); + } + elsif ($nfound == 0) { + return (0, 'write: no reader'); + } + + my $size = syswrite ($socket, $data); + + return defined($size) ? ($size, 'OK') : (-1, $!); +} + ###################################################################### # Write event into queue. ###################################################################### @@ -2315,14 +2361,12 @@ sub HMCCURPCPROC_Write ($$$$) if (defined($server->{hmccu}{eventqueue})) { my $queue = $server->{hmccu}{eventqueue}; - my $ev = $et."|".$cb."|".$msg; - + my $ev = "$et|$cb|$msg"; $server->{hmccu}{evttime} = time (); if (defined($server->{hmccu}{queuesize}) && scalar(@{$queue}) >= $server->{hmccu}{queuesize}) { - HMCCU_Log ($name, 1, "$cb maximum queue size reached. Dropping event."); - return; + return HMCCU_Log ($name, 1, "$cb maximum queue size reached. Dropping event."); } HMCCU_Log ($name, 2, "Event = $ev") if ($server->{hmccu}{ccuflags} =~ /logEvents/); @@ -2356,14 +2400,12 @@ sub HMCCURPCPROC_WriteStats ($$) my ($server, $clkey) = @_; my $name = $server->{hmccu}{name}; - my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO"); - if (defined ($server->{hmccu}{eventqueue})) { my $queue = $server->{hmccu}{eventqueue}; # Send statistic info my $st = $server->{hmccu}{snd}{total}; - foreach my $et (@eventtypes) { + foreach my $et (@RPC_EVENT_TYPES) { $st .= '|'.$server->{hmccu}{snd}{$et}; $server->{hmccu}{snd}{$et} = 0; } @@ -2388,7 +2430,7 @@ sub HMCCURPCPROC_HexDump ($$) my $offset = 0; foreach my $chunk (unpack "(a16)*", $data) { - my $hex = unpack "H*", $chunk; # hexadecimal magic + my $hex = unpack "H*", $chunk; # hexadecimal $chunk =~ tr/ -~/./c; # replace unprintables $hex =~ s/(.{1,8})/$1 /gs; # insert spaces HMCCU_Log ($name, 4, sprintf "0x%08x (%05u) %-*s %s", $offset, $offset, 36, $hex, $chunk); @@ -2516,7 +2558,7 @@ sub HMCCURPCPROC_ReaddDevicesCB ($$$) { my ($server, $cb, $a) = @_; my $name = $server->{hmccu}{name}; - my $devcount = scalar (@$a); + my $devcount = scalar(@$a); HMCCU_Log ($name, 2, "$cb ReaddDevice received $devcount device addresses"); foreach my $dev (@$a) { @@ -2587,7 +2629,7 @@ sub HMCCURPCPROC_EncValue ($$) # A float must contain at least a dot followed by a digit $type = 'float'; } - elsif ($value =~ /[a-zA-Z_ ]/ || $value =~ /^'.+'$/ || $value =~ /^".+"$/) { + elsif ($value eq '' || $value =~ /^([a-zA-Z_ ]+|'.+'|".+")$/) { $type = 'string'; } } @@ -2647,6 +2689,7 @@ sub HMCCURPCPROC_EncString ($) ###################################################################### # Encode name +# Encoded data will only contain the length and the name, no type. ###################################################################### sub HMCCURPCPROC_EncName ($) @@ -2781,40 +2824,47 @@ sub HMCCURPCPROC_EncType ($$) # type is STRING. # Return encoded data or empty string on error ###################################################################### +# Binary RPC request format: +# +# Offset Size Description +# 0 3 'Bin' +# 3 1 Type: 0=Request, 1=Response +# 4 4 Total length of data: n+4(ml)+4(pc)+p +# 8 4 Length of request method name (ml) +# 12 n Request method name +# 12+n 4 Number of parameters (pc) +# 16+n p Encoded parameters +###################################################################### sub HMCCURPCPROC_EncodeRequest ($$) { my ($method, $args) = @_; + + return '' if (!defined($method) || $method eq ''); # Encode method - my $m = HMCCURPCPROC_EncName ($method); + my $methodEnc = HMCCURPCPROC_EncName ($method); # Encode parameters my $re = ':('.join('|', keys(%BINRPC_TYPE_MAPPING)).')'; - my $r = ''; + my $content = ''; my $s = 0; if (defined($args)) { while (my $p = shift @$args) { my $pt = 'STRING'; - if ($p =~ /${re}/) { - $pt = $1; - $p =~ s/${re}//; - } - my ($e, $t) = split (':', $p); - $r .= HMCCURPCPROC_EncType ($BINRPC_TYPE_MAPPING{uc($pt)}, $p); + if ($p =~ /${re}/) { $pt = $1; $p =~ s/${re}//; } + my $encType = HMCCURPCPROC_EncType ($BINRPC_TYPE_MAPPING{uc($pt)}, $p); + return '' if ($encType eq ''); + $content .= $encType; $s++; } } - # Method, ParameterCount, Parameters - $r = $m.pack ('N', $s).$r; - - # Identifier, ContentLength, Content - # BINRPC is not a standard. Some implementations require an offset of 8 to be added - $r = pack ('NN', $BINRPC_REQUEST, length ($r)+8).$r; + my $header = pack ('NN', $BINRPC_REQUEST, 8+length($method)+length($content)). + $methodEnc.pack('N', $s); - return $r; + return $header.$content; } ###################################################################### @@ -3030,18 +3080,17 @@ sub HMCCURPCPROC_DecodeRequest ($) return (undef, undef) if (!defined ($method)); $i += $o; - my $c = unpack ('N', substr ($data, $i, 4)); $i += 4; for (my $n=0; $n<$c; $n++) { my ($d, $s) = HMCCURPCPROC_DecType ($data, $i); - return (undef, undef) if (!defined ($d)|| !defined($s)); + return (undef, undef) if (!defined($d)|| !defined($s)); push (@r, $d); $i += $s; } - return (lc ($method), \@r); + return (lc($method), \@r); } ###################################################################### @@ -3054,13 +3103,13 @@ sub HMCCURPCPROC_DecodeResponse ($) { my ($data) = @_; - return (undef, undef) if (length ($data) < 8); + return (undef, 0) if (length($data) < 8); my $id = unpack ('N', substr ($data, 0, 4)); if ($id == $BINRPC_RESPONSE) { # Data my ($result, $offset) = HMCCURPCPROC_DecType ($data, 8); - return ($result, 1); + return ($result, defined($result) ? 1 : 0); } elsif ($id == $BINRPC_ERROR) { # Error @@ -3071,7 +3120,7 @@ sub HMCCURPCPROC_DecodeResponse ($) # elsif ($id == 0x42696E41) { # } - return (undef, undef); + return (undef, 0); } @@ -3205,6 +3254,9 @@ sub HMCCURPCPROC_DecodeResponse ($) are forwarded to FHEM. In this case increase this value or increase attribute rpcMaxEvents. Default value is 500.
    +
  • rpcReadTimeout <seconds>
    + Wait the specified time for socket to become readable. Default value is 0.25 seconds. +
  • rpcServerAddr <ip-address>
    Set local IP address of RPC servers on FHEM system. If attribute is missing the corresponding attribute of I/O device (HMCCU device) is used or IP address is @@ -3221,8 +3273,7 @@ sub HMCCURPCPROC_DecodeResponse ($) is 500.

  • rpcWriteTimeout <seconds>
    - Wait the specified time for socket to become readable or writeable. Default value - is 0.001 seconds. + Wait the specified time for socket to become writeable. Default value is 0.001 seconds.
  • diff --git a/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm b/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm index 695ff3986..942469899 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 +# Version 4.8.002 # # Configuration parameters for HomeMatic devices. # @@ -100,7 +100,7 @@ use vars qw(%HMCCU_SCRIPTS); # Command-Defintion: # Command => 'Datapoint-Definition [...]' # Datapoint-Definition: -# Paramset:Datapoint:FixedValue[,FixedValue] +# Paramset:Datapoint:[Parameter=]FixedValue[,FixedValue] # Paramset:Datapoint:?Parameter # Paramset:Datapoint:?Parameter=Default-Value # Paramset:Datapoint:#Parameter @@ -115,21 +115,21 @@ use vars qw(%HMCCU_SCRIPTS); %HMCCU_ROLECMDS = ( 'MOTIONDETECTOR_TRANSCEIVER' => { - 'on' => 'V:MOTION_DETECTION_ACTIVE:true', - 'off' => 'V:MOTION_DETECTION_ACTIVE:false' + 'on' => 'V:MOTION_DETECTION_ACTIVE:active=true', + 'off' => 'V:MOTION_DETECTION_ACTIVE:active=false' }, 'SMOKE_DETECTOR' => { 'command' => 'V:SMOKE_DETECTOR_COMMAND:#command' }, 'KEY' => { - 'on' => 'V:PRESS_SHORT:true', - 'off' => 'V:PRESS_SHORT:true', - 'press' => 'V:PRESS_SHORT:true' + 'on' => 'V:PRESS_SHORT:1', + 'off' => 'V:PRESS_SHORT:1', + 'press' => 'V:PRESS_SHORT:1' }, 'KEY_TRANSCEIVER' => { - 'on' => 'V:PRESS_SHORT:true', - 'off' => 'V:PRESS_SHORT:true', - 'press' => 'V:PRESS_SHORT:true' + 'on' => 'V:PRESS_SHORT:1', + 'off' => 'V:PRESS_SHORT:1', + 'press' => 'V:PRESS_SHORT:1' }, 'BLIND' => { 'pct' => 'V:LEVEL:?level', @@ -137,7 +137,7 @@ use vars qw(%HMCCU_SCRIPTS); 'close' => 'V:LEVEL:0', 'up' => 'V:LEVEL:?delta=+10', 'down' => 'V:LEVEL:?delta=-10', - 'stop' => 'V:STOP:true' + 'stop' => 'V:STOP:1' }, 'BLIND_VIRTUAL_RECEIVER' => { 'pct' => 'V:LEVEL:?level', @@ -145,7 +145,7 @@ use vars qw(%HMCCU_SCRIPTS); 'close' => 'V:LEVEL:0', 'up' => 'V:LEVEL:?delta=+10', 'down' => 'V:LEVEL:?delta=-10', - 'stop' => 'V:STOP:true' + 'stop' => 'V:STOP:1' }, 'SHUTTER_VIRTUAL_RECEIVER' => { 'pct' => 'V:LEVEL:?level', @@ -153,30 +153,34 @@ use vars qw(%HMCCU_SCRIPTS); 'close' => 'V:LEVEL:0', 'up' => 'V:LEVEL:?delta=+10', 'down' => 'V:LEVEL:?delta=-10', - 'stop' => 'V:STOP:true' + 'stop' => 'V:STOP:1' }, 'SWITCH' => { - 'on' => 'V:STATE:true', - 'off' => 'V:STATE:false' + 'on' => 'V:STATE:1', + 'off' => 'V:STATE:0', + 'on-for-timer' => 'V:ON_TIME:?duration V:STATE:1', + 'on-till' => 'V:ON_TIME:?duration V:STATE:1' }, 'SWITCH_VIRTUAL_RECEIVER' => { - 'on' => 'V:STATE:true', - 'off' => 'V:STATE:false' + 'on' => 'V:STATE:1', + 'off' => 'V:STATE:0', + 'on-for-timer' => 'V:ON_TIME:?duration V:STATE:1', + 'on-till' => 'V:ON_TIME:?duration V:STATE:1' }, 'DIMMER' => { - 'pct' => 'V:LEVEL:?level', + 'pct' => 'V:LEVEL:?level V:ON_TIME:?time=0.0 V:RAMP_TIME:?ramp=0.5', 'on' => 'V:LEVEL:100', 'off' => 'V:LEVEL:0', - 'stop' => 'V:RAMP_STOP:true' + 'stop' => 'V:RAMP_STOP:1' }, 'DIMMER_VIRTUAL_RECEIVER' => { - 'pct' => 'V:LEVEL:?level', + 'pct' => 'V:LEVEL:?level V:ON_TIME:?time V:RAMP_TIME:?ramp', 'on' => 'V:LEVEL:100', 'off' => 'V:LEVEL:0' }, 'THERMALCONTROL_TRANSMIT' => { 'desired-temp' => 'V:SET_TEMPERATURE:?temperature', - 'manu' => 'V:MANU_MODE:?temperature', + 'manu' => 'V:MANU_MODE:?temperature=20', 'on' => 'V:MANU_MODE:30.5', 'off' => 'V:MANU_MODE:4.5', 'auto' => 'V:AUTO_MODE:1', @@ -185,7 +189,7 @@ use vars qw(%HMCCU_SCRIPTS); }, 'CLIMATECONTROL_RT_TRANSCEIVER' => { 'desired-temp' => 'V:SET_TEMPERATURE:?temperature', - 'manu' => 'V:MANU_MODE:?temperature', + 'manu' => 'V:MANU_MODE:?temperature=20', 'on' => 'V:MANU_MODE:30.5', 'off' => 'V:MANU_MODE:4.5', 'auto' => 'V:AUTO_MODE:1', @@ -196,7 +200,7 @@ use vars qw(%HMCCU_SCRIPTS); 'auto' => 'V:CONTROL_MODE:0', 'manu' => 'V:CONTROL_MODE:1', 'holiday' => 'V:CONTROL_MODE:2', - 'boost' => 'V:BOOST_MODE:true', + 'boost' => 'V:BOOST_MODE:1', 'on' => 'V:CONTROL_MODE:1 V:SET_POINT_TEMPERATURE:30.5', 'off' => 'V:CONTROL_MODE:1 V:SET_POINT_TEMPERATURE:4.5' } @@ -243,12 +247,12 @@ use vars qw(%HMCCU_SCRIPTS); }, 'CLIMATECONTROL_RT_TRANSCEIVER' => { 'cmdIcon' => 'auto:sani_heating_automatic manu:sani_heating_manual boost:sani_heating_boost on:general_an off:general_aus', - 'webCmd' => 'desired-temp', + 'webCmd' => 'desired-temp:auto:manu:boost:on:off', 'widgetOverride' => 'desired-temp:slider,4.5,0.5,30.5,1' }, 'HEATING_CLIMATECONTROL_TRANSCEIVER' => { 'cmdIcon' => 'auto:sani_heating_automatic manu:sani_heating_manual boost:sani_heating_boost on:general_an off:general_aus', - 'webCmd' => 'desired-temp:auto:manu:boost', + 'webCmd' => 'desired-temp:auto:manu:boost:on:off', 'widgetOverride' => 'desired-temp:slider,4.5,0.5,30.5,1' } );