From ae21975a532c47e3dd095c08f60cd9c79cbc2105 Mon Sep 17 00:00:00 2001 From: fhemzap <> Date: Wed, 2 Mar 2016 16:26:59 +0000 Subject: [PATCH] HMCCU: new version git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@10975 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- contrib/HMCCU/FHEM/88_HMCCU.pm | 447 ++++++++++++++++++++++++++------- 1 file changed, 358 insertions(+), 89 deletions(-) diff --git a/contrib/HMCCU/FHEM/88_HMCCU.pm b/contrib/HMCCU/FHEM/88_HMCCU.pm index a158a8ec0..ad238301d 100644 --- a/contrib/HMCCU/FHEM/88_HMCCU.pm +++ b/contrib/HMCCU/FHEM/88_HMCCU.pm @@ -1,14 +1,14 @@ -#################################################################### +######################################################################### # # 88_HMCCU.pm # # $Id$ # -# Version 2.7 +# Version 2.8 # # (c) 2016 zap (zap01 t-online de) # -#################################################################### +######################################################################### # # define HMCCU [] # @@ -30,10 +30,11 @@ # get configdesc {|} [] # get deviceinfo # get rpcstate -# get update [ [{ State | Value }]] +# get update [ [{ State | Value }]] +# get updateccu [ [{ State | Value }]] # # attr ccuget { State | Value } -# attr ccureadingfilter +# attr ccureadingfilter # attr ccureadingformat { name | address } # attr ccureadings { 0 | 1 } # attr ccutrace {|} @@ -49,8 +50,9 @@ # attr substitute # attr updatemode { client | both | hmccu } # -# subst_rule := [datapoint[,...]]!:[,...][;...] -#################################################################### +# filter_rule := [channel-regexp!]datapoint-regexp[,...] +# subst_rule := [datapoint[,...]!]:[,...][;...] +######################################################################### package main; @@ -102,13 +104,14 @@ sub HMCCU_Set ($@); sub HMCCU_Get ($@); sub HMCCU_Attr ($@); sub HMCCU_ParseObject ($$); +sub HMCCU_FilterReading ($$$); sub HMCCU_GetReadingName ($$$$$$); sub HMCCU_FormatReadingValue ($$); sub HMCCU_SetError ($$); sub HMCCU_SetState ($$); sub HMCCU_Substitute ($$$$); sub HMCCU_SubstRule ($$$); -sub HMCCU_UpdateClients ($$$); +sub HMCCU_UpdateClients ($$$$); sub HMCCU_UpdateClientReading ($@); sub HMCCU_DeleteDevices ($); sub HMCCU_StartRPCServer ($); @@ -119,16 +122,21 @@ sub HMCCU_CheckProcess ($$); sub HMCCU_GetDeviceInfo ($$$); sub HMCCU_GetDeviceList ($); sub HMCCU_GetAddress ($$$); +sub HMCCU_IsDevAddr ($$); +sub HMCCU_IsChnAddr ($$); +sub HMCCU_SplitChnAddr ($); sub HMCCU_GetCCUObjectAttribute ($$); sub HMCCU_GetHash ($@); sub HMCCU_GetAttribute ($$$$); sub HMCCU_GetSpecialDatapoints ($$$$$); sub HMCCU_IsValidDevice ($); +sub HMCCU_GetMatchingDevices ($$$$); sub HMCCU_GetDeviceName ($$); sub HMCCU_GetChannelName ($$); sub HMCCU_GetDeviceType ($$); sub HMCCU_GetDeviceChannels ($); sub HMCCU_GetDeviceInterface ($$); +sub HMCCU_ResetRPCQueue ($); sub HMCCU_ReadRPCQueue ($); sub HMCCU_HMScript ($$); sub HMCCU_GetDatapoint ($@); @@ -136,10 +144,11 @@ sub HMCCU_SetDatapoint ($$$); sub HMCCU_GetVariables ($$); sub HMCCU_SetVariable ($$$); sub HMCCU_GetUpdate ($$$); +sub HMCCU_UpdateDeviceReadings ($$); sub HMCCU_GetChannel ($$); sub HMCCU_RPCGetConfig ($$$$); sub HMCCU_RPCSetConfig ($$$); -sub HMCCU_State ($); +sub HMCCU_AggReadings ($$$$$); sub HMCCU_Dewpoint ($$$$); @@ -159,6 +168,16 @@ sub HMCCU_Initialize ($) $hash->{ShutdownFn} = "HMCCU_Shutdown"; $hash->{AttrList} = "stripchar stripnumber:0,1,2 ccureadings:0,1 ccureadingfilter ccureadingformat:name,address rpcinterval:3,5,10 rpcqueue rpcport rpcserver:on,off parfile statedatapoint statevals substitute updatemode:client,both,hmccu ccutrace ccuget:Value,State ". $readingFnAttributes; + + # Get list of CCU devices +# foreach my $d (keys %defs) { +# my $h = $defs{$d}; +# if ($h->{TYPE} eq "HMCCU") { +# HMCCU_GetDeviceList ($h); +# DoTrigger ($h->{NAME}, "Module HMCCU reloaded"); +# last; +# } +# } } ##################################### @@ -398,7 +417,7 @@ sub HMCCU_Get ($@) my ($hash, @a) = @_; my $name = shift @a; my $opt = shift @a; - my $options = "devicelist:noArg devstate datapoint vars channel update parfile config configdesc rpcstate:noArg deviceinfo"; + my $options = "devicelist:noArg devstate datapoint vars channel update updateccu parfile config configdesc rpcstate:noArg deviceinfo"; my $host = $hash->{host}; if (HMCCU_IsRPCStateBlocking ($hash)) { @@ -477,16 +496,16 @@ sub HMCCU_Get ($@) HMCCU_SetState ($hash, "OK"); return $ccureadings ? undef : $result; } - elsif ($opt eq 'update') { + elsif ($opt eq 'update' || $opt eq 'updateccu') { my $devexp = shift @a; $devexp = '.*' if (!defined ($devexp)); my $ccuget = shift @a; $ccuget = 'Attr' if (!defined ($ccuget)); if ($ccuget !~ /^(Attr|State|Value)$/) { - return HMCCU_SetError ($hash, "Usage: get $name update [device-expr [{'State'|'Value'}]]"); + return HMCCU_SetError ($hash, "Usage: get $name $opt [device-expr [{'State'|'Value'}]]"); } - my ($c_ok, $c_err) = HMCCU_UpdateClients ($hash, $devexp, $ccuget); + my ($c_ok, $c_err) = HMCCU_UpdateClients ($hash, $devexp, $ccuget, ($opt eq 'updateccu') ? 1 : 0); HMCCU_SetState ($hash, "OK"); return "$c_ok client devices successfully updated. Update for $c_err client devices failed"; @@ -701,6 +720,36 @@ sub HMCCU_ParseObject ($$) return ($i, $a, $c, $d, $n, $f); } +################################################################## +# Filter reading by datapoint and optionally by channel name +################################################################## + +sub HMCCU_FilterReading ($$$) +{ + my ($hash, $chn, $dpt) = @_; + my $name = $hash->{NAME}; + + my $rf = AttrVal ($name, 'ccureadingfilter', '.*'); + return 1 if ($rf eq '.*'); + + my $chnnam = HMCCU_GetChannelName ($chn, ''); + + my @rules = split (",", $rf); + foreach my $r (@rules) { + my ($c, $f) = split ("!", $r); + if (defined ($f) && $chnnam ne '') { + if ($chnnam =~ /$c/) { + return ($dpt =~ /$f/) ? 1 : 0; + } + } + else { + return 1 if ($dpt =~ /$r/); + } + } + + return 0; +} + ################################################################## # Build reading name # @@ -898,25 +947,49 @@ sub HMCCU_SubstRule ($$$) # will fail if attribute ccureadings of a device is set to 0. ################################################################## -sub HMCCU_UpdateClients ($$$) +sub HMCCU_UpdateClients ($$$$) { - my ($hash, $devexp, $ccuget) = @_; + my ($hash, $devexp, $ccuget, $fromccu) = @_; my $c_ok = 0; my $c_err = 0; - foreach my $d (keys %defs) { - # Get hash of client device - my $ch = $defs{$d}; - next if ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN'); - next if ($ch->{NAME} !~ /$devexp/); - next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr})); + if ($fromccu) { + foreach my $name (sort keys %HMCCU_Addresses) { + next if ($name !~ /$devexp/ || !($HMCCU_Addresses{$name}{valid})); - my $rc = HMCCU_GetUpdate ($ch, $ch->{ccuaddr}, $ccuget); - if ($rc <= 0) { - $c_err++; + foreach my $d (keys %defs) { + my $ch = $defs{$d}; + next if ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN'); + next if ($ch->{ccudevstate} ne 'Active'); + next if ($ch->{ccuaddr} ne $HMCCU_Addresses{$name}{address}); + next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr})); + + my $rc = HMCCU_GetUpdate ($ch, $HMCCU_Addresses{$name}{address}, $ccuget); + if ($rc <= 0) { + $c_err++; + } + else { + $c_ok++; + } + } } - else { - $c_ok++; + } + else { + foreach my $d (keys %defs) { + # Get hash of client device + my $ch = $defs{$d}; + next if ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN'); + next if ($ch->{ccudevstate} ne 'Active'); + next if ($ch->{NAME} !~ /$devexp/); + next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr})); + + my $rc = HMCCU_GetUpdate ($ch, $ch->{ccuaddr}, $ccuget); + if ($rc <= 0) { + $c_err++; + } + else { + $c_ok++; + } } } @@ -930,7 +1003,7 @@ sub HMCCU_UpdateClients ($$$) # hash, devadd, channelno, reading, value, [mode] # # Parameter devadd can be a device or a channel address. If -# devadd is a channel address parameter channelno should be ''. +# devadd is a channel address parameter channel should be ''. # Valid modes are: hmccu, rpcevent, client. # Reading values are substituted if attribute substitute is set # in client device. @@ -943,7 +1016,7 @@ sub HMCCU_UpdateClientReading ($@) my $hmccu_substitute = AttrVal ($name, 'substitute', ''); my $hmccu_updreadings = AttrVal ($name, 'ccureadings', 1); - my $hmccu_flt = AttrVal ($name, 'ccureadingfilter', '.*'); +# my $hmccu_flt = AttrVal ($name, 'ccureadingfilter', '.*'); my $updatemode = AttrVal ($name, 'updatemode', 'hmccu'); # Update mode can be: client, hmccu, both, rpcevent @@ -963,7 +1036,9 @@ sub HMCCU_UpdateClientReading ($@) if ($hmccu_updreadings && $updatemode ne 'client') { $hmccu_value = HMCCU_Substitute ($value, $hmccu_substitute, 0, $reading); $hmccu_value = HMCCU_FormatReadingValue ($hash, $hmccu_value); - if ($updatemode ne 'rpcevent' && ($dpt eq '' || $dpt =~ /$hmccu_flt/)) { +# if ($updatemode ne 'rpcevent' && ($dpt eq '' || $dpt =~ /$hmccu_flt/)) { + if ($updatemode ne 'rpcevent' && ($dpt eq '' || + HMCCU_FilterReading ($hash, $chnadd, $dpt))) { readingsSingleUpdate ($hash, $reading, $hmccu_value, 1); } return $hmccu_value if ($updatemode eq 'hmccu'); @@ -978,16 +1053,24 @@ sub HMCCU_UpdateClientReading ($@) next if ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN'); next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr})); next if ($ch->{IODev} != $hash); - next if ($ch->{ccuaddr} ne $devadd && $ch->{ccuaddr} ne $chnadd); + if ($ch->{ccuif} eq "VirtualDevices" && exists ($ch->{ccugroup})) { + my @gdevs = split (",", $ch->{ccugroup}); + next if (!($devadd ~~ @gdevs) && !($chnadd ~~ @gdevs)); + } + else { + next if ($ch->{ccuaddr} ne $devadd && $ch->{ccuaddr} ne $chnadd); + } # Get attributes of client device my $upd = AttrVal ($cn, 'ccureadings', 1); my $crf = AttrVal ($cn, 'ccureadingformat', 'name'); - my $flt = AttrVal ($cn, 'ccureadingfilter', '.*'); +# my $flt = AttrVal ($cn, 'ccureadingfilter', '.*'); + my $mapdatapoints = AttrVal ($cn, 'mapdatapoints', ''); my $substitute = AttrVal ($cn, 'substitute', ''); my ($sc, $st, $cc, $cd) = HMCCU_GetSpecialDatapoints ($ch, 'STATE', '', '', ''); last if ($upd == 0); - next if ($dpt eq '' || $dpt !~ /$flt/); + next if (!HMCCU_FilterReading ($ch, $chnadd, $dpt)); +# next if ($dpt eq '' || $dpt !~ /$flt/); my $clreading = HMCCU_GetReadingName ('', $devadd, $channel, $dpt, '', $crf); next if ($clreading eq ''); @@ -1002,6 +1085,7 @@ sub HMCCU_UpdateClientReading ($@) } $cl_value = HMCCU_FormatReadingValue ($ch, $cl_value); + # Update reading and control/state readings readingsSingleUpdate ($ch, $clreading, $cl_value, 1); if ($cd ne '' && $dpt eq $cd && $channel eq $cc) { readingsSingleUpdate ($ch, 'control', $cl_value, 1); @@ -1009,6 +1093,30 @@ sub HMCCU_UpdateClientReading ($@) if ($clreading =~ /\.$st$/ && ($sc eq '' || $sc eq $channel)) { HMCCU_SetState ($ch, $cl_value); } + + # Map datapoints for virtual devices (groups) + if ($mapdatapoints ne '') { + foreach my $m (split (",", $mapdatapoints)) { + my @mr = split ("=", $m); + next if (@mr != 2); + my ($i1, $a1, $c1, $d1, $n1, $f1) = + HMCCU_ParseObject ($mr[0], $HMCCU_FLAG_FULLADDR); + my ($i2, $a2, $c2, $d2, $n2, $f2) = + HMCCU_ParseObject ($mr[1], $HMCCU_FLAG_FULLADDR); + next if (($f1 & $HMCCU_FLAGS_AC) != $HMCCU_FLAGS_AC || + ($f2 & $HMCCU_FLAGS_AC) != $HMCCU_FLAGS_AC); + next if ($devadd ne $a1 || $channel ne $c1 || $dpt ne $d1); + my $mreading = HMCCU_GetReadingName ('', $a2, $c2, $d2, '', $crf); + next if ($mreading eq ''); + readingsSingleUpdate ($ch, $mreading, $cl_value, 1); + if ($cd ne '' && $d2 eq $cd && $c2 eq $cc) { + readingsSingleUpdate ($ch, 'control', $cl_value, 1); + } + if ($mreading =~ /\.$st/ && ($sc eq '' || $sc eq $c2)) { + HMCCU_SetState ($ch, $cl_value); + } + } + } } return $hmccu_value; @@ -1077,6 +1185,9 @@ sub HMCCU_StartRPCServer ($) return 0; } + # Clear RPC queues + HMCCU_ResetRPCQueue ($hash); + # Fork child process(es) foreach my $port (split (',', $rpcport)) { my $rpcqueueport = $rpcqueue."_".$port; @@ -1210,6 +1321,7 @@ sub HMCCU_IsRPCServerRunning ($$$) sub HMCCU_CheckProcess ($$) { my ($hash, $port) = @_; + my $name = $hash->{NAME}; my $modpath = AttrVal ('global', 'modpath', '/opt/fhem'); my $rpcserver = $modpath."/FHEM/ccurpcd.pl"; @@ -1345,7 +1457,10 @@ foreach(devid, root.Devices().EnumUsedIDs()) { foreach my $d (keys %defs) { # Get hash of client device my $ch = $defs{$d}; + next if ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN'); next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr})); + next if ($ch->{TYPE} eq 'HMCCUDEV' && $ch->{ccuif} eq "VirtualDevices" && + $ch->{ccuname} eq 'none'); my $add = $ch->{ccuaddr}; my $dadd = $add; $dadd =~ s/:[0-9]+$//; @@ -1386,6 +1501,28 @@ sub HMCCU_IsValidDevice ($) } } +#################################################### +# Get list of device or channel addresses with +# device or channel name matching regular expression. +# Parameter mode can be dev or chn. +# Return number of matching entries. +#################################################### + +sub HMCCU_GetMatchingDevices ($$$$) +{ + my ($hash, $regexp, $mode, $listref) = @_; + my $c = 0; + + foreach my $name (sort keys %HMCCU_Addresses) { + next if ($name !~/$regexp/ || $HMCCU_Addresses{$name}{addtype} ne $mode || + $HMCCU_Addresses{$name}{valid} == 0); + push (@$listref, $HMCCU_Addresses{$name}{address}); + $c++; + } + + return $c; +} + #################################################### # Get name of a CCU device by address. # Channel number will be removed if specified. @@ -1519,6 +1656,59 @@ sub HMCCU_GetAddress ($$$) return ($add, $chn); } +#################################################### +# Check if parameter is a channel address (syntax) +# f=1: Interface required. +#################################################### + +sub HMCCU_IsChnAddr ($$) +{ + my ($id, $f) = @_; + + if ($f) { + return ($id =~ /^.+\.[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) ? 1 : 0; + } + else { + return ($id =~ /^[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) ? 1 : 0; + } +} + +#################################################### +# Check if parameter is a device address (syntax) +# f=1: Interface required. +#################################################### + +sub HMCCU_IsDevAddr ($$) +{ + my ($id, $f) = @_; + + if ($f) { + return ($id =~ /^.+\.[A-Z]{3,3}[0-9]{7,7}$/) ? 1 : 0; + } + else { + return ($id =~ /^[A-Z]{3,3}[0-9]{7,7}$/) ? 1 : 0; + } +} + +#################################################### +# Split channel address into device address and +# channel number +#################################################### + +sub HMCCU_SplitChnAddr ($) +{ + my ($addr) = @_; + + if (HMCCU_IsChnAddr ($addr, 0)) { + return split (":", $addr); + } + elsif (HMCCU_IsDevAddr ($addr, 0)) { + return ($addr, ''); + } + + return ('', ''); +} + sub HMCCU_GetCCUObjectAttribute ($$) { my ($object, $attr) = @_; @@ -1605,6 +1795,26 @@ sub HMCCU_GetSpecialDatapoints ($$$$$) return ($sc, $sd, $cc, $cd); } +#################################################### +# Clear RPC queue +#################################################### + +sub HMCCU_ResetRPCQueue ($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue'); + my $rpcport = AttrVal ($name, 'rpcport', '2001'); + + my @portlist = split (',', $rpcport); + foreach my $port (@portlist) { + my $queue = new RPCQueue (File => $rpcqueue."_".$port, Mode => 0666); + $queue->reset(); + $queue->close(); + } +} + #################################################### # Timer function for reading RPC queue #################################################### @@ -1618,6 +1828,7 @@ sub HMCCU_ReadRPCQueue ($) my $f = 0; my @newdevices; my @deldevices; + my @termpids; my $newcount = 0; my $delcount = 0; @@ -1625,6 +1836,7 @@ sub HMCCU_ReadRPCQueue ($) my $ccureadingformat = AttrVal ($name, 'ccureadingformat', 'name'); my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue'); my $rpcport = AttrVal ($name, 'rpcport', '2001'); + my $rpctimeout = AttrVal ($name, 'rpctimeout', 300); my @portlist = split (',', $rpcport); foreach my $port (@portlist) { @@ -1664,6 +1876,7 @@ sub HMCCU_ReadRPCQueue ($) } elsif ($Tokens[0] eq 'EX') { #### RPC Server shut down #### + push (@termpids, $Tokens[2]); Log3 $name, 0, "HMCCU: Received EX event. RPC server terminated."; if ($hash->{RPCState} ne "restarting") { $hash->{RPCState} = "stopped"; @@ -1680,11 +1893,14 @@ sub HMCCU_ReadRPCQueue ($) $element = $queue->deq(); } + + $queue->close(); } - if ($HMCCU_EventTime > 0 && time()-$HMCCU_EventTime > 300) { - Log3 $name, 2, "HMCCU: Received no events from CCU since 300 seconds"; - DoTrigger ($name, "No events from CCU since 300 seconds"); + # Check if RPC server still running if events from CCU timed out + if ($HMCCU_EventTime > 0 && time()-$HMCCU_EventTime > $rpctimeout) { + Log3 $name, 2, "HMCCU: Received no events from CCU since $rpctimeout seconds"; + DoTrigger ($name, "No events from CCU since $rpctimeout seconds"); } # CCU devices deleted @@ -1704,39 +1920,42 @@ sub HMCCU_ReadRPCQueue ($) my @hm_pids; my @ex_pids; HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); - if ($f == 0 && @hm_pids > 0) { - if (scalar (@hm_pids) != scalar (@portlist)) { - Log3 $name, 1, "HMCCU: Number of RPC server process differs from number of CCU destination ports"; + my $nhm_pids = scalar (@hm_pids); + my $nex_pids = scalar (@ex_pids); + + if ($nex_pids > 0) { + Log3 $name, 1, "HMCCU: Externally launched RPC server(s) detected. Kill process(es) manually with command kill -SIGINT pid for pids ".join (',', @ex_pids)." f=$f"; + } + + if ($f > 0) { + # At least one RPC server has been stopped. Update PID list + $hash->{RPCPID} = $nhm_pids > 0 ? join(',',@hm_pids) : '0'; + Log3 $name, 0, "HMCCU: RPC server(s) with PID(s) ".join(',',@termpids)." shut down. f=$f"; + } + + if ($f == 2 && $nhm_pids == 0) { + # All RPC servers terminated and restart flag set + $HMCCU_EventTime = 0; + if (HMCCU_StartRPCServer ($hash)) { + InternalTimer (gettimeofday()+60, 'HMCCU_ReadRPCQueue', $hash, 0); + return; } + else { + Log3 $name, 0, "HMCCU: Restart of RPC server failed"; + } + } + + if ($nhm_pids > 0) { + # Reschedule reading of RPC queues if at least one RPC server is running InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0); } else { - my $nex_pids = scalar @ex_pids; - if ($nex_pids > 0) { - Log3 $name, 1, "HMCCU: Externally launched RPC server(s) detected. Kill process(es) manually with command kill -SIGINT pid for pids ".join (',', @ex_pids)." f=$f"; - } - else { - Log3 $name, 0, "HMCCU: RPC server has been shut down. f=$f"; - } - - $HMCCU_EventTime = 0; - - if ($f == 2) { - if ($nex_pids == 0 && HMCCU_StartRPCServer ($hash)) { - InternalTimer (gettimeofday()+60, - 'HMCCU_ReadRPCQueue', $hash, 0); - return; - } - else { - Log3 $name, 0, "HMCCU: Restart of RPC server failed"; - } - } - + # No more RPC servers active $hash->{RPCPID} = '0'; $hash->{RPCPRC} = 'none'; $hash->{RPCState} = "stopped"; - DoTrigger ($name, "RPC server stopped"); -# $attr{$name}{rpcserver} = "off"; + Log3 $name, 0, "HMCCU: All RPC servers stopped"; + DoTrigger ($name, "All RPC servers stopped"); } } @@ -1964,18 +2183,10 @@ sub HMCCU_GetUpdate ($$$) my $nam = ''; my $script; - my $cn = $cl_hash->{NAME}; - my $ccureadings = AttrVal ($cn, 'ccureadings', 1); - return -6 if ($ccureadings == 0); $ccuget = HMCCU_GetAttribute ($hmccu_hash, $cl_hash, 'ccuget', 'Value') if ($ccuget eq 'Attr'); my $ccutrace = AttrVal ($hmccu_hash->{NAME}, 'ccutrace', ''); - my $ccureadingfilter = AttrVal ($cn, 'ccureadingfilter', '.*'); - my $readingformat = AttrVal ($cn, 'ccureadingformat', 'name'); - my $substitute = AttrVal ($cn, 'substitute', ''); - my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints ( - $cl_hash, 'STATE', '', '', ''); - if ($addr =~ /^[A-Z]{3,3}[0-9]{7,7}:[0-9]{1,2}$/) { + if (HMCCU_IsChnAddr ($addr, 0)) { $nam = HMCCU_GetChannelName ($addr, ''); return -1 if ($nam eq ''); @@ -1993,7 +2204,7 @@ if (oChannel) { } ); } - elsif ($addr =~ /^[A-Z]{3,3}[0-9]{7,7}$/) { + elsif (HMCCU_IsDevAddr ($addr, 0)) { $nam = HMCCU_GetDeviceName ($addr, ''); return -1 if ($nam eq ''); @@ -2028,16 +2239,59 @@ if (odev) { } return -2 if ($response eq ''); + my @dpdef = split /\n/, $response; + + # Update client device + my $rc = HMCCU_UpdateDeviceReadings ($cl_hash, \@dpdef); + return $rc if ($rc < 0); + + # Update virtual devices + my ($da, $cno) = HMCCU_SplitChnAddr ($cl_hash->{ccuaddr}); + foreach my $dn (sort keys %defs) { + my $ch = $defs{$dn}; + my @vdevs = split (",", $ch->{ccugroup}); + next if ($ch->{TYPE} ne 'HMCCUDEV'); + next if ($ch->{ccuif} ne "VirtualDevices" || !exists ($ch->{ccugroup})); + if ($da ~~ @vdevs || ($cno ne '' && $cl_hash->{ccuaddr} ~~ @vdevs)) { + HMCCU_UpdateDeviceReadings ($ch, \@dpdef); + } + } + + return 1; +} + +#################################################### +# Update readings of client device. Parameter dp +# is a reference to an array of datapoint=value +# pairs. Returns number of updated readings. +#################################################### + +sub HMCCU_UpdateDeviceReadings ($$) +{ + my ($cl_hash, $dp) = @_; + + my $uc = 0; + + my $cn = $cl_hash->{NAME}; + my $ccureadings = AttrVal ($cn, 'ccureadings', 1); + return -6 if ($ccureadings == 0); +# my $ccureadingfilter = AttrVal ($cn, 'ccureadingfilter', '.*'); + my $readingformat = AttrVal ($cn, 'ccureadingformat', 'name'); + my $substitute = AttrVal ($cn, 'substitute', ''); + my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints ( + $cl_hash, 'STATE', '', '', ''); + readingsBeginUpdate ($cl_hash); - foreach my $dpdef (split /\n/, $response) { + foreach my $dpdef (@$dp) { my @dpdata = split /=/, $dpdef; next if (@dpdata < 2); my @adrtoks = split /\./, $dpdata[1]; next if (@adrtoks != 3); - next if ($adrtoks[2] !~ /$ccureadingfilter/); +# next if ($adrtoks[2] !~ /$ccureadingfilter/); my ($add, $chn) = split /:/, $adrtoks[1]; + next if (!HMCCU_FilterReading ($cl_hash, $adrtoks[1], $adrtoks[2])); my $reading = HMCCU_GetReadingName ($adrtoks[0], $add, $chn, $adrtoks[2], $dpdata[0], $readingformat); next if ($reading eq ''); @@ -2052,11 +2306,12 @@ if (odev) { if ($reading =~ /\.$statedpt$/ && ($statechn eq '' || $statechn eq $chn)) { readingsBulkUpdate ($cl_hash, "state", $value); } + $uc++; } readingsEndUpdate ($cl_hash, 1); - return 1; + return $uc; } #################################################### @@ -2304,30 +2559,41 @@ sub HMCCU_RPCSetConfig ($$$) } #################################################### -# Return string for internal STATE. This function -# can be used in attribute stateFormat. +# Aggregate readings. Valid operations are 'and', +# 'or' or 'cnt'. +# and: return v1 if all readings matching v1, +# otherwise return v2. +# or: return v1 if at least 1 reading matches v1, +# otherwise return v2. +# cnt: return number of readings matching v1. +# Ex 1: number of open windows: state, "cnt", "open", "" +# Ex 2: Status of windows: state, "and", "close", "open" #################################################### -sub HMCCU_State ($) +sub HMCCU_AggReadings ($$$$$) { - my ($name) = @_; + my ($name, $readexp, $oper, $v1, $v2) = @_; - my $hash = $defs{$name}; - my $sf = AttrVal ($name, 'ccustate', ''); + return undef if (!exists ($defs{$name})); - return ReadingsVal ($name, 'state', '') if ($sf eq ''); + my $mc = 0; + my $c = 0; - my $st = $sf; - my $r = $hash->{READINGS}; + foreach my $r (keys %{$defs{$name}{READINGS}}) { + next if ($r !~ /$readexp/); + $c++; + $mc++ if ($defs{$name}{READINGS}{$r}{VAL} eq $v1); + } - if ($r->{state}{VAL} ne "Error") { - $st =~ s/\b([A-Za-z\d_\.\:-]+)\b/($r->{$1} ? $r->{$1}{VAL} : $1)/ge; + if ($oper eq 'and') { + return ($mc < $c) ? $v2 : $v1; + } + elsif ($oper eq 'or') { + return ($mc > 0) ? $v1 : $v2; } else { - $st = "Error"; + return $mc; } - - return $st; } #################################################### @@ -2485,7 +2751,10 @@ sub HMCCU_Dewpoint ($$$$) Check if RPC server process is running.
  • get <name> update [<devexp> [<'State'|'Value'>]]
    - Update all datapoint / readings of client devices with device name matching <devexp> + Update all datapoints / readings of client devices with FHEM device name matching <devexp> +

  • +
  • get <name> updateccu [<devexp> [<'State'|'Value'>]]
    + Update all datapoints / readings of client devices with CCU device name matching <devexp>