|
|
|
|
@@ -12,7 +12,7 @@
|
|
|
|
|
# CCU group devices, HomeGear, CUxD, Osram Lightify, Homematic Virtual Layer
|
|
|
|
|
# and Philips Hue (not tested)
|
|
|
|
|
#
|
|
|
|
|
# (c) 2022 by zap (zap01 <at> t-online <dot> de)
|
|
|
|
|
# (c) 2023 by zap (zap01 <at> t-online <dot> 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<br/>";
|
|
|
|
|
$c_oaddr = $c_addr;
|
|
|
|
|
if (defined($d_flags)) {
|
|
|
|
|
$d_name =~ s/^[^:]+:(.+)$/$1/;
|
|
|
|
|
if ($c_addr ne $c_oaddr) {
|
|
|
|
|
$result .= "CHN $c_addr $c_name<br/>";
|
|
|
|
|
$c_oaddr = $c_addr;
|
|
|
|
|
}
|
|
|
|
|
my $dt = exists($vtypes{$d_type}) ? $vtypes{$d_type} : $d_type;
|
|
|
|
|
$result .= " $d_name = $d_value {$dt} [$d_flags]<br/>";
|
|
|
|
|
}
|
|
|
|
|
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]<br/>";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -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.<br/>
|
|
|
|
|
logEvents - Write events from CCU into FHEM logfile<br/>
|
|
|
|
|
logPong - Write log message when receiving pong event if verbose level is at least 3.<br/>
|
|
|
|
|
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<br/>
|
|
|
|
|
noEvents - Ignore events / device updates sent by CCU. No readings will be updated!<br/>
|
|
|
|
|
noInitialUpdate - Do not update datapoints of devices after RPC server start. Overrides
|
|
|
|
|
settings in RPC devices.
|
|
|
|
|
settings in RPC devices.<br/>
|
|
|
|
|
noAutoDetect - Do not detect any device (only for development and testing)<br/>
|
|
|
|
|
nonBlocking - Use non blocking (asynchronous) CCU requests<br/>
|
|
|
|
|
noReadings - Do not create or update readings<br/>
|
|
|
|
|
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'<br/>
|
|
|
|
|
reconnect - Automatically reconnect to CCU when events timeout occurred.<br/>
|
|
|
|
|
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.<br/>
|
|
|
|
|
updGroupMembers - Update readings of group members in virtual devices.
|
|
|
|
|
</li><br/>
|
|
|
|
|
<li><b>ccuget {State | <u>Value</u>}</b><br/>
|
|
|
|
|
|