From 357a06934cf08f08202c91abfc3ca9b48fb5544a Mon Sep 17 00:00:00 2001 From: fhemzap Date: Sun, 22 May 2016 16:48:17 +0000 Subject: [PATCH] HMCCU: Several updates and fixes git-svn-id: https://svn.fhem.de/fhem/trunk@11496 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/HMCCU/FHEM/88_HMCCU.pm | 1576 ++++++++++++++++++------ fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm | 35 +- fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm | 69 +- fhem/contrib/HMCCU/FHEM/HMCCUConf.pm | 122 ++ fhem/contrib/HMCCU/FHEM/RPCQueue.pm | 1 - fhem/contrib/HMCCU/FHEM/ccurpcd.pl | 21 +- 6 files changed, 1365 insertions(+), 459 deletions(-) create mode 100644 fhem/contrib/HMCCU/FHEM/HMCCUConf.pm diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm index 1d6356658..cbc8f84c2 100644 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm @@ -4,7 +4,7 @@ # # $Id$ # -# Version 3.1b +# Version 3.2 # # Module for communication between FHEM and Homematic CCU2. # Supports BidCos-RF, BidCos-Wired, HmIP-RF. @@ -20,8 +20,7 @@ # set devstate [...] # set execute # set hmscript -# set restartrpc -# set rpcserver {on|off} +# set rpcserver {on|off|restart} # set var [...] # # get channel {|}[.][=] @@ -33,23 +32,26 @@ # get devstate [] # get dump {devtypes|datapoints} [] # get parfile [] +# get rpcevents # get rpcstate # get update [ [{ State | Value }]] # get updateccu [ [{ State | Value }]] # get vars # -# attr ccuflags { singlerpc,extrpc,dptnocheck } +# attr ccuflags { singlerpc,intrpc,dptnocheck } # attr ccuget { State | Value } # attr ccureadingfilter # attr ccureadingformat { name | address } # attr ccureadings { 0 | 1 } # attr ccutrace {|} # attr parfile +# attr rpcevtimeout # attr rpcinterval # attr rpcport # attr rpcqueue # attr rpcserver { on | off } -# attr statedatapoint +# attr rpctimeout [,] +# attr statedatapoint [.] # attr statevals :[,...] # attr stripchar # attr stripnumber { 0 | 1 | 2 } @@ -59,6 +61,13 @@ # filter_rule := [channel-regexp!]datapoint-regexp[,...] # subst_rule := [datapoint[,...]!]:[,...][;...] ######################################################################### +# Verbose levels: +# +# 0 = Log start/stop and initialization messages +# 1 = Log errors +# 2 = Log counters and warnings +# 3 = Log events and runtime information +######################################################################### package main; @@ -66,36 +75,53 @@ no if $] >= 5.017011, warnings => 'experimental::smartmatch'; use strict; use warnings; -use SetExtensions; +use Data::Dumper; use IO::File; use Fcntl 'SEEK_END', 'SEEK_SET', 'O_CREAT', 'O_RDWR'; use RPC::XML::Client; use RPC::XML::Server; -# use File::Queue; -# use Data::Dumper; -# use FindBin qw($Bin); -# use lib "$Bin"; -# use RPCQueue; +use SetExtensions; +use SubProcess; +use HMCCUConf; -# RPC Ports -my %HMCCU_RPC_PORT = ('BidCos-Wired', 2000, 'BidCos-RF', 2001, 'HmIP-RF', 2010); +# Import configuration data +my $HMCCU_DEV_DEFAULTS = \%HMCCUConf::HMCCU_DEV_DEFAULTS; -# Initial interval for reading RPC queue -my $HMCCU_INIT_INTERVAL = 10; +# RPC Ports and URL extensions +my %HMCCU_RPC_PORT = ( + 'BidCos-Wired', 2000, 'BidCos-RF', 2001, 'HmIP-RF', 2010, 'VirtualDevices', 9292 +); +my %HMCCU_RPC_URL = ( + 9292, 'groups' +); # Initial intervals for registration of RPC callbacks and reading RPC queue # -# Y = Start of FHEM finished -# Y+HMCCU_INIT_INTERVAL0 = Start RPC server -# # X = Start RPC server # X+HMCCU_INIT_INTERVAL1 = Register RPC callback # X+HMCCU_INIT_INTERVAL2 = Read RPC Queue # my $HMCCU_INIT_INTERVAL0 = 12; -my $HMCCU_INIT_INTERVAL1 = 5; +my $HMCCU_INIT_INTERVAL1 = 7; my $HMCCU_INIT_INTERVAL2 = 5; +# Number of arguments in RPC events +my %rpceventargs = ( + "EV", 3, + "ND", 2, + "DD", 1, + "RD", 2, + "RA", 1, + "UD", 2, + "IN", 3, + "EX", 3, + "SL", 2, + "ST", 10 +); + +# Event statistics snapshots, filled after ST event +my %rpcevent_snapshot; + # CCU Device names, key = CCU device address my %HMCCU_Devices; # CCU Device addresses, key = CCU device name @@ -137,13 +163,21 @@ my $HMCCU_FLAGS_NC = $HMCCU_FLAG_NAME | $HMCCU_FLAG_CHANNEL; my $HMCCU_FLAGS_NCD = $HMCCU_FLAG_NAME | $HMCCU_FLAG_CHANNEL | $HMCCU_FLAG_DATAPOINT; +# Global variables for subprocess +my $ccurpc_server; +my %ccurpc_hash = (); +my $phash = \%ccurpc_hash; + # Declare functions +sub HMCCU_Initialize ($); sub HMCCU_Define ($$); sub HMCCU_Undef ($$); sub HMCCU_Shutdown ($); sub HMCCU_Set ($@); sub HMCCU_Get ($@); sub HMCCU_Attr ($@); +sub HMCCU_SetDefaults ($); +sub HMCCU_GetDefaults ($); sub HMCCU_Notify ($$); sub HMCCU_ParseObject ($$); sub HMCCU_FilterReading ($$$); @@ -157,9 +191,11 @@ sub HMCCU_UpdateClients ($$$$); sub HMCCU_UpdateClientReading ($@); sub HMCCU_DeleteDevices ($); sub HMCCU_RPCRegisterCallback ($); -sub HMCCU_StartRPCServer ($); +sub HMCCU_RPCDeRegisterCallback ($); +sub HMCCU_ResetCounters ($); +sub HMCCU_StartExtRPCServer ($); +sub HMCCU_StartIntRPCServer ($); sub HMCCU_StopRPCServer ($); -sub HMCCU_ForceStopRPCServer ($); sub HMCCU_IsRPCStateBlocking ($); sub HMCCU_IsRPCServerRunning ($$$); sub HMCCU_CheckProcess ($$); @@ -184,9 +220,11 @@ sub HMCCU_GetChannelName ($$); sub HMCCU_GetDeviceType ($$); sub HMCCU_GetDeviceChannels ($); sub HMCCU_GetDeviceInterface ($$); -sub HMCCU_ResetRPCQueue ($); +sub HMCCU_ResetRPCQueue ($$); sub HMCCU_ReadRPCQueue ($); +sub HMCCU_ProcessEvent ($$); sub HMCCU_HMScript ($$); +sub HMCCU_UpdateSingleReading ($$$$$); sub HMCCU_GetDatapoint ($@); sub HMCCU_SetDatapoint ($$$); sub HMCCU_GetVariables ($$); @@ -208,6 +246,18 @@ sub HMCCU_QueueDeq ($); sub HMCCU_AggReadings ($$$$$); sub HMCCU_Dewpoint ($$$$); +# Subprocess functions +sub HMCCU_CCURPC_Write ($$); +sub HMCCU_CCURPC_OnRun ($); +sub HMCCU_CCURPC_OnExit (); +sub HMCCU_CCURPC_NewDevicesCB ($$$); +sub HMCCU_CCURPC_DeleteDevicesCB ($$$); +sub HMCCU_CCURPC_UpdateDeviceCB ($$$$); +sub HMCCU_CCURPC_ReplaceDeviceCB ($$$$); +sub HMCCU_CCURPC_ReaddDevicesCB ($$$); +sub HMCCU_CCURPC_EventCB ($$$$$); +sub HMCCU_CCURPC_ListDevicesCB ($$); + ##################################### # Initialize module @@ -221,11 +271,12 @@ sub HMCCU_Initialize ($) $hash->{UndefFn} = "HMCCU_Undef"; $hash->{SetFn} = "HMCCU_Set"; $hash->{GetFn} = "HMCCU_Get"; + $hash->{ReadFn} = "HMCCU_Read"; $hash->{AttrFn} = "HMCCU_Attr"; $hash->{NotifyFn} = "HMCCU_Notify"; $hash->{ShutdownFn} = "HMCCU_Shutdown"; - $hash->{AttrList} = "stripchar stripnumber:0,1,2 ccuflags:multiple-strict,singlerpc,extrpc,dptnocheck ccureadings:0,1 ccureadingfilter ccureadingformat:name,address rpcinterval:3,5,7,10 rpcqueue rpcport:multiple-strict,2000,2001,2010 rpcserver:on,off parfile statedatapoint statevals substitute updatemode:client,both,hmccu ccutrace ccuget:Value,State ". $readingFnAttributes; + $hash->{AttrList} = "stripchar stripnumber:0,1,2 ccuflags:multiple-strict,singlerpc,intrpc,dptnocheck ccureadings:0,1 ccureadingfilter ccureadingformat:name,address rpcinterval:3,5,7,10 rpcqueue rpcport:multiple-strict,2000,2001,2010,9292 rpcserver:on,off rpctimeout rpcevtimeout parfile statedatapoint statevals substitute updatemode:client,both,hmccu ccutrace ccuget:Value,State ". $readingFnAttributes; } ##################################### @@ -245,14 +296,16 @@ sub HMCCU_Define ($$) $hash->{DevCount} = HMCCU_GetDeviceList ($hash); $hash->{NewDevices} = 0; - $hash->{DelDevices} = 0; + $hash->{DelDevices} = 0; $hash->{RPCState} = "stopped"; - $hash->{hmccu}{eventtime} = 0; + $hash->{hmccu}{evtime} = 0; $hash->{hmccu}{evtimeout} = 0; $hash->{hmccu}{updatetime} = 0; $hash->{hmccu}{rpccount} = 0; + readingsSingleUpdate ($hash, "state", "Initialized", 1); + return undef; } @@ -268,6 +321,53 @@ sub HMCCU_Attr ($@) return undef; } +##################################### +# Set default attributes +##################################### + +sub HMCCU_SetDefaults ($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + my $ccutype = $hash->{ccutype}; + + if ($type eq 'HMCCUDEV') { + return 0 if (!exists ($HMCCU_DEV_DEFAULTS->{$ccutype})); + + foreach my $a (keys %{$HMCCU_DEV_DEFAULTS->{$ccutype}}) { + $attr{$name}{$a} = $HMCCU_DEV_DEFAULTS->{$ccutype}{$a}; + } + + return 1; + } + + return 0; +} + +##################################### +# List default attributes +##################################### + +sub HMCCU_GetDefaults ($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + my $ccutype = $hash->{ccutype}; + my $result = 'no default attributes'; + + if ($type eq 'HMCCUDEV') { + return $result if (!exists ($HMCCU_DEV_DEFAULTS->{$ccutype})); + $result = ''; + foreach my $a (keys %{$HMCCU_DEV_DEFAULTS->{$ccutype}}) { + $result .= $a." = ".$HMCCU_DEV_DEFAULTS->{$ccutype}{$a}."\n"; + } + } + + return $result; +} + ##################################### # Handle global FHEM events ##################################### @@ -288,7 +388,12 @@ sub HMCCU_Notify ($$) if ($rpcserver eq 'on') { my $delay = $HMCCU_INIT_INTERVAL0; Log3 $name, 0, "HMCCU: Autostart of RPC server after FHEM initialization in $delay seconds"; - InternalTimer (gettimeofday()+$delay, "HMCCU_StartRPCServer", $hash, 0) ; + if ($ccuflags =~ /intrpc/) { + InternalTimer (gettimeofday()+$delay, "HMCCU_StartIntRPCServer", $hash, 0); + } + else { + InternalTimer (gettimeofday()+$delay, "HMCCU_StartExtRPCServer", $hash, 0); + } } return undef; @@ -327,8 +432,6 @@ sub HMCCU_Shutdown ($) # Shutdown RPC server HMCCU_StopRPCServer ($hash); - sleep (3); - HMCCU_ForceStopRPCServer ($hash); RemoveInternalTimer ($hash); return undef; @@ -343,17 +446,18 @@ sub HMCCU_Set ($@) my ($hash, @a) = @_; my $name = shift @a; my $opt = shift @a; - my $options = "devstate datapoint var execute hmscript config rpcserver:on,off restartrpc:noArg"; + my $options = "devstate datapoint var execute hmscript config rpcserver:on,off,restart restart"; my $host = $hash->{host}; if ($opt ne 'rpcserver' && HMCCU_IsRPCStateBlocking ($hash)) { - return "HMCCU: CCU busy, choose one of rpcserver:off" if ($opt eq '?'); - return HMCCU_SetState ($hash, "busy"); + HMCCU_SetState ($hash, "busy"); + return "HMCCU: CCU busy, choose one of rpcserver:off"; } my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my $stripchar = AttrVal ($name, "stripchar", ''); - my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); +# my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); + my ($sc, $statedatapoint, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash, '', 'STATE', '', ''); my $statevals = AttrVal ($name, "statevals", ''); my $ccureadings = AttrVal ($name, "ccureadings", 'name'); my $readingformat = AttrVal ($name, "ccureadingformat", 'name'); @@ -455,36 +559,36 @@ sub HMCCU_Set ($@) my $action = shift @a; return HMCCU_SetError ($hash, "Usage: set $name rpcserver {on|off}") - if (!defined ($action) || $action !~ /^(on|off)$/); + if (!defined ($action) || $action !~ /^(on|off|restart)$/); if ($action eq 'on') { - return HMCCU_SetError ($hash, "Start of RPC server failed") - if (!HMCCU_StartRPCServer ($hash)); + if ($ccuflags =~ /intrpc/) { + return HMCCU_SetError ($hash, "Start of RPC server failed") + if (!HMCCU_StartIntRPCServer ($hash)); + } + else { + return HMCCU_SetError ($hash, "Start of RPC server failed") + if (!HMCCU_StartExtRPCServer ($hash)); + } } elsif ($action eq 'off') { - HMCCU_StopRPCServer ($hash); + return HMCCU_SetError ($hash, "Stop of RPC server failed") + if (!HMCCU_StopRPCServer ($hash)); + } + elsif ($action eq 'restart') { + my @hm_pids; + my @ex_pids; + return "HMCCU: RPC server not running" + if (!HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids)); + return "HMCCU: Can't stop RPC server" if (!HMCCU_StopRPCServer ($hash)); + + $hash->{RPCState} = "restarting"; + readingsSingleUpdate ($hash, "rpcstate", "restarting", 1); + DoTrigger ($name, "RPC server restarting"); } return HMCCU_SetState ($hash, "OK"); } - elsif ($opt eq 'restartrpc') { - my @hm_pids; - my @ex_pids; - if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids)) { - if (HMCCU_StopRPCServer ($hash)) { - $hash->{RPCState} = "restarting"; - readingsSingleUpdate ($hash, "rpcstate", "restarting", 1); - DoTrigger ($name, "RPC server restarting"); - return undef; - } - else { - return "HMCCU: Can't stop RPC server"; - } - } - else { - return "HMCCU: RPC server not running"; - } - } else { return "HMCCU: Unknown argument $opt, choose one of ".$options; } @@ -499,18 +603,19 @@ sub HMCCU_Get ($@) my ($hash, @a) = @_; my $name = shift @a; my $opt = shift @a; - my $options = "devicelist:noArg devstate datapoint dump vars channel update updateccu parfile config configdesc rpcstate:noArg deviceinfo"; + my $options = "devicelist:noArg devstate datapoint dump vars channel update updateccu parfile config configdesc rpcevents:noArg rpcstate:noArg deviceinfo"; my $host = $hash->{host}; if ($opt ne 'rpcstate' && HMCCU_IsRPCStateBlocking ($hash)) { - return "HMCCU: CCU busy, choose one of rpcstate:noArg" if ($opt eq '?'); - return HMCCU_SetState ($hash, "busy"); + HMCCU_SetState ($hash, "busy"); + return "HMCCU: CCU busy, choose one of rpcstate:noArg"; } my $ccureadingformat = AttrVal ($name, "ccureadingformat", 'name'); my $ccureadings = AttrVal ($name, "ccureadings", 1); my $parfile = AttrVal ($name, "parfile", ''); - my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); +# my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); + my ($sc, $statedatapoint, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash, '', 'STATE', '', ''); my $substitute = AttrVal ($name, 'substitute', ''); my $rpcport = AttrVal ($name, 'rpcport', 2001); @@ -674,31 +779,30 @@ sub HMCCU_Get ($@) return HMCCU_SetError ($hash, -2) if ($result eq ''); return HMCCU_FormatDeviceInfo ($result); } + elsif ($opt eq 'rpcevents') { + return HMCCU_SetError ($hash, "No event statistics available") + if (!exists ($hash->{hmccu}{evs}) || !exists ($hash->{hmccu}{evr})); + foreach my $stkey (sort keys %{$hash->{hmccu}{evr}}) { + $result .= "S: ".$stkey." = ".$hash->{hmccu}{evs}{$stkey}."\n"; + $result .= "R: ".$stkey." = ".$hash->{hmccu}{evr}{$stkey}."\n"; + } + return $result; + } elsif ($opt eq 'rpcstate') { my @pidlist; foreach my $port (split (',', $rpcport)) { my $pid = HMCCU_CheckProcess ($hash, $port); push (@pidlist, $pid) if ($pid > 0); } - if (@pidlist > 0) { - return "RPC process(es) running with pid(s) ".join (',', @pidlist);; - } - else { - return "RPC process not running"; - } + return "RPC process(es) running with pid(s) ".join (',', @pidlist) if (@pidlist > 0); + return "RPC process not running"; } elsif ($opt eq 'devicelist') { my $dumplist = shift @a; $hash->{DevCount} = HMCCU_GetDeviceList ($hash); - - if ($hash->{DevCount} < 0) { - return HMCCU_SetError ($hash, -2); - } - elsif ($hash->{DevCount} == 0) { - return HMCCU_SetError ($hash, "No devices received from CCU"); - } - + return HMCCU_SetError ($hash, -2) if ($hash->{DevCount} < 0); + return HMCCU_SetError ($hash, "No devices received from CCU") if ($hash->{DevCount} == 0); HMCCU_SetState ($hash, "OK"); if (defined ($dumplist) && $dumplist eq 'dump') { @@ -707,7 +811,6 @@ sub HMCCU_Get ($@) } return $result; } - return "Read ".$hash->{DevCount}." devices/channels from CCU"; } elsif ($opt eq 'config') { @@ -740,7 +843,9 @@ sub HMCCU_Get ($@) } ################################################################## -# Parse CCU object specification +# Parse CCU object specification. +# Supports classic Homematic and Homematic-IP addresses. +# Supports team addresses with leading * for BidCos-RF. # # Possible syntax for datapoints: # Interface.Address:Channel.Datapoint @@ -764,7 +869,7 @@ sub HMCCU_ParseObject ($$) my ($object, $flags) = @_; my ($i, $a, $c, $d, $n, $f) = ('', '', '', '', '', '', 0); - if ($object =~ /^(.+?)\.([A-Z]{3}[0-9]{7}):([0-9]{1,2})\.(.+)$/ || + if ($object =~ /^(.+?)\.([\*]*[A-Z]{3}[0-9]{7}):([0-9]{1,2})\.(.+)$/ || $object =~ /^(.+?)\.([0-9A-F]{14}):([0-9]{1,2})\.(.+)$/) { # # Interface.Address:Channel.Datapoint [30=11110] @@ -772,7 +877,7 @@ sub HMCCU_ParseObject ($$) $f = $HMCCU_FLAGS_IACD; ($i, $a, $c, $d) = ($1, $2, $3, $4); } - elsif ($object =~ /^(.+)\.([A-Z]{3}[0-9]{7}):([0-9]{1,2})$/ || + elsif ($object =~ /^(.+)\.([\*]*[A-Z]{3}[0-9]{7}):([0-9]{1,2})$/ || $object =~ /^(.+)\.([0-9A-F]{14}):([0-9]{1,2})$/) { # # Interface.Address:Channel [26=11010] @@ -780,7 +885,7 @@ sub HMCCU_ParseObject ($$) $f = $HMCCU_FLAGS_IAC | ($flags & $HMCCU_FLAG_DATAPOINT); ($i, $a, $c, $d) = ($1, $2, $3, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : ''); } - elsif ($object =~ /^([A-Z]{3}[0-9]{7}):([0-9]){1,2}\.(.+)$/ || + elsif ($object =~ /^([\*]*[A-Z]{3}[0-9]{7}):([0-9]){1,2}\.(.+)$/ || $object =~ /^([0-9A-F]{14}):([0-9]){1,2}\.(.+)$/) { # # Address:Channel.Datapoint [14=01110] @@ -788,7 +893,7 @@ sub HMCCU_ParseObject ($$) $f = $HMCCU_FLAGS_ACD; ($a, $c, $d) = ($1, $2, $3); } - elsif ($object =~ /^([A-Z]{3}[0-9]{7}):([0-9]){1,2}$/ || + elsif ($object =~ /^([\*]*[A-Z]{3}[0-9]{7}):([0-9]){1,2}$/ || $object =~ /^([0-9A-Z]{14}):([0-9]){1,2}$/) { # # Address:Channel [10=01010] @@ -796,7 +901,7 @@ sub HMCCU_ParseObject ($$) $f = $HMCCU_FLAGS_AC | ($flags & $HMCCU_FLAG_DATAPOINT); ($a, $c, $d) = ($1, $2, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : ''); } - elsif ($object =~ /^([A-Z]{3}[0-9]{7})$/ || + elsif ($object =~ /^([\*]*[A-Z]{3}[0-9]{7})$/ || $object =~ /^([0-9A-Z]{14})$/) { # # Address @@ -980,7 +1085,8 @@ sub HMCCU_SetError ($$) -6 => 'Update of readings disabled. Set attribute ccureadings first', -7 => 'Invalid channel number', -8 => 'Invalid datapoint', - -9 => 'Interface does not support RPC calls' + -9 => 'Interface does not support RPC calls', + -10 => 'No readable datapoints found' ); $msg = exists ($errlist{$text}) ? $errlist{$text} : $text; @@ -1008,8 +1114,8 @@ sub HMCCU_SetState ($$) ################################################################## # Substitute first occurrence of regular expressions or fixed -# string. Floating point values are ignored. Integer values are -# compared with complete value. +# string. Floating point values are ignored without datapoint +# specification. Integer values are compared with complete value. # mode: 0=Substitute regular expression, 1=Substitute text ################################################################## @@ -1020,7 +1126,7 @@ sub HMCCU_Substitute ($$$$) my $newvalue; return $value if (!defined ($substrule) || $substrule eq ''); - return $value if ($value !~ /^[+-]?\d+$/ && $value =~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/); +# return $value if ($value !~ /^[+-]?\d+$/ && $value =~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/); $reading =~ s/.+\.(.+)$/$1/; @@ -1037,6 +1143,7 @@ sub HMCCU_Substitute ($$$$) } } elsif (@ruletoks == 1) { + return $value if ($value !~ /^[+-]?\d+$/ && $value =~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/); ($rc, $newvalue) = HMCCU_SubstRule ($value, $ruletoks[0], $mode); return $newvalue if ($rc == 1); } @@ -1098,7 +1205,12 @@ sub HMCCU_UpdateClients ($$$$) my $rc = HMCCU_GetUpdate ($ch, $HMCCU_Addresses{$name}{address}, $ccuget); if ($rc <= 0) { - Log3 $fhname, 1, "HMCCU: Update of device $name failed"; + if ($rc == -10) { + Log3 $fhname, 1, "HMCCU: Device $name has no readable datapoints"; + } + else { + Log3 $fhname, 1, "HMCCU: Update of device $name failed" if ($rc != -10); + } $c_err++; } else { @@ -1118,6 +1230,12 @@ sub HMCCU_UpdateClients ($$$$) my $rc = HMCCU_GetUpdate ($ch, $ch->{ccuaddr}, $ccuget); if ($rc <= 0) { + if ($rc == -10) { + Log3 $fhname, 2, "HMCCU: Device ".$ch->{ccuaddr}." has no readable datapoints"; + } + else { + Log3 $fhname, 2, "HMCCU: Update of device ".$ch->{ccuaddr}." failed"; + } $c_err++; } else { @@ -1149,7 +1267,6 @@ sub HMCCU_UpdateClientReading ($@) my $hmccu_substitute = AttrVal ($name, 'substitute', ''); my $hmccu_updreadings = AttrVal ($name, 'ccureadings', 1); -# my $hmccu_flt = AttrVal ($name, 'ccureadingfilter', '.*'); my $updatemode = AttrVal ($name, 'updatemode', 'hmccu'); # Update mode can be: client, hmccu, both, rpcevent @@ -1169,7 +1286,6 @@ 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 '' || HMCCU_FilterReading ($hash, $chnadd, $dpt))) { readingsSingleUpdate ($hash, $reading, $hmccu_value, 1); @@ -1186,25 +1302,26 @@ 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); - if ($ch->{ccuif} eq "VirtualDevices" && exists ($ch->{ccugroup})) { - my @gdevs = split (",", $ch->{ccugroup}); - next if (!($devadd ~~ @gdevs) && !($chnadd ~~ @gdevs)); - } - else { + + if ($ch->{ccuif} eq "VirtualDevices" && exists ($ch->{ccugroup})) { + # Store values of group devices in group readings + my @gdevs = split (",", $ch->{ccugroup}); + next if (!(grep { $_ eq $devadd } @gdevs) && !(grep { $_ eq $chnadd } @gdevs) && + $ch->{ccuaddr} ne $devadd && $ch->{ccuaddr} ne $chnadd); + } + else { next if ($ch->{ccuaddr} ne $devadd && $ch->{ccuaddr} ne $chnadd); - } + } # Get attributes of client device my $dis = AttrVal ($cn, 'disable', 0); my $upd = AttrVal ($cn, 'ccureadings', 1); my $crf = AttrVal ($cn, 'ccureadingformat', 'name'); -# my $flt = AttrVal ($cn, 'ccureadingfilter', '.*'); my $mapdatapoints = AttrVal ($cn, 'mapdatapoints', ''); 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 || $dis == 1); 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 ''); @@ -1233,7 +1350,6 @@ sub HMCCU_UpdateClientReading ($@) foreach my $m (split (",", $mapdatapoints)) { my @mr = split ("=", $m); next if (@mr != 2); -# Log3 $name, 1, "HMCCU: Mapping dp ".$mr[0]." to ".$mr[1]; my ($i1, $a1, $c1, $d1, $n1, $f1) = HMCCU_ParseObject ($mr[0], $HMCCU_FLAG_FULLADDR); my ($i2, $a2, $c2, $d2, $n2, $f2) = @@ -1291,7 +1407,8 @@ sub HMCCU_DeleteDevices ($) } #################################################### -# Register RPC callbacks at CCU +# Register RPC callbacks at CCU if RPC-Server +# already in server loop #################################################### sub HMCCU_RPCRegisterCallback ($) @@ -1309,29 +1426,72 @@ sub HMCCU_RPCRegisterCallback ($) my $clkey = 'CB'.$port; my $cburl = "http://".$localaddr.":".$hash->{hmccu}{rpc}{$clkey}{cbport}."/fh".$port; my $url = "http://$serveraddr:$port/"; -# $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port})); - next if ($hash->{hmccu}{rpc}{$clkey}{loop} != 1); - - $hash->{hmccu}{rpc}{$clkey}{port} = $port; - $hash->{hmccu}{rpc}{$clkey}{clurl} = $url; - $hash->{hmccu}{rpc}{$clkey}{cburl} = $cburl; - $hash->{hmccu}{rpc}{$clkey}{loop} = 2; + $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port})); + if ($hash->{hmccu}{rpc}{$clkey}{loop} == 1 || $hash->{hmccu}{rpc}{$clkey}{state} eq "register") { + $hash->{hmccu}{rpc}{$clkey}{port} = $port; + $hash->{hmccu}{rpc}{$clkey}{clurl} = $url; + $hash->{hmccu}{rpc}{$clkey}{cburl} = $cburl; + $hash->{hmccu}{rpc}{$clkey}{loop} = 2; + $hash->{hmccu}{rpc}{$clkey}{state} = "registered"; - Log3 $name, 1, "HMCCU: Registering callback $cburl with ID $clkey at $url"; - my $rpcclient = RPC::XML::Client->new ($url); - $rpcclient->send_request ("init", $cburl, $clkey); - Log3 $name, 1, "HMCCU: RPC callback with URL $cburl initialized"; + Log3 $name, 1, "HMCCU: Registering callback $cburl with ID $clkey at $url"; + my $rpcclient = RPC::XML::Client->new ($url); + $rpcclient->send_request ("init", $cburl, $clkey); + Log3 $name, 1, "HMCCU: RPC callback with URL $cburl initialized"; + } } - # Schedule reading of RPC queue (for external process only) + # Schedule reading of RPC queue InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0); } #################################################### -# Start RPC server +# Deregister RPC callbacks at CCU #################################################### -sub HMCCU_StartRPCServer ($) +sub HMCCU_RPCDeRegisterCallback ($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) { + my $rpchash = \%{$hash->{hmccu}{rpc}{$clkey}}; + if (exists ($rpchash->{cburl}) && $rpchash->{cburl} ne '') { + my $port = $rpchash->{port}; + my $rpcclient = RPC::XML::Client->new ($rpchash->{clurl}); + Log3 $name, 1, "HMCCU: Deregistering RPC server ".$rpchash->{cburl}. + " at ".$rpchash->{clurl}; + $rpcclient->send_request("init", $rpchash->{cburl}); + $rpchash->{cburl} = ''; + $rpchash->{clurl} = ''; + $rpchash->{cbport} = 0; + } + } +} + +#################################################### +# Initialize statistic counters +#################################################### + +sub HMCCU_ResetCounters ($) +{ + my ($hash) = @_; + my @counters = ('total', 'EV', 'ND', 'IN', 'DD', 'RA', 'RD', 'UD', 'EX', 'SL', 'ST'); + + foreach my $cnt (@counters) { + $hash->{hmccu}{ev}{$cnt} = 0; + } + delete $hash->{hmccu}{evs}; + delete $hash->{hmccu}{evr}; + + $hash->{hmccu}{evtimeout} = 0; + $hash->{hmccu}{evtime} = 0; +} + +#################################################### +# Start external RPC server +#################################################### + +sub HMCCU_StartExtRPCServer ($) { my ($hash) = @_; my $name = $hash->{NAME}; @@ -1342,6 +1502,8 @@ sub HMCCU_StartRPCServer ($) my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue'); my $rpcport = AttrVal ($name, 'rpcport', 2001); my $rpcinterval = AttrVal ($name, 'rpcinterval', $HMCCU_INIT_INTERVAL1); + my $verbose = AttrVal ($name, 'verbose', -1); + $verbose = AttrVal ('global', 'verbose', 0) if ($verbose == -1); my $serveraddr = $hash->{host}; my $localaddr = ''; @@ -1350,29 +1512,30 @@ sub HMCCU_StartRPCServer ($) my @ex_pids; HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); if (@hm_pids > 0) { - Log3 $name, 1, "HMCCU: RPC server(s) already running with PIDs ".join (',', @hm_pids); + Log3 $name, 0, "HMCCU: RPC server(s) already running with PIDs ".join (',', @hm_pids); return scalar (@hm_pids); } - elsif (@ex_pids > 0 && $ccuflags !~ /extrpc/) { + elsif (@ex_pids > 0) { Log3 $name, 1, "HMCCU: Externally launched RPC server(s) detected. Kill process(es) manually with command kill -SIGINT pid for pid=".join (',', @ex_pids); return 0; } + my $rpcserver = $modpath."/FHEM/ccurpcd.pl"; # Check if RPC server exists - my $rpcserver = $modpath."/FHEM/ccurpcd.pl"; - if (! -e $rpcserver) { - Log3 $name, 1, "HMCCU: RPC server file ccurpcd.pl not found in ".$modpath."/FHEM"; - return 0; - } - - # Clear RPC queues - HMCCU_ResetRPCQueue ($hash); + if (! -e $rpcserver) { + Log3 $name, 1, "HMCCU: RPC server file ccurpcd.pl not found in ".$modpath."/FHEM"; + return 0; + } my $fork_cnt = 0; my $callbackport = 0; # Fork child process(es) foreach my $port (split (',', $rpcport)) { + my $clkey = 'CB'.$port; + my $rpcqueueport = $rpcqueue."_".$port; + my $logfileport = $logfile."_".$port.".log"; + $callbackport = 5400+$port if ($callbackport == 0 || $ccuflags !~ /singlerpc/); # Detect local IP @@ -1386,75 +1549,58 @@ sub HMCCU_StartRPCServer ($) close ($socket); } - # Create RPC client - my $clkey = 'CB'.$port; - my $rpcclient = RPC::XML::Client->new ("http://$serveraddr:$port/"); - $hash->{hmccu}{rpc}{$clkey}{port} = $port; - - # Check if RPC daemon on CCU is listening on port - my $resp = $rpcclient->send_request ('system.listMethods'); - if (!ref($resp)) { - Log3 $name, 1, "No response from CCU on port $port. $resp"; - next; - } - - my $rpcqueueport = $rpcqueue."_".$port; - my $logfileport = $logfile."_".$port.".log"; - if ($fork_cnt == 0 || $ccuflags !~ /singlerpc/) { + # Cleat event queue + HMCCU_ResetRPCQueue ($hash, $port); + my $pid = fork (); if (!defined ($pid)) { Log3 $name, 1, "HMCCU: Can't fork child process for CCU port $port"; next; } + if (!$pid) { # Child process. Replaced by RPC server - exec ($rpcserver." ".$serveraddr." ".$port." ".$rpcqueueport." ".$logfileport); + exec ($rpcserver." ".$serveraddr." ".$port." ".$rpcqueueport." ".$logfileport." ".$verbose); # When we reach this line start of RPC server failed and child process can exit die; } # Parent process - # Wait 1 second to ensure that RPC server is listening - # sleep (1); # Store PID push (@hm_pids, $pid); $hash->{hmccu}{rpc}{$clkey}{pid} = $pid; $hash->{hmccu}{rpc}{$clkey}{queue} = $rpcqueueport; $hash->{hmccu}{rpc}{$clkey}{state} = "starting"; - Log3 $name, 0, "HMCCU: RPC server started with pid ".$pid; + Log3 $name, 0, "HMCCU: RPC server $clkey started with pid ".$pid; $fork_cnt++; } else { $hash->{hmccu}{rpc}{$clkey}{pid} = 0; - $hash->{hmccu}{rpc}{$clkey}{state} = "stopped"; + $hash->{hmccu}{rpc}{$clkey}{state} = "register"; $hash->{hmccu}{rpc}{$clkey}{queue} = ''; } $hash->{hmccu}{rpc}{$clkey}{cbport} = $callbackport; $hash->{hmccu}{rpc}{$clkey}{loop} = 0; - - # Register callback in CCU -# my $callbackurl = "http://".$localaddr.":".$callbackport."/fh".$port; -# $hash->{hmccu}{rpc}{$clkey}{cburl} = $callbackurl; -# $hash->{hmccu}{rpccount} = $fork_cnt; -# Log3 $name, 1, "HMCCU: Registering callback $callbackurl with ID CB".$port; -# $rpcclient->send_request ("init", $callbackurl, "CB".$port); -# Log3 $name, 1, "HMCCU: RPC callback with URL ".$callbackurl." initialized"; } $hash->{hmccu}{rpccount} = $fork_cnt; $hash->{hmccu}{localaddr} = $localaddr; - + if ($fork_cnt > 0) { $hash->{hmccu}{evtimeout} = 0; $hash->{hmccu}{eventtime} = 0; $hash->{RPCPID} = join (',', @hm_pids); $hash->{RPCPRC} = $rpcserver; $hash->{RPCState} = "starting"; + + # Initialize statistic counters + HMCCU_ResetCounters ($hash); + readingsSingleUpdate ($hash, "rpcstate", "starting", 1); DoTrigger ($name, "RPC server starting"); @@ -1468,45 +1614,151 @@ sub HMCCU_StartRPCServer ($) } #################################################### -# Stop RPC server(s) / send SIGINT to process(es) +# Start internal RPC server +#################################################### + +sub HMCCU_StartIntRPCServer ($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + # Timeouts + my $timeout = AttrVal ($name, 'rpctimeout', '0.01,0.25'); + my ($to_read, $to_write) = split (",", $timeout); + $to_write = $to_read if (!defined ($to_write)); + + # Address and ports + my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue'); + my $rpcport = AttrVal ($name, 'rpcport', 2001); + my $rpcinterval = AttrVal ($name, 'rpcinterval', $HMCCU_INIT_INTERVAL1); + my @rpcportlist = split (",", $rpcport); + my $serveraddr = $hash->{host}; + my $fork_cnt = 0; + + # Check for running RPC server processes + my @hm_pids; + my @ex_pids; + HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); + if (@hm_pids > 0) { + Log3 $name, 0, "HMCCU: RPC server(s) already running with PIDs ".join (',', @hm_pids); + return scalar (@hm_pids); + } + elsif (@ex_pids > 0) { + Log3 $name, 1, "HMCCU: Externally launched RPC server(s) detected. Kill process(es) manually with command kill -SIGINT pid for pid=".join (',', @ex_pids); + return 0; + } + + # Detect local IP address + my $socket = IO::Socket::INET->new (PeerAddr => $serveraddr, PeerPort => $rpcportlist[0]); + if (!$socket) { + Log3 $name, 1, "HMCCU: Can't connect to CCU port".$rpcportlist[0]; + return 0; + } + my $localaddr = $socket->sockhost (); + close ($socket); + + # Fork child processes + foreach my $port (@rpcportlist) { + my $clkey = 'CB'.$port; + my $rpcqueueport = $rpcqueue."_".$port; + my $callbackport = 5400+$port; + + # Clear event queue + HMCCU_ResetRPCQueue ($hash, $port); + + # Create child process + Log3 $name, 2, "HMCCU: Create child process with timeouts $to_read and $to_write"; + my $child = SubProcess->new ({ onRun => \&HMCCU_CCURPC_OnRun, + onExit => \&HMCCU_CCURPC_OnExit, timeoutread => $to_read, timeoutwrite => $to_write }); + $child->{serveraddr} = $serveraddr; + $child->{serverport} = $port; + $child->{callbackport} = $callbackport; + $child->{devname} = $name; + $child->{queue} = $rpcqueueport; + + # Start child process + my $pid = $child->run (); + if (!defined ($pid)) { + Log3 $name, 1, "HMCCU: No RPC process for server $clkey started"; + next; + } + + Log3 $name, 0, "HMCCU: Child process for server $clkey started with PID $pid"; + $fork_cnt++; + + # Store child process parameters + $hash->{hmccu}{rpc}{$clkey}{child} = $child; + $hash->{hmccu}{rpc}{$clkey}{cbport} = $callbackport; + $hash->{hmccu}{rpc}{$clkey}{loop} = 0; + $hash->{hmccu}{rpc}{$clkey}{pid} = $pid; + $hash->{hmccu}{rpc}{$clkey}{queue} = $rpcqueueport; + $hash->{hmccu}{rpc}{$clkey}{state} = "starting"; + push (@hm_pids, $pid); + } + + $hash->{hmccu}{rpccount} = $fork_cnt; + $hash->{hmccu}{localaddr} = $localaddr; + + if ($fork_cnt > 0) { + # Set internals + $hash->{RPCPID} = join (',', @hm_pids); + $hash->{RPCPRC} = "internal"; + $hash->{RPCState} = "starting"; + + # Initialize statistic counters + HMCCU_ResetCounters ($hash); + + readingsSingleUpdate ($hash, "rpcstate", "starting", 1); + Log3 $name, 0, "RPC server(s) starting"; + DoTrigger ($name, "RPC server starting"); + + InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0); + } + + return $fork_cnt; +} + +#################################################### +# Stop RPC server(s) +# Send SIGINT to process(es) #################################################### sub HMCCU_StopRPCServer ($) { my ($hash) = @_; my $name = $hash->{NAME}; + my $pid = 0; my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my $rpcport = AttrVal ($name, 'rpcport', 2001); my $serveraddr = $hash->{host}; # Deregister callback URLs in CCU - foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) { - if (exists ($hash->{hmccu}{rpc}{$clkey}{cburl}) && $hash->{hmccu}{rpc}{$clkey}{cburl} ne '') { - my $port = $hash->{hmccu}{rpc}{$clkey}{port}; - my $rpcclient = RPC::XML::Client->new ("http://$serveraddr:$port/"); - Log3 $name, 1, "HMCCU: Deregistering RPC server ".$hash->{hmccu}{rpc}{$clkey}{cburl}; - $rpcclient->send_request("init", $hash->{hmccu}{rpc}{$clkey}{cburl}); - $hash->{hmccu}{rpc}{$clkey}{cburl} = ''; - } - } - + HMCCU_RPCDeRegisterCallback ($hash); + # Send signal SIGINT to RPC server processes foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) { - my $pid = $hash->{hmccu}{rpc}{$clkey}{pid}; - if ($pid > 0) { - Log3 $name, 0, "HMCCU: Stopping RPC server $clkey with PID $pid"; - kill ('INT', $pid); - $hash->{hmccu}{rpc}{$clkey}{state} = "stopping"; + my $rpchash = \%{$hash->{hmccu}{rpc}{$clkey}}; + if (exists ($rpchash->{pid}) && $rpchash->{pid} > 0) { + Log3 $name, 0, "HMCCU: Stopping RPC server $clkey with PID ".$rpchash->{pid}; + kill ('INT', $rpchash->{pid}); + $rpchash->{state} = "stopping"; + } + else { + $rpchash->{state} = "stopped"; } } + # Update status if ($hash->{hmccu}{rpccount} > 0) { readingsSingleUpdate ($hash, "rpcstate", "stopping", 1); $hash->{RPCState} = "stopping"; } - # Kill the rest + # Wait + sleep (1); + + # Check if processes were terminated my @hm_pids; my @ex_pids; HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); @@ -1514,39 +1766,35 @@ sub HMCCU_StopRPCServer ($) foreach my $pid (@hm_pids) { Log3 $name, 0, "HMCCU: Stopping RPC server with PID $pid"; kill ('INT', $pid); - } + } } - else { - Log3 $name, 0, "HMCCU: RPC server not running"; - return 0; - } - - if (@ex_pids > 0 && $ccuflags !~ /extrpc/) { + if (@ex_pids > 0) { Log3 $name, 0, "HMCCU: Externally launched RPC server detected."; foreach my $pid (@ex_pids) { kill ('INT', $pid); } - return 0; } - - return 1; -} - -sub HMCCU_ForceStopRPCServer ($) -{ - my ($hash) = @_; - my $name = $hash->{NAME}; - # Send signal SIGINT to RPC server processes - my @hm_pids; - my @ex_pids; - HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); - if (@hm_pids > 0) { + # Wait + sleep (1); + + # Kill the rest + @hm_pids = (); + @ex_pids = (); + if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids)) { + push (@hm_pids, @ex_pids); foreach my $pid (@hm_pids) { - Log3 $name, 0, "HMCCU: RPC Server with PID $pid still running. Sending SIGINT again"; - kill ('INT', $pid); + kill ('KILL', $pid); } } + + @hm_pids = (); + @ex_pids = (); + HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); + push (@hm_pids, @ex_pids); + $hash->{hmccu}{rpccount} = scalar(@hm_pids); + + return $hash->{hmccu}{rpccount} > 0 ? 0 : 1; } #################################################### @@ -1581,29 +1829,44 @@ sub HMCCU_IsRPCStateBlocking ($) sub HMCCU_IsRPCServerRunning ($$$) { my ($hash, $hm_pids, $ex_pids) = @_; + my $name = $hash->{NAME}; + + my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my @rpcpids; if (defined ($hash->{RPCPID}) && $hash->{RPCPID} ne '0') { @rpcpids = split (',', $hash->{RPCPID}); } - my $rpcport = AttrVal ($hash->{NAME}, 'rpcport', 2001); - foreach my $port (split (',', $rpcport)) { - my $pid = HMCCU_CheckProcess ($hash, $port); - next if ($pid == 0); - if (grep { $_ eq $pid } @rpcpids) { - if (kill (0, $pid)) { - push (@$hm_pids, $pid); + if ($ccuflags =~ /intrpc/) { + foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) { + if (exists ($hash->{hmccu}{rpc}{$clkey}{pid}) && + defined ($hash->{hmccu}{rpc}{$clkey}{pid}) && + $hash->{hmccu}{rpc}{$clkey}{pid} > 0) { + my $pid = $hash->{hmccu}{rpc}{$clkey}{pid}; + push (@$hm_pids, $pid) if (kill (0, $pid)); + } + } + } + else { + my $rpcport = AttrVal ($hash->{NAME}, 'rpcport', 2001); + foreach my $port (split (',', $rpcport)) { + my $pid = HMCCU_CheckProcess ($hash, $port); + next if ($pid == 0); + if (grep { $_ eq $pid } @rpcpids) { + if (kill (0, $pid)) { + push (@$hm_pids, $pid); + } + else { + push (@$ex_pids, $pid); + } } else { push (@$ex_pids, $pid); } } - else { - push (@$ex_pids, $pid); - } } - + return (@$hm_pids > 0 || @$ex_pids > 0) ? 1 : 0; } @@ -1627,8 +1890,6 @@ sub HMCCU_CheckProcess ($$) # Remove leading blanks, fix for MacOS. Thanks to mcdeck $proc =~ s/^\s+//; my @procattr = split /\s+/, $proc; -# return $procattr[1] if ($procattr[1] != $$ && $procattr[7] =~ /perl$/ && -# $procattr[8] eq $rpcserver && $procattr[10] eq "$port"); return $procattr[0] if ($procattr[0] != $$ && $procattr[4] =~ /perl$/ && $procattr[5] eq $rpcserver && $procattr[7] eq "$port"); } @@ -1870,7 +2131,6 @@ foreach (sDevice, sDevList.Split(",")) { my $response = HMCCU_HMScript ($hash, $script); return 0 if ($response eq ''); -# Log3 $name, 1, $response; my $c = 0; foreach my $dpspec (split /\n/,$response) { @@ -1881,8 +2141,6 @@ foreach (sDevice, sDevList.Split(",")) { $c++; } -# Log3 $name, 1, Dumper($hash->{hmccu}{dp}); - return $c; } @@ -1953,7 +2211,7 @@ sub HMCCU_GetValidDatapoints ($$$$$) #################################################### # Check if datapoint is valid. # Parameter chn can be a channel address or a channel -# number. +# number. Parameter dpt can contain a channel number. # Return 1 if datapoint information is not available # in IO device. #################################################### @@ -1961,8 +2219,11 @@ sub HMCCU_GetValidDatapoints ($$$$$) sub HMCCU_IsValidDatapoint ($$$$$) { my ($hash, $devtype, $chn, $dpt, $oper) = @_; - + my $hmccu_hash = HMCCU_GetHash ($hash); + if ($hash->{TYPE} eq 'HMCCU' && !defined ($devtype)) { + $devtype = HMCCU_GetDeviceType ($chn, 'null'); + } my $ccuflags = AttrVal ($hmccu_hash->{NAME}, 'ccuflags', 'null'); return 1 if ($ccuflags =~ /dptnocheck/); @@ -2152,10 +2413,12 @@ sub HMCCU_IsChnAddr ($$) my ($id, $f) = @_; if ($f) { - return ($id =~ /^.+\.[A-Z]{3}[0-9]{7}:[0-9]{1,2}$/ || $id =~ /^.+\.[0-9A-F]{14}:[0-9]{1,2}$/) ? 1 : 0; + return ($id =~ /^.+\.[\*]*[A-Z]{3}[0-9]{7}:[0-9]{1,2}$/ || + $id =~ /^.+\.[0-9A-F]{14}:[0-9]{1,2}$/) ? 1 : 0; } else { - return ($id =~ /^[A-Z]{3}[0-9]{7}:[0-9]{1,2}$/ || $id =~ /^[0-9A-F]{14}:[0-9]{1,2}$/) ? 1 : 0; + return ($id =~ /^[\*]*[A-Z]{3}[0-9]{7}:[0-9]{1,2}$/ || + $id =~ /^[0-9A-F]{14}:[0-9]{1,2}$/) ? 1 : 0; } } @@ -2169,10 +2432,10 @@ sub HMCCU_IsDevAddr ($$) my ($id, $f) = @_; if ($f) { - return ($id =~ /^.+\.[A-Z]{3}[0-9]{7}$/ || $id =~ /^.+\.[0-9A-F]{14}$/) ? 1 : 0; + return ($id =~ /^.+\.[\*]*[A-Z]{3}[0-9]{7}$/ || $id =~ /^.+\.[0-9A-F]{14}$/) ? 1 : 0; } else { - return ($id =~ /^[A-Z]{3}[0-9]{7}$/ || $id =~ /^[0-9A-F]{14}$/) ? 1 : 0; + return ($id =~ /^[\*]*[A-Z]{3}[0-9]{7}$/ || $id =~ /^[0-9A-F]{14}$/) ? 1 : 0; } } @@ -2264,56 +2527,261 @@ sub HMCCU_GetAttribute ($$$$) # statechannel, statedatapoint and controldatapoint. # Return attribute values. Attribute controldatapoint # is splittet into controlchannel and datapoint name. +# If attribute statedatapoint contains channel number +# it is splitted into statechannel and datapoint +# name. #################################################### sub HMCCU_GetSpecialDatapoints ($$$$$) { - my ($hash, $defsd, $defsc, $defcd, $defcc) = @_; +# my ($hash, $defsc, $defsd, $defcc, $defcd) = @_; + my ($hash, $sc, $sd, $cc, $cd) = @_; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - my $sd = AttrVal ($name, 'statedatapoint', $defsd); - my $sc = AttrVal ($name, 'statechannel', $defsc); - my $ccd = AttrVal ($name, 'controldatapoint', ''); - if ($type eq 'HMCCUCHN') { - $ccd = $hash->{ccuaddr}.$ccd; - $ccd =~ s/^[A-Z]{3,3}[0-9]{7,7}://; + my $statedatapoint = AttrVal ($name, 'statedatapoint', ''); + my $statechannel = AttrVal ($name, 'statechannel', ''); + my $controldatapoint = AttrVal ($name, 'controldatapoint', ''); + + if ($statedatapoint ne '') { + if ($statedatapoint =~ /^([0-9]+)\.(.+)$/) { + ($sc, $sd) = ($1, $2); + } + else { + $sd = $statedatapoint; + } } - my $cd = $defcd; - my $cc = $defcc; + $sc = $statechannel if ($statechannel ne ''); - if ($ccd =~ /^([0-9]+)\.(.+)$/) { - ($cc, $cd) = ($1, $2); + if ($controldatapoint ne '') { + if ($controldatapoint =~ /^([0-9]+)\.(.+)$/) { + ($cc, $cd) = ($1, $2); + } + else { + $cd = $controldatapoint; + } } + + # For devices of type HMCCUCHN extract channel numbers from CCU device address + if ($type eq 'HMCCUCHN') { + $sc = $hash->{ccuaddr}; + $sc =~ s/^[\*]*[0-9A-Z]+://; + $cc = $sc; + } + +# my $sd = AttrVal ($name, 'statedatapoint', $defsd); +# my $sc = AttrVal ($name, 'statechannel', $defsc); +# my $ccd = AttrVal ($name, 'controldatapoint', ''); +# if ($type eq 'HMCCUCHN') { +# $ccd = $hash->{ccuaddr}.$ccd; +# $ccd =~ s/^[A-Z]{3,3}[0-9]{7,7}://; +# } +# my $cd = $defcd; +# my $cc = $defcc; +# +# if ($ccd =~ /^([0-9]+)\.(.+)$/) { +# ($cc, $cd) = ($1, $2); +# } return ($sc, $sd, $cc, $cd); } #################################################### -# Clear RPC queue(s) +# Clear RPC queue #################################################### -sub HMCCU_ResetRPCQueue ($) +sub HMCCU_ResetRPCQueue ($$) { - my ($hash) = @_; + my ($hash, $port) = @_; my $name = $hash->{NAME}; my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue'); - my $rpcport = AttrVal ($name, 'rpcport', 2001); + my $clkey = 'CB'.$port; - my @portlist = split (',', $rpcport); - foreach my $port (@portlist) { - my $clkey = 'CB'.$port; - if (HMCCU_QueueOpen ($hash, $rpcqueue."_".$port)) { - HMCCU_QueueReset ($hash); - HMCCU_QueueClose ($hash); - } - delete $hash->{hmccu}{rpc}{$clkey}{queue}; - -# my $queue = new RPCQueue (File => $rpcqueue."_".$port, Mode => 0666); -# $queue->reset(); -# $queue->close(); + if (HMCCU_QueueOpen ($hash, $rpcqueue."_".$port)) { + HMCCU_QueueReset ($hash); + while (HMCCU_QueueDeq ($hash)) { } + HMCCU_QueueClose ($hash); } + $hash->{hmccu}{rpc}{$clkey}{queue} = '' if (exists ($hash->{hmccu}{rpc}{$clkey}{queue})); +} + +#################################################### +# Process RPC server event +#################################################### + +sub HMCCU_ProcessEvent ($$) +{ + my ($hash, $event) = @_; + my $name = $hash->{NAME}; + my $rh = \%{$hash->{hmccu}{rpc}}; + + return undef if (!defined ($event) || $event eq ''); + + my $rf = AttrVal ($name, 'ccureadingformat', 'name'); + + my @t = split (/\|/, $event); + my $tc = scalar (@t); + + # Update statistic counters + if (exists ($hash->{hmccu}{ev}{$t[0]})) { + $hash->{hmccu}{evtime} = time (); + $hash->{hmccu}{ev}{total}++; + $hash->{hmccu}{ev}{$t[0]}++; + $hash->{hmccu}{evtimeout} = 0 if ($hash->{hmccu}{evtimeout} == 1); + } + else { + my $errtok = $t[0]; + $errtok =~ s/([\x00-\xFF])/sprintf("0x%X ",ord($1))/eg; + Log3 $name, 2, "HMCCU: Received unknown event from CCU: ".$errtok; + return undef; + } + + # Check event syntax + if (exists ($rpceventargs{$t[0]}) && ($tc-1) != $rpceventargs{$t[0]}) { + Log3 $name, 2, "HMCCU: Wrong number of parameters in event $event"; + return undef; + } + + if ($t[0] eq 'EV') { + # + # Update of datapoint + # Input: EV|Adress|Datapoint|Value + # Output: EV, DevAdd, ChnNo, Reading, Value + # + return undef if ($tc != 4 || !HMCCU_IsChnAddr ($t[1], 0)); + my ($add, $chn) = split (/:/, $t[1]); + my $reading = HMCCU_GetReadingName ('', $add, $chn, $t[2], '', $rf); + HMCCU_UpdateClientReading ($hash, $add, $chn, $reading, $t[3], 'rpcevent'); + return ($t[0], $add, $chn, $reading, $t[3]); + } + elsif ($t[0] eq 'SL') { + # + # RPC server enters server loop + # Input: SL|Pid|Servername + # Output: SL, Servername, Pid + # + my $clkey = $t[2]; + if (!exists ($rh->{$clkey})) { + Log3 $name, 0, "HMCCU: Received SL event for unknown RPC server $clkey"; + return undef; + } + Log3 $name, 0, "HMCCU: Received SL event. RPC server $clkey enters server loop"; + $rh->{$clkey}{loop} = 1 if ($rh->{$clkey}{pid} == $t[1]); + return ($t[0], $clkey, $t[1]); + } + elsif ($t[0] eq 'IN') { + # + # RPC server initialized + # Input: IN|INIT|State|Servername + # Output: IN, Servername, Running, NotRunning, ClientsUpdated, UpdateErrors + # + my $clkey = $t[3]; + my $norun = 0; + my $run = 0; + my $c_ok = 0; + my $c_err = 0; + if (!exists ($rh->{$clkey})) { + Log3 $name, 0, "HMCCU: Received IN event for unknown RPC server $clkey"; + return undef; + } + Log3 $name, 0, "HMCCU: Received IN event. RPC server $clkey initialized."; + $rh->{$clkey}{state} = $rh->{$clkey}{pid} > 0 ? "running" : "initialized"; + + # Check if all RPC servers were initialized. Set overall status + foreach my $ser (keys %{$rh}) { + $norun++ if ($rh->{$ser}{state} ne "running" && $rh->{$ser}{pid} > 0); + $norun++ if ($rh->{$ser}{state} ne "initialized" && $rh->{$ser}{pid} == 0); + $run++ if ($rh->{$ser}{state} eq "running"); + } + if ($norun == 0) { + $hash->{RPCState} = "running"; + readingsSingleUpdate ($hash, "rpcstate", "running", 1); + HMCCU_SetState ($hash, "OK"); + ($c_ok, $c_err) = HMCCU_UpdateClients ($hash, '.*', 'Attr', 0); + Log3 $name, 2, "HMCCU: Updated devices. Success=$c_ok Failed=$c_err"; + Log3 $name, 1, "HMCCU: All RPC servers running"; + DoTrigger ($name, "RPC server running"); + } + $hash->{hmccu}{rpcinit} = $run; + return ($t[0], $clkey, $run, $norun, $c_ok, $c_err); + } + elsif ($t[0] eq 'EX') { + # + # RPC server shutdown + # Input: EX|SHUTDOWN|Pid|Servername + # Output: EX, Servername, Pid, Flag, Run + # + my $clkey = $t[3]; + my $run = 0; + if (!exists ($rh->{$clkey})) { + Log3 $name, 0, "HMCCU: Received EX event for unknown RPC server $clkey"; + return undef; + } + + Log3 $name, 0, "HMCCU: Received EX event. RPC server $clkey terminated."; + my $f = $hash->{RPCState} eq "restarting" ? 2 : 1; + delete $rh->{$clkey}; + + # Check if all RPC servers were terminated. Set overall status + foreach my $ser (keys %{$rh}) { + $run++ if ($rh->{$ser}{state} ne "stopped"); + } + if ($run == 0) { + if ($f == 1) { + $hash->{RPCState} = "stopped"; + readingsSingleUpdate ($hash, "rpcstate", "stopped", 1); + } + $hash->{RPCPID} = '0'; + } + $hash->{hmccu}{rpccount} = $run; + $hash->{hmccu}{rpcinit} = $run; + return ($t[0], $clkey, $t[2], $f, $run); + } + elsif ($t[0] eq 'ND' || $t[0] eq 'DD' || $t[0] eq 'RA') { + # + # CCU device added, deleted or readded + # Input: {ND,DD,RA}|Address + # Output: {ND,DD,RA}, DevAdd + # + return ($t[0], $t[1]); + } + elsif ($t[0] eq 'UD') { + # + # CCU device updated + # Input: UD|Address|Hint + # Output: UD, DevAdd, Hint + # + return ($t[0], $t[1], $t[2]); + } + elsif ($t[0] eq 'RD') { + # + # CCU device replaced + # Input: RD|Address1|Address2 + # Output: RD, Address1, Address2 + # + return ($t[0], $t[1], $t[2]); + } + elsif ($t[0] eq 'ST') { + # + # Statistic data. Store snapshots of sent and received events. + # Input: ST|nTotal|nEV|nND|nDD|nRD|nRA|nUD|nIN|nSL|nEX + # Output: ST, ... + # + my @stkeys = ('total', 'EV', 'ND', 'DD', 'RD', 'RA', 'UD', 'IN', 'SL', 'EX'); + for (my $i=0; $i<10; $i++) { + $hash->{hmccu}{evs}{$stkeys[$i]} = $t[$i+1]; + $hash->{hmccu}{evr}{$stkeys[$i]} = $hash->{hmccu}{ev}{$stkeys[$i]}; + } + return @t; + } + else { + my $errtok = $t[0]; + $errtok =~ s/([\x00-\xFF])/sprintf("0x%X ",ord($1))/eg; + Log3 $name, 2, "HMCCU: Received unknown event from CCU: ".$errtok; + } + + return undef; } #################################################### @@ -2334,10 +2802,9 @@ sub HMCCU_ReadRPCQueue ($) my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my $rpcinterval = AttrVal ($name, 'rpcinterval', 5); - 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 $rpctimeout = AttrVal ($name, 'rpcevtimeout', 300); my $maxevents = $rpcinterval*10; $maxevents = 50 if ($maxevents > 50); $maxevents = 10 if ($maxevents < 10); @@ -2346,124 +2813,51 @@ sub HMCCU_ReadRPCQueue ($) foreach my $port (@portlist) { my $clkey = 'CB'.$port; next if (!exists ($hash->{hmccu}{rpc}{$clkey}{queue})); - next if ($hash->{hmccu}{rpc}{$clkey}{loop} == 1); my $queuename = $hash->{hmccu}{rpc}{$clkey}{queue}; next if ($queuename eq ''); if (!HMCCU_QueueOpen ($hash, $queuename)) { - Log3 $name, 2, "HMCCU: Can't open file queue $queuename"; + Log3 $name, 1, "HMCCU: Can't open file queue $queuename"; next; } - # my $queue = new RPCQueue (File => $rpcqueue."_".$port, Mode => 0666); my $element = HMCCU_QueueDeq ($hash); - # my $element = $queue->deq(); while ($element) { -# Log3 $name, 2, "HMCCU: queueelement = $element"; - $hash->{hmccu}{evtimeout} = 0 if ($hash->{hmccu}{evtimeout} == 1); - - $hash->{hmccu}{eventtime} = time () if ($eventno == 0); - my @Tokens = split (/\|/, $element); - if ($Tokens[0] eq 'EV') { - #### Event #### - $eventno++; - - # Handle special events - if ($Tokens[1] eq 'CENTRAL' && $Tokens[2] eq 'PONG') { - Log3 $name, 2, "HMCCU: Received PONG event from ".$Tokens[3]; + my ($et, @par) = HMCCU_ProcessEvent ($hash, $element); + if (defined ($et)) { + if ($et eq 'EV') { + $eventno++; + last if ($eventno == $maxevents); } - else { - # Standard event - my ($add, $chn) = split (/:/, $Tokens[1]); - my $reading = HMCCU_GetReadingName ('', $add, $chn, $Tokens[2], '', - $ccureadingformat); - HMCCU_UpdateClientReading ($hash, $add, $chn, $reading, $Tokens[3], 'rpcevent'); + elsif ($et eq 'ND') { + $newcount++ if (!exists ($HMCCU_Devices{$par[0]})); } - last if ($eventno == $maxevents); - } - elsif ($Tokens[0] eq 'ND') { - #### New device added in CCU #### - if (! exists ($HMCCU_Devices{$Tokens[1]})) { - $newcount++; + elsif ($et eq 'DD') { + push (@deldevices, $par[0]); + $delcount++; } - } - elsif ($Tokens[0] eq 'DD') { - #### Device deleted in CCU #### - push (@deldevices, $Tokens[1]); - $delcount++; - } - elsif ($Tokens[0] eq 'SL') { - #### RPC Server going to start server loop ### - Log3 $name, 0, "HMCCU: Received SL event. RPC server ".$Tokens[2]." starting server loop."; - $hash->{hmccu}{rpc}{$Tokens[2]}{loop} = 1; - InternalTimer (gettimeofday()+$HMCCU_INIT_INTERVAL1, 'HMCCU_RPCRegisterCallback', $hash, 0); - return; - } - elsif ($Tokens[0] eq 'IN') { - #### RPC Server initialized #### - Log3 $name, 0, "HMCCU: Received IN event. RPC server ".$Tokens[3]." initialized."; - $hash->{hmccu}{rpc}{$Tokens[3]}{state} = "running"; - my $norun = 0; - - # Check if all RPC servers were initialized. Set overall status - foreach my $ser (keys %{$hash->{hmccu}{rpc}}) { - $norun++ if ($hash->{hmccu}{rpc}{$ser}{state} ne "running" && - $hash->{hmccu}{rpc}{$ser}{pid} > 0); + elsif ($et eq 'SL') { + InternalTimer (gettimeofday()+$HMCCU_INIT_INTERVAL1, + 'HMCCU_RPCRegisterCallback', $hash, 0); + return; } - if ($norun == 0) { - $hash->{RPCState} = "running"; - readingsSingleUpdate ($hash, "rpcstate", "running", 1); - HMCCU_SetState ($hash, "OK"); - my ($c_ok, $c_err) = HMCCU_UpdateClients ($hash, '.*', 'Attr', 0); - DoTrigger ($name, "RPC server running"); - } - } - elsif ($Tokens[0] eq 'EX') { - #### RPC Server shut down #### - push (@termpids, $Tokens[2]); - Log3 $name, 0, "HMCCU: Received EX event. RPC server ".$Tokens[3]." terminated."; - if ($hash->{RPCState} ne "restarting") { - $hash->{hmccu}{rpc}{$Tokens[3]}{state} = "stopped"; - $hash->{hmccu}{rpc}{$Tokens[3]}{pid} = 0; - $hash->{hmccu}{rpc}{$Tokens[3]}{cburl} = ''; - $hash->{hmccu}{rpc}{$Tokens[3]}{clurl} = ''; - $hash->{hmccu}{rpc}{$Tokens[3]}{queue} = ''; - $hash->{hmccu}{rpc}{$Tokens[3]}{cbport} = 0; - $hash->{hmccu}{rpc}{$Tokens[3]}{loop} = 0; - - # Check if all RPC servers were terminated. Set overall status - my $run = 0; - foreach my $ser (keys %{$hash->{hmccu}{rpc}}) { - $run++ if ($hash->{hmccu}{rpc}{$ser}{state} ne "stopped"); - } - if ($run == 0) { - $hash->{RPCState} = "stopped"; - readingsSingleUpdate ($hash, "rpcstate", "stopped", 1); - $hash->{RPCPID} = '0'; - } - $hash->{hmccu}{rpccount} = $run; - $f = 1; + elsif ($et eq 'EX') { + push (@termpids, $par[1]); + $f = $par[2]; + last; } - else { - $f = 2; - } - last; - } - else { - Log3 $name, 2, "HMCCU: Unknown RPC event type [".$Tokens[0]."]"; } + # Read next element from queue $element = HMCCU_QueueDeq ($hash); - # $element = $queue->deq(); } HMCCU_QueueClose ($hash); - # $queue->close(); } - # Check if RPC server still running if events from CCU timed out - if ($hash->{hmccu}{eventtime} > 0 && time()-$hash->{hmccu}{eventtime} > $rpctimeout && + # Check if events from CCU timed out + if ($hash->{hmccu}{evtime} > 0 && time()-$hash->{hmccu}{evtime} > $rpctimeout && $hash->{hmccu}{evtimeout} == 0) { - $hash->{hmccu}{evtimeout} = 1; + $hash->{hmccu}{evtimeout} = 1; Log3 $name, 2, "HMCCU: Received no events from CCU since $rpctimeout seconds"; DoTrigger ($name, "No events from CCU since $rpctimeout seconds"); } @@ -2478,7 +2872,7 @@ sub HMCCU_ReadRPCQueue ($) # CCU devices added if ($newcount > 0) { - $hash->{NewDevices} = $newcount; + $hash->{NewDevices} += $newcount; DoTrigger ($name, "$newcount devices added in CCU"); } @@ -2488,20 +2882,24 @@ sub HMCCU_ReadRPCQueue ($) my $nhm_pids = scalar (@hm_pids); my $nex_pids = scalar (@ex_pids); - if ($nex_pids > 0 && $ccuflags !~ /extrpc/) { - 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 (1=Stop,2=Restart)"; + 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"; + + # Output statistic counters + foreach my $cnt (sort keys %{$hash->{hmccu}{ev}}) { + Log3 $name, 2, "HMCCU: Eventcount $cnt = ".$hash->{hmccu}{ev}{$cnt}; + } } if ($f == 2 && $nhm_pids == 0) { # All RPC servers terminated and restart flag set - $hash->{hmccu}{eventtime} = 0; - return if (HMCCU_StartRPCServer ($hash)); + return if (HMCCU_StartExtRPCServer ($hash)); Log3 $name, 0, "HMCCU: Restart of RPC server failed"; } @@ -2511,22 +2909,24 @@ sub HMCCU_ReadRPCQueue ($) } else { # No more RPC servers active + Log3 $name, 0, "HMCCU: Periodical check found no RPC Servers"; + # Deregister existing callbacks + HMCCU_RPCDeRegisterCallback ($hash); + + # Cleanup hash variables + my @clkeylist = keys %{$hash->{hmccu}{rpc}}; + foreach my $clkey (@clkeylist) { + delete $hash->{hmccu}{rpc}{$clkey}; + } + $hash->{hmccu}{rpccount} = 0; + $hash->{hmccu}{rpcinit} = 0; + $hash->{RPCPID} = '0'; $hash->{RPCPRC} = 'none'; $hash->{RPCState} = "stopped"; - readingsSingleUpdate ($hash, "rpcstate", "stopped", 1); - if ($hash->{hmccu}{rpccount} > 0) { - foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) { - $hash->{hmccu}{rpc}{$clkey}{state} = "stopped"; - $hash->{hmccu}{rpc}{$clkey}{pid} = 0; - $hash->{hmccu}{rpc}{$clkey}{queue} = ''; - $hash->{hmccu}{rpc}{$clkey}{cburl} = ''; - $hash->{hmccu}{rpc}{$clkey}{cbport} = 0; - $hash->{hmccu}{rpc}{$clkey}{loop} = 0; - } - $hash->{hmccu}{rpccount} = 0; - } + Log3 $name, 0, "HMCCU: All RPC servers stopped"; + readingsSingleUpdate ($hash, "rpcstate", "stopped", 1); DoTrigger ($name, "All RPC servers stopped"); } } @@ -2558,6 +2958,38 @@ sub HMCCU_HMScript ($$) } } +#################################################### +# Update a single client device reading considering +# reading format and value substitution +#################################################### + +sub HMCCU_UpdateSingleReading ($$$$$) +{ + my ($hash, $chn, $dpt, $reading, $value) = @_; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + my $ccureadings = AttrVal ($name, 'ccureadings', 1); + my $readingformat = AttrVal ($name, 'ccureadingformat', 'name'); + my $substitute = AttrVal ($name, 'substitute', ''); + my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints ( + $hash, '', 'STATE', '', ''); + + $value = HMCCU_Substitute ($value, $substitute, 0, $reading); + $value = HMCCU_FormatReadingValue ($hash, $value); + readingsSingleUpdate ($hash, $reading, $value, 1) if ($ccureadings); + if ($controldpt ne '' && $dpt eq $controldpt && $chn eq $controlchn) { + readingsSingleUpdate ($hash, 'control', $value, 1); + } + if (($reading =~ /\.$statedpt$/ || $reading eq $statedpt) && $ccureadings) { + if ($statechn eq '' || $statechn eq $chn) { + HMCCU_SetState ($hash, $value); + } + } + + return $value; +} + #################################################### # Get datapoint and update reading. #################################################### @@ -2578,7 +3010,7 @@ sub HMCCU_GetDatapoint ($@) my $readingformat = AttrVal ($name, 'ccureadingformat', 'name'); my $substitute = AttrVal ($name, 'substitute', ''); my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints ( - $hash, 'STATE', '', '', ''); + $hash, '', 'STATE', '', ''); my $ccuget = HMCCU_GetAttribute ($hmccu_hash, $hash, 'ccuget', 'Value'); my $ccutrace = AttrVal ($hmccu_hash->{NAME}, 'ccutrace', ''); @@ -2622,17 +3054,18 @@ sub HMCCU_GetDatapoint ($@) $value); } else { - $value = HMCCU_Substitute ($value, $substitute, 0, $reading); - $value = HMCCU_FormatReadingValue ($hash, $value); - readingsSingleUpdate ($hash, $reading, $value, 1) if ($ccureadings); - if ($controldpt ne '' && $dpt eq $controldpt && $chn eq $controlchn) { - readingsSingleUpdate ($hash, 'control', $value, 1); - } - if (($reading =~ /\.$statedpt$/ || $reading eq $statedpt) && $ccureadings) { - if ($statechn eq '' || $statechn eq $chn) { - HMCCU_SetState ($hash, $value); - } - } + $value = HMCCU_UpdateSingleReading ($hash, $chn, $dpt, $reading, $value); +# $value = HMCCU_Substitute ($value, $substitute, 0, $reading); +# $value = HMCCU_FormatReadingValue ($hash, $value); +# readingsSingleUpdate ($hash, $reading, $value, 1) if ($ccureadings); +# if ($controldpt ne '' && $dpt eq $controldpt && $chn eq $controlchn) { +# readingsSingleUpdate ($hash, 'control', $value, 1); +# } +# if (($reading =~ /\.$statedpt$/ || $reading eq $statedpt) && $ccureadings) { +# if ($statechn eq '' || $statechn eq $chn) { +# HMCCU_SetState ($hash, $value); +# } +# } } return (1, $value); @@ -2656,8 +3089,11 @@ sub HMCCU_SetDatapoint ($$$) return -3 if (!defined ($hmccu_hash)); return -4 if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'Deleted'); my $name = $hmccu_hash->{NAME}; + my $cdname = $hash->{NAME}; + my $readingformat = AttrVal ($cdname, 'ccureadingformat', 'name'); my $ccutrace = AttrVal ($name, 'ccutrace', ''); + my $ccuverify = AttrVal ($cdname, 'ccuverify', 0); my $url = 'http://'.$hmccu_hash->{host}.':8181/do.exe?r1=dom.GetObject("'; my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($param, $HMCCU_FLAG_INTERFACE); @@ -2677,11 +3113,25 @@ sub HMCCU_SetDatapoint ($$$) my $response = GetFileFromURL ($url); if ($ccutrace ne '' && ($addr =~ /$ccutrace/ || $nam =~ /$ccutrace/)) { Log3 $name, 2, "HMCCU: Addr=$addr Name=$nam"; - Log3 $name, 2, "HMCCU: Script response = \n".$response; + Log3 $name, 2, "HMCCU: Script response = \n".(defined ($response) ? $response: 'undef'); Log3 $name, 2, "HMCCU: Script = \n".$url; } + return -2 if (!defined ($response) || $response =~ /null{ccutype}, $addr, $dpt, 1)) { + if ($ccuverify == 1) { + usleep (100000); + my ($rc, $result) = HMCCU_GetDatapoint ($hash, $param); + return $rc; + } + elsif ($ccuverify == 2) { + my $reading = HMCCU_GetReadingName ($int, $add, $chn, $dpt, $nam, $readingformat); + HMCCU_UpdateSingleReading ($hash, $chn, $dpt, $reading, $value); + } + } + return 0; } @@ -2778,14 +3228,19 @@ sub HMCCU_GetUpdate ($$$) $script = qq( string sDPId; string sChnName = "$nam"; +integer c = 0; object oChannel = dom.GetObject (sChnName); if (oChannel) { foreach(sDPId, oChannel.DPs()) { object oDP = dom.GetObject(sDPId); if (oDP) { - WriteLine (sChnName # "=" # oDP.Name() # "=" # oDP.$ccuget()); + if (OPERATION_READ & oDP.Operations()) { + WriteLine (sChnName # "=" # oDP.Name() # "=" # oDP.$ccuget()); + c = c+1; + } } } + WriteLine (c); } ); } @@ -2796,6 +3251,7 @@ if (oChannel) { $script = qq( string chnid; string sDPId; +integer c = 0; object odev = dom.GetObject ("$nam"); if (odev) { foreach (chnid, odev.Channels()) { @@ -2804,11 +3260,15 @@ if (odev) { foreach(sDPId, ochn.DPs()) { object oDP = dom.GetObject(sDPId); if (oDP) { - WriteLine (ochn.Name() # "=" # oDP.Name() # "=" # oDP.$ccuget()); + if (OPERATION_READ & oDP.Operations()) { + WriteLine (ochn.Name() # "=" # oDP.Name() # "=" # oDP.$ccuget()); + c = c+1; + } } } } } + WriteLine (c); } ); } @@ -2825,6 +3285,8 @@ if (odev) { return -2 if ($response eq ''); my @dpdef = split /\n/, $response; + my $count = pop (@dpdef); + return -10 if (!defined ($count) || $count == 0); # Update client device my $rc = HMCCU_UpdateDeviceReadings ($cl_hash, \@dpdef); @@ -2837,7 +3299,8 @@ if (odev) { next if ($ch->{TYPE} ne 'HMCCUDEV'); next if ($ch->{ccuif} ne "VirtualDevices" || !exists ($ch->{ccugroup})); my @vdevs = split (",", $ch->{ccugroup}); - if ($da ~~ @vdevs || ($cno ne '' && $cl_hash->{ccuaddr} ~~ @vdevs)) { + if ((grep { $_ eq $da } @vdevs) || + ($cno ne '' && (grep { $_ eq $cl_hash->{ccuaddr} } @vdevs))) { HMCCU_UpdateDeviceReadings ($ch, \@dpdef); } } @@ -2866,7 +3329,7 @@ sub HMCCU_UpdateDeviceReadings ($$) my $readingformat = AttrVal ($cn, 'ccureadingformat', 'name'); my $substitute = AttrVal ($cn, 'substitute', ''); my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints ( - $cl_hash, 'STATE', '', '', ''); + $cl_hash, '', 'STATE', '', ''); readingsBeginUpdate ($cl_hash); @@ -2931,7 +3394,7 @@ sub HMCCU_GetChannel ($$) my $readingformat = AttrVal ($name, 'ccureadingformat', 'name'); my $defsubstitute = AttrVal ($name, 'substitute', ''); my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints ( - $hash, 'STATE', '', '', ''); + $hash, '', 'STATE', '', ''); # Build channel list foreach my $chndef (@$chnref) { @@ -3043,10 +3506,12 @@ sub HMCCU_RPCGetConfig ($$$) $addr = $add; $addr .= ':'.$chn if ($flags & $HMCCU_FLAG_CHANNEL); - return (-9, '') if (exists ($HMCCU_RPC_PORT{$int})); + return (-9, '') if (!exists ($HMCCU_RPC_PORT{$int})); my $port = $HMCCU_RPC_PORT{$int}; + my $url = "http://".$hmccu_hash->{host}.":".$port."/"; + $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port})); + my $client = RPC::XML::Client->new ($url); - my $client = RPC::XML::Client->new ("http://".$hmccu_hash->{host}.":".$port."/"); my $res = $client->simple_request ($mode, $addr, "MASTER"); if (! defined ($res)) { return (-5, "Function not available"); @@ -3124,7 +3589,9 @@ sub HMCCU_RPCSetConfig ($$$) return -9 if (!exists ($HMCCU_RPC_PORT{$int})); my $port = $HMCCU_RPC_PORT{$int}; - + my $url = "http://".$hmccu_hash->{host}.":".$port."/"; + $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port})); + # Build param set foreach my $pardef (@$parref) { my ($par,$val) = split ("=", $pardef); @@ -3132,7 +3599,7 @@ sub HMCCU_RPCSetConfig ($$$) $paramset{$par} = $val; } - my $client = RPC::XML::Client->new ("http://".$hmccu_hash->{host}.":".$port."/"); + my $client = RPC::XML::Client->new ($url); my $res = $client->simple_request ("putParamset", $addr, "MASTER", \%paramset); if (! defined ($res)) { return -5; @@ -3213,7 +3680,7 @@ sub HMCCU_QueueEnq ($$) return 0 if (!exists ($hash->{hmccu}{queue})); $hash->{hmccu}{queue}{queue}->sysseek(0, SEEK_END); - $element =~ s/$hash->{hmccu}{queue}{separator}//g; + $element =~ s/$hash->{hmccu}{queue}{seperator}//g; $hash->{hmccu}{queue}{queue}->syswrite($element.$hash->{hmccu}{queue}{seperator}) or return 0; return 1; @@ -3240,7 +3707,7 @@ sub HMCCU_QueueDeq ($) else { ## If seperator isn't found, go back 'sep_length' spaces to ensure we don't miss it between reads $element .= substr($_, 0, -$hash->{hmccu}{queue}{sep_length}, ''); - $hash->{hmccu}{queue}{ptr} += $hash->{hmccu}{queue}{block_size} - $hash->{hmccu}{queue}{sep_length}; + $hash->{hmccu}{queue}{ptr} += $hash->{hmccu}{queue}{block_size} - $hash->{hmccu}{queue}{sep_length}; $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr}, SEEK_SET); } } @@ -3331,25 +3798,334 @@ sub HMCCU_Dewpoint ($$$$) } #################################################### -# *** External process part *** +# *** Subprocess process part *** #################################################### -# Initialize hash -# my %rpchash; -# $rpchash{hmccu}{pid} = $$; -# $rpchash{hmccu}{name} = $0; -# my $pash = \%rpchash; +# Child process. Must be global to allow access by RPC callbacks +my $hmccu_child; -# foreach my $carg (@ARGV) { -# print "Arg=$carg\n"; -# } +# Queue file +my $queue; +my %child_queue; +my $cpqueue = \%child_queue; + +# Statistic data of child process +my %child_hash = ( + "total", 0, + "writeerror", 0, + "EV", 0, + "ND", 0, + "DD", 0, + "RD", 0, + "RA", 0, + "UD", 0, + "IN", 0, + "EX", 0, + "SL", 0 +); +my $cphash = \%child_hash; + + +##################################### +# Subprocess +# Write event to parent process +##################################### + +sub HMCCU_CCURPC_Write ($$) +{ + my ($et, $msg) = @_; + my $name = $hmccu_child->{devname}; + + $cphash->{total}++; + $cphash->{$et}++; + +# SUBPROCESS + HMCCU_QueueEnq ($cpqueue, $et."|".$msg); + +# SUBPROCESS +# Log3 $name, 1, "CCURPC: Write $et $msg"; +# my $bytes = $hmccu_child->writeToParent ($et."|".$msg); +# if (!defined ($bytes)){ +# $cphash->{writeerror}++; +# Log3 $name, 1, "CCURPC: Write to parent process failed [$et $msg]. Error=".$hmccu_child->lasterror(); +# return 0; +# } +# +# return $bytes; +} + +##################################### +# Subprocess +# Start RPC server. +# Return 1 on success. +##################################### + +sub HMCCU_CCURPC_OnRun ($) +{ + $hmccu_child = shift; + my $name = $hmccu_child->{devname}; + my $serveraddr = $hmccu_child->{serveraddr}; + my $serverport = $hmccu_child->{serverport}; + my $callbackport = $hmccu_child->{callbackport}; + my $queuefile = $hmccu_child->{queue}; + my $clkey = "CB".$serverport; + +# SUBPROCESS + # Create, open and reset queue file + Log3 $name, 0, "CCURPC: $clkey Creating file queue $queuefile"; + if (!HMCCU_QueueOpen ($cpqueue, $queuefile)) { + Log3 $name, 0, "CCURPC: $clkey Can't create queue"; + return 0; + } + +# SUBPROCESS + # Reset event queue + HMCCU_QueueReset ($cpqueue); + while (HMCCU_QueueDeq ($cpqueue)) { } + + # Create RPC server + Log3 $name, 0, "CCURPC: Initializing RPC server $clkey"; + $ccurpc_server = RPC::XML::Server->new (port=>$callbackport); + if (!ref($ccurpc_server)) + { + Log3 $name, 0, "CCURPC: Can't create RPC callback server on port $callbackport. Port in use?"; + return 0; + } + else { + Log3 $name, 0, "CCURPC: Callback server created listening on port $callbackport"; + } + + # Callback for events + Log3 $name, 1, "CCURPC: $clkey Adding callback for events"; + $ccurpc_server->add_method ( + { name=>"event", + signature=> ["string string string string int","string string string string double","string string string string boolean","string string string string i4"], + code=>\&HMCCU_CCURPC_EventCB + } + ); + + # Callback for new devices + Log3 $name, 1, "CCURPC: $clkey Adding callback for new devices"; + $ccurpc_server->add_method ( + { name=>"newDevices", + signature=>["string string array"], + code=>\&HMCCU_CCURPC_NewDevicesCB + } + ); + + # Callback for deleted devices + Log3 $name, 1, "CCURPC: $clkey Adding callback for deleted devices"; + $ccurpc_server->add_method ( + { name=>"deleteDevices", + signature=>["string string array"], + code=>\&HMCCU_CCURPC_DeleteDevicesCB + } + ); + + # Callback for modified devices + Log3 $name, 1, "CCURPC: $clkey Adding callback for modified devices"; + $ccurpc_server->add_method ( + { name=>"updateDevice", + signature=>["string string string int"], + code=>\&HMCCU_CCURPC_UpdateDeviceCB + } + ); + + # Callback for replaced devices + Log3 $name, 1, "CCURPC: $clkey Adding callback for replaced devices"; + $ccurpc_server->add_method ( + { name=>"replaceDevice", + signature=>["string string string string"], + code=>\&HMCCU_CCURPC_ReplaceDeviceCB + } + ); + + # Callback for readded devices + Log3 $name, 1, "CCURPC: $clkey Adding callback for readded devices"; + $ccurpc_server->add_method ( + { name=>"replaceDevice", + signature=>["string string array"], + code=>\&HMCCU_CCURPC_ReaddDeviceCB + } + ); + + # Dummy implementation, always return an empty array + Log3 $name, 1, "CCURPC: $clkey Adding callback for list devices"; + $ccurpc_server->add_method ( + { name=>"listDevices", + signature=>["array string"], + code=>\&HMCCU_CCURPC_ListDevicesCB + } + ); + + # Enter server loop +# SUBPROCESS +# sleep (5); + HMCCU_CCURPC_Write ("SL", "$$|$clkey"); + + Log3 $name, 0, "CCURPC: $clkey Entering server loop"; + $ccurpc_server->server_loop; + Log3 $name, 0, "CCURPC: $clkey Server loop terminated"; + + # Server loop exited by SIGINT + HMCCU_CCURPC_Write ("EX", "SHUTDOWN|$$|$clkey"); + + return 1; +} + +##################################### +# Subprocess +# RPC server loop terminated +##################################### + +sub HMCCU_CCURPC_OnExit () +{ + # Output statistics + foreach my $et (sort keys %child_hash) { + Log3 $hmccu_child->{devname}, 2, "CCURPC: Eventcount $et = ".$cphash->{$et}; + } +} + +##################################### +# Subprocess +# Callback for new devices +##################################### + +sub HMCCU_CCURPC_NewDevicesCB ($$$) +{ + my ($server, $cb, $a) = @_; + my $devcount = scalar (@$a); + my $name = $hmccu_child->{devname}; + my $c = 0; + my $msg = ''; + + Log3 $name, 2, "CCURPC: $cb NewDevice received $devcount device specifications"; + for my $dev (@$a) { +# SUBPROCESS +# if ($c < 2) { +# $msg .= ';' if ($c > 0); +# $msg .= $dev->{ADDRESS}."|".$dev->{TYPE}; +# $c++; +# next; +# } +# HMCCU_CCURPC_Write ("ND", $msg); +# $c = 0; +# $msg = ''; + HMCCU_CCURPC_Write ("ND", $dev->{ADDRESS}."|".$dev->{TYPE}); + } + + return; +} + +##################################### +# Subprocess +# Callback for deleted devices +##################################### + +sub HMCCU_CCURPC_DeleteDevicesCB ($$$) +{ + my ($server, $cb, $a) = @_; + my $name = $hmccu_child->{devname}; + my $devcount = scalar (@$a); + + Log3 $name, 2, "CCURPC: $cb DeleteDevice received $devcount device addresses"; + for my $dev (@$a) { + HMCCU_CCURPC_Write ("DD", $dev); + } + + return; +} + +##################################### +# Subprocess +# Callback for modified devices +##################################### + +sub HMCCU_CCURPC_UpdateDeviceCB ($$$$) +{ + my ($server, $cb, $devid, $hint) = @_; + + HMCCU_CCURPC_Write ("UD", $devid."|".$hint); + + return; +} + +##################################### +# Subprocess +# Callback for replaced devices +##################################### + +sub HMCCU_CCURPC_ReplaceDeviceCB ($$$$) +{ + my ($server, $cb, $devid1, $devid2) = @_; + + HMCCU_CCURPC_Write ("RD", $devid1."|".$devid2); + + return; +} + +##################################### +# Subprocess +# Callback for readded devices +##################################### + +sub HMCCU_CCURPC_ReaddDevicesCB ($$$) +{ + my ($server, $cb, $a) = @_; + my $name = $hmccu_child->{devname}; + my $devcount = scalar (@$a); + + Log3 $name, 2, "CCURPC: $cb ReaddDevice received $devcount device addresses"; + for my $dev (@$a) { + HMCCU_CCURPC_Write ("RA", $dev); + } + + return; +} + +##################################### +# Subprocess +# Callback for handling CCU events +##################################### + +sub HMCCU_CCURPC_EventCB ($$$$$) +{ + my ($server, $cb, $devid, $attr, $val) = @_; + my $name = $hmccu_child->{devname}; + + HMCCU_CCURPC_Write ("EV", $devid."|".$attr."|".$val); + if (($cphash->{EV} % 500) == 0) { + Log3 $name, 3, "CCURPC: $cb Received 500 events from CCU since last check"; + my @stkeys = ('total', 'EV', 'ND', 'DD', 'RD', 'RA', 'UD', 'IN', 'SL', 'EX'); + my $msg = ''; + foreach my $stkey (@stkeys) { + $msg .= '|' if ($msg ne ''); + $msg .= $cphash->{$stkey}; + } + HMCCU_CCURPC_Write ("ST", $msg); + } + + # Never remove this statement! + return; +} + +##################################### +# Subprocess +# Callback for list devices +##################################### + +sub HMCCU_CCURPC_ListDevicesCB ($$) +{ + my ($server, $cb) = @_; + my $name = $hmccu_child->{devname}; + + $cb = "unknown" if (!defined ($cb)); + Log3 $name, 1, "CCURPC: $cb ListDevices. Sending init to HMCCU"; + HMCCU_CCURPC_Write ("IN", "INIT|1|$cb"); + + return RPC::XML::array->new(); +} -# Process command line arguments -# if ($#ARGV+1 != 4) { -# print "Usage: $0 CCU-Host Port QueueFile LogFile\n"; -# print " $0 shutdown CCU-Host Port PID\n"; -# exit 1; -# } 1; @@ -3361,8 +4137,8 @@ sub HMCCU_Dewpoint ($$$$)

