mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-16 10:46:03 +00:00
HMCCU: Update for version 4.4 beta
git-svn-id: https://svn.fhem.de/fhem/trunk@20988 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
e105e07514
commit
be9bc8d368
@ -4,7 +4,7 @@
|
||||
#
|
||||
# $Id: 88_HMCCU.pm 18745 2019-02-26 17:33:23Z zap $
|
||||
#
|
||||
# Version 4.4.000
|
||||
# Version 4.4.002
|
||||
#
|
||||
# Module for communication between FHEM and Homematic CCU2/3.
|
||||
#
|
||||
@ -52,7 +52,7 @@ my %HMCCU_CUST_CHN_DEFAULTS;
|
||||
my %HMCCU_CUST_DEV_DEFAULTS;
|
||||
|
||||
# HMCCU version
|
||||
my $HMCCU_VERSION = '4.4.000';
|
||||
my $HMCCU_VERSION = '4.4.002';
|
||||
|
||||
# Constants and default values
|
||||
my $HMCCU_MAX_IOERRORS = 100;
|
||||
@ -222,9 +222,9 @@ sub HMCCU_SetRPCState ($@);
|
||||
# Filter and modify readings
|
||||
sub HMCCU_FilterReading ($$$);
|
||||
sub HMCCU_FormatReadingValue ($$$);
|
||||
sub HMCCU_GetReadingName ($$$$$$$);
|
||||
sub HMCCU_GetReadingName ($$$$$$$;$);
|
||||
sub HMCCU_ScaleValue ($$$$$);
|
||||
sub HMCCU_Substitute ($$$$$);
|
||||
sub HMCCU_Substitute ($$$$$;$);
|
||||
sub HMCCU_SubstRule ($$$);
|
||||
sub HMCCU_SubstVariables ($$$);
|
||||
|
||||
@ -284,6 +284,7 @@ sub HMCCU_AddDeviceModel ($$$$$$);
|
||||
sub HMCCU_CreateDevice ($$$$$);
|
||||
sub HMCCU_DeleteDevice ($);
|
||||
sub HMCCU_ExistsDeviceModel ($$$;$);
|
||||
sub HMCCU_FindParamDef ($$$);
|
||||
sub HMCCU_FormatDeviceInfo ($);
|
||||
sub HMCCU_GetAddress ($$$$);
|
||||
sub HMCCU_GetAffectedAddresses ($);
|
||||
@ -406,7 +407,7 @@ sub HMCCU_Initialize ($)
|
||||
" ccudef-hmstatevals:textField-long ccudef-substitute:textField-long".
|
||||
" ccudef-readingname:textField-long ccudef-readingfilter:textField-long".
|
||||
" ccudef-readingformat:name,namelc,address,addresslc,datapoint,datapointlc".
|
||||
" ccudef-stripnumber".
|
||||
" ccudef-stripnumber ccuReadingPrefix".
|
||||
" ccuflags:multiple-strict,procrpc,dptnocheck,logCommand,noagg,nohmstate,".
|
||||
"logEvents,noEvents,noInitialUpdate,noReadings,nonBlocking,reconnect,logPong,trace".
|
||||
" ccuReqTimeout ccuGetVars rpcinterval:2,3,5,7,10 rpcqueue rpcPingCCU".
|
||||
@ -2365,24 +2366,25 @@ sub HMCCU_FilterReading ($$$)
|
||||
my $hmccu_hash = HMCCU_GetHash ($hash);
|
||||
return 1 if (!defined ($hmccu_hash));
|
||||
|
||||
my $grf = AttrVal ($hmccu_hash->{NAME}, 'ccudef-readingfilter', '.*');
|
||||
my $rf = AttrVal ($name, 'ccureadingfilter', $grf);
|
||||
$rf = $grf.";".$rf if ($rf ne $grf && $grf ne '.*');
|
||||
# my $grf = AttrVal ($hmccu_hash->{NAME}, 'ccudef-readingfilter', '.*');
|
||||
# my $rf = AttrVal ($name, 'ccureadingfilter', $grf);
|
||||
# $rf = $grf.";".$rf if ($rf ne $grf && $grf ne '.*');
|
||||
my $rf = AttrVal ($name, 'ccureadingfilter', '.*');
|
||||
|
||||
my $chnnam = '';
|
||||
my $chnnum = '';
|
||||
my $devadd = '';
|
||||
# my $chnnum = '';
|
||||
# my $devadd = '';
|
||||
|
||||
# Get channel name and channel number
|
||||
if (HMCCU_IsValidChannel ($hmccu_hash, $chn, $HMCCU_FL_ADDRESS)) {
|
||||
my ($devadd, $chnnum) = HMCCU_SplitChnAddr ($chn);
|
||||
if ($chnnum ne 'd') {
|
||||
# Get channel name and channel number
|
||||
$chnnam = HMCCU_GetChannelName ($hmccu_hash, $chn, '');
|
||||
($devadd, $chnnum) = HMCCU_SplitChnAddr ($chn);
|
||||
if ($chnnam eq '') {
|
||||
($devadd, $chnnum) = HMCCU_GetAddress ($hash, $chn, '', '');
|
||||
$chnnam = $chn;
|
||||
}
|
||||
}
|
||||
else {
|
||||
($devadd, $chnnum) = HMCCU_GetAddress ($hash, $chn, '', '');
|
||||
$chnnam = $chn;
|
||||
}
|
||||
|
||||
|
||||
HMCCU_Trace ($hash, 2, $fnc, "chn=$chn, chnnam=$chnnam chnnum=$chnnum dpt=$dpt, rules=$rf");
|
||||
|
||||
foreach my $r (split (';', $rf)) {
|
||||
@ -2420,11 +2422,12 @@ sub HMCCU_FilterReading ($$$)
|
||||
(
|
||||
($cn ne '' && "$chnnum" eq "$cn") ||
|
||||
($c ne '' && $chnnam =~ /$c/) ||
|
||||
($cn eq '' && $c eq '')
|
||||
($cn eq '' && $c eq '') ||
|
||||
($chnnum eq 'd')
|
||||
) && $dpt =~ /$f/
|
||||
)
|
||||
);
|
||||
# Negate filter
|
||||
# Negative filter
|
||||
return 1 if (
|
||||
!$rm && (
|
||||
($cn ne '' && "$chnnum" ne "$cn") ||
|
||||
@ -2443,7 +2446,7 @@ sub HMCCU_FilterReading ($$$)
|
||||
#
|
||||
# Parameters:
|
||||
#
|
||||
# Interface,Address,ChannelNo,Datapoint,ChannelNam,Format
|
||||
# Interface,Address,ChannelNo,Datapoint,ChannelNam,Format,Paramset
|
||||
# Format := { name[lc] | datapoint[lc] | address[lc] | formatStr }
|
||||
# formatStr := Any text containing at least one format pattern
|
||||
# pattern := { %a, %c, %n, %d, %A, %C, %N, %D }
|
||||
@ -2456,17 +2459,20 @@ sub HMCCU_FilterReading ($$$)
|
||||
#
|
||||
# Reading names can be modified or new readings can be added by
|
||||
# setting attribut ccureadingname.
|
||||
# Returns list of readings names.
|
||||
# Returns list of readings names. Return empty list on error.
|
||||
######################################################################
|
||||
|
||||
sub HMCCU_GetReadingName ($$$$$$$)
|
||||
sub HMCCU_GetReadingName ($$$$$$$;$)
|
||||
{
|
||||
my ($hash, $i, $a, $c, $d, $n, $rf) = @_;
|
||||
my ($hash, $i, $a, $c, $d, $n, $rf, $ps) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my %prefix = ( 'MASTER' => 'R-', 'LINK' => 'L-', 'VALUES' => '', 'SERVICE' => 'S-' );
|
||||
|
||||
my $ioHash = HMCCU_GetHash ($hash);
|
||||
return '' if (!defined ($ioHash));
|
||||
return () if (!defined ($ioHash) || !defined($d) || $d eq '');
|
||||
|
||||
$ps = 'VALUES' if (!defined($ps));
|
||||
my $rn = '';
|
||||
my @rnlist;
|
||||
|
||||
@ -2477,8 +2483,13 @@ sub HMCCU_GetReadingName ($$$$$$$)
|
||||
my $sr = AttrVal ($name, 'ccureadingname', $gsr);
|
||||
$sr .= ";".$gsr if ($sr ne $gsr && $gsr ne '');
|
||||
|
||||
# Datapoint is mandatory
|
||||
return '' if ($d eq '');
|
||||
# Get reading prefix definitions
|
||||
my $readingPrefix = HMCCU_GetAttribute ($ioHash, $hash, 'ccuReadingPrefix', '');
|
||||
foreach my $pd (split (',', $readingPrefix)) {
|
||||
my ($rSet, $rPre) = split (':', $pd);
|
||||
$prefix{$rSet} = $rPre if (defined($rPre) && exists($prefix{$rSet}));
|
||||
}
|
||||
my $rpf = exists($prefix{$ps}) ? $prefix{$ps} : '';
|
||||
|
||||
# Complete missing values
|
||||
$c = '' if (!defined ($c));
|
||||
@ -2499,11 +2510,11 @@ sub HMCCU_GetReadingName ($$$$$$$)
|
||||
$rn = $c ne '' ? $c.'.'.$d : $d;
|
||||
}
|
||||
elsif ($rf eq 'name' || $rf =~ /^name(lc|uc)$/) {
|
||||
return '' if ($n eq '');
|
||||
return () if ($n eq '');
|
||||
$rn = $n.'.'.$d;
|
||||
}
|
||||
elsif ($rf eq 'address' || $rf =~ /^address(lc|uc)$/) {
|
||||
return '' if ($a eq '');
|
||||
return () if ($a eq '');
|
||||
my $t = $a;
|
||||
$t = $i.'.'.$t if ($i ne '');
|
||||
$t = $t.'.'.$c if ($c ne '');
|
||||
@ -2521,7 +2532,7 @@ sub HMCCU_GetReadingName ($$$$$$$)
|
||||
$rn =~ s/\%D/uc($d)/ge;
|
||||
}
|
||||
|
||||
push (@rnlist, $rn);
|
||||
push (@rnlist, $rpf.$rn);
|
||||
|
||||
# Rename and/or add reading names
|
||||
if ($sr ne '') {
|
||||
@ -2837,9 +2848,9 @@ sub HMCCU_SetRPCState ($@)
|
||||
# mode: 0=Substitute regular expression, 1=Substitute text
|
||||
######################################################################
|
||||
|
||||
sub HMCCU_Substitute ($$$$$)
|
||||
sub HMCCU_Substitute ($$$$$;$)
|
||||
{
|
||||
my ($value, $substrule, $mode, $chn, $dpt) = @_;
|
||||
my ($value, $substrule, $mode, $chn, $dpt, $type) = @_;
|
||||
my $rc = 0;
|
||||
my $newvalue;
|
||||
|
||||
@ -2853,26 +2864,34 @@ sub HMCCU_Substitute ($$$$$)
|
||||
my @rulelist = split (';', $substrule);
|
||||
foreach my $rule (@rulelist) {
|
||||
my @ruletoks = split ('!', $rule);
|
||||
if (@ruletoks == 2 && $dpt ne '' && $mode == 0) {
|
||||
my @dptlist = split (',', $ruletoks[0]);
|
||||
foreach my $d (@dptlist) {
|
||||
my $c = -1;
|
||||
if ($d =~ /^([0-9]{1,2})\.(.+)$/) {
|
||||
($c, $d) = ($1, $2);
|
||||
}
|
||||
if ($d eq $dpt && ($c == -1 || !defined($chn) || $c == $chn)) {
|
||||
($rc, $newvalue) = HMCCU_SubstRule ($value, $ruletoks[1], $mode);
|
||||
return $newvalue;
|
||||
if (scalar(@ruletoks) == 2 && $dpt ne '' && $mode == 0) {
|
||||
my ($r, $f) = split (':', $ruletoks[0], 2);
|
||||
if (!defined($f)) {
|
||||
$f = $r;
|
||||
$r = undef;
|
||||
}
|
||||
if (!defined($r) || (defined($type) && $r eq $type)) {
|
||||
my @dptlist = split (',', $f);
|
||||
foreach my $d (@dptlist) {
|
||||
my $c = -1;
|
||||
if ($d =~ /^([0-9]{1,2})\.(.+)$/) {
|
||||
($c, $d) = ($1, $2);
|
||||
}
|
||||
if ($d eq $dpt && ($c == -1 || !defined($chn) || $c == $chn)) {
|
||||
($rc, $newvalue) = HMCCU_SubstRule ($value, $ruletoks[1], $mode);
|
||||
return $newvalue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif (@ruletoks == 1) {
|
||||
elsif (scalar(@ruletoks) == 1) {
|
||||
return $value if ($value !~ /^[+-]?\d+$/ && $value =~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/);
|
||||
($rc, $newvalue) = HMCCU_SubstRule ($value, $ruletoks[0], $mode);
|
||||
return $newvalue if ($rc == 1);
|
||||
}
|
||||
}
|
||||
|
||||
# Original value not modified by rules. Use default conversion
|
||||
return $value;
|
||||
}
|
||||
|
||||
@ -3365,7 +3384,9 @@ sub HMCCU_AddDeviceDesc ($$$$)
|
||||
$hash->{hmccu}{device}{$iface}{$k}{$p} = join(',', @{$desc->{$p}});
|
||||
}
|
||||
else {
|
||||
$hash->{hmccu}{device}{$iface}{$k}{$p} = $desc->{$p};
|
||||
my $d = $desc->{$p};
|
||||
$d =~ s/ /,/g;
|
||||
$hash->{hmccu}{device}{$iface}{$k}{$p} = $d;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3392,7 +3413,8 @@ sub HMCCU_AddDeviceDesc ($$$$)
|
||||
# Get device description.
|
||||
# Parameters:
|
||||
# $hash - Hash reference of IO device.
|
||||
# $address - Address of device or channel.
|
||||
# $address - Address of device or channel. Accepts a channel address
|
||||
# with channel number 'd' as an alias for device address.
|
||||
# $iface - Interface name.
|
||||
# Return hash reference for device description or undef on error.
|
||||
######################################################################
|
||||
@ -3409,6 +3431,7 @@ sub HMCCU_GetDeviceDesc ($$;$)
|
||||
push (@ifaceList, keys %${$hash->{hmccu}{device}});
|
||||
}
|
||||
|
||||
$address =~ s/:d//;
|
||||
foreach my $i (@ifaceList) {
|
||||
return $hash->{hmccu}{device}{$i}{$address}
|
||||
if (exists($hash->{hmccu}{device}{$i}{$address}));
|
||||
@ -3589,19 +3612,12 @@ sub HMCCU_GetParamDef ($$$$)
|
||||
{
|
||||
my ($hash, $object, $paramset, $parameter) = @_;
|
||||
|
||||
my $devDesc;
|
||||
if (ref($object) eq 'HASH') {
|
||||
$devDesc = $object;
|
||||
}
|
||||
else {
|
||||
$devDesc = HMCCU_GetDeviceDesc ($hash, $object);
|
||||
}
|
||||
my $devDesc = ref($object) eq 'HASH' ? $object : HMCCU_GetDeviceDesc ($hash, $object);
|
||||
|
||||
if (defined($devDesc)) {
|
||||
# Build device address and channel number
|
||||
my $address = $devDesc->{ADDRESS};
|
||||
my ($devAddr, $chnNo) = ($address =~ /:[0-9]{1,2}$/) ?
|
||||
HMCCU_SplitChnAddr ($address) : ($address, 'd');
|
||||
my $a = $devDesc->{ADDRESS};
|
||||
my ($devAddr, $chnNo) = ($a =~ /:[0-9]{1,2}$/) ? HMCCU_SplitChnAddr ($a) : ($a, 'd');
|
||||
|
||||
my $model = HMCCU_GetDeviceModel ($hash, $devDesc->{_model}, $devDesc->{_fw_ver}, $chnNo);
|
||||
if (defined($model) && exists($model->{$paramset}) && exists($model->{$paramset}{$parameter})) {
|
||||
@ -3612,6 +3628,39 @@ sub HMCCU_GetParamDef ($$$$)
|
||||
return undef;
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Find parameter defintion of device model
|
||||
# Parameters:
|
||||
# $hash - Hash reference of IO device.
|
||||
# $object - Device or channel address or device description
|
||||
# reference.
|
||||
# $parameter - Parameter name.
|
||||
# Returns (undef,undef) on error. Otherwise parameter set name and
|
||||
# reference to the parameter definition.
|
||||
######################################################################
|
||||
|
||||
sub HMCCU_FindParamDef ($$$)
|
||||
{
|
||||
my ($hash, $object, $parameter) = @_;
|
||||
|
||||
my $devDesc = ref($object) eq 'HASH' ? $object : HMCCU_GetDeviceDesc ($hash, $object);
|
||||
|
||||
if (defined($devDesc)) {
|
||||
# Build device address and channel number
|
||||
my $a = $devDesc->{ADDRESS};
|
||||
my ($devAddr, $chnNo) = ($a =~ /:[0-9]{1,2}$/) ? HMCCU_SplitChnAddr ($a) : ($a, 'd');
|
||||
|
||||
my $model = HMCCU_GetDeviceModel ($hash, $devDesc->{_model}, $devDesc->{_fw_ver}, $chnNo);
|
||||
if (defined($model)) {
|
||||
foreach my $ps (keys %$model) {
|
||||
return ($ps, $model->{$ps}{$parameter}) if (exists($model->{$ps}{$parameter}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (undef, undef);
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Convert parameter value
|
||||
# Parameters:
|
||||
@ -3635,7 +3684,6 @@ sub HMCCU_GetParamValue ($$$$$)
|
||||
my $paramDef = HMCCU_GetParamDef ($hash, $object, $paramset, $parameter);
|
||||
if (defined($paramDef)) {
|
||||
my $type = $paramDef->{TYPE};
|
||||
HMCCU_Log ($hash, 2, "Checking type $type");
|
||||
|
||||
return $ct{$type}{$value} if (exists($ct{$type}) && exists($ct{$type}{$value}));
|
||||
|
||||
@ -3715,6 +3763,138 @@ sub HMCCU_UpdateSingleDatapoint ($$$$)
|
||||
return (ref ($rc)) ? $rc->{$devaddr}{$chn}{$dpt} : $value;
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Update readings of client device.
|
||||
# Parameter objects is a hash reference which contains updated data
|
||||
# for devices:
|
||||
# {devaddr}{channelno}{paramset}{parameter} = value
|
||||
# channelno = 'd' for device parameters.
|
||||
# Return hash reference for results or undef on error.
|
||||
######################################################################
|
||||
|
||||
sub HMCCU_UpdateParamsetReadings ($$$;$)
|
||||
{
|
||||
my ($ioHash, $clHash, $objects, $addListRef) = @_;
|
||||
|
||||
my $clName = $clHash->{NAME};
|
||||
my $clType = $clHash->{TYPE};
|
||||
|
||||
return undef if (!defined($clHash->{IODev}) || !defined($clHash->{ccuaddr}) ||
|
||||
$clHash->{IODev} != $ioHash);
|
||||
|
||||
# Store the resulting readings
|
||||
my %results;
|
||||
|
||||
# Updated internal values
|
||||
my @chKeys = ();
|
||||
|
||||
# Check if update of device allowed
|
||||
my $disable = AttrVal ($clName, 'disable', 0);
|
||||
my $update = AttrVal ($clName, 'ccureadings', 1);
|
||||
return undef if ($update == 0 || $disable == 1 || $clHash->{ccudevstate} ne 'active');
|
||||
|
||||
# Build list of affected addresses
|
||||
my ($devAddr, $chnNo) = HMCCU_SplitChnAddr ($clHash->{ccuaddr});
|
||||
my @addList = defined ($addListRef) ? @$addListRef : ($devAddr);
|
||||
|
||||
# Determine virtual device flag
|
||||
my $vg = (($clHash->{ccuif} eq 'VirtualDevices' || $clHash->{ccuif} eq 'fhem') &&
|
||||
exists($clHash->{ccugroup})) ? 1 : 0;
|
||||
|
||||
# Get attributes considering default attributes in IO device
|
||||
my $substitute = HMCCU_GetAttrSubstitute ($clHash, $ioHash);
|
||||
|
||||
# Get client device attributes
|
||||
my $clFlags = HMCCU_GetFlags ($clName);
|
||||
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, '', 'STATE', '', '');
|
||||
|
||||
readingsBeginUpdate ($clHash);
|
||||
|
||||
# Loop over all addresses
|
||||
foreach my $a (@addList) {
|
||||
# Loop over all channels of device, including channel 'd'
|
||||
foreach my $c (keys %{$objects->{$a}}) {
|
||||
next if (($clType eq 'HMCCUCHN' && "$c" ne "$chnNo" && "$c" ne "0" && "$c" ne "d") ||
|
||||
("$c" eq "0" && $clFlags =~ /nochn0/));
|
||||
|
||||
my $chnAddr = "$devAddr:$c";
|
||||
my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $chnAddr, $clHash->{ccuif});
|
||||
my $chnType = defined($devDesc) ? $devDesc->{TYPE} : undef;
|
||||
|
||||
# Loop over all parameter sets
|
||||
foreach my $ps (keys %{$objects->{$a}{$c}}) {
|
||||
# Loop over all parameters
|
||||
foreach my $p (keys %{$objects->{$a}{$c}{$ps}}) {
|
||||
my $v = $objects->{$a}{$c}{$ps}{$p};
|
||||
next if (!defined($v) || !HMCCU_FilterReading ($clHash, $chnAddr, $p));
|
||||
my $fv = $v;
|
||||
my $cv = $v;
|
||||
my $sv;
|
||||
|
||||
# Key for storing values in client device hash. Indirect updates of virtual
|
||||
# devices are stored with device address in key.
|
||||
my $chKey = $devAddr ne $a ? "$chnAddr.$p" : "$c.$p";
|
||||
|
||||
# Store raw value in client device hash
|
||||
HMCCU_UpdateInternalValues ($clHash, $chKey, 'VAL', $v) if ($ps eq 'VALUES');
|
||||
|
||||
# Store the resulting value after scaling, formatting and substitution
|
||||
if ($ps eq 'VALUES') {
|
||||
# Modify value: scale, format, substitute
|
||||
$sv = HMCCU_ScaleValue ($clHash, $c, $p, $v, 0);
|
||||
$fv = HMCCU_FormatReadingValue ($clHash, $sv, $p);
|
||||
$cv = HMCCU_Substitute ($fv, $substitute, 0, $c, $p, $chnType);
|
||||
if ("$cv" eq "$fv") {
|
||||
# If value has not been changed by rules, use default conversion
|
||||
$cv = HMCCU_GetParamValue ($ioHash, $devDesc, $ps, $p, $fv);
|
||||
}
|
||||
|
||||
HMCCU_UpdateInternalValues ($clHash, $chKey, 'SVAL', $cv);
|
||||
push @chKeys, $chKey;
|
||||
|
||||
# Update 'state' and 'control'
|
||||
HMCCU_BulkUpdate ($clHash, 'control', $fv, $cv)
|
||||
if ($cd ne '' && $p eq $cd && $c eq $cc);
|
||||
HMCCU_BulkUpdate ($clHash, 'state', $fv, $cv)
|
||||
if ($p eq $sd && ($sc eq '' || $sc eq $c));
|
||||
|
||||
# Update peers
|
||||
HMCCU_UpdatePeers ($clHash, "$c.$p", $cv, $peer) if (!$vg && $peer ne 'null');
|
||||
}
|
||||
else {
|
||||
$fv = $v;
|
||||
$cv = HMCCU_GetParamValue ($ioHash, $devDesc, $ps, $p, $v);
|
||||
}
|
||||
|
||||
# Store result, but not for indirect updates of virtual devices
|
||||
$results{$devAddr}{$c}{$p} = $cv if ($devAddr eq $a);
|
||||
|
||||
# Update readings
|
||||
my @rnList = HMCCU_GetReadingName ($clHash, $clInt, $a, $c, $p, '', $clRF, $ps);
|
||||
foreach my $rn (@rnList) {
|
||||
HMCCU_BulkUpdate ($clHash, $rn, $fv, $cv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate additional readings
|
||||
if (scalar (@chKeys) > 0) {
|
||||
my %calc = HMCCU_CalculateReading ($clHash, \@chKeys);
|
||||
foreach my $cr (keys %calc) {
|
||||
HMCCU_BulkUpdate ($clHash, $cr, $calc{$cr}, $calc{$cr});
|
||||
}
|
||||
}
|
||||
|
||||
readingsEndUpdate ($clHash, 1);
|
||||
|
||||
return \%results;
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Update readings of client device.
|
||||
# Parameter objects is a hash reference which contains updated data
|
||||
@ -3744,7 +3924,7 @@ sub HMCCU_UpdateSingleDevice ($$$$)
|
||||
# Check if update of device allowed
|
||||
my $disable = AttrVal ($cltname, 'disable', 0);
|
||||
my $update = AttrVal ($cltname, 'ccureadings', 1);
|
||||
next if ($update == 0 || $disable == 1 || $clthash->{ccudevstate} ne 'active');
|
||||
return 0 if ($update == 0 || $disable == 1 || $clthash->{ccudevstate} ne 'active');
|
||||
|
||||
# Get device parameters and attributes
|
||||
my $ccuflags = HMCCU_GetFlags ($ccuname);
|
||||
@ -5991,10 +6171,10 @@ sub HMCCU_GetHash ($@)
|
||||
|
||||
sub HMCCU_GetAttribute ($$$$)
|
||||
{
|
||||
my ($hmccu_hash, $cl_hash, $attr_name, $attr_def) = @_;
|
||||
my ($ioHash, $clHash, $attrName, $attrDefault) = @_;
|
||||
|
||||
my $value = AttrVal ($cl_hash->{NAME}, $attr_name, '');
|
||||
$value = AttrVal ($hmccu_hash->{NAME}, $attr_name, $attr_def) if ($value eq '');
|
||||
my $value = AttrVal ($clHash->{NAME}, $attrName, '');
|
||||
$value = AttrVal ($ioHash->{NAME}, $attrName, $attrDefault) if ($value eq '');
|
||||
|
||||
return $value;
|
||||
}
|
||||
@ -7403,7 +7583,8 @@ sub HMCCU_RPCRequest ($$$$$;$)
|
||||
my $type = $clHash->{TYPE};
|
||||
my $fnc = "RPCRequest";
|
||||
|
||||
my $reqMethod = $method eq 'listParamset' || $method eq 'listRawParamset' ? 'getParamset' : $method;
|
||||
my $reqMethod = $method eq 'listParamset' || $method eq 'listRawParamset' ||
|
||||
$method eq 'getRawParamset' ? 'getParamset' : $method;
|
||||
$filter = '.*' if (!defined ($filter));
|
||||
my $addr = '';
|
||||
my $result = '';
|
||||
@ -7437,10 +7618,19 @@ sub HMCCU_RPCRequest ($$$$$;$)
|
||||
|
||||
# Build parameter array: (Address, Paramset [, Parameter ...])
|
||||
# Paramset := VALUE | MASTER | LINK or any paramset supported by device
|
||||
# Parameter := Name=Value
|
||||
# Parameter := Name=Value[:Type]
|
||||
my @parArray = ($addr, $paramset);
|
||||
if (defined ($parref)) {
|
||||
foreach my $k (keys %{$parref}) { push @parArray, "$k=$parref->{$k}"; };
|
||||
if (defined($parref)) {
|
||||
foreach my $k (keys %{$parref}) {
|
||||
my ($pv, $pt) = split (':', $parref->{$k});
|
||||
if (!defined($pt)) {
|
||||
my $paramDef = HMCCU_GetParamDef ($ioHash, $addr, $paramset, $k);
|
||||
$pt = defined($paramDef) && defined($paramDef->{TYPE}) && $paramDef->{TYPE} ne '' ?
|
||||
$paramDef->{TYPE} : "STRING";
|
||||
}
|
||||
$pv .= ":$pt";
|
||||
push @parArray, "$k=$pv";
|
||||
};
|
||||
}
|
||||
|
||||
# Submit RPC request
|
||||
@ -7469,7 +7659,7 @@ sub HMCCU_RPCRequest ($$$$$;$)
|
||||
if ($method eq 'listParamset') {
|
||||
$result = join ("\n", map { $_ =~ /$filter/ ? $_.'='.$reqResult->{$_} : () } keys %$reqResult);
|
||||
}
|
||||
elsif ($method eq 'listRawParamset') {
|
||||
elsif ($method eq 'listRawParamset' || $method eq 'getRawParamset') {
|
||||
$result = $reqResult;
|
||||
}
|
||||
elsif ($method eq 'getDeviceDescription') {
|
||||
|
@ -4,7 +4,7 @@
|
||||
#
|
||||
# $Id: 88_HMCCUCHN.pm 18552 2019-02-10 11:52:28Z zap $
|
||||
#
|
||||
# Version 4.4.001
|
||||
# Version 4.4.002
|
||||
#
|
||||
# (c) 2020 zap (zap01 <at> t-online <dot> de)
|
||||
#
|
||||
@ -45,7 +45,7 @@ sub HMCCUCHN_Initialize ($)
|
||||
$hash->{AttrList} = "IODev ccucalculate ".
|
||||
"ccuflags:multiple-strict,ackState,logCommand,nochn0,trace ccureadingfilter ".
|
||||
"ccureadingformat:name,namelc,address,addresslc,datapoint,datapointlc ".
|
||||
"ccureadingname:textField-long ccuSetOnChange ".
|
||||
"ccureadingname:textField-long ccuSetOnChange ccuReadingPrefix".
|
||||
"ccureadings:0,1 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;
|
||||
@ -67,7 +67,7 @@ sub HMCCUCHN_Define ($@)
|
||||
my $devtype = shift @$a;
|
||||
my $devspec = shift @$a;
|
||||
|
||||
my $hmccu_hash = undef;
|
||||
my $ioHash = undef;
|
||||
|
||||
# Store some definitions for delayed initialization
|
||||
$hash->{hmccu}{devspec} = $devspec;
|
||||
@ -93,16 +93,16 @@ sub HMCCUCHN_Define ($@)
|
||||
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');
|
||||
$hmccu_hash = $defs{$h->{iodev}};
|
||||
$ioHash = $defs{$h->{iodev}};
|
||||
}
|
||||
else {
|
||||
# The following call will fail during FHEM start if CCU is not ready
|
||||
$hmccu_hash = HMCCU_FindIODevice ($devspec);
|
||||
$ioHash = HMCCU_FindIODevice ($devspec);
|
||||
}
|
||||
|
||||
if ($init_done) {
|
||||
# Interactive define command while CCU not ready or no IO device defined
|
||||
if (!defined ($hmccu_hash)) {
|
||||
if (!defined ($ioHash)) {
|
||||
my ($ccuactive, $ccuinactive) = HMCCU_IODeviceStates ();
|
||||
if ($ccuinactive > 0) {
|
||||
return "CCU and/or IO device not ready. Please try again later";
|
||||
@ -114,7 +114,7 @@ sub HMCCUCHN_Define ($@)
|
||||
}
|
||||
else {
|
||||
# CCU not ready during FHEM start
|
||||
if (!defined ($hmccu_hash) || $hmccu_hash->{ccustate} ne 'active') {
|
||||
if (!defined ($ioHash) || $ioHash->{ccustate} ne 'active') {
|
||||
Log3 $name, 2, "HMCCUCHN: [$devname] Cannot detect IO device, maybe CCU not ready. Trying later ...";
|
||||
# readingsSingleUpdate ($hash, "state", "Pending", 1);
|
||||
$hash->{ccudevstate} = 'pending';
|
||||
@ -123,9 +123,9 @@ sub HMCCUCHN_Define ($@)
|
||||
}
|
||||
|
||||
# Initialize FHEM device, set IO device
|
||||
my $rc = HMCCUCHN_InitDevice ($hmccu_hash, $hash);
|
||||
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 ".$hmccu_hash->{NAME} if ($rc == 2);
|
||||
return "Can't assign I/O device ".$ioHash->{NAME} if ($rc == 2);
|
||||
|
||||
return undef;
|
||||
}
|
||||
@ -223,7 +223,7 @@ sub HMCCUCHN_Set ($@)
|
||||
return "No set command specified" if (!defined ($opt));
|
||||
|
||||
my $rocmds = "clear defaults:noArg";
|
||||
my $rwcmds = "clear config control datapoint defaults:noArg paramset devstate";
|
||||
my $rwcmds = "clear config control datapoint defaults:noArg paramset link devstate values";
|
||||
|
||||
# Get I/O device, check device state
|
||||
return undef if (!defined ($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' ||
|
||||
@ -234,9 +234,9 @@ sub HMCCUCHN_Set ($@)
|
||||
my $disable = AttrVal ($name, "disable", 0);
|
||||
return undef if ($disable == 1);
|
||||
|
||||
my $hmccu_hash = $hash->{IODev};
|
||||
my $hmccu_name = $hmccu_hash->{NAME};
|
||||
if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) {
|
||||
my $ioHash = $hash->{IODev};
|
||||
my $hmccu_name = $ioHash->{NAME};
|
||||
if (HMCCU_IsRPCStateBlocking ($ioHash)) {
|
||||
return undef if ($opt eq '?');
|
||||
return "HMCCUCHN: CCU busy";
|
||||
}
|
||||
@ -419,20 +419,31 @@ sub HMCCUCHN_Set ($@)
|
||||
HMCCU_DeleteReadings ($hash, $rnexp);
|
||||
return HMCCU_SetState ($hash, "OK");
|
||||
}
|
||||
elsif ($opt eq 'paramset' || $opt eq 'rpcparameter' || $opt eq 'config') {
|
||||
return HMCCU_SetError ($hash, "Usage: set $name rpcparameter [device] [paramset] {parameter}={value}[:{type}] [...]")
|
||||
elsif ($opt =~ /^(paramset|rpcparameter|config|link|values)$/) {
|
||||
my %parSets = ('config' => 'MASTER', 'links' => 'LINK', 'values' => 'VALUES');
|
||||
my $paramset = '';
|
||||
|
||||
return HMCCU_SetError ($hash, "Usage: set $name $opt [device] [paramset] {parameter}={value}[:{type}] [...]")
|
||||
if ((scalar keys %{$h}) < 1);
|
||||
|
||||
|
||||
if (exists($parSets{$opt})) {
|
||||
$paramset = $parSets{$opt};
|
||||
}
|
||||
|
||||
my $ccuobj = $ccuaddr;
|
||||
my $p = shift @$a;
|
||||
if (defined($p) && $p eq 'device') {
|
||||
($ccuobj, undef) = HMCCU_SplitChnAddr ($ccuaddr);
|
||||
$p = shift @$a;
|
||||
}
|
||||
|
||||
my $paramset = defined($p) ? uc($p) : ($opt eq 'config' ? 'MASTER' : 'VALUES');
|
||||
|
||||
my $devDesc = HMCCU_GetDeviceDesc ($hmccu_hash, $ccuobj, $ccuif);
|
||||
if ($opt =~ /(paramset|rpcparameter)/) {
|
||||
return HMCCU_SetError ($hash, "Command $opt requires a parameter set")
|
||||
if (!defined($p));
|
||||
$paramset = $p;
|
||||
}
|
||||
|
||||
my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $ccuobj, $ccuif);
|
||||
return HMCCU_SetError ($hash, "Can't get device description")
|
||||
if (!defined($devDesc));
|
||||
return HMCCU_SetError ($hash, "Paramset $paramset not supported by device")
|
||||
@ -492,9 +503,9 @@ sub HMCCUCHN_Get ($@)
|
||||
my $disable = AttrVal ($name, "disable", 0);
|
||||
return undef if ($disable == 1);
|
||||
|
||||
my $hmccu_hash = $hash->{IODev};
|
||||
my $hmccu_name = $hmccu_hash->{NAME};
|
||||
if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) {
|
||||
my $ioHash = $hash->{IODev};
|
||||
my $hmccu_name = $ioHash->{NAME};
|
||||
if (HMCCU_IsRPCStateBlocking ($ioHash)) {
|
||||
return undef if ($opt eq '?');
|
||||
return "HMCCUCHN: CCU busy";
|
||||
}
|
||||
@ -552,67 +563,77 @@ sub HMCCUCHN_Get ($@)
|
||||
return HMCCU_SetError ($hash, -2) if ($result eq '');
|
||||
return HMCCU_FormatDeviceInfo ($result);
|
||||
}
|
||||
elsif ($opt eq 'config' || $opt eq 'configlist') {
|
||||
my $ccuobj = $ccuaddr;
|
||||
my $method = $opt eq 'config' ? 'getParamset' : 'listRawParamset';
|
||||
elsif ($opt =~ /^(paramset|paramsetlist|config|links|values)$/) {
|
||||
my $defParamset = '';
|
||||
my $method = $opt =~ /list$/ ? 'listRawParamset' : 'getRawParamset';
|
||||
my %parSets = ('config' => 'MASTER', 'links' => 'LINK', 'values' => 'VALUES',
|
||||
'configlist' => 'MASTER');
|
||||
my ($devAddr, undef) = HMCCU_SplitChnAddr ($ccuaddr);
|
||||
my @addList = ();
|
||||
my @addList = ($devAddr, "$devAddr:0", $ccuaddr);
|
||||
my $par = shift @$a;
|
||||
if (defined ($par)) {
|
||||
if ($par eq 'device') {
|
||||
push (@addList, $devAddr);
|
||||
$par = shift @$a;
|
||||
}
|
||||
elsif ($par eq 'channel') {
|
||||
$par = shift @$a;
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(@addList) == 0) {
|
||||
push (@addList, "$devAddr:0", $ccuaddr);
|
||||
if (exists($parSets{$opt})) {
|
||||
$defParamset = $parSets{$opt};
|
||||
}
|
||||
|
||||
my $paramset = 'ALL';
|
||||
if (defined($par)) {
|
||||
$paramset = $par;
|
||||
$par = shift @$a;
|
||||
else {
|
||||
if (defined($par)) {
|
||||
$defParamset = $par;
|
||||
$par = shift @$a;
|
||||
}
|
||||
}
|
||||
|
||||
$par = '.*' if (!defined ($par));
|
||||
|
||||
my $res = '';
|
||||
my %objects;
|
||||
foreach my $a (@addList) {
|
||||
my $devDesc = HMCCU_GetDeviceDesc ($hmccu_hash, $a, $ccuif);
|
||||
my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $a, $ccuif);
|
||||
return HMCCU_SetError ($hash, "Can't get device description") if (!defined($devDesc));
|
||||
|
||||
my $paramset = $defParamset eq '' ? $devDesc->{PARAMSETS} : $defParamset;
|
||||
my ($da, $dc) = HMCCU_SplitChnAddr ($a);
|
||||
$dc = 'd' if ($dc eq '');
|
||||
|
||||
$res .= $a eq $devAddr ? "Device $a\n" : "Channel $a\n";
|
||||
foreach my $ps (split (',', $devDesc->{PARAMSETS})) {
|
||||
next if (($ps ne $paramset && $paramset ne 'ALL') || ($ps ne 'MASTER' && $ps ne 'LINK'));
|
||||
foreach my $ps (split (',', $paramset)) {
|
||||
next if ($devDesc->{PARAMSETS} !~ /$ps/);
|
||||
|
||||
$res .= " Paramset $ps\n";
|
||||
($rc, $result) = HMCCU_RPCRequest ($hash, $method, $a, $ps, undef, $par);
|
||||
return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0);
|
||||
$res .= $opt eq 'config' ?
|
||||
$result :
|
||||
join ("\n", map { $_ =~ /$par/ ?
|
||||
" ".$_.' = '.HMCCU_GetParamValue ($hmccu_hash, $devDesc, $ps, $_, $result->{$_}) : ()
|
||||
} sort keys %$result)."\n";
|
||||
|
||||
if ($opt =~ /list$/) {
|
||||
$res .= join ("\n", map { $_ =~ /$par/ ?
|
||||
" ".$_.' = '.HMCCU_GetParamValue ($ioHash, $devDesc, $ps, $_, $result->{$_}) : ()
|
||||
} sort keys %$result)."\n";
|
||||
}
|
||||
else {
|
||||
foreach my $p (keys %$result) { $objects{$da}{$dc}{$ps}{$p} = $result->{$p}; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ccureadings && $opt eq 'config' ? undef : $res;
|
||||
if ($opt !~ /list$/ && scalar(keys %objects) > 0) {
|
||||
my $convRes = HMCCU_UpdateParamsetReadings ($ioHash, $hash, \%objects);
|
||||
if (defined($convRes)) {
|
||||
$res .= join ("\n", map { $_ =~ /$par/ ?
|
||||
" ".$_.' = '.$convRes->{$_} : ()
|
||||
} sort keys %$convRes)."\n";
|
||||
}
|
||||
}
|
||||
|
||||
return (!$ccureadings || $opt =~ /list$/) ? $res : undef;
|
||||
}
|
||||
elsif ($opt eq 'paramsetdesc') {
|
||||
my $ccuobj = $ccuaddr;
|
||||
my $par = shift @$a;
|
||||
my ($devAddr, $chnNo) = HMCCU_SplitChnAddr ($ccuaddr);
|
||||
$chnNo = 'd' if (defined ($par) && $par eq 'device');
|
||||
|
||||
my $model = HMCCU_GetClientDeviceModel ($hash);
|
||||
return HMCCU_SetError ($hash, "Can't get device model") if (!defined($model));
|
||||
|
||||
my $res = '';
|
||||
foreach my $c (sort keys %{$model}) {
|
||||
next if (($chnNo eq 'd' && $c ne 'd') || ($chnNo ne 'd' && $c ne '0' && $c ne $chnNo));
|
||||
# my @chnList = sort keys %{$model};
|
||||
my @chnList = ('d', 0, $chnNo);
|
||||
# unshift (@chnList, pop(@chnList)) if (exists($model->{'d'}));
|
||||
foreach my $c (@chnList) {
|
||||
$res .= $c eq 'd' ? "Device\n" : "Channel $c\n";
|
||||
foreach my $ps (sort keys %{$model->{$c}}) {
|
||||
$res .= " Paramset $ps\n";
|
||||
@ -633,26 +654,20 @@ sub HMCCUCHN_Get ($@)
|
||||
elsif ($opt eq 'devicedesc') {
|
||||
my $ccuobj = $ccuaddr;
|
||||
my $par = shift @$a;
|
||||
my @addList = ();
|
||||
my ($devAddr, $chnNo) = HMCCU_SplitChnAddr ($ccuaddr);
|
||||
|
||||
if (defined($par) && $par eq 'device') {
|
||||
push (@addList, $devAddr);
|
||||
}
|
||||
else {
|
||||
push (@addList, "$devAddr:0");
|
||||
push (@addList, $ccuaddr);
|
||||
}
|
||||
my @addList = ($devAddr, "$devAddr:0", $ccuaddr);
|
||||
|
||||
$result = '';
|
||||
foreach my $a (@addList) {
|
||||
my $devDesc = HMCCU_GetDeviceDesc ($hmccu_hash, $a, $ccuif);
|
||||
my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $a, $ccuif);
|
||||
return HMCCU_SetError ($hash, "Can't get device description")
|
||||
if (!defined($devDesc));
|
||||
|
||||
$result .= $a eq $devAddr ? "Device $a\n" : "Channel $a\n";
|
||||
$result .= $a eq $devAddr ? "Device $a" : "Channel $a";
|
||||
$result .= " $devDesc->{_name} [$devDesc->{TYPE}]\n";
|
||||
foreach my $n (sort keys %{$devDesc}) {
|
||||
next if ($n =~ /^_/);
|
||||
next if ($n =~ /^_/ || $n eq 'ADDRESS' || $n eq 'TYPE' ||
|
||||
!defined($devDesc->{$n}) || $devDesc->{$n} eq '');
|
||||
$result .= " $n: ".HMCCU_FlagsToStr ('device', $n, $devDesc->{$n}, ',', '')."\n";
|
||||
}
|
||||
}
|
||||
@ -669,8 +684,8 @@ sub HMCCUCHN_Get ($@)
|
||||
my @valuelist;
|
||||
my $valuecount = HMCCU_GetValidDatapoints ($hash, $hash->{ccutype}, $c, 1, \@valuelist);
|
||||
$retmsg .= ":".join(",",@valuelist) if ($valuecount > 0);
|
||||
$retmsg .= " update:noArg deviceinfo:noArg config configlist:channel,device".
|
||||
" devicedesc:channel,device paramsetdesc:channel,device";
|
||||
$retmsg .= " update:noArg deviceinfo:noArg config links".
|
||||
" devicedesc:noArg paramset paramsetlist paramsetdesc:noArg values";
|
||||
|
||||
return $retmsg;
|
||||
}
|
||||
@ -720,7 +735,7 @@ sub HMCCUCHN_Get ($@)
|
||||
Readings 'state' and 'control' are not deleted.
|
||||
</li><br/>
|
||||
<li><b>set <name> config</b><br/>
|
||||
Alias for command 'set paramset' with default value for parameter set = 'MASTER'.
|
||||
Alias for command 'set paramset' for parameter set MASTER.
|
||||
</li><br/>
|
||||
<li><b>set <name> datapoint <datapoint> <value> [...]</b><br/>
|
||||
Set datapoint values of a CCU channel. If parameter <i>value</i> contains special
|
||||
@ -745,6 +760,9 @@ sub HMCCUCHN_Get ($@)
|
||||
Decrement value of datapoint LEVEL. This command is only available if channel contains
|
||||
a datapoint LEVEL. Default for <i>value</i> is 10.
|
||||
</li><br/>
|
||||
<li><b>set <name> link</b><br/>
|
||||
Alias for command 'set paramset' for parameter set LINK.
|
||||
</li><br/>
|
||||
<li><b>set <name> <statevalue></b><br/>
|
||||
Set state of a CCU device channel to <i>StateValue</i>. The state datapoint of a channel
|
||||
must be defined by setting attribute 'statedatapoint'. The available state values must
|
||||
@ -781,7 +799,8 @@ sub HMCCUCHN_Get ($@)
|
||||
Parameter <i>paramset</i> is a valid parameter set name (i.e. MASTER, LINK, ...). The
|
||||
default parameter set is 'VALUES'.
|
||||
Supports attribute 'ccuscaleval' for datapoints. Parameter <i>parameter</i> must be a valid
|
||||
datapoint or config parameter name. The default <i>type</i> is STRING.
|
||||
datapoint or config parameter name. If <i>type</i> is not specified, it's taken from
|
||||
parameter set definition. The default <i>type</i> is STRING.
|
||||
Valid types are STRING, BOOL, INTEGER, FLOAT, DOUBLE.
|
||||
</li><br/>
|
||||
<li><b>set <name> pct <value> [<ontime> [<ramptime>]]</b><br/>
|
||||
@ -818,21 +837,17 @@ sub HMCCUCHN_Get ($@)
|
||||
Increment value of datapoint LEVEL. This command is only available if channel contains
|
||||
a datapoint LEVEL. Default for <i>value</i> is 10.
|
||||
</li><br/>
|
||||
<li><b>set <name> values</b><br/>
|
||||
Alias for command 'set paramset' for parameter set VALUES.
|
||||
</li>
|
||||
</ul>
|
||||
<br/>
|
||||
|
||||
<a name="HMCCUCHNget"></a>
|
||||
<b>Get</b><br/><br/>
|
||||
<ul>
|
||||
<li><b>get <name> config [<u>channel</u>|device] [<paramset>|<u>ALL</u>] [<filter-expr>]</b><br/>
|
||||
Get configuration parameters of CCU channel. If attribute 'ccureadings' is 0 results
|
||||
are displayed in browser window. Otherwise they are stored as readings beginning with "R_".
|
||||
Parameters can be filtered by <i>filter-expr</i>.
|
||||
If option 'device' is specified parameters of device are read. Without option 'device'
|
||||
parameters of current channel and channel 0 are read.
|
||||
</li><br/>
|
||||
<li><b>get <name> configlist [<u>channel</u>|device] [<paramset>|<u>ALL</u>] [<filter-expr>]</b><br/>
|
||||
Same as 'get config' without storing parameters as readings.
|
||||
<li><b>get <name> config [<filter-expr>]</b><br/>
|
||||
Same as 'get paramset', but read only parameter set MASTER.
|
||||
</li><br/>
|
||||
<li><b>get <name> datapoint <datapoint></b><br/>
|
||||
Get value of a CCU channel datapoint.
|
||||
@ -840,7 +855,7 @@ sub HMCCUCHN_Get ($@)
|
||||
<li><b>get <name> defaults</b><br/>
|
||||
Display default attributes for CCU device type.
|
||||
</li><br/>
|
||||
<li><b>get <name> devicedesc [<u>channel</u>|device]</b><br/>
|
||||
<li><b>get <name> devicedesc</b><br/>
|
||||
Display device or channel description. A channel description always includes channel 0.
|
||||
</li><br/>
|
||||
<li><b>get <name> deviceinfo</b><br/>
|
||||
@ -851,12 +866,32 @@ sub HMCCUCHN_Get ($@)
|
||||
attribute 'statedatapoint'. Command will fail if state datapoint does not exist in
|
||||
channel.
|
||||
</li><br/>
|
||||
<li><b>get <name> paramsetdesc [<u>channel</u>|device]</b><br/>
|
||||
Display description of parameter sets of CCU channel or device.
|
||||
<li><b>get <name> links [<filter-expr>]</b><br/>
|
||||
Same as 'get paramset', but read only parameter set LINK.
|
||||
</li><br/>
|
||||
<li><b>get <name> paramset [<paramset>[,...]] [<filter-expr>]</b><br/>
|
||||
Get parameters from all or specific parameter sets of device and channel.
|
||||
If attribute 'ccureadings' is 0 results are displayed in browser window. Otherwise
|
||||
they are stored as readings beginning with "R-" for MASTER parameters and "L-" for
|
||||
LINK parameters. Values from other parameter sets are stored without prefix.
|
||||
Prefixes can be modified with attribute 'ccuReadingPrefix'. If no <i>paramset</i>
|
||||
is specified, all parameter sets will be read.
|
||||
Parameters can be filtered by <i>filter-expr</i>. If option 'device' is specified parameters
|
||||
of device are read. Without option 'device' parameters of current channel and channel 0 are read.
|
||||
</li><br/>
|
||||
<li><b>get <name> paramsetdesc</b><br/>
|
||||
Display description of parameter sets of channel and device.
|
||||
</li><br/>
|
||||
<li><b>get <name> paramsetlist [<paramset>[,...]] [<filter-expr>]</b><br/>
|
||||
Same as 'get paramset' without storing parameters as readings.
|
||||
</li><br/>
|
||||
<li><b>get <name> update [{State | <u>Value</u>}]</b><br/>
|
||||
Update all datapoints / readings of channel. With option 'State' the device is queried.
|
||||
This request method is more accurate but slower then 'Value'.
|
||||
</li><br/>
|
||||
<li><b>get <name> values [<filter-expr>]</b><br/>
|
||||
Same as 'get update' but using RPC instead of ReGa. Datapoint read from CCU can be
|
||||
filtered by <i>filter-expr</i>.
|
||||
</li>
|
||||
</ul>
|
||||
<br/>
|
||||
@ -955,6 +990,9 @@ sub HMCCUCHN_Get ($@)
|
||||
attr mydev ccureadingname [1-4].PRESSED_SHORT:+pressed
|
||||
</code>
|
||||
</li><br/>
|
||||
<li><b>ccuReadingPrefix <paramset>:<prefix>[,...]</b><br/>
|
||||
Set reading name prefix for parameter sets.
|
||||
</li><br/>
|
||||
<li><b>ccuscaleval <[channelno.]datapoint>:<factor>[,...]</b><br/>
|
||||
<b>ccuscaleval <[!][channelno.]datapoint>:<min>:<max>:<minn>:<maxn>[,...]
|
||||
</b><br/>
|
||||
@ -1082,8 +1120,9 @@ sub HMCCUCHN_Get ($@)
|
||||
</li><br/>
|
||||
<li><b>substitute <subst-rule>[;...]</b><br/>
|
||||
Define substitutions for datapoint/reading values. Syntax of <i>subst-rule</i> is<br/><br/>
|
||||
[[<channelno>.]<datapoint>[,...]!]<{#n1-m1|regexp}>:<text>[,...]
|
||||
[[<type>:][<channelno>.]<datapoint>[,...]!]<{#n1-m1|regexp}>:<text>[,...]
|
||||
<br/><br/>
|
||||
Parameter <i>type</i> is a valid channel type, i.e. "SHUTTER_CONTACT".
|
||||
Parameter <i>text</i> can contain variables in format ${<i>varname</i>}. The variable
|
||||
${value} is
|
||||
substituted by the original datapoint value. All other variables must match with a valid
|
||||
|
@ -83,7 +83,7 @@ sub HMCCUDEV_Define ($@)
|
||||
my $devtype = shift @$a;
|
||||
my $devspec = shift @$a;
|
||||
|
||||
my $hmccu_hash = undef;
|
||||
my $ioHash = undef;
|
||||
|
||||
# Store some definitions for delayed initialization
|
||||
$hash->{hmccu}{devspec} = $devspec;
|
||||
@ -120,16 +120,16 @@ sub HMCCUDEV_Define ($@)
|
||||
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');
|
||||
$hmccu_hash = $defs{$h->{iodev}};
|
||||
$ioHash = $defs{$h->{iodev}};
|
||||
}
|
||||
else {
|
||||
# The following call will fail for non virtual devices during FHEM start if CCU is not ready
|
||||
$hmccu_hash = $devspec eq 'virtual' ? HMCCU_GetHash (0) : HMCCU_FindIODevice ($devspec);
|
||||
$ioHash = $devspec eq 'virtual' ? HMCCU_GetHash (0) : HMCCU_FindIODevice ($devspec);
|
||||
}
|
||||
|
||||
if ($init_done) {
|
||||
# Interactive define command while CCU not ready
|
||||
if (!defined ($hmccu_hash)) {
|
||||
if (!defined ($ioHash)) {
|
||||
my ($ccuactive, $ccuinactive) = HMCCU_IODeviceStates ();
|
||||
if ($ccuinactive > 0) {
|
||||
return "CCU and/or IO device not ready. Please try again later";
|
||||
@ -141,7 +141,7 @@ sub HMCCUDEV_Define ($@)
|
||||
}
|
||||
else {
|
||||
# CCU not ready during FHEM start
|
||||
if (!defined ($hmccu_hash) || $hmccu_hash->{ccustate} ne 'active') {
|
||||
if (!defined ($ioHash) || $ioHash->{ccustate} ne 'active') {
|
||||
Log3 $name, 2, "HMCCUDEV: [$devname] Cannot detect IO device, maybe CCU not ready. Trying later ...";
|
||||
# readingsSingleUpdate ($hash, "state", "Pending", 1);
|
||||
$hash->{ccudevstate} = 'pending';
|
||||
@ -150,7 +150,7 @@ sub HMCCUDEV_Define ($@)
|
||||
}
|
||||
|
||||
# Initialize FHEM device, set IO device
|
||||
my $rc = HMCCUDEV_InitDevice ($hmccu_hash, $hash);
|
||||
my $rc = HMCCUDEV_InitDevice ($ioHash, $hash);
|
||||
return $errmsg[$rc] if ($rc > 0);
|
||||
|
||||
return undef;
|
||||
@ -171,7 +171,7 @@ sub HMCCUDEV_Define ($@)
|
||||
|
||||
sub HMCCUDEV_InitDevice ($$)
|
||||
{
|
||||
my ($hmccu_hash, $dev_hash) = @_;
|
||||
my ($ioHash, $dev_hash) = @_;
|
||||
my $name = $dev_hash->{NAME};
|
||||
my $devspec = $dev_hash->{hmccu}{devspec};
|
||||
my $gdcount = 0;
|
||||
@ -187,7 +187,7 @@ sub HMCCUDEV_InitDevice ($$)
|
||||
# Search for free address. Maximum of 10000 virtual devices allowed.
|
||||
for (my $i=1; $i<=10000; $i++) {
|
||||
my $va = sprintf ("VIR%07d", $i);
|
||||
if (!HMCCU_IsValidDevice ($hmccu_hash, $va, 1)) {
|
||||
if (!HMCCU_IsValidDevice ($ioHash, $va, 1)) {
|
||||
$no = $i;
|
||||
last;
|
||||
}
|
||||
@ -200,9 +200,9 @@ sub HMCCUDEV_InitDevice ($$)
|
||||
$dev_hash->{ccuname} = $name;
|
||||
}
|
||||
else {
|
||||
return 1 if (!HMCCU_IsValidDevice ($hmccu_hash, $devspec, 7));
|
||||
return 1 if (!HMCCU_IsValidDevice ($ioHash, $devspec, 7));
|
||||
|
||||
my ($di, $da, $dn, $dt, $dc) = HMCCU_GetCCUDeviceParam ($hmccu_hash, $devspec);
|
||||
my ($di, $da, $dn, $dt, $dc) = HMCCU_GetCCUDeviceParam ($ioHash, $devspec);
|
||||
return 1 if (!defined ($da));
|
||||
$gdname = $dn;
|
||||
|
||||
@ -218,7 +218,7 @@ sub HMCCUDEV_InitDevice ($$)
|
||||
my @devlist = ();
|
||||
if (exists ($dev_hash->{hmccu}{groupexp})) {
|
||||
# Group devices specified by name expression
|
||||
$gdcount = HMCCU_GetMatchingDevices ($hmccu_hash, $dev_hash->{hmccu}{groupexp}, 'dev', \@devlist);
|
||||
$gdcount = HMCCU_GetMatchingDevices ($ioHash, $dev_hash->{hmccu}{groupexp}, 'dev', \@devlist);
|
||||
return 4 if ($gdcount == 0);
|
||||
}
|
||||
elsif (exists ($dev_hash->{hmccu}{group})) {
|
||||
@ -228,9 +228,9 @@ sub HMCCUDEV_InitDevice ($$)
|
||||
foreach my $gd (@gdevlist) {
|
||||
my ($gda, $gdc, $gdo) = ('', '', '', '');
|
||||
|
||||
return 1 if (!HMCCU_IsValidDevice ($hmccu_hash, $gd, 7));
|
||||
return 1 if (!HMCCU_IsValidDevice ($ioHash, $gd, 7));
|
||||
|
||||
($gda, $gdc) = HMCCU_GetAddress ($hmccu_hash, $gd, '', '');
|
||||
($gda, $gdc) = HMCCU_GetAddress ($ioHash, $gd, '', '');
|
||||
$gdo = $gda;
|
||||
$gdo .= ':'.$gdc if ($gdc ne '');
|
||||
push @devlist, $gdo;
|
||||
@ -239,7 +239,7 @@ sub HMCCUDEV_InitDevice ($$)
|
||||
}
|
||||
else {
|
||||
# Group specified by CCU virtual group name
|
||||
@devlist = HMCCU_GetGroupMembers ($hmccu_hash, $gdname);
|
||||
@devlist = HMCCU_GetGroupMembers ($ioHash, $gdname);
|
||||
$gdcount = scalar (@devlist);
|
||||
}
|
||||
|
||||
@ -248,10 +248,10 @@ sub HMCCUDEV_InitDevice ($$)
|
||||
$dev_hash->{ccugroup} = join (',', @devlist);
|
||||
if ($devspec eq 'virtual') {
|
||||
my $dev = shift @devlist;
|
||||
my $devtype = HMCCU_GetDeviceType ($hmccu_hash, $dev, 'n/a');
|
||||
my $devtype = HMCCU_GetDeviceType ($ioHash, $dev, 'n/a');
|
||||
my $devna = $devtype eq 'n/a' ? 1 : 0;
|
||||
for my $d (@devlist) {
|
||||
if (HMCCU_GetDeviceType ($hmccu_hash, $d, 'n/a') ne $devtype) {
|
||||
if (HMCCU_GetDeviceType ($ioHash, $d, 'n/a') ne $devtype) {
|
||||
$devna = 1;
|
||||
last;
|
||||
}
|
||||
@ -261,11 +261,11 @@ sub HMCCUDEV_InitDevice ($$)
|
||||
if ($devna) {
|
||||
$dev_hash->{ccutype} = 'n/a';
|
||||
$dev_hash->{statevals} = 'readonly';
|
||||
$rc = HMCCU_CreateDevice ($hmccu_hash, $dev_hash->{ccuaddr}, $name, undef, $dev);
|
||||
$rc = HMCCU_CreateDevice ($ioHash, $dev_hash->{ccuaddr}, $name, undef, $dev);
|
||||
}
|
||||
else {
|
||||
$dev_hash->{ccutype} = $devtype;
|
||||
$rc = HMCCU_CreateDevice ($hmccu_hash, $dev_hash->{ccuaddr}, $name, $devtype, $dev);
|
||||
$rc = HMCCU_CreateDevice ($ioHash, $dev_hash->{ccuaddr}, $name, $devtype, $dev);
|
||||
}
|
||||
return $rc+4 if ($rc > 0);
|
||||
|
||||
@ -275,7 +275,7 @@ sub HMCCUDEV_InitDevice ($$)
|
||||
}
|
||||
|
||||
# Inform HMCCU device about client device
|
||||
return 2 if (!HMCCU_AssignIODevice ($dev_hash, $hmccu_hash->{NAME}, undef));
|
||||
return 2 if (!HMCCU_AssignIODevice ($dev_hash, $ioHash->{NAME}, undef));
|
||||
|
||||
# readingsSingleUpdate ($dev_hash, "state", "Initialized", 1);
|
||||
$dev_hash->{ccudevstate} = 'active';
|
||||
@ -347,8 +347,8 @@ sub HMCCUDEV_Set ($@)
|
||||
# Get I/O device, check device state
|
||||
return undef if (!defined ($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' ||
|
||||
!defined ($hash->{IODev}));
|
||||
my $hmccu_hash = $hash->{IODev};
|
||||
my $hmccu_name = $hmccu_hash->{NAME};
|
||||
my $ioHash = $hash->{IODev};
|
||||
my $hmccu_name = $ioHash->{NAME};
|
||||
|
||||
# Handle read only and disabled devices
|
||||
return undef if ($hash->{statevals} eq 'readonly' && $opt ne '?'
|
||||
@ -357,7 +357,7 @@ sub HMCCUDEV_Set ($@)
|
||||
return undef if ($disable == 1);
|
||||
|
||||
# Check if CCU is busy
|
||||
if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) {
|
||||
if (HMCCU_IsRPCStateBlocking ($ioHash)) {
|
||||
return undef if ($opt eq '?');
|
||||
return "HMCCUDEV: CCU busy";
|
||||
}
|
||||
@ -670,15 +670,15 @@ sub HMCCUDEV_Get ($@)
|
||||
# Get I/O device
|
||||
return undef if (!defined ($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' ||
|
||||
!defined ($hash->{IODev}));
|
||||
my $hmccu_hash = $hash->{IODev};
|
||||
my $hmccu_name = $hmccu_hash->{NAME};
|
||||
my $ioHash = $hash->{IODev};
|
||||
my $hmccu_name = $ioHash->{NAME};
|
||||
|
||||
# Handle disabled devices
|
||||
my $disable = AttrVal ($name, "disable", 0);
|
||||
return undef if ($disable == 1);
|
||||
|
||||
# Check if CCU is busy
|
||||
if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) {
|
||||
if (HMCCU_IsRPCStateBlocking ($ioHash)) {
|
||||
return undef if ($opt eq '?');
|
||||
return "HMCCUDEV: CCU busy";
|
||||
}
|
||||
@ -816,43 +816,55 @@ sub HMCCUDEV_Get ($@)
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
elsif ($opt eq 'configdesc') {
|
||||
my $ccuobj = $ccuaddr;
|
||||
my $par = shift @$a;
|
||||
if (defined ($par)) {
|
||||
if ($par =~ /^([0-9]{1,2})$/) {
|
||||
return HMCCU_SetError ($hash, -7) if ($1 >= $hash->{channels});
|
||||
$ccuobj .= ':'.$1;
|
||||
}
|
||||
else {
|
||||
return HMCCU_SetError ($hash, -7) if ($1 >= $hash->{channels});
|
||||
elsif ($opt eq 'paramsetdesc') {
|
||||
my ($devAddr, $chnNo) = HMCCU_SplitChnAddr ($ccuaddr);
|
||||
my $model = HMCCU_GetClientDeviceModel ($hash);
|
||||
return HMCCU_SetError ($hash, "Can't get device model") if (!defined($model));
|
||||
|
||||
my $res = '';
|
||||
my @chnList = sort keys %{$model};
|
||||
unshift (@chnList, pop(@chnList)) if (exists($model->{'d'}));
|
||||
foreach my $c (@chnList) {
|
||||
$res .= $c eq 'd' ? "Device\n" : "Channel $c\n";
|
||||
foreach my $ps (sort keys %{$model->{$c}}) {
|
||||
$res .= " Paramset $ps\n";
|
||||
$result = join ("\n", map {
|
||||
" ".$_.": ".
|
||||
$model->{$c}{$ps}{$_}{TYPE}.
|
||||
" [".HMCCU_FlagsToStr ('model', 'OPERATIONS', $model->{$c}{$ps}{$_}{OPERATIONS}, ',', '')."]".
|
||||
" [".HMCCU_FlagsToStr ('model', 'FLAGS', $model->{$c}{$ps}{$_}{FLAGS}, ',', '')."]".
|
||||
" RANGE=".$model->{$c}{$ps}{$_}{MIN}."-".$model->{$c}{$ps}{$_}{MAX}.
|
||||
" DFLT=".$model->{$c}{$ps}{$_}{DEFAULT}.
|
||||
" UNIT=".$model->{$c}{$ps}{$_}{UNIT}
|
||||
} sort keys %{$model->{$c}{$ps}});
|
||||
$res .= "$result\n";
|
||||
}
|
||||
}
|
||||
|
||||
my $res = "MASTER:\n";
|
||||
($rc, $result) = HMCCU_RPCRequest ($hash, "getParamsetDescription", $ccuobj, "MASTER", undef);
|
||||
return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0);
|
||||
$res .= "$result\nLINK:\n";
|
||||
($rc, $result) = HMCCU_RPCRequest ($hash, "getParamsetDescription", $ccuobj, "MASTER", undef);
|
||||
return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0);
|
||||
HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error");
|
||||
return $res.$result;
|
||||
return $res;
|
||||
}
|
||||
elsif ($opt eq 'devicedesc') {
|
||||
my $ccuobj = $ccuaddr;
|
||||
my $par = shift @$a;
|
||||
if (defined ($par)) {
|
||||
if ($par =~ /^([0-9]{1,2})$/) {
|
||||
return HMCCU_SetError ($hash, -7) if ($1 >= $hash->{channels});
|
||||
$ccuobj .= ':'.$1;
|
||||
}
|
||||
else {
|
||||
return HMCCU_SetError ($hash, -7) if ($1 >= $hash->{channels});
|
||||
}
|
||||
}
|
||||
my $devDesc = HMCCU_GetDeviceDesc ($ioHash, $ccuaddr, $ccuif);
|
||||
return HMCCU_SetError ($hash, "Can't get device description") if (!defined($devDesc));
|
||||
|
||||
my @addList = ();
|
||||
push (@addList, split (',', $devDesc->{CHILDREN}))
|
||||
if (defined($devDesc->{CHILDREN}) && $devDesc->{CHILDREN} ne '');
|
||||
|
||||
($rc, $result) = HMCCU_RPCRequest ($hash, "getDeviceDescription", $ccuobj, "MASTER", undef);
|
||||
return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0);
|
||||
my $a = $ccuaddr;
|
||||
$result = '';
|
||||
while (1) {
|
||||
$result .= $a eq $ccuaddr ? "Device $a" : "Channel $a";
|
||||
$result .= " $devDesc->{_name} [$devDesc->{TYPE}]\n";
|
||||
foreach my $n (sort keys %{$devDesc}) {
|
||||
next if ($n =~ /^_/ || $n eq 'ADDRESS' || $n eq 'TYPE' ||
|
||||
!defined($devDesc->{$n}) || $devDesc->{$n} eq '');
|
||||
$result .= " $n: ".HMCCU_FlagsToStr ('device', $n, $devDesc->{$n}, ',', '')."\n";
|
||||
}
|
||||
$a = shift (@addList);
|
||||
last if (!defined($a));
|
||||
$devDesc = HMCCU_GetDeviceDesc ($ioHash, $a, $ccuif);
|
||||
return HMCCU_SetError ($hash, "Can't get device description") if (!defined($devDesc));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
elsif ($opt eq 'defaults') {
|
||||
@ -866,7 +878,7 @@ sub HMCCUDEV_Get ($@)
|
||||
my $valuecount = HMCCU_GetValidDatapoints ($hash, $ccutype, -1, 1, \@valuelist);
|
||||
|
||||
$retmsg .= ":".join(",", @valuelist) if ($valuecount > 0);
|
||||
$retmsg .= " defaults:noArg update:noArg config configlist configdesc devicedesc".
|
||||
$retmsg .= " defaults:noArg update:noArg config configlist paramsetdesc:noArg devicedesc:noArg".
|
||||
" deviceinfo:noArg";
|
||||
$retmsg .= ' devstate:noArg' if ($sc ne '');
|
||||
|
||||
|
@ -404,13 +404,13 @@ sub HMCCURPCPROC_InitDevice ($$) {
|
||||
}
|
||||
|
||||
# Read RPC device descriptions
|
||||
if ($dev_hash->{rpcinterface} ne 'CUxD') {
|
||||
HMCCU_Log ($dev_hash, 1, "Updating internal device tables");
|
||||
HMCCU_ResetDeviceTables ($hmccu_hash, $dev_hash->{rpcinterface});
|
||||
my $cd = HMCCURPCPROC_GetDeviceDesc ($dev_hash);
|
||||
my $cm = HMCCURPCPROC_GetParamsetDesc ($dev_hash);
|
||||
HMCCU_Log ($dev_hash, 1, "Read $cd channel and device descriptions and $cm device models from CCU");
|
||||
}
|
||||
if ($dev_hash->{rpcinterface} ne 'CUxD') {
|
||||
HMCCU_Log ($dev_hash, 1, "Updating internal device tables");
|
||||
HMCCU_ResetDeviceTables ($hmccu_hash, $dev_hash->{rpcinterface});
|
||||
my $cd = HMCCURPCPROC_GetDeviceDesc ($dev_hash);
|
||||
my $cm = HMCCURPCPROC_GetParamsetDesc ($dev_hash);
|
||||
HMCCU_Log ($dev_hash, 1, "Read $cd channel and device descriptions and $cm device models from CCU");
|
||||
}
|
||||
|
||||
# RPC device ready
|
||||
HMCCURPCPROC_ResetRPCState ($dev_hash);
|
||||
|
Loading…
x
Reference in New Issue
Block a user