diff --git a/fhem/CHANGED b/fhem/CHANGED index cdff4d513..e3df22fca 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - change: 88_HMCCU: Fixed substitute and authentication - change: 93_DbRep: Improvement of markouts in special cases, Forum:#134973 - bugfix: 36_ShellyMonitor: Change written reading from relay_0 to state for non-relais devices diff --git a/fhem/FHEM/88_HMCCU.pm b/fhem/FHEM/88_HMCCU.pm index 583247e9f..88f792d31 100755 --- a/fhem/FHEM/88_HMCCU.pm +++ b/fhem/FHEM/88_HMCCU.pm @@ -12,7 +12,7 @@ # CCU group devices, HomeGear, CUxD, Osram Lightify, Homematic Virtual Layer # and Philips Hue (not tested) # -# (c) 2022 by zap (zap01 t-online de) +# (c) 2023 by zap (zap01 t-online de) # ############################################################################## # @@ -57,7 +57,7 @@ my %HMCCU_CUST_CHN_DEFAULTS; my %HMCCU_CUST_DEV_DEFAULTS; # HMCCU version -my $HMCCU_VERSION = '5.0 222930908'; +my $HMCCU_VERSION = '5.0 232641921'; # Timeout for CCU requests (seconds) my $HMCCU_TIMEOUT_REQUEST = 4; @@ -250,7 +250,8 @@ sub HMCCU_CheckParameter ($$;$$$); sub HMCCU_DetectDevice ($$$); sub HMCCU_CreateFHEMDevices ($@); sub HMCCU_CreateDevice ($@); -sub HMCCU_IdentifyRole ($$$$$); +sub HMCCU_IdentifyDeviceRoles ($$$$$$); +sub HMCCU_IdentifyChannelRole ($$$$$); sub HMCCU_DetectRolePattern ($;$$$$); sub HMCCU_DeviceDescToStr ($$); sub HMCCU_ExecuteRoleCommand ($@); @@ -389,7 +390,8 @@ sub HMCCU_Initialize ($) ' ccudef-readingformat:name,namelc,address,addresslc,datapoint,datapointlc'. ' ccudef-stripnumber ccudef-attributes ccuReadingPrefix'. ' ccuflags:multiple-strict,procrpc,dptnocheck,logCommand,noagg,nohmstate,updGroupMembers,'. - 'logEvents,noEvents,noInitialUpdate,noReadings,nonBlocking,reconnect,logPong,trace,logEnhanced'. + 'logEvents,noEvents,noInitialUpdate,noReadings,nonBlocking,reconnect,logPong,trace,logEnhanced,'. + 'noAutoDetect,noAutoSubstitute,unknownDeviceRoles'. ' ccuReqTimeout ccuGetVars rpcPingCCU rpcinterfaces ccuAdminURLs'. ' rpcserver:on,off rpcserveraddr rpcserverport rpctimeout rpcevtimeout substitute'. ' ccuget:Value,State '. @@ -404,7 +406,7 @@ sub HMCCU_Define ($$$) { my ($hash, $a, $h) = @_; my $name = $hash->{NAME}; - my $usage = "Usage: define $name HMCCU {NameOrIP} [{ccunum}] [nosync] ccudelay={time} waitforccu={time} delayedinit={time}"; + my $usage = "Usage: define $name HMCCU {NameOrIP} [{ccunum}] [nosync] [ccudelay={time}] [waitforccu={time}] [delayedinit={time}]"; return $usage if (scalar(@$a) < 3); @@ -484,6 +486,11 @@ sub HMCCU_Define ($$$) $hash->{hmccu}{rpcports} = undef; $hash->{hmccu}{postInit} = 0; + # Check if authentication is active + my ($erruser, $encuser) = getKeyValue ($name.'_username'); + my ($errpass, $encpass) = getKeyValue ($name.'_password'); + $hash->{authentication} = (defined($encuser) && defined($encpass)) ? 'on' : 'off'; + HMCCU_Log ($hash, 1, "Initialized version $HMCCU_VERSION"); my $rc = 0; @@ -545,11 +552,7 @@ sub HMCCU_InitDevice ($) $attributes =~ s/rpcinterfaces/$rpcinterfaces/; setDevAttrList ($name, $attributes); - HMCCU_Log ($hash, 1, [ - "Read $devcnt devices with $chncnt channels from CCU $host", - "Read $prgcount programs from CCU $host", - "Read $gcount virtual groups from CCU $host" - ]); + HMCCU_Log ($hash, 1, "Read $devcnt devices with $chncnt channels, $prgcount programs, $gcount virtual groups from CCU $host"); # Interactive device definition or delayed initialization if ($init_done && !HMCCU_IsDelayedInit ($hash)) { @@ -1505,6 +1508,7 @@ sub HMCCU_Set ($@) if (!defined($username)) { setKeyValue ($name."_username", undef); setKeyValue ($name."_password", undef); + $hash->{authentication} = 'off'; return 'Credentials for CCU authentication deleted'; } return HMCCU_SetError ($hash, $usage) if (!defined($password)); @@ -1517,8 +1521,10 @@ sub HMCCU_Set ($@) 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)); + + $hash->{authentication} = 'on'; - return 'Credentials for CCU authentication stored'; + return 'Credentials for CCU authentication stored'; } elsif ($opt eq 'clear') { my $rnexp = shift @$a; @@ -2913,6 +2919,12 @@ sub HMCCU_Substitute ($$$$$;$$) my $ioHash; my $rc = 0; my $newvalue; + my $noAutoSubstitute = 0; + + if ($mode == -1) { + $mode = 0; + $noAutoSubstitute = 1; + } if (defined($hashOrRule)) { if (ref($hashOrRule) eq 'HASH') { @@ -2933,15 +2945,20 @@ sub HMCCU_Substitute ($$$$$;$$) foreach my $rule (@rulelist) { my @ruletoks = split ('!', $rule); if (scalar(@ruletoks) == 2 && $dpt ne '' && $mode == 0) { + # Substitute if current role and/or datapoint is matching rule + + # Left part of subst rule. r=role, f=channel/datapoint filter my ($r, $f) = split (':', $ruletoks[0], 2); if (!defined($f)) { + # No role specified $f = $r; $r = undef; } - if (!defined($r) || (defined($type) && $r eq $type)) { + if (!defined($r) || (defined($type) && defined($r) && $r eq $type)) { + # List of datapoints where rule should be applied on my @dptlist = split (',', $f); foreach my $d (@dptlist) { - my $c = -1; + my $c = -1; # Channel number (optional) if ($d =~ /^([0-9]{1,2})\.(.+)$/) { ($c, $d) = ($1, $2); } @@ -2953,7 +2970,11 @@ sub HMCCU_Substitute ($$$$$;$$) } } elsif (scalar(@ruletoks) == 1) { + # Substitute independent from role/datapoint + + # Do not substitute floating point values ??? return $value if ($value !~ /^[+-]?\d+$/ && $value =~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/); + ($rc, $newvalue) = HMCCU_SubstRule ($value, $ruletoks[0], $mode); return $newvalue if ($rc == 1); } @@ -2961,6 +2982,7 @@ sub HMCCU_Substitute ($$$$$;$$) # Original value not modified by rules. Use default conversion depending on type/role # Default conversion can be overriden by attribute ccudef-substitute in I/O device + return $value if ($noAutoSubstitute); # Substitute by rules defined in CONVERSIONS table if (!defined($type) || $type eq '') { @@ -2972,7 +2994,7 @@ sub HMCCU_Substitute ($$$$$;$$) elsif (exists($HMCCU_CONVERSIONS->{DEFAULT}{$dpt}{$value})) { return $HMCCU_CONVERSIONS->{DEFAULT}{$dpt}{$value}; } - + # Substitute enumerations and default parameter type conversions if (defined($devDesc) && defined($ioHash)) { my $paramDef = HMCCU_GetParamDef ($ioHash, $devDesc, 'VALUES', $dpt); @@ -3082,7 +3104,7 @@ sub HMCCU_SubstVariables ($$$) if (defined ($clhash->{hmccu}{dp}{$dp}{VALUES}{VAL})) { $text =~ s/\%\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{VALUES}{VAL}/g; $text =~ s/\%\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{VALUES}{VAL}/g; - $text =~ s/$dp/$clhash->{hmccu}{dp}{$dp}{VALUES}{VAL}/g; +# $text =~ s/$dp/$clhash->{hmccu}{dp}{$dp}{VALUES}{VAL}/g; } } @@ -3204,7 +3226,7 @@ sub HMCCU_UpdateDeviceTable ($$) my $devcount = 0; my $chncount = 0; - HMCCU_Log ($hash, 2, "Updating device table"); + HMCCU_Log ($hash, 3, "Updating device table"); # Update internal device table foreach my $da (keys %{$devices}) { @@ -3790,41 +3812,43 @@ sub HMCCU_SetSCAttributes ($$;$) my $ccuType = $clHash->{ccutype} // return; my $ccuAddr = $clHash->{ccuaddr} // return; my $ccuIf = $clHash->{ccuif} // return; - $detect //= HMCCU_DetectDevice ($ioHash, $ccuAddr, $ccuIf); +# $detect //= HMCCU_DetectDevice ($ioHash, $ccuAddr, $ccuIf); # Get readable and writeable datapoints my @dpWrite = (); my @dpRead = (); - my ($da, $dc) = HMCCU_SplitChnAddr ($ccuAddr, -2); +# my ($da, $dc) = HMCCU_SplitChnAddr ($ccuAddr, -2); + my ($da, $dc) = HMCCU_SplitChnAddr ($ccuAddr, -1); my $dpWriteCnt = HMCCU_GetValidDatapoints ($clHash, $ccuType, $dc, 2, \@dpWrite); my $dpReadCnt = HMCCU_GetValidDatapoints ($clHash, $ccuType, $dc, 5, \@dpRead); # Detect device and initialize attribute lists for statedatapoint and controldatapoint my @userattr = grep (!/statedatapoint|controldatapoint/, split(' ', $modules{$clHash->{TYPE}}{AttrList})); - if (defined($detect) && $detect->{level} > 0) { - $clHash->{hmccu}{detect} = $detect->{level}; - if ($type eq 'HMCCUDEV') { - push @userattr, 'statedatapoint:select,'. - join(',', sort map { $_.'.'.$detect->{stateRole}{$_}{datapoint} } keys %{$detect->{stateRole}}) - if ($detect->{stateRoleCount} > 0); - push @userattr, 'controldatapoint:select,'. - join(',', sort map { $_.'.'.$detect->{controlRole}{$_}{datapoint} } keys %{$detect->{controlRole}}) - if ($detect->{controlRoleCount} > 0); - } - elsif ($type eq 'HMCCUCHN') { - push @userattr, 'statedatapoint:select,'. - join(',', sort map { $detect->{stateRole}{$_}{datapoint} } keys %{$detect->{stateRole}}) - if ($detect->{stateRoleCount} > 0); - push @userattr, 'controldatapoint:select,'. - join(',', sort map { $detect->{controlRole}{$_}{datapoint} } keys %{$detect->{controlRole}}) - if ($detect->{controlRoleCount} > 0); - } - } - else { + # if (defined($detect) && $detect->{level} > 0) { + # $clHash->{hmccu}{detect} = $detect->{level}; + # if ($type eq 'HMCCUDEV') { + # push @userattr, 'statedatapoint:select,'. + # join(',', sort map { $_.'.'.$detect->{stateRole}{$_}{datapoint} } keys %{$detect->{stateRole}}) + # if ($detect->{stateRoleCount} > 0); + # push @userattr, 'controldatapoint:select,'. + # join(',', sort map { $_.'.'.$detect->{controlRole}{$_}{datapoint} } keys %{$detect->{controlRole}}) + # if ($detect->{controlRoleCount} > 0); + # } + # elsif ($type eq 'HMCCUCHN') { + # push @userattr, 'statedatapoint:select,'. + # join(',', sort map { $detect->{stateRole}{$_}{datapoint} } keys %{$detect->{stateRole}}) + # if ($detect->{stateRoleCount} > 0); + # push @userattr, 'controldatapoint:select,'. + # join(',', sort map { $detect->{controlRole}{$_}{datapoint} } keys %{$detect->{controlRole}}) + # if ($detect->{controlRoleCount} > 0); + # } + # } + # else { push @userattr, 'statedatapoint:select,'.join(',', sort @dpRead) if ($dpReadCnt > 0); push @userattr, 'controldatapoint:select,'.join(',', sort @dpWrite) if ($dpWriteCnt > 0); - $clHash->{hmccu}{detect} = 0; - } +# $clHash->{hmccu}{detect} = 0; + $clHash->{hmccu}{detect} = defined($detect) ? $detect->{level} : 0; +# } # Make sure that generic attributes are available, if no role attributes found push @userattr, 'statedatapoint' if (!grep(/statedatapoint/, @userattr)); @@ -3904,22 +3928,24 @@ sub HMCCU_GetDeviceConfig ($) my $c = 0; my $interfaces = HMCCU_GetRPCInterfaceList ($ioHash, 0); - foreach my $iface (keys %$interfaces) { + my @ifList = keys %$interfaces; + HMCCU_Log ($ioHash, 2, "Reading device configuration for interfaces ".join(',', @ifList)); + foreach my $iface (@ifList) { my ($rpcdev, $save) = HMCCU_GetRPCDevice ($ioHash, 1, $iface); if ($rpcdev ne '') { my $rpcHash = $defs{$rpcdev}; HMCCURPCPROC_Connect ($rpcHash, $ioHash); - HMCCU_Log ($ioHash, 2, "Reading Device Descriptions for interface $iface"); + HMCCU_Log ($ioHash, 5, "Reading Device Descriptions for interface $iface"); $c = HMCCURPCPROC_GetDeviceDesc ($rpcHash); - HMCCU_Log ($ioHash, 2, "Read $c Device Descriptions for interface $iface"); + HMCCU_Log ($ioHash, 5, "Read $c Device Descriptions for interface $iface"); $cDev += $c; - HMCCU_Log ($ioHash, 2, "Reading Paramset Descriptions for interface $iface"); + HMCCU_Log ($ioHash, 5, "Reading Paramset Descriptions for interface $iface"); $c = HMCCURPCPROC_GetParamsetDesc ($rpcHash); - HMCCU_Log ($ioHash, 2, "Read $c Paramset Descriptions for interface $iface"); + HMCCU_Log ($ioHash, 5, "Read $c Paramset Descriptions for interface $iface"); $cPar += $c; - HMCCU_Log ($ioHash, 2, "Reading Peer Descriptions for interface $iface"); + HMCCU_Log ($ioHash, 5, "Reading Peer Descriptions for interface $iface"); $c = HMCCURPCPROC_GetPeers ($rpcHash); - HMCCU_Log ($ioHash, 2, "Read $c Peer Descriptions for interface $iface"); + HMCCU_Log ($ioHash, 5, "Read $c Peer Descriptions for interface $iface"); $cLnk += $c; HMCCURPCPROC_Disconnect ($rpcHash, $ioHash); } @@ -3927,12 +3953,15 @@ sub HMCCU_GetDeviceConfig ($) HMCCU_Log ($ioHash, 2, "No RPC device found for interface $iface. Can't read device config."); } } + HMCCU_Log ($ioHash, 2, "Read descriptions of $cDev devices, $cPar paramsets, $cLnk links"); my @ccuDevList = (); my @ccuSuppDevList = (); my @ccuSuppTypes = (); my @ccuNotSuppTypes = (); - foreach my $di (sort keys %{$ioHash->{hmccu}{device}}) { + @ifList = sort keys %{$ioHash->{hmccu}{device}}; + HMCCU_Log ($ioHash, 2, "Detecting devices of interfaces ".join(',', @ifList)); + foreach my $di (@ifList) { foreach my $da (sort keys %{$ioHash->{hmccu}{device}{$di}}) { next if ($ioHash->{hmccu}{device}{$di}{$da}{_addtype} ne 'dev'); my $devName = $ioHash->{hmccu}{device}{$di}{$da}{_name}; @@ -3943,12 +3972,19 @@ sub HMCCU_GetDeviceConfig ($) } push @ccuDevList, $devName; my $detect = HMCCU_DetectDevice ($ioHash, $da, $di); - if (defined($detect) && $da ne 'HmIP-RCV-1' && $da ne 'BidCoS-RF') { - push @ccuSuppDevList, $devName; - push @ccuSuppTypes, $devModel; + if (defined($detect)) { + if ($da ne 'HmIP-RCV-1' && $da ne 'BidCoS-RF') { + push @ccuSuppDevList, $devName; + push @ccuSuppTypes, $devModel; + HMCCU_Log ($ioHash, 5, "Device $da $devName detected"); + } + else { + HMCCU_Log ($ioHash, 5, "Device $da $devName ignored"); + } } else { push @ccuNotSuppTypes, $devModel; + HMCCU_Log ($ioHash, 5, "Device $da $devName not detected"); } } } @@ -4741,7 +4777,7 @@ sub HMCCU_UpdateParamsetReadings ($$$;$) my $vg = ($clHash->{ccuif} eq 'VirtualDevices' && exists($clHash->{ccugroup}) && $clHash->{ccugroup} ne '') ? 1 : 0; # Get client device attributes - my $clFlags = HMCCU_GetFlags ($clName); + my $substMode = HMCCU_IsFlag($ioName,'noAutoSubstitute') || HMCCU_IsFlag($clName,'noAutoSubstitute') ? -1 : 0; my $clRF = HMCCU_GetAttrReadingFormat ($clHash, $ioHash); my $peer = AttrVal ($clName, 'peer', 'null'); my $clInt = $clHash->{ccuif}; @@ -4794,7 +4830,7 @@ sub HMCCU_UpdateParamsetReadings ($$$;$) $sv = HMCCU_ScaleValue ($clHash, $c, $p, $v, 0, $ps); HMCCU_UpdateInternalValues ($clHash, $chKey, $ps, 'NVAL', $sv); $fv = HMCCU_FormatReadingValue ($clHash, $sv, $p); - $cv = HMCCU_Substitute ($fv, $clHash, 0, $c, $p, $chnType, $devDesc); + $cv = HMCCU_Substitute ($fv, $clHash, $substMode, $c, $p, $chnType, $devDesc); HMCCU_UpdateInternalValues ($clHash, $chKey, $ps, 'SVAL', $cv); push @chKeys, $chKey; @@ -5393,6 +5429,8 @@ sub HMCCU_FormatDeviceInfo ($) 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 = ''; + + return 'Device info is empty' if (!defined($devinfo) || $devinfo eq ''); foreach my $dpspec (split ("\n", $devinfo)) { if ($dpspec =~ /^D/) { @@ -5401,13 +5439,18 @@ sub HMCCU_FormatDeviceInfo ($) } else { my ($t, $c_addr, $c_name, $d_name, $d_type, $d_value, $d_flags) = split (';', $dpspec); - $d_name =~ s/^[^:]+:(.+)$/$1/; - if ($c_addr ne $c_oaddr) { - $result .= "CHN $c_addr $c_name
"; - $c_oaddr = $c_addr; + if (defined($d_flags)) { + $d_name =~ s/^[^:]+:(.+)$/$1/; + if ($c_addr ne $c_oaddr) { + $result .= "CHN $c_addr $c_name
"; + $c_oaddr = $c_addr; + } + my $dt = exists($vtypes{$d_type}) ? $vtypes{$d_type} : $d_type; + $result .= "   $d_name = $d_value {$dt} [$d_flags]
"; + } + else { + return "Datapoint specification incomplete: $dpspec"; } - my $dt = exists($vtypes{$d_type}) ? $vtypes{$d_type} : $d_type; - $result .= "   $d_name = $d_value {$dt} [$d_flags]
"; } } @@ -5655,7 +5698,7 @@ sub HMCCU_GetDeviceList ($) $hash->{ccustate} = 'active'; # Delete old entries - HMCCU_Log ($hash, 2, "Deleting old CCU configuration data"); + HMCCU_Log ($hash, 5, "Deleting old CCU configuration data"); %{$hash->{hmccu}{dev}} = (); %{$hash->{hmccu}{adr}} = (); %{$hash->{hmccu}{interfaces}} = (); @@ -6104,7 +6147,8 @@ sub HMCCU_GetValidDatapoints ($$$$;$) else { if (exists ($ioHash->{hmccu}{dp}{$devtype})) { foreach my $ch (sort keys %{$ioHash->{hmccu}{dp}{$devtype}{ch}}) { - next if ($ch == 0 && $chn == -2); +# next if ($ch == 0 && $chn == -2); + next if ($ch == 0); foreach my $dp (sort keys %{$ioHash->{hmccu}{dp}{$devtype}{ch}{$ch}}) { if ($ioHash->{hmccu}{dp}{$devtype}{ch}{$ch}{$dp}{oper} & $oper) { push @$dplistref, $ch.".".$dp if (defined($dplistref)); @@ -7039,7 +7083,7 @@ sub HMCCU_UpdateRoleCommands ($$;$) my %pset = ('V' => 'VALUES', 'M' => 'MASTER', 'D' => 'MASTER', 'I' => 'INTERNAL'); my @cmdSetList = (); my @cmdGetList = (); - return if (!defined($clHash->{hmccu}{role}) || $clHash->{hmccu}{role} eq ''); + return if (HMCCU_IsFlag ($ioHash, 'noAutoDetect') || !defined($clHash->{hmccu}{role}) || $clHash->{hmccu}{role} eq ''); # Delete existing role commands delete $clHash->{hmccu}{roleCmds} if (exists($clHash->{hmccu}{roleCmds})); @@ -8068,8 +8112,12 @@ sub HMCCU_SetSCDatapoints ($$;$$$) HMCCU_Log ($clHash, 2, "f=$f chn not defined in $d $v".stacktraceAsString(undef)) if (!defined($chn)); HMCCU_Log ($clHash, 2, "f=$f dpt not defined in $d $v".stacktraceAsString(undef)) if (!defined($dpt) && !($f & 5)); - return 0 if ($init_done && defined($chn) && $chn ne '' && defined($dpt) && $dpt ne '' && - !HMCCU_IsValidParameter ($clHash, HMCCU_GetChannelAddr ($clHash, $chn), 'VALUES', $dpt, $f & 3 ? 5 : 2)); + if ($init_done && defined($chn) && $chn ne '' && defined($dpt) && $dpt ne '' && + !HMCCU_IsValidParameter ($clHash, HMCCU_GetChannelAddr ($clHash, $chn), 'VALUES', $dpt, $f & 3 ? 5 : 2)) + { + HMCCU_Log ($clHash, 2, "Invalid datapoint $chn.$dpt for parameter $d"); + return 0; + } $clHash->{ccurolestate} = $r if ($r ne '' && $f & 3); $clHash->{ccurolectrl} = $r if ($r ne '' && $f & 12); @@ -8292,75 +8340,6 @@ sub HMCCU_IsValidStateDatapoint ($;$) return $sc ne '' && $sd ne '' && ($checkHashOnly || HMCCU_IsValidParameter ($clHash, "$da:$sc", 'VALUES', $sd, 5)) ? 1 : 0; } -###################################################################### -# Get state and control datapoints from attributes -# Return defaults passed as parameters if attribute(s) not defined -###################################################################### - -sub HMCCU_DetectSCAttr ($$$$$) -{ - my ($clHash, $sc, $sd, $cc, $cd) = @_; - my $name = $clHash->{NAME}; - my $type = $clHash->{TYPE}; - $sc //= ''; - $cc //= ''; - - my $da; - my $dc; - if (defined($clHash->{ccuaddr})) { - ($da, $dc) = HMCCU_SplitChnAddr ($clHash->{ccuaddr}); - } - - $sc = $dc if ($sc eq ''); - $cc = $dc if ($cc eq ''); - - my $statedatapoint = AttrVal ($name, 'statedatapoint', ''); - my $controldatapoint = AttrVal ($name, 'controldatapoint', ''); - - # Attributes controlchannel and statechannel are only valid for HMCCUDEV devices - if ($type eq 'HMCCUDEV') { - $sc = AttrVal ($name, 'statechannel', $sc); - $cc = AttrVal ($name, 'controlchannel', $cc); - } - - # If attribute statedatapoint is specified, use it. - if ($statedatapoint ne '') { - if ($statedatapoint =~ /^([0-9]+)\.(.+)$/) { - # Attribute statechannel overrides channel specification. - ($sc, $sd) = $sc eq '' ? ($1, $2) : ($sc, $2); - } - else { - $sd = $statedatapoint; - if ($sc eq '') { - # Try to find state channel (datapoint must be readable or provide events) - my $c = HMCCU_FindDatapoint ($clHash, $type, -1, $sd, 5); - $sc = $c if ($c >= 0); - } - } - } - - # If attribute controldatapoint is specified, use it. - if ($controldatapoint ne '') { - if ($controldatapoint =~ /^([0-9]+)\.(.+)$/) { - # Attribute controlchannel overrides channel specification in controldatapoint - ($cc, $cd) = $cc eq '' ? ($1, $2) : ($cc, $2); - } - else { - $cd = $controldatapoint; - if ($cc eq '') { - # Try to find control channel (datapoint must be writeable) - my $c = HMCCU_FindDatapoint ($clHash, $type, -1, $cd, 4); - $cc = $c if ($c >= 0); - } - } - } - - my $rsdCnt = $sc ne '' && $sd ne '' ? 1 : 0; - my $rcdCnt = $cc ne '' && $cd ne '' ? 1 : 0; - - return ($sc, $sd, $cc, $cd, $rsdCnt, $rcdCnt); -} - ###################################################################### # Detect roles, channel and datapoint to be used for controlling and # displaying the state of a device or channel identified by its @@ -8410,11 +8389,10 @@ sub HMCCU_DetectDevice ($$$) { my ($ioHash, $address, $iface) = @_; - my @allRoles = (); + my @definitions = (); # Detected device definitions + my @allRoles = (); # Channel roles, index = channel number my @stateRoles = (); my @controlRoles = (); - my @unknownStateRoles = (); # State roles not known by HMCCU - my @unknownControlRoles = (); # Control roles not known by HMCCU my ($prioState, $prioControl) = (-1, -1); if (!defined($address)) { @@ -8424,31 +8402,18 @@ sub HMCCU_DetectDevice ($$$) my ($devAdd, $devChn) = HMCCU_SplitChnAddr ($address); - my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $address, $iface); - if (!defined($devDesc)) { - HMCCU_Log ($ioHash, 2, "Can't get device description for $address ".stacktraceAsString(undef)); + my $roleCnt = HMCCU_IdentifyDeviceRoles ($ioHash, $address, $iface, \@allRoles, \@stateRoles, \@controlRoles); + if ($roleCnt == 0 && HMCCU_IsFlag($ioHash, 'unknownDeviceRoles')) { + $roleCnt = HMCCU_UnknownDeviceRoles ($ioHash, $address, $iface, \@stateRoles, \@controlRoles); + } + if ($roleCnt == 0) { + HMCCU_Log ($ioHash, 5, "No roles detected for device $address"); return undef; } - # Identify known roles - if ($devDesc->{_addtype} eq 'dev') { - foreach my $child (split(',', $devDesc->{CHILDREN})) { - my $chnDesc = HMCCU_GetDeviceDesc ($ioHash, $child, $devDesc->{_interface}) // next; - push @allRoles, $chnDesc->{TYPE}; - my $known = HMCCU_IdentifyRole ($ioHash, $chnDesc, $iface, \@stateRoles, \@controlRoles); -# HMCCU_DetectUnknownRoles ($ioHash, $chnDesc, \@unknownStateRoles, \@unknownControlRoles) if (!$known); - } - } - elsif ($devDesc->{_addtype} eq 'chn') { - my $known = HMCCU_IdentifyRole ($ioHash, $devDesc, $iface, \@stateRoles, \@controlRoles); -# HMCCU_DetectUnknownRoles ($ioHash, $devDesc, \@unknownStateRoles, \@unknownControlRoles) if (!$known); - } - # Count roles and unique roles my $stateRoleCnt = scalar(@stateRoles); my $ctrlRoleCnt = scalar(@controlRoles); - my $unknownStateRoleCnt = scalar(@unknownStateRoles); - my $unknownCtrlRoleCnt = scalar(@unknownControlRoles); my %uniqStateRoles; my %uniqCtrlRoles; $uniqStateRoles{$_->{role}}++ for @stateRoles; @@ -8595,8 +8560,6 @@ sub HMCCU_DetectDevice ($$$) } } } - elsif ($stateRoleCnt == 0 && $ctrlRoleCnt == 0 && ($unknownStateRoleCnt > 0 || $unknownCtrlRoleCnt > 0)) { - } if ($di{defSCh} != -1 && exists($di{stateRole}{$di{defSCh}})) { my $dpn = $di{stateRole}{$di{defSCh}}{datapoint} // ''; @@ -8612,60 +8575,133 @@ sub HMCCU_DetectDevice ($$$) return \%di; } +###################################################################### +# Identify device roles +# Return 0 on error or number of roles +###################################################################### + +sub HMCCU_IdentifyDeviceRoles ($$$$$$) +{ + my ($ioHash, $address, $iface, $allRoles, $stateRoles, $controlRoles) = @_; + + return 0 if (HMCCU_IsFlag ($ioHash, 'noAutoDetect')); + + my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $address, $iface); + if (!defined($devDesc)) { + HMCCU_Log ($ioHash, 2, "Can't get device description for $address ".stacktraceAsString(undef)); + return 0; + } + + # Identify roles + if ($devDesc->{_addtype} eq 'dev') { + foreach my $child (split(',', $devDesc->{CHILDREN})) { + my $chnDesc = HMCCU_GetDeviceDesc ($ioHash, $child, $devDesc->{_interface}); + if (defined($chnDesc)) { + push @$allRoles, $chnDesc->{TYPE}; + HMCCU_IdentifyChannelRole ($ioHash, $chnDesc, $iface, $stateRoles, $controlRoles); + } + else { + push @$allRoles, 'UNKNOWN'; + } + } + } + elsif ($devDesc->{_addtype} eq 'chn') { + push @$allRoles, $devDesc->{TYPE}; + HMCCU_IdentifyChannelRole ($ioHash, $devDesc, $iface, $stateRoles, $controlRoles); + } + + return scalar(@$stateRoles)+scalar(@$controlRoles); +} + ###################################################################### # Identify a channel role ###################################################################### -sub HMCCU_IdentifyRole ($$$$$) +sub HMCCU_IdentifyChannelRole ($$$$$) { my ($ioHash, $chnDesc, $iface, $stateRoles, $controlRoles) = @_; my $t = $chnDesc->{TYPE}; # Channel role - return 0 if ($HMCCU_IGNORE_ROLES ne '' && $t =~ /$HMCCU_IGNORE_ROLES/); + return if (!exists($HMCCU_STATECONTROL->{$t}) || ($HMCCU_IGNORE_ROLES ne '' && $t =~ /$HMCCU_IGNORE_ROLES/)); - if (exists($HMCCU_STATECONTROL->{$t})) { - my ($a, $c) = HMCCU_SplitChnAddr ($chnDesc->{ADDRESS}); - my $p = $HMCCU_STATECONTROL->{$t}{P}; + # Role supported by HMCCU + my ($a, $c) = HMCCU_SplitChnAddr ($chnDesc->{ADDRESS}); + my $p = $HMCCU_STATECONTROL->{$t}{P}; - # State datapoint must be of type readable and/or event - my $sDP = HMCCU_DetectSCDatapoint ($HMCCU_STATECONTROL->{$t}{S}, $iface); - push @$stateRoles, { 'channel' => $c, 'role' => $t, 'datapoint' => $sDP, 'priority' => $p } - if (HMCCU_IsValidParameter ($ioHash, $chnDesc, 'VALUES', $sDP, 5)); + # State datapoint must be of type readable and/or event + my $sDP = HMCCU_DetectSCDatapoint ($HMCCU_STATECONTROL->{$t}{S}, $iface); + push @$stateRoles, { 'channel' => $c, 'role' => $t, 'datapoint' => $sDP, 'priority' => $p } + if (HMCCU_IsValidParameter ($ioHash, $chnDesc, 'VALUES', $sDP, 5)); - # Control datapoint must be writeable - my $cDP = HMCCU_DetectSCDatapoint ($HMCCU_STATECONTROL->{$t}{C}, $iface); - push @$controlRoles, { 'channel' => $c, 'role' => $t, 'datapoint' => $cDP, 'priority' => $p } - if (HMCCU_IsValidParameter ($ioHash, $chnDesc, 'VALUES', $cDP, 2)); - - return 1; - } - else { - # Role not supported by HMCCU - return 0; - } + # Control datapoint must be writeable + my $cDP = HMCCU_DetectSCDatapoint ($HMCCU_STATECONTROL->{$t}{C}, $iface); + push @$controlRoles, { 'channel' => $c, 'role' => $t, 'datapoint' => $cDP, 'priority' => $p } + if (HMCCU_IsValidParameter ($ioHash, $chnDesc, 'VALUES', $cDP, 2)); } -###################################################################### -# Check if unknown roles can be used as state and/or control role -###################################################################### +sub HMCCU_UnknownDeviceRoles ($$$$$) +{ + my ($ioHash, $address, $iface, $stateRoles, $controlRoles) = @_; -sub HMCCU_DetectUnknownRoles ($$$$) + my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $address, $iface); + if (!defined($devDesc)) { + HMCCU_Log ($ioHash, 2, "Can't get device description for $address ".stacktraceAsString(undef)); + return 0; + } + + # Identify roles + if ($devDesc->{_addtype} eq 'dev') { + foreach my $child (split(',', $devDesc->{CHILDREN})) { + my $chnDesc = HMCCU_GetDeviceDesc ($ioHash, $child, $devDesc->{_interface}); + if (defined($chnDesc)) { + HMCCU_UnknownChannelRole ($ioHash, $chnDesc, $stateRoles, $controlRoles); + } + } + } + elsif ($devDesc->{_addtype} eq 'chn') { + HMCCU_UnknownChannelRole ($ioHash, $devDesc, $stateRoles, $controlRoles); + } + + return scalar(@$stateRoles)+scalar(@$controlRoles); +} + +sub HMCCU_UnknownChannelRole ($$$$) { my ($ioHash, $chnDesc, $stateRoles, $controlRoles) = @_; + + my $t = $chnDesc->{TYPE}; # Channel role + return if ($HMCCU_IGNORE_ROLES ne '' && $t =~ /$HMCCU_IGNORE_ROLES/); + + # Role not supported by HMCCU, check for usable datapoints my $model = HMCCU_GetDeviceModel ($ioHash, $chnDesc->{_model}, $chnDesc->{_fw_ver}, $chnDesc->{INDEX}); if (defined($model) && exists($model->{VALUES})) { my $sdp = ''; my $cdp = ''; + my @sdpList = (); + my @cdpList = (); foreach my $p (keys %{$model->{VALUES}}) { - $sdp = $p if (($p->{OPERATIONS} & 5) && $sdp eq '' && $sdp ne 'STATE' && $sdp ne 'LEVEL'); - $cdp = $p if (($p->{OPERATIONS} & 2) && $cdp eq '' && $cdp ne 'STATE' && $cdp ne 'LEVEL'); + if (exists($model->{VALUES}{$p}{OPERATIONS})) { + if ($model->{VALUES}{$p}{OPERATIONS} & 5) { + push @sdpList, $p; + $sdp = $p if ($sdp eq '' || ($sdp ne 'STATE' && $sdp ne 'LEVEL')); + } + if ($model->{VALUES}{$p}{OPERATIONS} & 2) { + push @cdpList, $p; + $cdp = $p if ($cdp eq '' || ($cdp ne 'STATE' && $cdp ne 'LEVEL')); + } + } } - push @$stateRoles, { 'channel' => $chnDesc->{INDEX}, 'role' => $chnDesc->{TYPE}, 'datapoint' => $sdp, 'priority' => 1 } - if ($sdp ne ''); - push @$controlRoles, { 'channel' => $chnDesc->{INDEX}, 'role' => $chnDesc->{TYPE}, 'datapoint' => $cdp, 'priority' => 1 } - if ($cdp ne ''); + push @$stateRoles, { + 'channel' => $chnDesc->{INDEX}, 'role' => $chnDesc->{TYPE}, 'datapoint' => $sdp, + 'dptList' => join(',', @sdpList), 'priority' => 0 + } if ($sdp ne ''); + push @$controlRoles, { + 'channel' => $chnDesc->{INDEX}, 'role' => $chnDesc->{TYPE}, 'datapoint' => $cdp, + 'dptList' => join(',',@cdpList), 'priority' => 0 + } if ($cdp ne ''); + HMCCU_Log ($ioHash, 3, "Unknown role $t. sdp=$sdp, cdp=$cdp") if ($t ne 'MAINTENANCE'); } } @@ -8774,11 +8810,14 @@ sub HMCCU_GetFlags ($) ###################################################################### # Check if specific CCU flag is set. +# Parameter $flag is a regular expression. ###################################################################### sub HMCCU_IsFlag ($$) { - my ($name, $flag) = @_; + my ($nameOrHash, $flag) = @_; + + my $name = ref($nameOrHash) eq 'HASH' ? $nameOrHash->{NAME} : $nameOrHash; my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); return $ccuflags =~ /$flag/ ? 1 : 0; @@ -8866,13 +8905,14 @@ sub HMCCU_HMCommand ($$$) my $io_hash = HMCCU_GetHash ($cl_hash); my $ccureqtimeout = AttrVal ($io_hash->{NAME}, 'ccuReqTimeout', $HMCCU_TIMEOUT_REQUEST); - my $url = HMCCU_BuildURL ($io_hash, 'rega'); + my ($url, $auth) = HMCCU_BuildURL ($io_hash, 'rega'); my $value; 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 }; + $param->{header} = "Authorization: Basic $auth" if ($auth ne ''); my ($err, $response) = HttpUtils_BlockingGet ($param); if ($err eq '') { @@ -8904,7 +8944,7 @@ sub HMCCU_HMCommandNB ($$$) my $ioHash = HMCCU_GetHash ($clHash); my $ccureqtimeout = AttrVal ($ioHash->{NAME}, 'ccuReqTimeout', $HMCCU_TIMEOUT_REQUEST); - my $url = HMCCU_BuildURL ($ioHash, 'rega'); + my ($url, $auth) = HMCCU_BuildURL ($ioHash, 'rega'); HMCCU_Trace ($ioHash, 2, "Executing command $cmd non blocking"); HMCCU_Trace ($clHash, 2, "URL=$url"); @@ -8912,6 +8952,7 @@ sub HMCCU_HMCommandNB ($$$) my $param = { url => $url, timeout => $ccureqtimeout, data => $cmd, method => "POST", callback => \&HMCCU_HMScriptCB, cbFunc => $cbFunc, devhash => $clHash, ioHash => $ioHash }; $param->{sslargs} = { SSL_verify_mode => 0 }; + $param->{header} = "Authorization: Basic $auth" if ($auth ne ''); HttpUtils_NonblockingGet ($param); } @@ -8996,7 +9037,9 @@ sub HMCCU_HMScriptExt ($$;$$$) HMCCU_Trace ($hash, 2, "Code=$code"); # Execute script on CCU - my $url = HMCCU_BuildURL ($hash, 'rega'); + my ($url, $auth) = HMCCU_BuildURL ($hash, 'rega'); + my %header = ('Content-Type' => 'text/plain'); + $header{'Authorization'} = "Basic $auth" if ($auth ne ''); if (defined($cbFunc)) { # Non blocking HMCCU_Trace ($hash, 2, "Executing $hmscript non blocking"); @@ -9006,6 +9049,7 @@ sub HMCCU_HMScriptExt ($$;$$$) foreach my $p (keys %{$cbParam}) { $param->{$p} = $cbParam->{$p}; } } $param->{sslargs} = { SSL_verify_mode => 0 }; + $param->{header} = \%header; HttpUtils_NonblockingGet ($param); return ''; } @@ -9013,8 +9057,9 @@ sub HMCCU_HMScriptExt ($$;$$$) # Blocking request my $param = { url => $url, timeout => $ccureqtimeout, data => $code, method => "POST" }; $param->{sslargs} = { SSL_verify_mode => 0 }; + $param->{header} = \%header; my ($err, $response) = HttpUtils_BlockingGet ($param); - HMCCU_Trace ($hash, 2, "err=$err\nresponse=$response"); + HMCCU_Trace ($hash, 2, "err=$err\nresponse=".($response // '')); if ($err eq '') { return HMCCU_FormatScriptResponse ($response); } @@ -9324,11 +9369,7 @@ sub HMCCU_ScaleValue ($$$$$;$) my $name = $hash->{NAME}; my $ioHash = HMCCU_GetHash ($hash); - if (!defined($value) || !HMCCU_IsFltNum($value)) { - my $logValue = $value // 'undef'; - HMCCU_Log ($hash, 5, "Value $logValue is not numeric. chn=$chnno, dpt=$dpt"); - return $value; - } + return $value if (!defined($value) || !HMCCU_IsFltNum($value)); my $boundsChecking = ( $mode == 2 || @@ -10027,27 +10068,30 @@ sub HMCCU_MinMax ($$$) # Build ReGa or RPC client URL # Parameter backend specifies type of URL, 'rega' or name or port of # RPC interface. -# Return empty string on error. +# Return array in format (url, authorization) +# Return empty strings on error. ###################################################################### sub HMCCU_BuildURL ($$) { my ($hash, $backend) = @_; my $name = $hash->{NAME}; - + my $url = ''; + my $username = ''; my $password = ''; + my $authorization = ''; 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); + $authorization = encode_base64 ("$username:$password", ''); } - my $auth = ($username ne '' && $password ne '') ? "$username:$password".'@' : ''; if ($backend eq 'rega') { - $url = $hash->{prot}."://$auth".$hash->{host}.':'. + $url = $hash->{prot}."://".$hash->{host}.':'. $HMCCU_REGA_PORT{$hash->{prot}}.'/tclrega.exe'; } else { @@ -10055,7 +10099,7 @@ sub HMCCU_BuildURL ($$) if (defined($url)) { if (exists($HMCCU_RPC_SSL{$backend})) { my $p = $hash->{prot} eq 'https' ? '4' : ''; - $url =~ s/^http:\/\//$hash->{prot}:\/\/$auth/; + $url =~ s/^http:\/\//$hash->{prot}:\/\//; $url =~ s/:([0-9]+)/:$p$1/; } } @@ -10064,7 +10108,10 @@ sub HMCCU_BuildURL ($$) } } - return $url; + HMCCU_Trace ($hash, 2, "Build URL = " . $url); + HMCCU_Trace ($hash, 2, "Authorization = " . $authorization); + + return ($url, $authorization); } ###################################################################### @@ -10496,7 +10543,7 @@ sub HMCCU_GetDutyCycle ($) foreach my $port (values %$interfaces) { next if ($port != 2001 && $port != 2010); - my $url = HMCCU_BuildURL ($hash, $port) // next; + my ($url, $auth) = HMCCU_BuildURL ($hash, $port) // next; my $rpcclient = RPC::XML::Client->new ($url); my $response = $rpcclient->simple_request ('listBidcosInterfaces'); next if (!defined($response) || ref($response) ne 'ARRAY'); @@ -10959,15 +11006,18 @@ sub HMCCU_MaxHashEntries ($$) 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.
+ noAutoSubstitute - Do not substitute reading values by names. This global flag affects all devices. Set this flag in client devices to turn off substitutions in single devices
noEvents - Ignore events / device updates sent by CCU. No readings will be updated!
noInitialUpdate - Do not update datapoints of devices after RPC server start. Overrides - settings in RPC devices. + settings in RPC devices.
+ noAutoDetect - Do not detect any device (only for development and testing)
nonBlocking - Use non blocking (asynchronous) CCU requests
noReadings - Do not create or update readings
procrpc - Use external RPC server provided by module HMCCPRPCPROC. During first RPC server start HMCCU will create a HMCCURPCPROC device for each interface confiugured in attribute 'rpcinterface'
reconnect - Automatically reconnect to CCU when events timeout occurred.
+ unknownDeviceRoles - Command createDev will create HMCCUDEV or HMCCUCHN devices even if none of the device roles are known by HMCCU. This will become the default in a future version.
updGroupMembers - Update readings of group members in virtual devices.
  • ccuget {State | Value}
    diff --git a/fhem/FHEM/88_HMCCUCHN.pm b/fhem/FHEM/88_HMCCUCHN.pm index 9d0360f5f..d4f1b3206 100644 --- a/fhem/FHEM/88_HMCCUCHN.pm +++ b/fhem/FHEM/88_HMCCUCHN.pm @@ -30,7 +30,7 @@ sub HMCCUCHN_Set ($@); sub HMCCUCHN_Get ($@); sub HMCCUCHN_Attr ($@); -my $HMCCUCHN_VERSION = '5.0 222930908'; +my $HMCCUCHN_VERSION = '5.0 232641921'; ###################################################################### # Initialize module @@ -51,7 +51,7 @@ sub HMCCUCHN_Initialize ($) $hash->{parseParams} = 1; $hash->{AttrList} = 'IODev ccucalculate '. - 'ccuflags:multiple-strict,hideStdReadings,replaceStdReadings,noBoundsChecking,ackState,logCommand,noReadings,trace,simulate,showMasterReadings,showLinkReadings,showDeviceReadings,showServiceReadings '. + 'ccuflags:multiple-strict,hideStdReadings,replaceStdReadings,noBoundsChecking,ackState,logCommand,noAutoSubstitute,noReadings,trace,simulate,showMasterReadings,showLinkReadings,showDeviceReadings,showServiceReadings '. 'ccureadingfilter:textField-long statedatapoint controldatapoint '. 'ccureadingformat:name,namelc,address,addresslc,datapoint,datapointlc '. 'ccureadingname:textField-long ccuSetOnChange ccuReadingPrefix '. @@ -765,6 +765,7 @@ sub HMCCUCHN_Get ($@) replaceStdReadings: Replace original readings like 'ACTUAL_TEMPERATURE' by standard readings like 'measured-temp' instead of adding standard readings
    logCommand: Write get and set commands to FHEM log with verbose level 3.
    noBoundsChecking: Datapoint values are not checked for min/max boundaries
    + noAutoSubstitute - Do not substitute reading values by names. This local flag affects only the current device. You can turn off all substitutes by setting this flag in I/O device.
    noReadings: Do not update readings
    simulate: Do not execute set datapoint commands. Use this flag together with 'trace'
    showDeviceReadings: Show readings of device and channel 0.
    diff --git a/fhem/FHEM/88_HMCCUDEV.pm b/fhem/FHEM/88_HMCCUDEV.pm index 2b5a1ace7..e131ce972 100644 --- a/fhem/FHEM/88_HMCCUDEV.pm +++ b/fhem/FHEM/88_HMCCUDEV.pm @@ -31,7 +31,7 @@ sub HMCCUDEV_Set ($@); sub HMCCUDEV_Get ($@); sub HMCCUDEV_Attr ($@); -my $HMCCUDEV_VERSION = '5.0 222930908'; +my $HMCCUDEV_VERSION = '5.0 232641921'; ###################################################################### # Initialize module @@ -52,7 +52,7 @@ sub HMCCUDEV_Initialize ($) $hash->{parseParams} = 1; $hash->{AttrList} = 'IODev ccuaggregate:textField-long ccucalculate:textField-long '. - 'ccuflags:multiple-strict,ackState,hideStdReadings,replaceStdReadings,noBoundsChecking,logCommand,noReadings,trace,simulate,showMasterReadings,showLinkReadings,showDeviceReadings,showServiceReadings '. + 'ccuflags:multiple-strict,ackState,hideStdReadings,replaceStdReadings,noAutoSubstitute,noBoundsChecking,logCommand,noReadings,trace,simulate,showMasterReadings,showLinkReadings,showDeviceReadings,showServiceReadings '. 'ccureadingfilter:textField-long '. 'ccureadingformat:name,namelc,address,addresslc,datapoint,datapointlc '. 'ccureadingname:textField-long ccuSetOnChange ccuReadingPrefix devStateFlags '. diff --git a/fhem/FHEM/88_HMCCURPCPROC.pm b/fhem/FHEM/88_HMCCURPCPROC.pm index 53125bbc5..c14bab0eb 100755 --- a/fhem/FHEM/88_HMCCURPCPROC.pm +++ b/fhem/FHEM/88_HMCCURPCPROC.pm @@ -8,7 +8,7 @@ # # Subprocess based RPC Server module for HMCCU. # -# (c) 2022 by zap (zap01 t-online de) +# (c) 2023 by zap (zap01 t-online de) # ############################################################################## # @@ -39,7 +39,7 @@ use SetExtensions; ###################################################################### # HMCCURPC version -my $HMCCURPCPROC_VERSION = '5.0 222930908'; +my $HMCCURPCPROC_VERSION = '5.0 232641921'; # Maximum number of events processed per call of Read() my $HMCCURPCPROC_MAX_EVENTS = 100; @@ -446,7 +446,7 @@ sub HMCCURPCPROC_InitDevice ($$) $devHash->{hmccu}{rpc}{methods} =~ /(system\.multicall)/i) { $devHash->{hmccu}{rpc}{multicall} = $1; - HMCCU_Log ($devHash, 2, "CCU interface $ifname supports RPC multicalls"); + HMCCU_Log ($devHash, 5, "CCU interface $ifname supports RPC multicalls"); } else { HMCCU_Log ($devHash, 2, "CCU interface $ifname doesn't support RPC multicalls"); @@ -461,6 +461,7 @@ sub HMCCURPCPROC_InitDevice ($$) # Set some attributes if ($init_done) { $attr{$name}{stateFormat} = 'rpcstate/state'; + $attr{$name}{room} = 'Homematic'; $attr{$name}{verbose} = 2; } @@ -1381,13 +1382,14 @@ sub HMCCURPCPROC_RegisterCallback ($$) 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); + my ($clurl, $auth) = HMCCU_BuildURL ($ioHash, $port); my ($rpctype) = HMCCU_GetRPCServerInfo ($ioHash, $port, 'type'); return (0, "Can't get RPC parameters for ID $clkey") if (!defined($cburl) || !defined($clurl) || !defined($rpctype)); $hash->{hmccu}{rpc}{port} = $port; $hash->{hmccu}{rpc}{clurl} = $clurl; + $hash->{hmccu}{rpc}{auth} = $auth; $hash->{hmccu}{rpc}{cburl} = $cburl; HMCCU_Log ($hash, 2, "Registering callback $cburl of type $rpctype with ID $clkey at $clurl"); @@ -1416,6 +1418,7 @@ sub HMCCURPCPROC_DeRegisterCallback ($$) my $localaddr = $hash->{hmccu}{localaddr}; my $cburl = ''; my $clurl = ''; + my $auth = ''; my $rpchash = \%{$hash->{hmccu}{rpc}}; return (0, "RPC server $clkey not in state registered or running") @@ -1423,8 +1426,9 @@ sub HMCCURPCPROC_DeRegisterCallback ($$) $cburl = $rpchash->{cburl} if (exists($rpchash->{cburl})); $clurl = $rpchash->{clurl} if (exists($rpchash->{clurl})); + $auth = $rpchash->{auth} if (exists($rpchash->{auth})); $cburl = HMCCU_GetRPCCallbackURL ($ioHash, $localaddr, $rpchash->{cbport}, $clkey, $port) if ($cburl eq ''); - $clurl = HMCCU_BuildURL ($ioHash, $port) if ($clurl eq ''); + ($clurl, $auth) = HMCCU_BuildURL ($ioHash, $port) if ($clurl eq ''); return (0, "Can't get RPC parameters for ID $clkey") if ($cburl eq '' || $clurl eq ''); HMCCU_Log ($hash, 1, "Deregistering RPC server $cburl with ID $clkey at $clurl"); @@ -1440,6 +1444,7 @@ sub HMCCURPCPROC_DeRegisterCallback ($$) $rpchash->{cburl} = ''; $rpchash->{clurl} = ''; + $rpchash->{auth} = ''; $rpchash->{cbport} = 0; return (1, 'working'); @@ -1926,20 +1931,24 @@ sub HMCCURPCPROC_Connect ($;$) if (HMCCU_IsRPCType ($ioHash, $hash->{rpcport}, 'A')) { # Build the request URL - my $clurl = HMCCU_BuildURL ($ioHash, $hash->{rpcport}); - return HMCCU_Log ($hash, 2, "Can't get RPC client URL for port $hash->{rpcport}", 0) if (!defined($clurl)); + my ($clurl, $auth) = HMCCU_BuildURL ($ioHash, $hash->{rpcport}); + HMCCU_Log ($hash, 5, "Connecting to " . $clurl); + return HMCCU_Log ($hash, 1, "Can't get RPC client URL for port $hash->{rpcport}", 0) if (!defined($clurl)); my $header = HTTP::Headers->new ('Connection' => 'Keep-Alive'); + $header->header('Authorization' => "Basic $auth") if ($auth) ne ''; $hash->{hmccu}{rpc}{connection} = RPC::XML::Client->new ($clurl, useragent => [ - ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0 }, - default_headers => $header + ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0 } ] ); + $hash->{hmccu}{rpc}{connection}->useragent->default_headers($header); + $hash->{hmccu}{rpc}{clurl} = $clurl; + $hash->{hmccu}{rpc}{auth} = $auth; } elsif (HMCCU_IsRPCType ($ioHash, $hash->{rpcport}, 'B')) { my ($serveraddr) = HMCCU_GetRPCServerInfo ($ioHash, $hash->{rpcport}, 'host'); - return HMCCU_Log ($ioHash, 2, "Can't get server address for port $hash->{rpcport}", 0) if (!defined($serveraddr)); + return HMCCU_Log ($ioHash, 1, "Can't get server address for port $hash->{rpcport}", 0) if (!defined($serveraddr)); $hash->{hmccu}{rpc}{connection} = IO::Socket::INET->new ( PeerHost => $serveraddr, PeerPort => $hash->{rpcport}, Proto => 'tcp', Timeout => 3 @@ -1950,7 +1959,7 @@ sub HMCCURPCPROC_Connect ($;$) } } - return HMCCU_Log ($hash, 2, "Can't connect to RPC interface", 0) if (!defined($hash->{hmccu}{rpc}{connection})); + return HMCCU_Log ($hash, 1, "Can't connect to RPC interface", 0) if (!defined($hash->{hmccu}{rpc}{connection})); return 1; } @@ -1972,6 +1981,8 @@ sub HMCCURPCPROC_Disconnect ($;$) } delete $hash->{hmccu}{rpc}{connection}; + $hash->{hmccu}{rpc}{clurl} = ''; + $hash->{hmccu}{rpc}{auth} = ''; } ###################################################################### @@ -2171,12 +2182,13 @@ sub HMCCURPCPROC_SendXMLRequest ($@) my $re = ':('.join('|', keys(%BINRPC_TYPE_MAPPING)).')'; # Build the request URL - my $clurl = HMCCU_BuildURL ($ioHash, $port); - if (!defined($clurl)) { - HMCCU_Log ($hash, 2, "Can't get RPC client URL for port $port"); - return (undef, "Can't get RPC client URL for port $port"); - } - HMCCU_Log ($hash, 4, "Send ASCII XML RPC request $request to $clurl"); + # my $clurl = HMCCU_BuildURL ($ioHash, $port); + # if (!defined($clurl)) { + # HMCCU_Log ($hash, 2, "Can't get RPC client URL for port $port"); + # return (undef, "Can't get RPC client URL for port $port"); + # } +# HMCCU_Log ($hash, 1, stacktraceAsString(undef)); +# HMCCU_Log ($hash, 1, "Send ASCII XML RPC request $request to " . $hash->{hmccu}{rpc}{clurl}); # my $rpcclient = RPC::XML::Client->new ($clurl, useragent => [ # ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0 } @@ -2185,6 +2197,7 @@ sub HMCCURPCPROC_SendXMLRequest ($@) my @rpcParam = map { HMCCURPCPROC_XMLEncValue ($_) } @param; # Submit RPC request +# HMCCU_Log($hash, 2, Dumper($hash->{hmccu}{rpc}{connection})); my $resp = $hash->{hmccu}{rpc}{connection}->simple_request ($request, @rpcParam); if (!defined($resp)) { HMCCU_Log ($hash, 2, "RPC request $request failed: ".$RPC::XML::ERROR); @@ -2217,6 +2230,8 @@ sub HMCCURPCPROC_SendBINRequest ($@) # return (undef, "Can't get server address for port $port"); # } +# HMCCU_Log ($hash, 1, "Send BIN XML RPC request $request"); + my $timeoutRead = AttrVal ($name, 'rpcReadTimeout', $HMCCURPCPROC_TIMEOUT_READ); my $timeoutWrite = AttrVal ($name, 'rpcWriteTimeout', $HMCCURPCPROC_TIMEOUT_WRITE); my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); diff --git a/fhem/FHEM/HMCCUConf.pm b/fhem/FHEM/HMCCUConf.pm index a87992a3d..a06463eaf 100644 --- a/fhem/FHEM/HMCCUConf.pm +++ b/fhem/FHEM/HMCCUConf.pm @@ -192,7 +192,7 @@ $HMCCU_CONFIG_VERSION = '5.0'; }, 'SWITCH' => { F => 3, S => 'STATE', C => 'STATE', V => 'on:true,off:false', P => 2 - }, + }, 'SWITCH_PANIC' => { F => 3, S => 'STATE', C => 'STATE', V => 'on:true,off:false', P => 2 }, @@ -314,7 +314,7 @@ $HMCCU_CONFIG_VERSION = '5.0'; '^([0-9]{1,2}\.)?ACTUAL_HUMIDITY$:+humidity' ); -###################################################################### +####################################################################################### # Set commands related to channel role # Role => { Command-Definition, ... } # Command-Defintion: @@ -325,11 +325,12 @@ $HMCCU_CONFIG_VERSION = '5.0'; # Function: # A Perl function name # Datapoint-Def: -# Paramset:Datapoints:[Parameter=]FixedValue -# Paramset:Datapoints:?Parameter -# Paramset:Datapoints:?Parameter=Default-Value -# Paramset:Datapoints:#Parameter[=FixedValue,[...]] -# Paramset:Datapoints:*Parameter=Default-Value +# No parameters: Paramset:Datapoints:[Parameter=]FixedValue[,...] +# Toggle command: Paramset:Datapoints +# One parameter: Paramset:Datapoints:?Parameter +# Optional parameter with default: Paramset:Datapoints:?Parameter=Default-Value +# List of values: Paramset:Datapoints:#Parameter[=FixedValue[,...]] +# Internal value (paramset "I"): Paramset:Datapoints:*Parameter=Default-Value # Paramset: # V=VALUES, M=MASTER (channel), D=MASTER (device), I=INTERNAL # Datapoints: @@ -340,6 +341,7 @@ $HMCCU_CONFIG_VERSION = '5.0'; # parameter set description. Otherwise a list of values must # be specified after '='. # * = internal value $hash->{hmccu}{values}{parameterName} +# See also paramset "I" # FixedValue: Parameter values are detected in the following order: # 1. If command parameter name is identical with controldatapoint, # option values are taken from controldatapoint definition {V}. The @@ -351,7 +353,7 @@ $HMCCU_CONFIG_VERSION = '5.0'; # 3. As a fallback command options and option values are identical. # If Default-Value is preceeded by + or -, value is added to or # subtracted from current datapoint value -###################################################################### +####################################################################################### %HMCCU_ROLECMDS = ( 'ALARM_SWITCH_VIRTUAL_RECEIVER' => {