HMCCU

    The module provides an easy get/set interface for Homematic CCU. It acts as an - IO device for HMCCUDEV client devices. The module requires additional Perl modules - XML::Simple and File::Queue. + IO device for HMCCUDEV and HMCCUCHN client devices. The module requires additional Perl modules + RPC::XML::Client, RPC::XML::Server and SubProcess (part of FHEM).

    Define @@ -3415,11 +4191,9 @@ sub HMCCU_Dewpoint ($$$$) Object=Value readings will be set. Object can be the name of a CCU system variable or a valid datapoint specification.
    -
  • set <name> restartrpc
    - Restart RPC server(s). -

  • -
  • set <name> rpcserver {on|off}
    - Start or stop RPC server. +
  • set <name> rpcserver {on|off|restart}
    + Start, stop or restart RPC server. Until operation is completed only a few set/get + commands are available.

@@ -3514,7 +4288,7 @@ sub HMCCU_Dewpoint ($$$$)
  • rpcserver <on | off>
    Specify if RPC server is automatically started on FHEM startup.

  • -
  • statedatapoint <datapoint>
    +
  • statedatapoint [<channel-number>.]<datapoint>
    Set datapoint for devstate commands. Default is 'STATE'.

  • statevals <text:substext[,...]>
    diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm index 5b8629c1c..198d7111d 100644 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm @@ -29,9 +29,10 @@ # attr ccureadings { 0 | 1 } # attr ccureadingfilter # attr ccureadingformat { name | address | datapoint } -# attr ccuverify { 0 | 1 } +# attr ccuverify { 0 | 1 | 2 } # attr controldatapoint # attr disable { 0 | 1 } +# attr statedatapoint # attr statevals :[,...] # attr substitute [;...] # @@ -66,7 +67,7 @@ sub HMCCUCHN_Initialize ($) $hash->{GetFn} = "HMCCUCHN_Get"; $hash->{AttrFn} = "HMCCUCHN_Attr"; - $hash->{AttrList} = "IODev ccureadingfilter ccureadingformat:name,address,datapoint ccureadings:0,1 ccuverify:0,1 ccuget:State,Value controldatapoint disable:0,1 statedatapoint statevals substitute stripnumber:0,1,2 ". $readingFnAttributes; + $hash->{AttrList} = "IODev ccureadingfilter ccureadingformat:name,address,datapoint ccureadings:0,1 ccuverify:0,1,2 ccuget:State,Value controldatapoint disable:0,1 statedatapoint statevals substitute stripnumber:0,1,2 ". $readingFnAttributes; } ##################################### @@ -185,9 +186,10 @@ sub HMCCUCHN_Set ($@) } my $statevals = AttrVal ($name, "statevals", ''); - my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); - my $controldatapoint = AttrVal ($name, "controldatapoint", ''); - my $ccuverify = AttrVal ($name, "ccuverify", 0); +# my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); +# my $controldatapoint = AttrVal ($name, "controldatapoint", ''); + my ($sc, $statedatapoint, $cc, $controldatapoint) = HMCCU_GetSpecialDatapoints ( + $hash, '', 'STATE', '', ''); my $result = ''; my $rc; @@ -209,18 +211,13 @@ sub HMCCUCHN_Set ($@) $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); return HMCCU_SetError ($hash, $rc) if ($rc < 0); - if ($ccuverify) { - usleep (100000); - ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - } HMCCU_SetState ($hash, "OK"); return undef; } elsif ($opt eq 'control') { return HMCCU_SetError ($hash, "Attribute controldatapoint not set") if ($controldatapoint eq ''); my $objvalue = shift @a; - my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$controldatapoint; + my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$controldatapoint; $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); return HMCCU_SetError ($hash, $rc) if ($rc < 0); HMCCU_SetState ($hash, "OK"); @@ -240,11 +237,6 @@ sub HMCCUCHN_Set ($@) $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); return HMCCU_SetError ($hash, $rc) if ($rc < 0); - if ($ccuverify) { - usleep (100000); - ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - } HMCCU_SetState ($hash, "OK"); return undef; } @@ -297,7 +289,7 @@ sub HMCCUCHN_Set ($@) if ($hash->{statevals} ne '') { my @cmdlist = split /\|/,$hash->{statevals}; shift @cmdlist; - $retmsg .= ':'.join(',',@cmdlist); + $retmsg .= ':'.join(',',@cmdlist) if (@cmdlist > 0); foreach my $sv (@cmdlist) { $retmsg .= ' '.$sv.':noArg'; } @@ -329,7 +321,8 @@ sub HMCCUCHN_Get ($@) return "HMCCUCHN: CCU busy"; } - my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); +# my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); + my ($sc, $statedatapoint, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash, '', 'STATE', '', ''); my $ccureadings = AttrVal ($name, "ccureadings", 1); my $result = ''; @@ -533,8 +526,10 @@ sub HMCCUCHN_SetError ($$) Syntax for filter rule is: [channel-no:]RegExp
    If channel-no is specified the following rule applies only to this channel.

  • -
  • ccuverify <0 | 1>
    - If set to 1 a datapoint is read for verification after set operation. +
  • ccuverify <0 | 1 | 2>
    + If set to 1 a datapoint is read for verification after set operation. If set to 2 the + corresponding reading will be set to the new value directly after setting a datapoint + in CCU.

  • controldatapoint <datapoint>
    Set datapoint for device control. Can be use to realize user defined control elements for diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm index ee2ddc1ae..7ea4e7d06 100644 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm @@ -4,7 +4,7 @@ # # $Id:$ # -# Version 3.1 +# Version 3.2 # # (c) 2016 zap (zap01 t-online de) # @@ -16,6 +16,7 @@ # set config [] = [...] # set control # set datapoint . +# set defaults # set devstate # set on-for-timer # set @@ -23,6 +24,7 @@ # # get devstate # get datapoint . +# get defaults # get channel [.] # get config [] # get configdesc [] @@ -32,12 +34,12 @@ # attr ccureadings { 0 | 1 } # attr ccureadingformat { address | name } # attr ccureadingfilter [,...] -# attr ccuverify { 0 | 1 } -# attr controldatapoint . +# attr ccuverify { 0 | 1 | 2} +# attr controldatapoint . # attr disable { 0 | 1 } # attr mapdatapoints .=.[,...] # attr statechannel -# attr statedatapoint +# attr statedatapoint [.] # attr statevals :[,...] # attr substitute :[,...] # @@ -73,7 +75,7 @@ sub HMCCUDEV_Initialize ($) $hash->{GetFn} = "HMCCUDEV_Get"; $hash->{AttrFn} = "HMCCUDEV_Attr"; - $hash->{AttrList} = "IODev ccureadingfilter:textField-long ccureadingformat:name,address ccureadings:0,1 ccuget:State,Value ccuverify:0,1 disable:0,1 mapdatapoints:textField-long statevals substitute statechannel statedatapoint controldatapoint stripnumber:0,1,2 ". $readingFnAttributes; + $hash->{AttrList} = "IODev ccureadingfilter:textField-long ccureadingformat:name,address ccureadings:0,1 ccuget:State,Value ccuverify:0,1,2 disable:0,1 mapdatapoints:textField-long statevals substitute statechannel statedatapoint controldatapoint stripnumber:0,1,2 ". $readingFnAttributes; } ##################################### @@ -278,11 +280,12 @@ sub HMCCUDEV_Set ($@) return "HMCCUDEV: CCU busy"; } - my $statechannel = AttrVal ($name, "statechannel", ''); - my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); +# my $statechannel = AttrVal ($name, "statechannel", ''); +# my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); my $statevals = AttrVal ($name, "statevals", ''); - my $controldatapoint = AttrVal ($name, "controldatapoint", ''); - my $ccuverify = AttrVal ($name, "ccuverify", 0); +# my $controldatapoint = AttrVal ($name, "controldatapoint", ''); + my ($statechannel, $statedatapoint, $controlchannel, $controldatapoint) = + HMCCU_GetSpecialDatapoints ($hash, '', 'STATE', '', ''); my $result = ''; my $rc; @@ -305,20 +308,13 @@ sub HMCCUDEV_Set ($@) $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); return HMCCU_SetError ($hash, $rc) if ($rc < 0); - if ($ccuverify && HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, $hash->{ccuaddr}, - $objname, 1)) { - usleep (100000); - ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - } - HMCCU_SetState ($hash, "OK"); return undef; } elsif ($opt eq 'control') { return HMCCU_SetError ($hash, "Attribute controldatapoint not set") if ($controldatapoint eq ''); my $objvalue = shift @a; - my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$controldatapoint; + my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$controlchannel.'.'.$controldatapoint; $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); return HMCCU_SetError ($hash, $rc) if ($rc < 0); @@ -340,12 +336,6 @@ sub HMCCUDEV_Set ($@) $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); return HMCCU_SetError ($hash, $rc) if ($rc < 0); - if ($ccuverify) { - usleep (100000); - ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - } - HMCCU_SetState ($hash, "OK"); return undef; } @@ -423,8 +413,13 @@ sub HMCCUDEV_Set ($@) HMCCU_SetState ($hash, "OK"); return undef; } + elsif ($opt eq 'defaults') { + HMCCU_SetDefaults ($hash); + HMCCU_SetState ($hash, "OK"); + return undef; + } else { - my $retmsg = "HMCCUDEV: Unknown argument $opt, choose one of config control datapoint"; + my $retmsg = "HMCCUDEV: Unknown argument $opt, choose one of config control datapoint defaults:noArg"; return undef if ($hash->{statevals} eq 'readonly'); if ($statechannel ne '') { @@ -432,7 +427,7 @@ sub HMCCUDEV_Set ($@) if ($hash->{statevals} ne '') { my @cmdlist = split /\|/,$hash->{statevals}; shift @cmdlist; - $retmsg .= ':'.join(',',@cmdlist); + $retmsg .= ':'.join(',',@cmdlist) if (@cmdlist > 0); foreach my $sv (@cmdlist) { $retmsg .= ' '.$sv.':noArg'; } @@ -467,9 +462,11 @@ sub HMCCUDEV_Get ($@) return "HMCCUDEV: CCU busy"; } - my $statechannel = AttrVal ($name, 'statechannel', ''); - my $statedatapoint = AttrVal ($name, 'statedatapoint', 'STATE'); +# my $statechannel = AttrVal ($name, 'statechannel', ''); +# my $statedatapoint = AttrVal ($name, 'statedatapoint', 'STATE'); my $ccureadings = AttrVal ($name, 'ccureadings', 1); + my ($statechannel, $statedatapoint, $cc, $cd) = HMCCU_GetSpecialDatapoints ( + $hash, '', 'STATE', '', ''); my $result = ''; my $rc; @@ -591,6 +588,10 @@ sub HMCCUDEV_Get ($@) HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); return $res; } + elsif ($opt eq 'defaults') { + $result = HMCCU_GetDefaults ($hash); + return $result; + } else { my $retmsg = "HMCCUDEV: Unknown argument $opt, choose one of datapoint"; @@ -598,7 +599,7 @@ sub HMCCUDEV_Get ($@) my $valuecount = HMCCU_GetValidDatapoints ($hash, $hash->{ccutype}, -1, 1, \@valuelist); $retmsg .= ":".join(",", @valuelist) if ($valuecount > 0); - $retmsg .= " channel update:noArg config configdesc deviceinfo:noArg"; + $retmsg .= " defaults:noArg channel update:noArg config configdesc deviceinfo:noArg"; if ($statechannel ne '') { $retmsg .= ' devstate:noArg'; @@ -676,6 +677,9 @@ sub HMCCUDEV_SetError ($$) Example:
    set light_entrance devstate on

  • +
  • set <name> defaults
    + Set default attributes for CCU device type. +

  • set <name> on-for-timer <seconds>
    Switch device on for specified time. Requires that 'statechannel' is set and contains datapoint ON_TIME. In addition 'statevals' must contain value 'on'. @@ -725,6 +729,9 @@ sub HMCCUDEV_SetError ($$)
  • get <name> configdesc [<channel-number>] [<rpcport>]
    Get description of configuration parameters for CCU device.

  • +
  • get <name> defaults
    + Display default attributes for CCU device type. +

  • get <name> update [{'State'|'Value'}]
    Update datapoints / readings of device.

  • @@ -753,8 +760,10 @@ sub HMCCUDEV_SetError ($$)
  • ccureadingformat <address | name>
    Set format of readings. Default is 'name'.

  • -
  • ccuverify <0 | 1>
    - If set to 1 a datapoint is read for verification after set operation. +
  • ccuverify <0 | 1 | 2>
    + If set to 1 a datapoint is read for verification after set operation. If set to 2 the + corresponding reading will be set to the new value directly after setting a datapoint + in CCU.

  • controldatapoint <channel-number.datapoint>
    Set datapoint for device control. Can be use to realize user defined control elements for diff --git a/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm b/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm new file mode 100644 index 000000000..f3afea26e --- /dev/null +++ b/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm @@ -0,0 +1,122 @@ +######################################################################### +# +# HMCCUConf.pm +# +# $Id:$ +# +# Version 3.2 +# +# Configuration parameters for Homematic devices. +# +# (c) 2016 zap (zap01 t-online de) +# +######################################################################### + +package HMCCUConf; + +use strict; +use warnings; + +use vars qw(%HMCCU_DEV_DEFAULTS); + +# Default attributes for Homematic devices of type HMCCUDEV +%HMCCU_DEV_DEFAULTS = ( + "HM-Sec-SCo" => { # Tuer/Fensterkontakt optisch + ccureadingfilter => "(ERROR|UNREACH|LOWBAT|STATE)", + statechannel => 1, + substitute => "STATE!(0|false):closed,(1|true):open;LOWBAT!(0|false):no,(1|true):yes" + }, + "HM-Sec-SC" => { # Tuer/Fensterkontakt magnetisch + ccureadingfilter => "(ERROR|UNREACH|LOWBAT|STATE)", + statechannel => 1, + substitute => "STATE!(0|false):closed,(1|true):open;LOWBAT!(0|false):no,(1|true):yes" + }, + "HM-LC-Sw1-Pl-2" => { # Steckdose + ccureadingfilter => "(STATE|UNREACH)", + controldatapoint => "1.STATE", + statechannel => 1, + statevals => "on:true,off:false", + substitute => "STATE!(1|true):on,(0|false):off", + webCmd => "control", + widgetOverride => "control:uzsuToggle,off,on" + }, + "HMIP-PS" => { # Steckdose (IP) + ccureadingfilter => "(STATE|UNREACH)", + statechannel => 3, + statevals => "on:1,off:0", + substitute => "STATE!(1|true):on,(0|false):off" + }, + "HM-ES-PMSw1-Pl" => { # Steckdose mit Energiemessung + ccureadingfilter => "(STATE|UNREACH|CURRENT|ENERGY_COUNTER|POWER)", + statechannel => 1, + statevals => "on:1,off:0", + stripnumber => 1, + substitute => "STATE!(1|true):on,(0|false):off" + }, + "HMIP-PSM" => { # Steckdose mit Energiemessung (IP) + ccureadingfilter => "(STATE|UNREACH|CURRENT|ENERGY_COUNTER|POWER)", + statechannel => 3, + statevals => "on:true,off:false", + stripnumber => 1, + substitute => "STATE!(1|true):on,(0|false):off" + }, + "HM-LC-Bl1PBU-FM" => { # Rolladenaktor + cmdIcon => "up:fts_shutter_up stop:fts_shutter_manual down:fts_shutter_down", + controldatapoint => "1.LEVEL", + eventMap => "/datapoint 1.STOP 1:stop/datapoint 1.LEVEL 1:down/datapoint 1.LEVEL 0:up/", + statechannel => 1, + statevals => "up:0.0,down:1.0", + stripnumber => 1, + webCmd => "control:up:stop:down", + widgetOverride => "control:slider,0,0.05,1,1" + }, + "HM-TC-IT-WM-W-EU" => { # Wandthermostat + ccureadingfilter => "(UNREACH|^HUMIDITY|^TEMPERATURE|^SET_TEMPERATURE|^LOWBAT$|^WINDOW_OPEN)", + cmdIcon => "Auto:sani_heating_automatic Manu:sani_heating_manual Boost:sani_heating_boost on:general_an off:general_aus", + controldatapoint => "2.SET_TEMPERATURE", + eventMap => "/datapoint 2.MANU_MODE 20.0:Manu/datapoint 2.AUTO_MODE 1:Auto/datapoint 2.BOOST_MODE 1:Boost/datapoint 2.MANU_MODE 4.5:off/datapoint 2.MANU_MODE 30.5:on/", + statechannel => 2, + statedatapoint => "SET_TEMPERATURE", + stripnumber => 1, + substitute => "LOWBAT!(0|false):no,(1|true):yes;CONTROL_MODE!0:AUTO,1:MANU,2:PARTY,3:BOOST;WINDOW_OPEN_REPORTING!(true|1):open,(false|0):closed", + webCmd => "control:Auto:Manu:Boost:on:off", + widgetOverride => "control:slider,10,1,25" + }, + "HM-CC-RT-DN" => { # Heizkörperthermostat + ccureadingfilter => "(UNREACH|LOWBAT|TEMPERATURE|VALVE_STATE|CONTROL)", + cmdIcon => "Auto:sani_heating_automatic Manu:sani_heating_manual Boost:sani_heating_boost on:general_an off:general_aus", + controldatapoint => "4.SET_TEMPERATURE", + eventMap => "/datapoint 4.MANU_MODE 20.0:Manu/datapoint 4.AUTO_MODE 1:Auto/datapoint 4.BOOST_MODE 1:Boost/datapoint 4.MANU_MODE 4.5:off/datapoint 4.MANU_MODE 30.5:on/", + statechannel => 4, + statedatapoint => "SET_TEMPERATURE", + stripnumber => 1, + substitute => "LOWBAT!(0|false):no,(1|true):yes;CONTROL_MODE!0:AUTO,1:MANU,2:PARTY,3:BOOST", + webCmd => "control:Auto:Manu:Boost:on:off", + widgetOverride => "control:slider,10,1,25" + }, + "HM-WDS40-TH-I" => { # Temperatur/Luftfeuchte Sensor + ccureadingfilter => "(UNREACH|^HUMIDITY|^TEMPERATURE|^LOWBAT$)", + statechannel => 1, + statedatapoint => "TEMPERATURE", + stripnumber => 1, + substitute => "LOWBAT!(0|false):no,(1|true):yes" + }, + "HM-ES-TX-WM" => { # Stromzähler Sensor + ccureadingfilter => "(UNREACH|LOWBAT|^ENERGY_COUNTER|^POWER)", + substitute => "LOWBAT!(true|1):yes,(false|0):no" + }, + "HM-CC-VG-1" => { # Heizungsgruppe + ccureadingfilter => "(^SET_TEMPERATURE|^TEMPERATURE|^HUMIDITY|LOWBAT$|^VALVE|^CONTROL|^WINDOW_OPEN)", + cmdIcon => "Auto:sani_heating_automatic Manu:sani_heating_manual Boost:sani_heating_boost on:general_an off:general_aus", + controldatapoint => "1.SET_TEMPERATURE", + eventMap => "/datapoint 1.MANU_MODE 20.0:Manu/datapoint 1.AUTO_MODE 1:Auto/datapoint 1.BOOST_MODE 1:Boost/datapoint 1.MANU_MODE 4.5:off/datapoint 1.MANU_MODE 30.5:on/", + statechannel => 1, + statedatapoint => "SET_TEMPERATURE", + stripnumber => 1, + substitute => "LOWBAT!(0|false):no,(1|true):yes;CONTROL_MODE!0:AUTO,1:MANU,2:PARTY,3:BOOST;WINDOW_OPEN_REPORTING!(true|1):open,(false|0):closed", + webCmd => "control:Auto:Manu:Boost:on:off", + widgetOverride => "control:slider,10,1,25" + } +); + +1; diff --git a/fhem/contrib/HMCCU/FHEM/RPCQueue.pm b/fhem/contrib/HMCCU/FHEM/RPCQueue.pm index de42b9c1a..93acbdf8f 100644 --- a/fhem/contrib/HMCCU/FHEM/RPCQueue.pm +++ b/fhem/contrib/HMCCU/FHEM/RPCQueue.pm @@ -1,6 +1,5 @@ package RPCQueue; - use strict; use IO::File; use Fcntl 'SEEK_END', 'SEEK_SET', 'O_CREAT', 'O_RDWR'; diff --git a/fhem/contrib/HMCCU/FHEM/ccurpcd.pl b/fhem/contrib/HMCCU/FHEM/ccurpcd.pl index 5f67dd62f..284bbf14b 100755 --- a/fhem/contrib/HMCCU/FHEM/ccurpcd.pl +++ b/fhem/contrib/HMCCU/FHEM/ccurpcd.pl @@ -50,7 +50,7 @@ my $shutdown = 0; my $eventcount = 0; my $totalcount = 0; my $loglevel = 0; -my %ev = ('total', 0, 'EV', 0, 'ND', 0, 'DD', 0, 'UD', 0, 'RD', 0, 'RA', 0, 'IN', 0, 'EX', 0); +my %ev = ('total', 0, 'EV', 0, 'ND', 0, 'DD', 0, 'UD', 0, 'RD', 0, 'RA', 0, 'SL', 0, 'IN', 0, 'EX', 0); # Functions sub CheckProcess ($$); @@ -244,11 +244,11 @@ sub CCURPC_NewDevicesCB ($$$) { my ($server, $cb, $a) = @_; - $ev{total}++; - $ev{ND}++; Log "NewDevice: received ".scalar(@$a)." device specifications"; for my $dev (@$a) { + $ev{total}++; + $ev{ND}++; WriteQueue ("ND|".$dev->{ADDRESS}."|".$dev->{TYPE}); } @@ -264,11 +264,11 @@ sub CCURPC_DeleteDevicesCB ($$$) { my ($server, $cb, $a) = @_; - $ev{total}++; - $ev{DD}++; Log "DeleteDevice: received ".scalar(@$a)." device addresses"; for my $dev (@$a) { + $ev{total}++; + $ev{DD}++; WriteQueue ("DD|".$dev); } @@ -313,11 +313,11 @@ sub CCURPC_ReaddDevicesCB ($$$) { my ($server, $cb, $a) = @_; - $ev{total}++; - $ev{RA}++; Log "ReaddDevice: received ".scalar(@$a)." device addresses"; for my $dev (@$a) { + $ev{total}++; + $ev{RA}++; WriteQueue ("RA|".$dev); } @@ -339,6 +339,12 @@ sub CCURPC_EventCB ($$$$$) $eventcount++; if (($eventcount % 500) == 0 && $loglevel == 2) { Log "Received $eventcount events from CCU since last check"; + my @stkeys = ('total', 'EV', 'ND', 'DD', 'RD', 'RA', 'UD', 'IN', 'SL', 'EX'); + my $msg = "ST"; + foreach my $stkey (@stkeys) { + $msg .= "|".$ev{$stkey}; + } + WriteQueue ($msg); } # Never remove this statement! @@ -434,3 +440,4 @@ if ($loglevel == 2) { Log "Events $cnt = ".$ev{$cnt}; } } +