From 4f2f7948f3d37b157c3ea620a7525ed24a6caf54 Mon Sep 17 00:00:00 2001 From: zap <> Date: Sun, 28 Oct 2018 11:31:20 +0000 Subject: [PATCH] HMCCU: Optimized client device update git-svn-id: https://svn.fhem.de/fhem/trunk@17633 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/88_HMCCU.pm | 565 ++++++++++++++++++++++++++------------- fhem/FHEM/88_HMCCUDEV.pm | 106 +++++--- 3 files changed, 450 insertions(+), 222 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 31b0bcfff..f4a90486a 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 88_HMCCU: client device update optimized - bugfix: 89_FULLY: fixed version check bug - feature: 74_AMADtaskerset: import with share link - change: 42_AptToDate: change to package methode diff --git a/fhem/FHEM/88_HMCCU.pm b/fhem/FHEM/88_HMCCU.pm index 683a36d02..2b5166397 100755 --- a/fhem/FHEM/88_HMCCU.pm +++ b/fhem/FHEM/88_HMCCU.pm @@ -4,9 +4,9 @@ # # $Id$ # -# Version 4.3.004 +# Version 4.3.005 # -# Module for communication between FHEM and Homematic CCU2. +# Module for communication between FHEM and Homematic CCU2/3. # # Supports BidCos-RF, BidCos-Wired, HmIP-RF, virtual CCU channels, # CCU group devices, HomeGear, CUxD, Osram Lightify, Homematic Virtual Layer @@ -38,7 +38,7 @@ # [defattr] [duplicates] [save] [= [...]]}] # get dump {devtypes|datapoints} [] # get dutycycle -# get exportdefaults {filename} [csv] +# get exportdefaults {filename} [csv] [all] # get firmware [{type-expr}|full] # get parfile [] # get rpcevents @@ -108,7 +108,7 @@ my %HMCCU_CUST_CHN_DEFAULTS; my %HMCCU_CUST_DEV_DEFAULTS; # HMCCU version -my $HMCCU_VERSION = '4.3.004'; +my $HMCCU_VERSION = '4.3.005'; # Default RPC port (BidCos-RF) my $HMCCU_RPC_PORT_DEFAULT = 2001; @@ -245,8 +245,8 @@ sub HMCCU_AggregateReadings ($$); sub HMCCU_AggregationRules ($$); # Handling of default attributes -sub HMCCU_ExportDefaults ($); -sub HMCCU_ExportDefaultsCSV ($); +sub HMCCU_ExportDefaults ($$); +sub HMCCU_ExportDefaultsCSV ($$); sub HMCCU_ImportDefaults ($); sub HMCCU_FindDefaults ($$); sub HMCCU_SetDefaults ($); @@ -272,10 +272,11 @@ sub HMCCU_SubstVariables ($$$); sub HMCCU_BulkUpdate ($$$$); sub HMCCU_GetUpdate ($$$); sub HMCCU_UpdateClients ($$$$$); +sub HMCCU_UpdateInternalValues ($$$$); sub HMCCU_UpdateMultipleDevices ($$); sub HMCCU_UpdatePeers ($$$$); sub HMCCU_UpdateSingleDatapoint ($$$$); -sub HMCCU_UpdateSingleDevice ($$$); +sub HMCCU_UpdateSingleDevice ($$$$); # RPC functions sub HMCCU_GetRPCCallbackURL ($$$$$); @@ -316,8 +317,10 @@ sub HMCCU_IsFlag ($$); # Handle interfaces, devices and channels sub HMCCU_CreateDevice ($$$$$); +sub HMCCU_DeleteDevice ($); sub HMCCU_FormatDeviceInfo ($); sub HMCCU_GetAddress ($$$$); +sub HMCCU_GetAffectedAddresses ($); sub HMCCU_GetCCUDeviceParam ($$); sub HMCCU_GetChannelName ($$$); sub HMCCU_GetDeviceChannels ($$$); @@ -369,7 +372,7 @@ sub HMCCU_QueueEnq ($$); sub HMCCU_QueueDeq ($); # Helper functions -sub HMCCU_CalculateReading ($$$); +sub HMCCU_CalculateReading ($$); sub HMCCU_EncodeEPDisplay ($); sub HMCCU_ExprMatch ($$$); sub HMCCU_ExprNotMatch ($$$); @@ -419,7 +422,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". - " ccuflags:multiple-strict,extrpc,intrpc,procrpc,dptnocheck,noagg,nohmstate,logEvents,noReadings,nonBlocking". + " ccuflags:multiple-strict,extrpc,intrpc,procrpc,dptnocheck,noagg,nohmstate,logEvents,noEvents,noReadings,nonBlocking". " ccuReqTimeout ccuGetVars rpcinterval:2,3,5,7,10 rpcqueue". " rpcport:multiple-strict,".join(',',sort keys %HMCCU_RPC_NUMPORT). " rpcserver:on,off rpcserveraddr rpcserverport rpctimeout rpcevtimeout parfile substitute". @@ -831,9 +834,9 @@ sub HMCCU_AggregationRules ($$) # Export default attributes. ###################################################################### -sub HMCCU_ExportDefaults ($) +sub HMCCU_ExportDefaults ($$) { - my ($filename) = @_; + my ($filename, $all) = @_; return 0 if (!open (DEFFILE, ">$filename")); @@ -852,6 +855,24 @@ sub HMCCU_ExportDefaults ($) print DEFFILE "$a=".$HMCCU_DEV_DEFAULTS->{$t}{$a}."\n"; } } + + if ($all) { + print DEFFILE "# HMCCU custom default attributes for channels\n"; + foreach my $t (keys %HMCCU_CUST_CHN_DEFAULTS) { + print DEFFILE "\nchannel:$t\n"; + foreach my $a (sort keys %{$HMCCU_CUST_CHN_DEFAULTS{$t}}) { + print DEFFILE "$a=".$HMCCU_CUST_CHN_DEFAULTS{$t}{$a}."\n"; + } + } + + print DEFFILE "\n# HMCCU custom default attributes for devices\n"; + foreach my $t (keys %HMCCU_CUST_DEV_DEFAULTS) { + print DEFFILE "\ndevice:$t\n"; + foreach my $a (sort keys %{$HMCCU_CUST_DEV_DEFAULTS{$t}}) { + print DEFFILE "$a=".$HMCCU_CUST_DEV_DEFAULTS{$t}{$a}."\n"; + } + } + } close (DEFFILE); @@ -862,9 +883,9 @@ sub HMCCU_ExportDefaults ($) # Export default attributes as CSV file. ###################################################################### -sub HMCCU_ExportDefaultsCSV ($) +sub HMCCU_ExportDefaultsCSV ($$) { - my ($filename) = @_; + my ($filename, $all) = @_; my %attrlist = ( '_type' => '', '_description' => '', '_channels' => '', @@ -902,6 +923,30 @@ sub HMCCU_ExportDefaultsCSV ($) } print DEFFILE "\n"; } + + if ($all) { + # Write channel configurations + foreach my $t (keys %HMCCU_CUST_CHN_DEFAULTS) { + print DEFFILE "C"; + $attrlist{'_type'} = $t; + foreach $a (sort keys %attrlist) { + my $v = exists ($HMCCU_CUST_CHN_DEFAULTS{$t}{$a}) ? $HMCCU_CUST_CHN_DEFAULTS{$t}{$a} : $attrlist{$a}; + print DEFFILE ",\"$v\""; + } + print DEFFILE "\n"; + } + + # Write device configurations + foreach my $t (keys %HMCCU_CUST_DEV_DEFAULTS) { + print DEFFILE "D"; + $attrlist{'_type'} = $t; + foreach $a (sort keys %attrlist) { + my $v = exists ($HMCCU_CUST_DEV_DEFAULTS{$t}{$a}) ? $HMCCU_CUST_DEV_DEFAULTS{$t}{$a} : $attrlist{$a}; + print DEFFILE ",\"$v\""; + } + print DEFFILE "\n"; + } + } close (DEFFILE); @@ -1961,14 +2006,19 @@ sub HMCCU_Get ($@) } elsif ($opt eq 'exportdefaults') { my $filename = shift @$a; - my $csv = shift @$a; + $usage = "Usage: get $name $opt filename ['all'] ['csv']"; + my $csv = 0; + my $all = 0; + + foreach my $defopt (@$a) { + if ($defopt eq 'csv') { $csv = 1; } + elsif ($defopt eq 'all') { $all = 1; } + else { return HMCCU_SetError ($hash, $usage); } + } - $csv = 'default' if (!defined ($csv)); - $usage = "Usage: get $name $opt filename [{csv|default}]"; return HMCCU_SetError ($hash, $usage) if (!defined ($filename)); - return HMCCU_SetError ($hash, $usage) if ($csv !~ /^(default|csv)$/); - my $rc = $csv ne 'csv' ? HMCCU_ExportDefaults ($filename) : HMCCU_ExportDefaultsCSV ($filename); + my $rc = $csv ? HMCCU_ExportDefaultsCSV ($filename, $all) : HMCCU_ExportDefaults ($filename, $all); return HMCCU_SetError ($hash, -16) if ($rc == 0); return HMCCU_SetState ($hash, "OK", "Default attributes written to $filename"); } @@ -2671,11 +2721,14 @@ sub HMCCU_SubstRule ($$$) # %% = Previous original / raw value # $ = Converted / formatted value # $$ = Previous converted / formatted value +# Parameter dplist is a comma separated list of value keys in format +# [address:]Channel.Datapoint. ###################################################################### sub HMCCU_SubstVariables ($$$) { my ($clhash, $text, $dplist) = @_; + my $fnc = "HMCCU_SubstVariables"; my @varlist; if (defined ($dplist)) { @@ -2684,10 +2737,15 @@ sub HMCCU_SubstVariables ($$$) else { @varlist = keys %{$clhash->{hmccu}{dp}}; } + + HMCCU_Trace ($clhash, 2, $fnc, "text=$text"); # Substitute datapoint variables by value foreach my $dp (@varlist) { my ($chn, $dpt) = split (/\./, $dp); + + HMCCU_Trace ($clhash, 2, $fnc, "var=$dp"); + if (defined ($clhash->{hmccu}{dp}{$dp}{OSVAL})) { $text =~ s/\$\$\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{OSVAL}/g; $text =~ s/\$\$\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{OSVAL}/g; @@ -2703,8 +2761,11 @@ sub HMCCU_SubstVariables ($$$) if (defined ($clhash->{hmccu}{dp}{$dp}{VAL})) { $text =~ s/\%\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{VAL}/g; $text =~ s/\%\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{VAL}/g; + $text =~ s/$dp/$clhash->{hmccu}{dp}{$dp}{VAL}/g; } } + + HMCCU_Trace ($clhash, 2, $fnc, "text=$text"); return $text; } @@ -2837,6 +2898,44 @@ sub HMCCU_CreateDevice ($$$$$) return 0; } +########################################################################## +# Remove virtual device from internal device tables. +########################################################################## + +sub HMCCU_DeleteDevice ($) +{ + my ($clthash) = @_; + my $name = $clthash->{NAME}; + + return if (!exists ($clthash->{IODev}) || !exists ($clthash->{ccuif}) || + !exists ($clthash->{ccuaddr})); + return if ($clthash->{ccuif} ne 'fhem'); + + my $hmccu_hash = $clthash->{IODev}; + my $devaddr = $clthash->{ccuaddr}; + my $channels = exists ($clthash->{channels}) ? $clthash->{channels} : 0; + + # Delete device address entries + if (exists ($hmccu_hash->{hmccu}{dev}{$devaddr})) { + delete $hmccu_hash->{hmccu}{dev}{$devaddr}; + } + + # Delete channel address and name entries + for (my $chn=0; $chn<=$channels; $chn++) { + if (exists ($hmccu_hash->{hmccu}{dev}{"$devaddr:$chn"})) { + delete $hmccu_hash->{hmccu}{dev}{"$devaddr:$chn"}; + } + if (exists ($hmccu_hash->{hmccu}{adr}{"$name:$chn"})) { + delete $hmccu_hash->{hmccu}{adr}{"$name:$chn"}; + } + } + + # Delete device name entries + if (exists ($hmccu_hash->{hmccu}{adr}{$name})) { + delete $hmccu_hash->{hmccu}{adr}{$name}; + } +} + ########################################################################## # Update parameters in internal device tables and client devices. # Parameter devices is a hash reference with following keys: @@ -3034,14 +3133,14 @@ sub HMCCU_UpdateSingleDatapoint ($$$$) my ($devaddr, $chnnum) = HMCCU_SplitChnAddr ($ccuaddr); $objects{$devaddr}{$chn}{$dpt} = $value; - my $rc = HMCCU_UpdateSingleDevice ($hmccu_hash, $hash, \%objects); + my $rc = HMCCU_UpdateMultipleDevices ($hmccu_hash, \%objects); return (ref ($rc)) ? $rc->{$devaddr}{$chn}{$dpt} : $value; } ###################################################################### # Update readings of client device. # Parameter objects is a hash reference which contains updated data -# for any device: +# for devices: # {devaddr}{channelno}{datapoint} = value # If client device is virtual device group: check if group members are # affected by updates and update readings in virtual group device. @@ -3049,9 +3148,9 @@ sub HMCCU_UpdateSingleDatapoint ($$$$) # {devaddr}{datapoint} = value ###################################################################### -sub HMCCU_UpdateSingleDevice ($$$) +sub HMCCU_UpdateSingleDevice ($$$$) { - my ($ccuhash, $clthash, $objects) = @_; + my ($ccuhash, $clthash, $objects, $alref) = @_; my $ccuname = $ccuhash->{NAME}; my $cltname = $clthash->{NAME}; my $clttype = $clthash->{TYPE}; @@ -3060,143 +3159,140 @@ sub HMCCU_UpdateSingleDevice ($$$) return 0 if (!defined ($clthash->{IODev}) || !defined ($clthash->{ccuaddr})); return 0 if ($clthash->{IODev} != $ccuhash); - # Check for updated data + # Build list of relevant addresses in object data hash my ($devaddr, $cnum) = HMCCU_SplitChnAddr ($clthash->{ccuaddr}); - return 0 if (!exists ($objects->{$devaddr})); - return 0 if ($clttype eq 'HMCUCCHN' && !exists ($objects->{$devaddr}{$cnum}) && - !exists ($objects->{$devaddr}{0})); + my @addlist = defined ($alref) ? @$alref : ($devaddr); + # 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'); + + # Get device parameters and attributes my $ccuflags = HMCCU_GetFlags ($ccuname); - - # Build device list including virtual devices - my @grplist = ($cltname); - my @virlist = HMCCU_FindClientDevices ($ccuhash, "HMCCUDEV", undef, "ccuif=(VirtualDevices|fhem)"); - foreach my $vd (@virlist) { - my $vh = $defs{$vd}; - next if (!defined ($vh->{ccugroup})); - foreach my $gadd (split (",", $vh->{ccugroup})) { - if ("$gadd" eq "$devaddr") { - push @grplist, $vd; - last; - } - } - } + my $cf = HMCCU_GetFlags ($cltname); + my $peer = AttrVal ($cltname, 'peer', 'null'); + my $crf = HMCCU_GetAttrReadingFormat ($clthash, $ccuhash); + my $substitute = HMCCU_GetAttrSubstitute ($clthash, $ccuhash); + my ($sc, $st, $cc, $cd) = HMCCU_GetSpecialDatapoints ($clthash, '', 'STATE', '', ''); - HMCCU_Trace ($clthash, 2, $fnc, - "$cltname Virlist = ".join(',', @virlist)."
". - "$cltname Grplist = ".join(',', @grplist)."
". - "$cltname Objects = ".join(',', keys %{$objects})); + # Virtual device flag + my $vg = 0; + $vg = 1 if (($clthash->{ccuif} eq 'VirtualDevices' || $clthash->{ccuif} eq 'fhem') && + exists ($clthash->{ccugroup})); + + + HMCCU_Trace ($clthash, 2, $fnc, "$cltname Objects = ".join(',', @addlist)); # Store the resulting readings my %results; - # Update device considering foreign device data assigned to group device. - # Devices are not updated when: - # - Device is disabled - # - Attribute ccureadings is set to 0 - # - Device is not in state active - foreach my $cn (@grplist) { - my $ch = $defs{$cn}; - my $ct = $ch->{TYPE}; - my $disable = AttrVal ($cn, 'disable', 0); - my $update = AttrVal ($cn, 'ccureadings', 1); - next if ($update == 0 || $disable == 1 || $ch->{ccudevstate} ne 'active'); - my $cf = HMCCU_GetFlags ($cn); - my $peer = AttrVal ($cn, 'peer', 'null'); - - HMCCU_Trace ($ch, 2, $fnc, "Processing device $cn"); + # Updated internal values + my @chkeys = (); + + # Update readings of client device with data from address list + readingsBeginUpdate ($clthash); + + foreach my $addr (@addlist) { + next if (!exists ($objects->{$addr})); - my $crf = HMCCU_GetAttrReadingFormat ($ch, $ccuhash); - my $substitute = HMCCU_GetAttrSubstitute ($ch, $ccuhash); - my ($sc, $st, $cc, $cd) = HMCCU_GetSpecialDatapoints ($ch, '', 'STATE', '', ''); - - my @devlist = ($ch->{ccuaddr}); - push @devlist, split (",", $ch->{ccugroup}) - if (($ch->{ccuif} eq 'VirtualDevices' || $ch->{ccuif} eq 'fhem') && exists ($ch->{ccugroup})); + HMCCU_Trace ($clthash, 2, $fnc, "Processing object $addr"); + + # Update channels of device + foreach my $chnnum (keys (%{$objects->{$addr}})) { + next if ($clttype eq 'HMCCUCHN' && "$chnnum" ne "$cnum" && "$chnnum" ne "0"); + next if ("$chnnum" eq "0" && $cf =~ /nochn0/); + my $chnadd = "$addr:$chnnum"; + + # Update datapoints of channel + foreach my $dpt (keys (%{$objects->{$addr}{$chnnum}})) { + my $value = $objects->{$addr}{$chnnum}{$dpt}; + next if (!defined ($value)); - readingsBeginUpdate ($ch); - - # Update devices - foreach my $dev (@devlist) { - my ($da, $cnum) = HMCCU_SplitChnAddr ($dev); - next if (!exists ($objects->{$da})); - next if ($clttype eq 'HMCUCCHN' && !exists ($objects->{$da}{$cnum}) && - !exists ($objects->{$da}{0})); + # Key for storing values in client hash. Indirect updates of virtual devices + # are stored with device address in key. + my $chkey = $devaddr ne $addr ? "$chnadd.$dpt" : "$chnnum.$dpt"; + + # Store datapoint raw value in device hash + HMCCU_UpdateInternalValues ($clthash, $chkey, 'VAL', $value); - # Update channels of device - foreach my $chnnum (keys (%{$objects->{$da}})) { - next if ($ct eq 'HMCCUCHN' && "$chnnum" ne "$cnum" && "$chnnum" ne "0"); - next if ("$chnnum" eq "0" && $cf =~ /nochn0/); - my $chnadd = "$da:$chnnum"; - - # Update datapoints of channel - foreach my $dpt (keys (%{$objects->{$da}{$chnnum}})) { - my $value = $objects->{$da}{$chnnum}{$dpt}; - next if (!defined ($value)); + HMCCU_Trace ($clthash, 2, $fnc, "dev=$cltname, chnadd/object=$chnadd, dpt=$dpt, key=$chkey, value=$value"); + + if (HMCCU_FilterReading ($clthash, $chnadd, $dpt)) { + # Modify reading name and value + my @readings = HMCCU_GetReadingName ($clthash, '', $addr, $chnnum, $dpt, '', $crf); + my $svalue = HMCCU_ScaleValue ($clthash, $chnnum, $dpt, $value, 0); + my $fvalue = HMCCU_FormatReadingValue ($clthash, $svalue, $dpt); + my $cvalue = HMCCU_Substitute ($fvalue, $substitute, 0, $chnnum, $dpt); +# my %calcs = HMCCU_CalculateReading ($clthash, $chkey); + + # Store the resulting value after scaling, formatting and substitution + HMCCU_UpdateInternalValues ($clthash, $chkey, 'SVAL', $cvalue); + push @chkeys, $chkey; - # Store datapoint raw value in device hash - if (exists ($clthash->{hmccu}{dp}{"$chnnum.$dpt"}{VAL})) { - $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{OVAL} = $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{VAL}; + # Store result, but not for indirect updates of virtual devices + $results{$devaddr}{$chnnum}{$dpt} = $cvalue if ($devaddr eq $addr); + + HMCCU_Trace ($clthash, 2, $fnc, + "device=$cltname, readings=".join(',', @readings). + ", orgvalue=$value value=$cvalue peer=$peer"); + + # Update readings + foreach my $rn (@readings) { + HMCCU_BulkUpdate ($clthash, $rn, $fvalue, $cvalue) if ($rn ne ''); } - else { - $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{OVAL} = $value; - } - $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{VAL} = $value; +# foreach my $clcr (keys %calcs) { +# HMCCU_BulkUpdate ($clthash, $clcr, $calcs{$clcr}, $calcs{$clcr}); +# } + HMCCU_BulkUpdate ($clthash, 'control', $fvalue, $cvalue) + if ($cd ne '' && $dpt eq $cd && $chnnum eq $cc); + HMCCU_BulkUpdate ($clthash, 'state', $fvalue, $cvalue) + if ($dpt eq $st && ($sc eq '' || $sc eq $chnnum)); - HMCCU_Trace ($ch, 2, $fnc, "dev=$cn, chnadd=$chnadd, dpt=$dpt, value=$value"); - - if (HMCCU_FilterReading ($ch, $chnadd, $dpt)) { - my @readings = HMCCU_GetReadingName ($ch, '', $da, $chnnum, $dpt, '', $crf); - my $svalue = HMCCU_ScaleValue ($ch, $chnnum, $dpt, $value, 0); - my $fvalue = HMCCU_FormatReadingValue ($ch, $svalue, $dpt); - my $cvalue = HMCCU_Substitute ($fvalue, $substitute, 0, $chnnum, $dpt); - my %calcs = HMCCU_CalculateReading ($ch, $chnnum, $dpt); - - # Store the resulting value after scaling, formatting and substitution - if (exists ($clthash->{hmccu}{dp}{"$chnnum.$dpt"}{OSVAL})) { - $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{OSVAL} = $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{SVAL}; - } - else { - $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{OSVAL} = $cvalue; - } - $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{SVAL} = $cvalue; - $results{$da}{$chnnum}{$dpt} = $cvalue; - - HMCCU_Trace ($ch, 2, $fnc, - "device=$cltname, readings=".join(',', @readings). - ", orgvalue=$value value=$cvalue peer=$peer"); - - # Update readings - foreach my $rn (@readings) { - HMCCU_BulkUpdate ($ch, $rn, $fvalue, $cvalue) if ($rn ne ''); - } - foreach my $clcr (keys %calcs) { - HMCCU_BulkUpdate ($ch, $clcr, $calcs{$clcr}, $calcs{$clcr}); - } - HMCCU_BulkUpdate ($ch, 'control', $fvalue, $cvalue) - if ($cd ne '' && $dpt eq $cd && $chnnum eq $cc); - HMCCU_BulkUpdate ($ch, 'state', $fvalue, $cvalue) - if ($dpt eq $st && ($sc eq '' || $sc eq $chnnum)); - - # Update peers - HMCCU_UpdatePeers ($ch, "$chnnum.$dpt", $cvalue, $peer) if ($peer ne 'null'); - } + # Update peers + HMCCU_UpdatePeers ($clthash, "$chnnum.$dpt", $cvalue, $peer) if (!$vg && $peer ne 'null'); } } - } - - # Calculate and update HomeMatic state - if ($ccuflags !~ /nohmstate/) { - my ($hms_read, $hms_chn, $hms_dpt, $hms_val) = HMCCU_GetHMState ($cn, $ccuname, undef); - HMCCU_BulkUpdate ($ch, $hms_read, $hms_val, $hms_val) if (defined ($hms_val)); - } - - readingsEndUpdate ($ch, 1); + } } + if (scalar (@chkeys) > 0) { + my %calcs = HMCCU_CalculateReading ($clthash, \@chkeys); + foreach my $clcr (keys %calcs) { + HMCCU_BulkUpdate ($clthash, $clcr, $calcs{$clcr}, $calcs{$clcr}); + } + } + + # Calculate and update HomeMatic state + if ($ccuflags !~ /nohmstate/) { + my ($hms_read, $hms_chn, $hms_dpt, $hms_val) = HMCCU_GetHMState ($cltname, $ccuname, undef); + HMCCU_BulkUpdate ($clthash, $hms_read, $hms_val, $hms_val) if (defined ($hms_val)); + } + + readingsEndUpdate ($clthash, 1); + return \%results; } +###################################################################### +# Store datapoint values in device hash. +# Parameter type is VAL or SVAL. +###################################################################### + +sub HMCCU_UpdateInternalValues ($$$$) +{ + my ($ch, $chkey, $type, $value) = @_; + my $otype = "O".$type; + + if (exists ($ch->{hmccu}{dp}{$chkey}{$type})) { + $ch->{hmccu}{dp}{$chkey}{$otype} = $ch->{hmccu}{dp}{$chkey}{$type}; + } + else { + $ch->{hmccu}{dp}{$chkey}{$otype} = $value; + } + $ch->{hmccu}{dp}{$chkey}{$type} = $value; +} + ###################################################################### # Update readings of multiple client devices. # Parameter objects is a hash reference: @@ -3221,13 +3317,43 @@ sub HMCCU_UpdateMultipleDevices ($$) "ccudevstate=active"); foreach my $d (@devlist) { my $ch = $defs{$d}; - my $rc = HMCCU_UpdateSingleDevice ($hash, $ch, $objects); - $c++ if (ref ($rc)); + my @addrlist = HMCCU_GetAffectedAddresses ($ch); + next if (scalar (@addrlist) == 0); + foreach my $addr (@addrlist) { + if (exists ($objects->{$addr})) { + my $rc = HMCCU_UpdateSingleDevice ($hash, $ch, $objects, \@addrlist); + $c++ if (ref ($rc)); + last; + } + } } return $c; } +###################################################################### +# Get list of device addresses including group device members. +###################################################################### + +sub HMCCU_GetAffectedAddresses ($) +{ + my ($clthash) = @_; + my @addlist = (); + + if ($clthash->{TYPE} eq 'HMCCUDEV' || $clthash->{TYPE} eq 'HMCCUCHN') { + if (exists ($clthash->{ccuaddr})) { + my ($devaddr, $cnum) = HMCCU_SplitChnAddr ($clthash->{ccuaddr}); + push @addlist, $devaddr; + } + if (($clthash->{ccuif} eq 'VirtualDevices' || $clthash->{ccuif} eq 'fhem') && + exists ($clthash->{ccugroup})) { + push @addlist, split (',', $clthash->{ccugroup}); + } + } + + return @addlist; +} + ###################################################################### # Update peer devices. # Syntax of peer definitions is: @@ -5956,14 +6082,23 @@ sub HMCCU_SetDatapoint ($$$) } if ($flags == $HMCCU_FLAGS_IACD) { - $cmd = '(datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).State('.$value.')'; +# $cmd = '(datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).State('.$value.')'; $nam = HMCCU_GetChannelName ($hmccu_hash, $add.":".$chn, ''); } elsif ($flags == $HMCCU_FLAGS_NCD) { - $cmd = '(dom.GetObject(ID_CHANNELS)).Get("'.$nam.'").DPByHssDP("'.$dpt.'").State('.$value.')'; +# $cmd = '(dom.GetObject(ID_CHANNELS)).Get("'.$nam.'").DPByHssDP("'.$dpt.'").State('.$value.')'; ($add, $chn) = HMCCU_GetAddress ($hmccu_hash, $nam, '', ''); } + if ($type eq 'HMCCUDEV' && $hash->{ccuif} eq 'fhem' && $hash->{ccutype} ne 'n/a' && exists ($hash->{ccugroup})) { + for my $gaddr (split (',', $hash->{ccugroup})) { + $cmd .= '(datapoints.Get("'.$int.'.'.$gaddr.':'.$chn.'.'.$dpt.'")).State('.$value.");\n"; + } + } + else { + $cmd = '(datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).State('.$value.')'; + } + my $addr = $add.":".$chn; if ($ccuflags =~ /nonBlocking/) { @@ -6238,23 +6373,23 @@ sub HMCCU_GetUpdate ($$$) next if ($chn eq ''); $events{$add}{$chn}{$dpt} = $value; } + +# if ($cl_hash->{ccuif} eq 'fhem') { +# # Calculate datapoints of virtual group device +# if ($cl_hash->{ccutype} ne 'n/a') { +# foreach my $da (split (",", $cl_hash->{ccugroup})) { +# foreach my $cn (keys %{$events{$da}}) { +# foreach my $dp (keys %{$events{$da}{$cn}}) { +# if (defined ($events{$da}{$cn}{$dp})) { +# $events{$cl_hash->{ccuaddr}}{$cn}{$dp} = $events{$da}{$cn}{$dp} +# } +# } +# } +# } +# } +# } - if ($cl_hash->{ccuif} eq 'fhem') { - # Calculate datapoints of virtual group device - if ($cl_hash->{ccutype} ne 'n/a') { - foreach my $da (split (",", $cl_hash->{ccugroup})) { - foreach my $cn (keys %{$events{$da}}) { - foreach my $dp (keys %{$events{$da}{$cn}}) { - if (defined ($events{$da}{$cn}{$dp})) { - $events{$cl_hash->{ccuaddr}}{$cn}{$dp} = $events{$da}{$cn}{$dp} - } - } - } - } - } - } - - HMCCU_UpdateSingleDevice ($hmccu_hash, $cl_hash, \%events); + HMCCU_UpdateMultipleDevices ($hmccu_hash, \%events); return 1; } @@ -6747,10 +6882,11 @@ sub HMCCU_GetTimeSpec ($) # Return readings array with reading/value pairs. ###################################################################### -sub HMCCU_CalculateReading ($$$) +sub HMCCU_CalculateReading ($$) { - my ($cl_hash, $chnno, $dpt) = @_; + my ($cl_hash, $chkeys) = @_; my $name = $cl_hash->{NAME}; + my $fnc = "HMCCU_CalculateReading"; my @result = (); @@ -6759,22 +6895,39 @@ sub HMCCU_CalculateReading ($$$) my @calclist = split (/[;\n]+/, $ccucalculate); foreach my $calculation (@calclist) { - my ($vt, $rn, $dpts) = split (':', $calculation); - next if (!defined ($rn)); - - # Get parameters values stored in device hash - my @dplist = defined ($dpts) ? split (',', $dpts) : (); - next if (@dplist > 0 && !(grep { $_ eq "$chnno.$dpt"} @dplist)); - my @pars = (); - foreach my $dp (@dplist) { - if (exists ($cl_hash->{hmccu}{dp}{$dp}{VAL})) { - push @pars, $cl_hash->{hmccu}{dp}{$dp}{VAL}; + my ($vt, $rn, $dpts) = split (':', $calculation, 3); + next if (!defined ($dpts)); + my $tmpdpts = ",$dpts,"; + $tmpdpts =~ s/[\$\%\{\}]+//g; + HMCCU_Trace ($cl_hash, 2, $fnc, "vt=$vt, rn=$rn, dpts=$dpts, tmpdpts=$tmpdpts"); + my $f = 0; + foreach my $chkey (@$chkeys) { + if ($tmpdpts =~ /,$chkey,/) { + $f = 1; + last; } } + next if ($f == 0); + my @dplist = split (',', $dpts); + + # Get parameters values stored in device hash + my $newdpts = HMCCU_SubstVariables ($cl_hash, $dpts, undef); + my @pars = split (',', $newdpts); + my $pc = scalar (@pars); + next if ($pc != scalar(@dplist)); + $f = 0; + for (my $i=0; $i<$pc; $i++) { + $pars[$i] =~ s/^#//; + if ($pars[$i] eq $dplist[$i]) { + $f = 1; + last; + } + } + next if ($f); if ($vt eq 'dewpoint' || $vt eq 'abshumidity') { # Dewpoint and absolute humidity - next if (scalar (@pars) < 2); + next if ($pc < 2); my ($tmp, $hum) = @pars; if ($tmp >= 0.0) { $a = 7.5; @@ -6799,18 +6952,29 @@ sub HMCCU_CalculateReading ($$$) } } } + elsif ($vt eq 'equ') { + # Set reading to value if all variables have the same value + next if ($pc < 1); + my $curval = shift @pars; + my $f = 1; + foreach my $newval (@pars) { + $f = 0 if ("$newval" ne "$curval"); + } + push (@result, $rn, $f ? $curval : "n/a"); + } elsif ($vt eq 'min' || $vt eq 'max') { # Minimum or maximum values - next if (scalar (@pars) < 1); - my $newval = shift @pars; - my $curval = ReadingsVal ($name, $rn, 0); - $curval = $newval if ($vt eq 'min' && $newval < $curval); - $curval = $newval if ($vt eq 'max' && $newval > $curval); + next if ($pc < 1); + my $curval = $pc > 1 ? shift @pars : ReadingsVal ($name, $rn, 0); + foreach my $newval (@pars) { + $curval = $newval if ($vt eq 'min' && $newval < $curval); + $curval = $newval if ($vt eq 'max' && $newval > $curval); + } push (@result, $rn, $curval); } elsif ($vt eq 'inc' || $vt eq 'dec') { # Increasing or decreasing values without reset - next if (scalar (@pars) < 1); + next if ($pc < 1); my $newval = shift @pars; my $oldval = ReadingsVal ($name, $rn."_old", 0); my $curval = ReadingsVal ($name, $rn, 0); @@ -6823,7 +6987,7 @@ sub HMCCU_CalculateReading ($$$) } elsif ($vt eq 'avg') { # Average value - next if (scalar (@pars) < 1); + next if ($pc < 1); my $newval = shift @pars; my $cnt = ReadingsVal ($name, $rn."_cnt", 0); my $sum = ReadingsVal ($name, $rn."_sum", 0); @@ -6834,10 +6998,29 @@ sub HMCCU_CalculateReading ($$$) } elsif ($vt eq 'sum') { # Sum of values - next if (scalar (@pars) < 1); - my $newval = shift @pars; - my $curval = ReadingsVal ($name, $rn, 0); - $curval += $newval; + next if ($pc < 1); + my $curval = $pc > 1 ? 0 : ReadingsVal ($name, $rn, 0); + foreach my $newval (@pars) { + $curval += $newval; + } + push (@result, $rn, $curval); + } + elsif ($vt eq 'or') { + # Logical OR + next if ($pc < 1); + my $curval = $pc > 1 ? 0 : ReadingsVal ($name, $rn, 0); + foreach my $newval (@pars) { + $curval |= $newval; + } + push (@result, $rn, $curval); + } + elsif ($vt eq 'and') { + # Logical AND + next if ($pc < 1); + my $curval = $pc > 1 ? 1 : ReadingsVal ($name, $rn, 1); + foreach my $newval (@pars) { + $curval &= $newval; + } push (@result, $rn, $curval); } } @@ -7620,8 +7803,9 @@ sub HMCCU_CCURPC_ListDevicesCB ($$) iface_conn_n = interface connection state (1=connected, 0=disconnected)
iface_ducy_n = duty cycle of interface (0-100)
-
  • get <name> exportdefaults <filename> [{default|csv}]
    - Export default attributes into file. +
  • get <name> exportdefaults <filename> [csv] [all]
    + Export default attributes into file. If option all is specified, also defaults imported + by customer will be exported.

  • get <name> firmware [{<type-expr> | full}]
    Get available firmware downloads from eq-3.de. List FHEM devices with current and available @@ -7743,6 +7927,7 @@ sub HMCCU_CCURPC_ListDevicesCB ($$) intrpc - Use internal RPC server. This is the default.
    extrpc - Same as procrpc (see below)
    logEvents - Write events from CCU into FHEM logfile
    + noEvents - Ignore events / device updates sent by CCU. No readings will be updated!
    nonBlocking - Use non blocking (asynchronous) CCU requests
    noReadings - Do not create or update readings
    procrpc - Use external RPC server provided by module HMCCPRPCPROC. During first RPC @@ -7763,8 +7948,9 @@ sub HMCCU_CCURPC_ListDevicesCB ($$)
  • ccuReqTimeout <Seconds>
    Set timeout for CCU request. Default is 4 seconds. This timeout affects several set and get commands, i.e. "set datapoint" or "set var". If a command runs into - a timeout FHEM will block for Seconds. -
  • + a timeout FHEM will block for Seconds. To prevent blocking set flag 'nonBlocking' + in attribute ccuflags. +
  • ccureadings {0 | 1}
    Deprecated. Readings are written by default. To deactivate readings set flag noReadings in attribute ccuflags. @@ -7786,11 +7972,12 @@ sub HMCCU_CCURPC_ListDevicesCB ($$)

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

  • rpcport <value[,...]>
    - Deprecated, use 'rpcinterfaces' instead. Specify list of RPC ports on CCU. Default is - 2001. Valid RPC ports are:

    + Deprecated, use attribute 'rpcinterfaces' instead. Specify list of RPC ports on CCU. + Default is 2001. Valid RPC ports are:

    • 2000 = Wired components
    • 2001 = BidCos-RF (wireless 868 MHz components with BidCos protocol)
    • diff --git a/fhem/FHEM/88_HMCCUDEV.pm b/fhem/FHEM/88_HMCCUDEV.pm index 0ba1af0c1..8c235cfeb 100644 --- a/fhem/FHEM/88_HMCCUDEV.pm +++ b/fhem/FHEM/88_HMCCUDEV.pm @@ -4,7 +4,7 @@ # # $Id$ # -# Version 4.3.003 +# Version 4.3.004 # # (c) 2018 zap (zap01 t-online de) # @@ -67,6 +67,7 @@ use SetExtensions; sub HMCCUDEV_Initialize ($); sub HMCCUDEV_Define ($@); +sub HMCCUDEV_Delete ($$); sub HMCCUDEV_InitDevice ($$); sub HMCCUDEV_Set ($@); sub HMCCUDEV_Get ($@); @@ -81,18 +82,20 @@ sub HMCCUDEV_Initialize ($) my ($hash) = @_; $hash->{DefFn} = "HMCCUDEV_Define"; + $hash->{DeleteFn} = "HMCCUDEV_Delete"; $hash->{SetFn} = "HMCCUDEV_Set"; $hash->{GetFn} = "HMCCUDEV_Get"; $hash->{AttrFn} = "HMCCUDEV_Attr"; $hash->{parseParams} = 1; - $hash->{AttrList} = "IODev ccucalculate:textField-long ". + $hash->{AttrList} = "IODev ccuaggregate:textField-long ccucalculate:textField-long ". "ccuflags:multiple-strict,ackState,nochn0,trace ccureadingfilter:textField-long ". "ccureadingformat:name,namelc,address,addresslc,datapoint,datapointlc ". "ccureadingname:textField-long ". "ccureadings:0,1 ccuget:State,Value ccuscaleval ccuverify:0,1,2 disable:0,1 ". "hmstatevals:textField-long statevals substexcl substitute:textField-long statechannel ". - "statedatapoint controldatapoint stripnumber peer:textField-long ".$readingFnAttributes; + "statedatapoint controldatapoint stripnumber peer:textField-long ". + $readingFnAttributes; } ##################################### @@ -105,7 +108,7 @@ sub HMCCUDEV_Define ($@) my $name = $hash->{NAME}; my $usage = "Usage: define $name HMCCUDEV {device|'virtual'} [state-channel] ". - "['readonly'] ['defaults'] [iodev={iodev-name}] ". + "['readonly'] ['defaults'] [iodev={iodev-name}] [address={virtual-device-no}]". "[{groupexp=regexp|group={device|channel}[,...]]"; return $usage if (scalar (@$a) < 3); @@ -116,7 +119,8 @@ sub HMCCUDEV_Define ($@) "No devices in group", "No matching CCU devices found", "Type of virtual device not defined", - "Device type not found" + "Device type not found", + "Too many virtual devices" ); my $devname = shift @$a; @@ -126,9 +130,21 @@ sub HMCCUDEV_Define ($@) my $hmccu_hash = undef; # Store some definitions for delayed initialization - $hash->{hmccu}{devspec} = $devspec; + $hash->{hmccu}{devspec} = $devspec; $hash->{hmccu}{groupexp} = $h->{groupexp} if (exists ($h->{groupexp})); - $hash->{hmccu}{group} = $h->{group} if (exists ($h->{group})); + $hash->{hmccu}{group} = $h->{group} if (exists ($h->{group})); + + if (exists ($h->{address})) { + if ($init_done || $devspec ne 'virtual') { + return "Option address not allowed"; + } + else { + $hash->{hmccu}{address} = $h->{address}; + } + } + else { + return "Option address not specified" if (!$init_done && $devspec eq 'virtual'); + } # Defaults $hash->{statevals} = 'devstate'; @@ -151,10 +167,10 @@ sub HMCCUDEV_Define ($@) $hmccu_hash = $defs{$h->{iodev}}; } else { - # The following call will fail during FHEM start if CCU is not ready + # 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); } - + if ($init_done) { # Interactive define command while CCU not ready if (!defined ($hmccu_hash)) { @@ -194,6 +210,7 @@ sub HMCCUDEV_Define ($@) # 4 = No matching CCU devices found # 5 = Type of virtual device not defined # 6 = Device type not found +# 7 = Too many virtual devices ###################################################################### sub HMCCUDEV_InitDevice ($$) @@ -205,18 +222,26 @@ sub HMCCUDEV_InitDevice ($$) my $gdname = $devspec; if ($devspec eq 'virtual') { - # Virtual device FHEM only, search for free address my $no = 0; - foreach my $d (sort keys %defs) { - my $ch = $defs{$d}; - next if (!exists ($ch->{TYPE})); - next if ($ch->{TYPE} ne 'HMCCUDEV' || $d eq $name); - next if ($ch->{ccuif} ne 'fhem' || $ch->{ccuname} ne 'virtual'); - $no++; + if (exists ($dev_hash->{hmccu}{address})) { + # Only true during FHEM start + $no = $dev_hash->{hmccu}{address}; } - $dev_hash->{ccuif} = 'fhem'; - $dev_hash->{ccuaddr} = sprintf ("VIR%07d", $no+1); - $dev_hash->{ccuname} = 'virtual'; + else { + # 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)) { + $no = $i; + last; + } + } + return 7 if ($no == 0); + $dev_hash->{DEF} .= " address=$no"; + } + $dev_hash->{ccuif} = 'fhem'; + $dev_hash->{ccuaddr} = sprintf ("VIR%07d", $no); + $dev_hash->{ccuname} = $name; } else { return 1 if (!HMCCU_IsValidDevice ($hmccu_hash, $devspec, 7)); @@ -303,6 +328,21 @@ sub HMCCUDEV_InitDevice ($$) return 0; } +##################################### +# Delete device +##################################### + +sub HMCCUDEV_Delete ($$) +{ + my ($hash, $name) = @_; + + if ($hash->{ccuif} eq 'fhem') { + HMCCU_DeleteDevice ($hash); + } + + return undef; +} + ##################################### # Set attribute ##################################### @@ -396,7 +436,7 @@ sub HMCCUDEV_Set ($@) } } } - + return HMCCU_SetError ($hash, $usage) if (!defined ($objvalue) || $objvalue eq ''); if ($objname =~ /^([0-9]+)\..+$/) { @@ -677,7 +717,8 @@ sub HMCCUDEV_Get ($@) my $result = ''; my $rc; - if ($ccuif eq "VirtualDevices" && $hash->{ccuname} eq "virtual" && $opt ne 'update') { + # Virtual devices only support command get update + if ($ccuif eq 'fhem' && $opt ne 'update') { return "HMCCUDEV: Unknown argument $opt, choose one of update:noArg"; } @@ -723,19 +764,18 @@ sub HMCCUDEV_Get ($@) return HMCCU_SetError ($hash, "Usage: get $name update [{'State'|'Value'}]"); } - if ($hash->{ccuname} ne 'virtual') { + if ($hash->{ccuif} ne 'fhem') { $rc = HMCCU_GetUpdate ($hash, $ccuaddr, $ccuget); return HMCCU_SetError ($hash, $rc) if ($rc < 0); } - - # Update other devices belonging to group -# if ($hash->{ccuif} eq "VirtualDevices" && exists ($hash->{ccugroup})) { -# my @vdevs = split (",", $hash->{ccugroup}); -# foreach my $vd (@vdevs) { -# $rc = HMCCU_GetUpdate ($hash, $vd, $ccuget); -# return HMCCU_SetError ($hash, $rc) if ($rc < 0); -# } -# } + else { + # Update all devices belonging to group + my @vdevs = split (",", $hash->{ccugroup}); + foreach my $vd (@vdevs) { + $rc = HMCCU_GetUpdate ($hash, $vd, $ccuget); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); + } + } return undef; } @@ -856,8 +896,8 @@ sub HMCCUDEV_Get ($@) expression for CCU device or channel names. Since version 4.2.009 of HMCCU HMCCUDEV is able to detect members of group devices automatically. So options 'group' or 'groupexp' are no longer necessary to define a group device.
      - It's also possible to group any kind of CCU devices or channels without defining a real - group in CCU by using option 'virtual' instead of a CCU device specification. + It's also possible to group any kind of CCU devices without defining a real group + in CCU by using option 'virtual' instead of a CCU device specification.

      Examples: