HMCCU: new version

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@10975 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
fhemzap 2016-03-02 16:26:59 +00:00
parent d859f4468e
commit ae21975a53

View File

@ -1,14 +1,14 @@
#################################################################### #########################################################################
# #
# 88_HMCCU.pm # 88_HMCCU.pm
# #
# $Id$ # $Id$
# #
# Version 2.7 # Version 2.8
# #
# (c) 2016 zap (zap01 <at> t-online <dot> de) # (c) 2016 zap (zap01 <at> t-online <dot> de)
# #
#################################################################### #########################################################################
# #
# define <name> HMCCU <host_or_ip> [<read_interval>] # define <name> HMCCU <host_or_ip> [<read_interval>]
# #
@ -30,10 +30,11 @@
# get <name> configdesc {<device>|<channel>} [<rpcport>] # get <name> configdesc {<device>|<channel>} [<rpcport>]
# get <name> deviceinfo <device> # get <name> deviceinfo <device>
# get <name> rpcstate # get <name> rpcstate
# get <name> update [<devexp> [{ State | Value }]] # get <name> update [<fhemdevexp> [{ State | Value }]]
# get <name> updateccu [<devexp> [{ State | Value }]]
# #
# attr <name> ccuget { State | Value } # attr <name> ccuget { State | Value }
# attr <name> ccureadingfilter <datapoint_exp> # attr <name> ccureadingfilter <filter_rule>
# attr <name> ccureadingformat { name | address } # attr <name> ccureadingformat { name | address }
# attr <name> ccureadings { 0 | 1 } # attr <name> ccureadings { 0 | 1 }
# attr <name> ccutrace {<ccudevname_exp>|<ccudevaddr_exp>} # attr <name> ccutrace {<ccudevname_exp>|<ccudevaddr_exp>}
@ -49,8 +50,9 @@
# attr <name> substitute <subst_rule> # attr <name> substitute <subst_rule>
# attr <name> updatemode { client | both | hmccu } # attr <name> updatemode { client | both | hmccu }
# #
# subst_rule := [datapoint[,...]]!<regexp1>:<subtext1>[,...][;...] # filter_rule := [channel-regexp!]datapoint-regexp[,...]
#################################################################### # subst_rule := [datapoint[,...]!]<regexp>:<subtext>[,...][;...]
#########################################################################
package main; package main;
@ -102,13 +104,14 @@ sub HMCCU_Set ($@);
sub HMCCU_Get ($@); sub HMCCU_Get ($@);
sub HMCCU_Attr ($@); sub HMCCU_Attr ($@);
sub HMCCU_ParseObject ($$); sub HMCCU_ParseObject ($$);
sub HMCCU_FilterReading ($$$);
sub HMCCU_GetReadingName ($$$$$$); sub HMCCU_GetReadingName ($$$$$$);
sub HMCCU_FormatReadingValue ($$); sub HMCCU_FormatReadingValue ($$);
sub HMCCU_SetError ($$); sub HMCCU_SetError ($$);
sub HMCCU_SetState ($$); sub HMCCU_SetState ($$);
sub HMCCU_Substitute ($$$$); sub HMCCU_Substitute ($$$$);
sub HMCCU_SubstRule ($$$); sub HMCCU_SubstRule ($$$);
sub HMCCU_UpdateClients ($$$); sub HMCCU_UpdateClients ($$$$);
sub HMCCU_UpdateClientReading ($@); sub HMCCU_UpdateClientReading ($@);
sub HMCCU_DeleteDevices ($); sub HMCCU_DeleteDevices ($);
sub HMCCU_StartRPCServer ($); sub HMCCU_StartRPCServer ($);
@ -119,16 +122,21 @@ sub HMCCU_CheckProcess ($$);
sub HMCCU_GetDeviceInfo ($$$); sub HMCCU_GetDeviceInfo ($$$);
sub HMCCU_GetDeviceList ($); sub HMCCU_GetDeviceList ($);
sub HMCCU_GetAddress ($$$); sub HMCCU_GetAddress ($$$);
sub HMCCU_IsDevAddr ($$);
sub HMCCU_IsChnAddr ($$);
sub HMCCU_SplitChnAddr ($);
sub HMCCU_GetCCUObjectAttribute ($$); sub HMCCU_GetCCUObjectAttribute ($$);
sub HMCCU_GetHash ($@); sub HMCCU_GetHash ($@);
sub HMCCU_GetAttribute ($$$$); sub HMCCU_GetAttribute ($$$$);
sub HMCCU_GetSpecialDatapoints ($$$$$); sub HMCCU_GetSpecialDatapoints ($$$$$);
sub HMCCU_IsValidDevice ($); sub HMCCU_IsValidDevice ($);
sub HMCCU_GetMatchingDevices ($$$$);
sub HMCCU_GetDeviceName ($$); sub HMCCU_GetDeviceName ($$);
sub HMCCU_GetChannelName ($$); sub HMCCU_GetChannelName ($$);
sub HMCCU_GetDeviceType ($$); sub HMCCU_GetDeviceType ($$);
sub HMCCU_GetDeviceChannels ($); sub HMCCU_GetDeviceChannels ($);
sub HMCCU_GetDeviceInterface ($$); sub HMCCU_GetDeviceInterface ($$);
sub HMCCU_ResetRPCQueue ($);
sub HMCCU_ReadRPCQueue ($); sub HMCCU_ReadRPCQueue ($);
sub HMCCU_HMScript ($$); sub HMCCU_HMScript ($$);
sub HMCCU_GetDatapoint ($@); sub HMCCU_GetDatapoint ($@);
@ -136,10 +144,11 @@ sub HMCCU_SetDatapoint ($$$);
sub HMCCU_GetVariables ($$); sub HMCCU_GetVariables ($$);
sub HMCCU_SetVariable ($$$); sub HMCCU_SetVariable ($$$);
sub HMCCU_GetUpdate ($$$); sub HMCCU_GetUpdate ($$$);
sub HMCCU_UpdateDeviceReadings ($$);
sub HMCCU_GetChannel ($$); sub HMCCU_GetChannel ($$);
sub HMCCU_RPCGetConfig ($$$$); sub HMCCU_RPCGetConfig ($$$$);
sub HMCCU_RPCSetConfig ($$$); sub HMCCU_RPCSetConfig ($$$);
sub HMCCU_State ($); sub HMCCU_AggReadings ($$$$$);
sub HMCCU_Dewpoint ($$$$); sub HMCCU_Dewpoint ($$$$);
@ -159,6 +168,16 @@ sub HMCCU_Initialize ($)
$hash->{ShutdownFn} = "HMCCU_Shutdown"; $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; $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 ($hash, @a) = @_;
my $name = shift @a; my $name = shift @a;
my $opt = 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}; my $host = $hash->{host};
if (HMCCU_IsRPCStateBlocking ($hash)) { if (HMCCU_IsRPCStateBlocking ($hash)) {
@ -477,16 +496,16 @@ sub HMCCU_Get ($@)
HMCCU_SetState ($hash, "OK"); HMCCU_SetState ($hash, "OK");
return $ccureadings ? undef : $result; return $ccureadings ? undef : $result;
} }
elsif ($opt eq 'update') { elsif ($opt eq 'update' || $opt eq 'updateccu') {
my $devexp = shift @a; my $devexp = shift @a;
$devexp = '.*' if (!defined ($devexp)); $devexp = '.*' if (!defined ($devexp));
my $ccuget = shift @a; my $ccuget = shift @a;
$ccuget = 'Attr' if (!defined ($ccuget)); $ccuget = 'Attr' if (!defined ($ccuget));
if ($ccuget !~ /^(Attr|State|Value)$/) { 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"); HMCCU_SetState ($hash, "OK");
return "$c_ok client devices successfully updated. Update for $c_err client devices failed"; 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); 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 # Build reading name
# #
@ -898,25 +947,49 @@ sub HMCCU_SubstRule ($$$)
# will fail if attribute ccureadings of a device is set to 0. # 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_ok = 0;
my $c_err = 0; my $c_err = 0;
foreach my $d (keys %defs) { if ($fromccu) {
# Get hash of client device foreach my $name (sort keys %HMCCU_Addresses) {
my $ch = $defs{$d}; next if ($name !~ /$devexp/ || !($HMCCU_Addresses{$name}{valid}));
next if ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN');
next if ($ch->{NAME} !~ /$devexp/);
next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr}));
my $rc = HMCCU_GetUpdate ($ch, $ch->{ccuaddr}, $ccuget); foreach my $d (keys %defs) {
if ($rc <= 0) { my $ch = $defs{$d};
$c_err++; 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] # hash, devadd, channelno, reading, value, [mode]
# #
# Parameter devadd can be a device or a channel address. If # 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. # Valid modes are: hmccu, rpcevent, client.
# Reading values are substituted if attribute substitute is set # Reading values are substituted if attribute substitute is set
# in client device. # in client device.
@ -943,7 +1016,7 @@ sub HMCCU_UpdateClientReading ($@)
my $hmccu_substitute = AttrVal ($name, 'substitute', ''); my $hmccu_substitute = AttrVal ($name, 'substitute', '');
my $hmccu_updreadings = AttrVal ($name, 'ccureadings', 1); 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'); my $updatemode = AttrVal ($name, 'updatemode', 'hmccu');
# Update mode can be: client, hmccu, both, rpcevent # Update mode can be: client, hmccu, both, rpcevent
@ -963,7 +1036,9 @@ sub HMCCU_UpdateClientReading ($@)
if ($hmccu_updreadings && $updatemode ne 'client') { if ($hmccu_updreadings && $updatemode ne 'client') {
$hmccu_value = HMCCU_Substitute ($value, $hmccu_substitute, 0, $reading); $hmccu_value = HMCCU_Substitute ($value, $hmccu_substitute, 0, $reading);
$hmccu_value = HMCCU_FormatReadingValue ($hash, $hmccu_value); $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); readingsSingleUpdate ($hash, $reading, $hmccu_value, 1);
} }
return $hmccu_value if ($updatemode eq 'hmccu'); 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 ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN');
next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr})); next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr}));
next if ($ch->{IODev} != $hash); 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 # Get attributes of client device
my $upd = AttrVal ($cn, 'ccureadings', 1); my $upd = AttrVal ($cn, 'ccureadings', 1);
my $crf = AttrVal ($cn, 'ccureadingformat', 'name'); 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 $substitute = AttrVal ($cn, 'substitute', '');
my ($sc, $st, $cc, $cd) = HMCCU_GetSpecialDatapoints ($ch, 'STATE', '', '', ''); my ($sc, $st, $cc, $cd) = HMCCU_GetSpecialDatapoints ($ch, 'STATE', '', '', '');
last if ($upd == 0); 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); my $clreading = HMCCU_GetReadingName ('', $devadd, $channel, $dpt, '', $crf);
next if ($clreading eq ''); next if ($clreading eq '');
@ -1002,6 +1085,7 @@ sub HMCCU_UpdateClientReading ($@)
} }
$cl_value = HMCCU_FormatReadingValue ($ch, $cl_value); $cl_value = HMCCU_FormatReadingValue ($ch, $cl_value);
# Update reading and control/state readings
readingsSingleUpdate ($ch, $clreading, $cl_value, 1); readingsSingleUpdate ($ch, $clreading, $cl_value, 1);
if ($cd ne '' && $dpt eq $cd && $channel eq $cc) { if ($cd ne '' && $dpt eq $cd && $channel eq $cc) {
readingsSingleUpdate ($ch, 'control', $cl_value, 1); readingsSingleUpdate ($ch, 'control', $cl_value, 1);
@ -1009,6 +1093,30 @@ sub HMCCU_UpdateClientReading ($@)
if ($clreading =~ /\.$st$/ && ($sc eq '' || $sc eq $channel)) { if ($clreading =~ /\.$st$/ && ($sc eq '' || $sc eq $channel)) {
HMCCU_SetState ($ch, $cl_value); 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; return $hmccu_value;
@ -1077,6 +1185,9 @@ sub HMCCU_StartRPCServer ($)
return 0; return 0;
} }
# Clear RPC queues
HMCCU_ResetRPCQueue ($hash);
# Fork child process(es) # Fork child process(es)
foreach my $port (split (',', $rpcport)) { foreach my $port (split (',', $rpcport)) {
my $rpcqueueport = $rpcqueue."_".$port; my $rpcqueueport = $rpcqueue."_".$port;
@ -1210,6 +1321,7 @@ sub HMCCU_IsRPCServerRunning ($$$)
sub HMCCU_CheckProcess ($$) sub HMCCU_CheckProcess ($$)
{ {
my ($hash, $port) = @_; my ($hash, $port) = @_;
my $name = $hash->{NAME};
my $modpath = AttrVal ('global', 'modpath', '/opt/fhem'); my $modpath = AttrVal ('global', 'modpath', '/opt/fhem');
my $rpcserver = $modpath."/FHEM/ccurpcd.pl"; my $rpcserver = $modpath."/FHEM/ccurpcd.pl";
@ -1345,7 +1457,10 @@ foreach(devid, root.Devices().EnumUsedIDs()) {
foreach my $d (keys %defs) { foreach my $d (keys %defs) {
# Get hash of client device # Get hash of client device
my $ch = $defs{$d}; my $ch = $defs{$d};
next if ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN');
next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr})); 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 $add = $ch->{ccuaddr};
my $dadd = $add; my $dadd = $add;
$dadd =~ s/:[0-9]+$//; $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. # Get name of a CCU device by address.
# Channel number will be removed if specified. # Channel number will be removed if specified.
@ -1519,6 +1656,59 @@ sub HMCCU_GetAddress ($$$)
return ($add, $chn); 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 ($$) sub HMCCU_GetCCUObjectAttribute ($$)
{ {
my ($object, $attr) = @_; my ($object, $attr) = @_;
@ -1605,6 +1795,26 @@ sub HMCCU_GetSpecialDatapoints ($$$$$)
return ($sc, $sd, $cc, $cd); 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 # Timer function for reading RPC queue
#################################################### ####################################################
@ -1618,6 +1828,7 @@ sub HMCCU_ReadRPCQueue ($)
my $f = 0; my $f = 0;
my @newdevices; my @newdevices;
my @deldevices; my @deldevices;
my @termpids;
my $newcount = 0; my $newcount = 0;
my $delcount = 0; my $delcount = 0;
@ -1625,6 +1836,7 @@ sub HMCCU_ReadRPCQueue ($)
my $ccureadingformat = AttrVal ($name, 'ccureadingformat', 'name'); my $ccureadingformat = AttrVal ($name, 'ccureadingformat', 'name');
my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue'); my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue');
my $rpcport = AttrVal ($name, 'rpcport', '2001'); my $rpcport = AttrVal ($name, 'rpcport', '2001');
my $rpctimeout = AttrVal ($name, 'rpctimeout', 300);
my @portlist = split (',', $rpcport); my @portlist = split (',', $rpcport);
foreach my $port (@portlist) { foreach my $port (@portlist) {
@ -1664,6 +1876,7 @@ sub HMCCU_ReadRPCQueue ($)
} }
elsif ($Tokens[0] eq 'EX') { elsif ($Tokens[0] eq 'EX') {
#### RPC Server shut down #### #### RPC Server shut down ####
push (@termpids, $Tokens[2]);
Log3 $name, 0, "HMCCU: Received EX event. RPC server terminated."; Log3 $name, 0, "HMCCU: Received EX event. RPC server terminated.";
if ($hash->{RPCState} ne "restarting") { if ($hash->{RPCState} ne "restarting") {
$hash->{RPCState} = "stopped"; $hash->{RPCState} = "stopped";
@ -1680,11 +1893,14 @@ sub HMCCU_ReadRPCQueue ($)
$element = $queue->deq(); $element = $queue->deq();
} }
$queue->close();
} }
if ($HMCCU_EventTime > 0 && time()-$HMCCU_EventTime > 300) { # Check if RPC server still running if events from CCU timed out
Log3 $name, 2, "HMCCU: Received no events from CCU since 300 seconds"; if ($HMCCU_EventTime > 0 && time()-$HMCCU_EventTime > $rpctimeout) {
DoTrigger ($name, "No events from CCU since 300 seconds"); Log3 $name, 2, "HMCCU: Received no events from CCU since $rpctimeout seconds";
DoTrigger ($name, "No events from CCU since $rpctimeout seconds");
} }
# CCU devices deleted # CCU devices deleted
@ -1704,39 +1920,42 @@ sub HMCCU_ReadRPCQueue ($)
my @hm_pids; my @hm_pids;
my @ex_pids; my @ex_pids;
HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids);
if ($f == 0 && @hm_pids > 0) { my $nhm_pids = scalar (@hm_pids);
if (scalar (@hm_pids) != scalar (@portlist)) { my $nex_pids = scalar (@ex_pids);
Log3 $name, 1, "HMCCU: Number of RPC server process differs from number of CCU destination ports";
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); InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0);
} }
else { else {
my $nex_pids = scalar @ex_pids; # No more RPC servers active
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";
}
}
$hash->{RPCPID} = '0'; $hash->{RPCPID} = '0';
$hash->{RPCPRC} = 'none'; $hash->{RPCPRC} = 'none';
$hash->{RPCState} = "stopped"; $hash->{RPCState} = "stopped";
DoTrigger ($name, "RPC server stopped"); Log3 $name, 0, "HMCCU: All RPC servers stopped";
# $attr{$name}{rpcserver} = "off"; DoTrigger ($name, "All RPC servers stopped");
} }
} }
@ -1964,18 +2183,10 @@ sub HMCCU_GetUpdate ($$$)
my $nam = ''; my $nam = '';
my $script; 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'); $ccuget = HMCCU_GetAttribute ($hmccu_hash, $cl_hash, 'ccuget', 'Value') if ($ccuget eq 'Attr');
my $ccutrace = AttrVal ($hmccu_hash->{NAME}, 'ccutrace', ''); 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, ''); $nam = HMCCU_GetChannelName ($addr, '');
return -1 if ($nam eq ''); 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, ''); $nam = HMCCU_GetDeviceName ($addr, '');
return -1 if ($nam eq ''); return -1 if ($nam eq '');
@ -2028,16 +2239,59 @@ if (odev) {
} }
return -2 if ($response eq ''); 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); readingsBeginUpdate ($cl_hash);
foreach my $dpdef (split /\n/, $response) { foreach my $dpdef (@$dp) {
my @dpdata = split /=/, $dpdef; my @dpdata = split /=/, $dpdef;
next if (@dpdata < 2); next if (@dpdata < 2);
my @adrtoks = split /\./, $dpdata[1]; my @adrtoks = split /\./, $dpdata[1];
next if (@adrtoks != 3); next if (@adrtoks != 3);
next if ($adrtoks[2] !~ /$ccureadingfilter/); # next if ($adrtoks[2] !~ /$ccureadingfilter/);
my ($add, $chn) = split /:/, $adrtoks[1]; 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], my $reading = HMCCU_GetReadingName ($adrtoks[0], $add, $chn, $adrtoks[2],
$dpdata[0], $readingformat); $dpdata[0], $readingformat);
next if ($reading eq ''); next if ($reading eq '');
@ -2052,11 +2306,12 @@ if (odev) {
if ($reading =~ /\.$statedpt$/ && ($statechn eq '' || $statechn eq $chn)) { if ($reading =~ /\.$statedpt$/ && ($statechn eq '' || $statechn eq $chn)) {
readingsBulkUpdate ($cl_hash, "state", $value); readingsBulkUpdate ($cl_hash, "state", $value);
} }
$uc++;
} }
readingsEndUpdate ($cl_hash, 1); readingsEndUpdate ($cl_hash, 1);
return 1; return $uc;
} }
#################################################### ####################################################
@ -2304,30 +2559,41 @@ sub HMCCU_RPCSetConfig ($$$)
} }
#################################################### ####################################################
# Return string for internal STATE. This function # Aggregate readings. Valid operations are 'and',
# can be used in attribute stateFormat. # '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}; return undef if (!exists ($defs{$name}));
my $sf = AttrVal ($name, 'ccustate', '');
return ReadingsVal ($name, 'state', '') if ($sf eq ''); my $mc = 0;
my $c = 0;
my $st = $sf; foreach my $r (keys %{$defs{$name}{READINGS}}) {
my $r = $hash->{READINGS}; next if ($r !~ /$readexp/);
$c++;
$mc++ if ($defs{$name}{READINGS}{$r}{VAL} eq $v1);
}
if ($r->{state}{VAL} ne "Error") { if ($oper eq 'and') {
$st =~ s/\b([A-Za-z\d_\.\:-]+)\b/($r->{$1} ? $r->{$1}{VAL} : $1)/ge; return ($mc < $c) ? $v2 : $v1;
}
elsif ($oper eq 'or') {
return ($mc > 0) ? $v1 : $v2;
} }
else { else {
$st = "Error"; return $mc;
} }
return $st;
} }
#################################################### ####################################################
@ -2485,7 +2751,10 @@ sub HMCCU_Dewpoint ($$$$)
Check if RPC server process is running. Check if RPC server process is running.
</li><br/> </li><br/>
<li>get &lt;name&gt; update [&lt;devexp&gt; [&lt;'State'|'Value'&gt;]]<br/> <li>get &lt;name&gt; update [&lt;devexp&gt; [&lt;'State'|'Value'&gt;]]<br/>
Update all datapoint / readings of client devices with device name matching &lt;devexp&gt; Update all datapoints / readings of client devices with FHEM device name matching &lt;devexp&gt;
</li><br/>
<li>get &lt;name&gt; updateccu [&lt;devexp&gt; [&lt;'State'|'Value'&gt;]]<br/>
Update all datapoints / readings of client devices with CCU device name matching &lt;devexp&gt;
</li> </li>
</ul> </ul>
<br/> <br/>