diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm index 3b63b92eb..6dfee87b3 100644 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm @@ -4,7 +4,10 @@ # # $Id$ # -# Version 2.9 +# Version 3.0 +# +# Module for communication between FHEM and Homematic CCU2. +# Supports BidCos-RF, BidCos-Wired, HmIP-RF. # # (c) 2016 zap (zap01 t-online de) # @@ -12,34 +15,36 @@ # # define HMCCU # -# set devstate [...] +# set config {|} [] = [...] # set datapoint {|}. [...] -# set var [...] +# set devstate [...] # set execute # set hmscript -# set config {|} [] = [...] # set restartrpc +# set var [...] # -# get devicelist [dump] -# get devstate [] -# get vars # get channel {|}[.][=] -# get datapoint . [] -# get parfile [] # get config {|} [] # get configdesc {|} [] +# get datapoint . [] # get deviceinfo +# get devicelist [dump] +# get devstate [] +# get dump {devtypes|datapoints} [] +# get parfile [] # get rpcstate # get update [ [{ State | Value }]] # get updateccu [ [{ State | Value }]] +# get vars # +# attr ccuflags { singlerpc,extrpc,dptnocheck } # attr ccuget { State | Value } # attr ccureadingfilter # attr ccureadingformat { name | address } # attr ccureadings { 0 | 1 } # attr ccutrace {|} # attr parfile -# attr rpcinterval { 3 | 5 | 10 } +# attr rpcinterval # attr rpcport # attr rpcqueue # attr rpcserver { on | off } @@ -59,12 +64,15 @@ package main; use strict; use warnings; use SetExtensions; +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 FindBin qw($Bin); +# use lib "$Bin"; +# use RPCQueue; # Initial interval for reading RPC queue my $HMCCU_INIT_INTERVAL = 10; @@ -78,6 +86,17 @@ my %HMCCU_Addresses; # Last event from CCU # my $HMCCU_EventTime = 0; +# Datapoint operations +my $HMCCU_OPER_READ = 1; +my $HMCCU_OPER_WRITE = 2; +my $HMCCU_OPER_EVENT = 4; + +# Datapoint types +my $HMCCU_TYPE_BINARY = 2; +my $HMCCU_TYPE_FLOAT = 4; +my $HMCCU_TYPE_INTEGER = 16; +my $HMCCU_TYPE_STRING = 20; + # Flags for CCU object specification my $HMCCU_FLAG_NAME = 1; my $HMCCU_FLAG_CHANNEL = 2; @@ -123,7 +142,9 @@ sub HMCCU_IsRPCStateBlocking ($); sub HMCCU_IsRPCServerRunning ($$$); sub HMCCU_CheckProcess ($$); sub HMCCU_GetDeviceInfo ($$$); +sub HMCCU_FormatDeviceInfo ($); sub HMCCU_GetDeviceList ($); +sub HMCCU_GetDatapointList ($); sub HMCCU_GetAddress ($$$); sub HMCCU_IsDevAddr ($$); sub HMCCU_IsChnAddr ($$); @@ -133,6 +154,8 @@ sub HMCCU_GetHash ($@); sub HMCCU_GetAttribute ($$$$); sub HMCCU_GetSpecialDatapoints ($$$$$); sub HMCCU_IsValidDevice ($); +sub HMCCU_GetValidDatapoints ($$$$$); +sub HMCCU_IsValidDatapoint ($$$$$); sub HMCCU_GetMatchingDevices ($$$$); sub HMCCU_GetDeviceName ($$); sub HMCCU_GetChannelName ($$); @@ -151,6 +174,15 @@ sub HMCCU_UpdateDeviceReadings ($$); sub HMCCU_GetChannel ($$); sub HMCCU_RPCGetConfig ($$$$); sub HMCCU_RPCSetConfig ($$$); + +# File queue functions +sub HMCCU_QueueOpen ($$); +sub HMCCU_QueueClose ($); +sub HMCCU_QueueReset ($); +sub HMCCU_QueueEnq ($$); +sub HMCCU_QueueDeq ($); + +# Helper functions sub HMCCU_AggReadings ($$$$$); sub HMCCU_Dewpoint ($$$$); @@ -170,17 +202,7 @@ sub HMCCU_Initialize ($) $hash->{AttrFn} = "HMCCU_Attr"; $hash->{ShutdownFn} = "HMCCU_Shutdown"; - $hash->{AttrList} = "stripchar stripnumber:0,1,2 ccureadings:0,1 ccureadingfilter ccureadingformat:name,address rpcinterval:3,5,10 rpcqueue rpcport rpcserver:on,off parfile statedatapoint statevals substitute updatemode:client,both,hmccu ccutrace ccuget:Value,State ". $readingFnAttributes; - - # Get list of CCU devices -# foreach my $d (keys %defs) { -# my $h = $defs{$d}; -# if ($h->{TYPE} eq "HMCCU") { -# HMCCU_GetDeviceList ($h); -# DoTrigger ($h->{NAME}, "Module HMCCU reloaded"); -# last; -# } -# } + $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; } ##################################### @@ -222,14 +244,7 @@ sub HMCCU_Attr ($@) if ($attrname eq "rpcport") { foreach my $port (split (",", $attrval)) { my $clkey = 'rpccl'.$port; - $hash->{hmccu}{$clkey}{cburl} = ''; - } - } - elsif ($attrname eq 'rpcport') { - my @hm_pids; - my @ex_pids; - if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids)) { - return "HMCCU: Restart RPC server for port changes to take effect"; + $hash->{hmccu}{rpc}{$clkey}{cburl} = ''; } } elsif ($attrname eq "rpcserver") { @@ -240,9 +255,6 @@ sub HMCCU_Attr ($@) } elsif ($attrval eq 'off') { HMCCU_StopRPCServer ($hash); -# -# Do not remove internal timer. Wait for 'EX' event to arrive -# RemoveInternalTimer ($hash); } } } @@ -396,8 +408,8 @@ sub HMCCU_Set ($@) elsif ($opt eq 'config') { my $ccuobj = shift @a; - return HMCCU_SetError ($hash, - "Usage: set $name config {devicename|deviceaddress|channelname|channeladdress} {param=value} [...]") if (!defined ($ccuobj) || @a < 1); + return HMCCU_SetError ($hash, "Usage: set $name config {device|channel} {param=value} [...]") + if (!defined ($ccuobj) || @a < 1); my $rc = HMCCU_RPCSetConfig ($hash, $ccuobj, \@a); @@ -435,7 +447,7 @@ sub HMCCU_Get ($@) my ($hash, @a) = @_; my $name = shift @a; my $opt = shift @a; - my $options = "devicelist:noArg devstate datapoint vars channel update updateccu parfile config configdesc rpcstate:noArg deviceinfo"; + my $options = "devicelist:noArg devstate datapoint dump vars channel update updateccu parfile config configdesc rpcstate:noArg deviceinfo"; my $host = $hash->{host}; if (HMCCU_IsRPCStateBlocking ($hash)) { @@ -459,10 +471,8 @@ sub HMCCU_Get ($@) my $ccuobj = shift @a; my $reading = shift @a; - if (!defined ($ccuobj)) { - return HMCCU_SetError ($hash, - "Usage: get $name devstate {channel-name|channel-address} [reading]"); - } + return HMCCU_SetError ($hash, "Usage: get $name devstate {channel} [reading]") + if (!defined ($ccuobj)); $reading = '' if (!defined ($reading)); ($rc, $result) = HMCCU_GetDatapoint ($hash, $ccuobj.'.'.$statedatapoint, $reading); @@ -473,8 +483,8 @@ sub HMCCU_Get ($@) my $ccuobj = shift @a; my $reading = shift @a; - return HMCCU_SetError ($hash, - "Usage: get $name datapoint {channel-name|channel-address}.{datapoint} [reading]") if (!defined ($ccuobj)); + return HMCCU_SetError ($hash, "Usage: get $name datapoint {channel}.{datapoint} [reading]") + if (!defined ($ccuobj)); $reading = '' if (!defined ($reading)); ($rc, $result) = HMCCU_GetDatapoint ($hash, $ccuobj, $reading); @@ -483,10 +493,48 @@ sub HMCCU_Get ($@) HMCCU_SetState ($hash, "OK"); return $ccureadings ? undef : $result; } + elsif ($opt eq 'dump') { + my $content = shift @a; + my $filter = shift @a; + $filter = '.*' if (!defined ($filter)); + + my %foper = (1, "R", 2, "W", 4, "E", 3, "RW", 5, "RE", 6, "WE", 7, "RWE"); + my %ftype = (2, "B", 4, "F", 16, "I", 20, "S"); + + return HMCCU_SetError ($hash, "Usage: get $name dump {datapoints|devtypes} [filter]") + if (!defined ($content)); + + if ($content eq 'devtypes') { + foreach my $devtype (sort keys %{$hash->{hmccu}{dp}}) { + $result .= $devtype."\n" if ($devtype =~ /$filter/); + } + } + elsif ($content eq 'datapoints') { + foreach my $devtype (sort keys %{$hash->{hmccu}{dp}}) { + next if ($devtype !~ /$filter/); + foreach my $chn (sort keys %{$hash->{hmccu}{dp}{$devtype}{ch}}) { + foreach my $dpt (sort keys %{$hash->{hmccu}{dp}{$devtype}{ch}{$chn}}) { + my $t = $hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dpt}{type}; + my $o = $hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dpt}{oper}; + $result .= $devtype.".".$chn.".".$dpt." [". + (exists($ftype{$t}) ? $ftype{$t} : $t)."] [". + (exists($foper{$o}) ? $foper{$o} : $o)."]\n"; + } + } + } + } + else { + return HMCCU_SetError ($hash, "Usage: get $name dump {datapoints|devtypes} {filter}"); + } + + return "No data found" if ($result eq ''); + return $result; + } elsif ($opt eq 'vars') { my $varname = shift @a; - return HMCCU_SetError ($hash, "Usage: get $name vars {regexp}[,...]") if (!defined ($varname)); + return HMCCU_SetError ($hash, "Usage: get $name vars {regexp}[,...]") + if (!defined ($varname)); ($rc, $result) = HMCCU_GetVariables ($hash, $varname); return HMCCU_SetError ($hash, $rc) if ($rc < 0); @@ -504,9 +552,9 @@ sub HMCCU_Get ($@) } push (@chnlist, $objname); } - if (@chnlist == 0) { - return HMCCU_SetError ($hash, "Usage: get $name channel {channel-name|channel-address}[.{datapoint-expr}] [...]"); - } + + return HMCCU_SetError ($hash, "Usage: get $name channel {channel}[.{datapoint-expr}] [...]") + if (@chnlist == 0); ($rc, $result) = HMCCU_GetChannel ($hash, \@chnlist); return HMCCU_SetError ($hash, $rc) if ($rc < 0); @@ -561,17 +609,18 @@ sub HMCCU_Get ($@) elsif ($opt eq 'deviceinfo') { my $device = shift @a; - return HMCCU_SetError ($hash, "Usage: get $name deviceinfo {device-name|device-address} [{'State'|'Value'}]") if (!defined ($device)); + return HMCCU_SetError ($hash, "Usage: get $name deviceinfo {device} [{'State'|'Value'}]") + if (!defined ($device)); my $ccuget = shift @a; $ccuget = 'Attr' if (!defined ($ccuget)); - if ($ccuget !~ /^(Attr|State|Value)$/) { - return HMCCU_SetError ($hash, "Usage: get $name deviceinfo {device-name|device-address} [{'State'|'Value'}]"); - } + return HMCCU_SetError ($hash, "Usage: get $name deviceinfo {device} [{'State'|'Value'}]") + if ($ccuget !~ /^(Attr|State|Value)$/); + return HMCCU_SetError ($hash, -1) if (!HMCCU_IsValidDevice ($device)); $result = HMCCU_GetDeviceInfo ($hash, $device, $ccuget); return HMCCU_SetError ($hash, -2) if ($result eq ''); - return $result; + return HMCCU_FormatDeviceInfo ($result); } elsif ($opt eq 'rpcstate') { my @pidlist; @@ -613,8 +662,8 @@ sub HMCCU_Get ($@) my $ccuobj = shift @a; my $port = shift @a; - return HMCCU_SetError ($hash, - "Usage: get $name config {devicename|deviceaddress|channelname|channeladdress}") if (!defined ($ccuobj)); + return HMCCU_SetError ($hash, "Usage: get $name config {device|channel}") + if (!defined ($ccuobj)); $port = 2001 if (!defined ($port)); my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset", $port); @@ -627,8 +676,8 @@ sub HMCCU_Get ($@) my $ccuobj = shift @a; my $port = shift @a; - return HMCCU_SetError ($hash, - "Usage: get $name configdesc {devicename|deviceaddress|channelname|channeladdress}") if (!defined ($ccuobj)); + return HMCCU_SetError ($hash, "Usage: get $name configdesc {device|channel}") + if (!defined ($ccuobj)); $port = 2001 if (!defined ($port)); my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription", $port); @@ -667,28 +716,32 @@ sub HMCCU_ParseObject ($$) my ($object, $flags) = @_; my ($i, $a, $c, $d, $n, $f) = ('', '', '', '', '', '', 0); - if ($object =~ /^(.+?)\.([A-Z]{3,3}[0-9]{7,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] # $f = $HMCCU_FLAGS_IACD; ($i, $a, $c, $d) = ($1, $2, $3, $4); } - elsif ($object =~ /^(.+)\.([A-Z]{3,3}[0-9]{7,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] # $f = $HMCCU_FLAGS_IAC | ($flags & $HMCCU_FLAG_DATAPOINT); ($i, $a, $c, $d) = ($1, $2, $3, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : ''); } - elsif ($object =~ /^([A-Z]{3,3}[0-9]{7,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] # $f = $HMCCU_FLAGS_ACD | ($flags & $HMCCU_FLAG_INTERFACE); ($i, $a, $c, $d) = ($flags & $HMCCU_FLAG_INTERFACE ? 'BidCos-RF' : '', $1, $2, $3); } - elsif ($object =~ /^([A-Z]{3,3}[0-9]{7,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] # @@ -696,7 +749,8 @@ sub HMCCU_ParseObject ($$) ($i, $a, $c, $d) = ($flags & $HMCCU_FLAG_INTERFACE ? 'BidCos-RF' : '', $1, $2, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : ''); } - elsif ($object =~ /^([A-Z]{3,3}[0-9]{7,7})$/) { + elsif ($object =~ /^([A-Z]{3}[0-9]{7})$/ || + $object =~ /^([0-9A-Z]{14})$/) { # # Address # @@ -740,6 +794,7 @@ sub HMCCU_ParseObject ($$) ################################################################## # Filter reading by datapoint and optionally by channel name +# Parameters: hash, channel, datapoint ################################################################## sub HMCCU_FilterReading ($$$) @@ -750,7 +805,7 @@ sub HMCCU_FilterReading ($$$) my $rf = AttrVal ($name, 'ccureadingfilter', '.*'); return 1 if ($rf eq '.*'); - my $chnnam = HMCCU_GetChannelName ($chn, ''); + my $chnnam = HMCCU_IsChnAddr ($chn, 0) ? HMCCU_GetChannelName ($chn, '') : $chn; my @rules = split (",", $rf); foreach my $r (@rules) { @@ -789,6 +844,8 @@ sub HMCCU_GetReadingName ($$$$$$) my ($i, $a, $c, $d, $n, $rf) = @_; my $rn = ''; + Log3 undef, 1, "HMCCU: ChannelNo undefined: Addr=".$a if (!defined ($c)); + # Datapoint is mandatory return '' if ($d eq ''); @@ -860,18 +917,21 @@ sub HMCCU_SetError ($$) { my ($hash, $text) = @_; my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; my $msg; my %errlist = ( - -1 => 'Channel name or address invalid', + -1 => 'Invalid name or address', -2 => 'Execution of CCU script or command failed', -3 => 'Cannot detect IO device', -4 => 'Device deleted in CCU', -5 => 'No response from CCU', - -6 => 'Update of readings disabled. Set attribute ccureadings first' + -6 => 'Update of readings disabled. Set attribute ccureadings first', + -7 => 'Invalid channel number', + -8 => 'Invalid datapoint' ); $msg = exists ($errlist{$text}) ? $errlist{$text} : $text; - $msg = "HMCCU: ".$name." ". $msg; + $msg = $type.": ".$name." ". $msg; HMCCU_SetState ($hash, "Error"); Log3 $name, 1, $msg; @@ -1082,13 +1142,14 @@ sub HMCCU_UpdateClientReading ($@) } # 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', '', '', ''); - last if ($upd == 0); + last if ($upd == 0 || $dis == 1); next if (!HMCCU_FilterReading ($ch, $chnadd, $dpt)); # next if ($dpt eq '' || $dpt !~ /$flt/); @@ -1119,14 +1180,18 @@ 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) = HMCCU_ParseObject ($mr[1], $HMCCU_FLAG_FULLADDR); +# Log3 $name, 1, "HMCCU: f1 or f2 != FLAGS_AC" if (($f1 & $HMCCU_FLAGS_AC) != $HMCCU_FLAGS_AC || ($f2 & $HMCCU_FLAGS_AC) != $HMCCU_FLAGS_AC); next if (($f1 & $HMCCU_FLAGS_AC) != $HMCCU_FLAGS_AC || ($f2 & $HMCCU_FLAGS_AC) != $HMCCU_FLAGS_AC); +# Log3 $name, 1, "HMCCU: $devadd ne $a1 or $channel ne $c1 or $dpt ne $d1" if ($devadd ne $a1 || $channel ne $c1 || $dpt ne $d1); next if ($devadd ne $a1 || $channel ne $c1 || $dpt ne $d1); my $mreading = HMCCU_GetReadingName ('', $a2, $c2, $d2, '', $crf); +# Log3 $name, 1, "HMCCU: Can't get reading name for $a2, $c2, $d2" if ($mreading eq ''); next if ($mreading eq ''); readingsSingleUpdate ($ch, $mreading, $cl_value, 1); if ($cd ne '' && $d2 eq $cd && $c2 eq $cc) { @@ -1181,6 +1246,7 @@ sub HMCCU_StartRPCServer ($) my ($hash) = @_; my $name = $hash->{NAME}; + my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my $modpath = AttrVal ('global', 'modpath', '/opt/fhem'); my $logfile = $modpath."/log/ccurpcd"; my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue'); @@ -1196,7 +1262,7 @@ sub HMCCU_StartRPCServer ($) Log3 $name, 1, "HMCCU: RPC server(s) already running with PIDs ".join (',', @hm_pids); return scalar (@hm_pids); } - elsif (@ex_pids > 0) { + elsif (@ex_pids > 0 && $ccuflags !~ /extrpc/) { 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; } @@ -1204,16 +1270,19 @@ sub HMCCU_StartRPCServer ($) # 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"; + Log3 $name, 1, "HMCCU: RPC server file ccurpcd.pl not found in ".$modpath."/FHEM"; return 0; } # Clear RPC queues HMCCU_ResetRPCQueue ($hash); + my $fork_cnt = 0; + my $callbackport = 0; + # Fork child process(es) foreach my $port (split (',', $rpcport)) { - my $callbackport = 5400+$port; + $callbackport = 5400+$port if ($callbackport == 0 || $ccuflags !~ /singlerpc/); # Detect local IP if ($localaddr eq '') { @@ -1240,35 +1309,39 @@ sub HMCCU_StartRPCServer ($) my $rpcqueueport = $rpcqueue."_".$port; my $logfileport = $logfile."_".$port.".log"; - my $pid = fork (); - if (!defined ($pid)) { - Log3 $name, 1, "HMCCU: Can't fork child process for CCU port $port"; - next; - } + if ($fork_cnt == 0 || $ccuflags !~ /singlerpc/) { + 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); - if (!$pid) { - # Child process. Replaced by RPC server - exec ($rpcserver." ".$serveraddr." ".$port." ".$rpcqueueport." ".$logfileport); - - # When we reach this line start of RPC server failed and child process can exit - die; - } - - # Parent process - # Wait 2 seconds to ensure that RPC server is listening - sleep (2); + # When we reach this line start of RPC server failed and child process can exit + die; + } + + # Parent process + # Wait 2 seconds to ensure that RPC server is listening + sleep (2); + + # Store PID + push (@hm_pids, $pid); + $hash->{hmccu}{rpc}{$clkey}{pid} = $pid; + $hash->{hmccu}{rpc}{$clkey}{queue} = $rpcqueueport; + Log3 $name, 0, "HMCCU: RPC server started with pid ".$pid; + $fork_cnt++; + } + # Register callback in CCU my $callbackurl = "http://".$localaddr.":".$callbackport."/fh".$port; - $hash->{hmccu}{$clkey}{cburl} = $callbackurl; + $hash->{hmccu}{rpc}{$clkey}{cburl} = $callbackurl; 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"; - - # Store PID - push (@hm_pids, $pid); - $hash->{hmccu}{$clkey}{pid} = $pid; - Log3 $name, 0, "HMCCU: RPC server started with pid ".$pid; } $hash->{RPCPID} = join (',', @hm_pids); @@ -1288,19 +1361,21 @@ sub HMCCU_StopRPCServer ($) my ($hash) = @_; my $name = $hash->{NAME}; + my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); my $rpcport = AttrVal ($name, 'rpcport', '2001'); my $serveraddr = $hash->{host}; # Deregister callback URLs in CCU foreach my $port (split (',', $rpcport)) { my $clkey = 'rpccl'.$port; - if (exists ($hash->{hmccu}{$clkey}{cburl}) && $hash->{hmccu}{$clkey}{cburl} ne '') { + if (exists ($hash->{hmccu}{rpc}{$clkey}{cburl}) && $hash->{hmccu}{rpc}{$clkey}{cburl} ne '') { my $rpcclient = RPC::XML::Client->new ("http://$serveraddr:$port/"); - Log3 $name, 1, "HMCCU: Deregistering RPC server ".$hash->{hmccu}{$clkey}{cburl}; - $rpcclient->send_request("init", $hash->{hmccu}{$clkey}{cburl}); - $hash->{hmccu}{$clkey}{cburl} = ''; + Log3 $name, 1, "HMCCU: Deregistering RPC server ".$hash->{hmccu}{rpc}{$clkey}{cburl}; + $rpcclient->send_request("init", $hash->{hmccu}{rpc}{$clkey}{cburl}); } } + + delete $hash->{hmccu}{rpc}; # Send signal SIGINT to RPC server processes my @hm_pids; @@ -1318,7 +1393,7 @@ sub HMCCU_StopRPCServer ($) return 0; } - if (@ex_pids > 0) { + if (@ex_pids > 0 && $ccuflags !~ /extrpc/) { foreach my $pid (@ex_pids) { Log3 $name, 0, "HMCCU: Externally launched RPC server detected. Kill process manually with command kill -SIGINT $pid"; } @@ -1447,11 +1522,17 @@ object odev = dom.GetObject ("$devname"); if (odev) { foreach (chnid, odev.Channels()) { object ochn = dom.GetObject(chnid); - WriteLine("Channel " # ochn.Address() # " " # ochn.Name()); - foreach(sDPId, ochn.DPs()) { - object oDP = dom.GetObject(sDPId); - if (oDP) { - WriteLine (" DP " # oDP.Name() # " = " # oDP.$ccuget()); + if (ochn) { + foreach(sDPId, ochn.DPs()) { + object oDP = dom.GetObject(sDPId); + if (oDP) { + integer op = oDP.Operations(); + string flags = ""; + if (OPERATION_READ & op) { flags = flags # "R"; } + if (OPERATION_WRITE & op) { flags = flags # "W"; } + if (OPERATION_EVENT & op) { flags = flags # "E"; } + WriteLine ("C;" # ochn.Address() # ";" # ochn.Name() # ";" # oDP.Name() # ";" # oDP.ValueType() # ";" # oDP.$ccuget() # ";" # flags); + } } } } @@ -1467,6 +1548,31 @@ if (odev) { return $response; } +#################################################### +# Make device info readable +#################################################### + +sub HMCCU_FormatDeviceInfo ($) +{ + my ($devinfo) = @_; + + my %vtypes = (2, "b", 4, "f", 11, "s", 16, "i", 20, "s", 29, "e"); + my $result = ''; + my $c_oaddr = ''; + + foreach my $dpspec (split ("\n", $devinfo)) { + my ($c, $c_addr, $c_name, $d_name, $d_type, $d_value, $d_flags) = split (";", $dpspec); + if ($c_addr ne $c_oaddr) { + $result .= "CHN $c_addr $c_name\n"; + $c_oaddr = $c_addr; + } + my $t = exists ($vtypes{$d_type}) ? $vtypes{$d_type} : $d_type; + $result .= " DPT {$t} $d_name = $d_value [$d_flags]\n"; + } + + return $result; +} + #################################################### # Read list of CCU devices via Homematic Script. # Update data of client devices if not current. @@ -1529,6 +1635,8 @@ foreach(devid, root.Devices().EnumUsedIDs()) { } } + HMCCU_GetDatapointList ($hash); + # Update client devices foreach my $d (keys %defs) { # Get hash of client device @@ -1558,6 +1666,80 @@ foreach(devid, root.Devices().EnumUsedIDs()) { return $count; } +#################################################### +# Read list of datapoints for CCU device types. +# Function must not be called before GetDeviceList. +# Return number of datapoints. +#################################################### + +sub HMCCU_GetDatapointList ($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + if (exists ($hash->{hmccu}{dp})) { + delete $hash->{hmccu}{dp}; + } + + # Get unique device types + my %alltypes; + my @devunique; + foreach my $add (sort keys %HMCCU_Devices) { + next if ($HMCCU_Devices{$add}{addtype} ne 'dev' || + $HMCCU_Devices{$add}{interface} eq 'CUxD'); + my $dt = $HMCCU_Devices{$add}{type}; + if ($dt ne '' && !exists ($alltypes{$dt})) { + $alltypes{$dt} = 1; + push @devunique, $HMCCU_Devices{$add}{name}; + } + } + my $devlist = join (',', @devunique); + + my $script = qq( +string chnid; +string sDPId; +string sDevice; +string sDevList = "$devlist"; +foreach (sDevice, sDevList.Split(",")) { + object odev = dom.GetObject (sDevice); + if (odev) { + string sType = odev.HssType(); + foreach (chnid, odev.Channels()) { + object ochn = dom.GetObject(chnid); + if (ochn) { + string sAddr = ochn.Address(); + string sChnNo = sAddr.StrValueByIndex(":",1); + foreach(sDPId, ochn.DPs()) { + object oDP = dom.GetObject(sDPId); + if (oDP) { + string sDPName = oDP.Name().StrValueByIndex(".",2); + WriteLine (sType # ";" # sChnNo # ";" # sDPName # ";" # oDP.ValueType() # ";" # oDP.Operations()); + } + } + } + } + } +} + ); + + my $response = HMCCU_HMScript ($hash, $script); + return 0 if ($response eq ''); +# Log3 $name, 1, $response; + + my $c = 0; + foreach my $dpspec (split /\n/,$response) { + my ($devt, $devc, $dptn, $dptt, $dpto) = split (";", $dpspec); + $hash->{hmccu}{$devt}{ontime} = $devc.".".$dptn if ($dptn eq "ON_TIME"); + $hash->{hmccu}{dp}{$devt}{ch}{$devc}{$dptn}{type} = $dptt; + $hash->{hmccu}{dp}{$devt}{ch}{$devc}{$dptn}{oper} = $dpto; + $c++; + } + +# Log3 $name, 1, Dumper($hash->{hmccu}{dp}); + + return $c; +} + #################################################### # Check if device/channel name or address is valid # and refers to an existing device or channel. @@ -1567,7 +1749,7 @@ sub HMCCU_IsValidDevice ($) { my ($param) = @_; - if ($param =~ /^[A-Z]{3,3}[0-9]{7,7}$/ || $param =~ /^[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) { + if (HMCCU_IsDevAddr ($param, 0) || HMCCU_IsChnAddr ($param, 0)) { return 0 if (! exists ($HMCCU_Devices{$param})); return $HMCCU_Devices{$param}{valid}; } @@ -1578,9 +1760,89 @@ sub HMCCU_IsValidDevice ($) } #################################################### -# Get list of device or channel addresses with -# device or channel name matching regular expression. -# Parameter mode can be dev or chn. +# Get list of valid datapoints for device type. +# hash = hash of client or IO device +# devtype = Homematic device type +# chn = Channel number, -1=all channels +# oper = Valid operation: 1=Read, 2=Write, 4=Event +# dplistref = Reference for array with datapoints. +# Return number of datapoints. +#################################################### + +sub HMCCU_GetValidDatapoints ($$$$$) +{ + my ($hash, $devtype, $chn, $oper, $dplistref) = @_; + + my $hmccu_hash = HMCCU_GetHash ($hash); + + my $ccuflags = AttrVal ($hmccu_hash->{NAME}, 'ccuflags', 'null'); + return 0 if ($ccuflags =~ /dptnocheck/); + + return 0 if (!exists ($hmccu_hash->{hmccu}{dp})); + + if ($chn >= 0) { + if (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn})) { + foreach my $dp (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn}}) { + if ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dp}{oper} & $oper) { + push @$dplistref, $dp; + } + } + } + } + else { + if (exists ($hmccu_hash->{hmccu}{dp}{$devtype})) { + foreach my $ch (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}}) { + foreach my $dp (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$ch}}) { + if ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$ch}{$dp}{oper} & $oper) { + push @$dplistref, $ch.".".$dp; + } + } + } + } + } + + return scalar (@$dplistref); +} + +#################################################### +# Check if datapoint is valid. +# Parameter chn can be a channel address or a channel +# number. +# Return 1 if datapoint information is not available +# in IO device. +#################################################### + +sub HMCCU_IsValidDatapoint ($$$$$) +{ + my ($hash, $devtype, $chn, $dpt, $oper) = @_; + + my $hmccu_hash = HMCCU_GetHash ($hash); + + my $ccuflags = AttrVal ($hmccu_hash->{NAME}, 'ccuflags', 'null'); + return 1 if ($ccuflags =~ /dptnocheck/); + + return 1 if (!exists ($hmccu_hash->{hmccu}{dp})); + + my $chnno = $chn; + if (HMCCU_IsChnAddr ($chn, 0)) { + my ($a, $c) = split(":",$chn); + $chnno = $c; + } + + # If datapoint name has format channel-number.datapoint ignore parameter chn + if ($dpt =~ /^([0-9]{1,2})\.(.+)$/) { + $chnno = $1; + $dpt = $2; + } + + return (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chnno}{$dpt}) && + ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chnno}{$dpt}{oper} & $oper)) ? 1 : 0; +} + +#################################################### +# Get list of device or channel addresses for which +# device or channel name matches regular expression. +# Parameter mode can be 'dev' or 'chn'. # Return number of matching entries. #################################################### @@ -1608,7 +1870,7 @@ sub HMCCU_GetDeviceName ($$) { my ($addr, $default) = @_; - if ($addr =~ /^[A-Z]{3,3}[0-9]{7,7}$/ || $addr =~ /^[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) { + if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) { $addr =~ s/:[0-9]+$//; if (exists ($HMCCU_Devices{$addr})) { return $HMCCU_Devices{$addr}{name}; @@ -1626,7 +1888,7 @@ sub HMCCU_GetChannelName ($$) { my ($addr, $default) = @_; - if ($addr =~ /^[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) { + if (HMCCU_IsChnAddr ($addr, 0)) { if (exists ($HMCCU_Devices{$addr})) { return $HMCCU_Devices{$addr}{name}; } @@ -1644,7 +1906,7 @@ sub HMCCU_GetDeviceType ($$) { my ($addr, $default) = @_; - if ($addr =~ /^[A-Z]{3,3}[0-9]{7,7}$/ || $addr =~ /^[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) { + if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) { $addr =~ s/:[0-9]+$//; if (exists ($HMCCU_Devices{$addr})) { return $HMCCU_Devices{$addr}{type}; @@ -1664,7 +1926,7 @@ sub HMCCU_GetDeviceChannels ($) { my ($addr, $default) = @_; - if ($addr =~ /^[A-Z]{3,3}[0-9]{7,7}$/ || $addr =~ /^[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) { + if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) { $addr =~ s/:[0-9]+$//; if (exists ($HMCCU_Devices{$addr})) { return $HMCCU_Devices{$addr}{channels}; @@ -1683,7 +1945,7 @@ sub HMCCU_GetDeviceInterface ($$) { my ($addr, $default) = @_; - if ($addr =~ /^[A-Z]{3,3}[0-9]{7,7}$/ || $addr =~ /^[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) { + if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) { $addr =~ s/:[0-9]+$//; if (exists ($HMCCU_Devices{$addr})) { return $HMCCU_Devices{$addr}{interface}; @@ -1705,24 +1967,26 @@ sub HMCCU_GetAddress ($$$) my $chn = $defchn; if (exists ($HMCCU_Addresses{$name})) { + # Address known by HMCCU my $addr = $HMCCU_Addresses{$name}{address}; - if ($addr =~ /^([A-Z]{3,3}[0-9]{7,7}):([0-9]+)$/) { - ($add, $chn) = ($1, $2); + if (HMCCU_IsChnAddr ($addr, 0)) { + ($add, $chn) = split (":", $addr); } - elsif ($addr =~ /^[A-Z]{3,3}[0-9]{7,7}$/) { + elsif (HMCCU_IsDevAddr ($addr, 0)) { $add = $addr; } } else { + # Address not known. Query CCU my $response = HMCCU_GetCCUObjectAttribute ($name, "Address()"); if (defined ($response)) { - if ($response =~ /^([A-Z]{3,3}[0-9]{7,7}):([0-9]+)$/) { - ($add, $chn) = ($1, $2); + if (HMCCU_IsChnAddr ($response, 0)) { + ($add, $chn) = split (":", $response); $HMCCU_Addresses{$name}{address} = $response; $HMCCU_Addresses{$name}{addtype} = 'chn'; } - elsif ($response =~ /^([A-Z]{3,3}[0-9]{7,7})$/) { - $add = $1; + elsif (HMCCU_IsDevAddr ($response, 0)) { + $add = $response; $HMCCU_Addresses{$name}{address} = $response; $HMCCU_Addresses{$name}{addtype} = 'dev'; } @@ -1742,10 +2006,10 @@ sub HMCCU_IsChnAddr ($$) my ($id, $f) = @_; if ($f) { - return ($id =~ /^.+\.[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) ? 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,3}[0-9]{7,7}:[0-9]+$/) ? 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; } } @@ -1759,10 +2023,10 @@ sub HMCCU_IsDevAddr ($$) my ($id, $f) = @_; if ($f) { - return ($id =~ /^.+\.[A-Z]{3,3}[0-9]{7,7}$/) ? 1 : 0; + return ($id =~ /^.+\.[A-Z]{3}[0-9]{7}$/ || $id =~ /^.+\.[0-9A-F]{14}$/) ? 1 : 0; } else { - return ($id =~ /^[A-Z]{3,3}[0-9]{7,7}$/) ? 1 : 0; + return ($id =~ /^[A-Z]{3}[0-9]{7}$/ || $id =~ /^[0-9A-F]{14}$/) ? 1 : 0; } } @@ -1785,6 +2049,12 @@ sub HMCCU_SplitChnAddr ($) return ('', ''); } +#################################################### +# Query object attribute from CCU. Attribute must +# be a valid method for specified object, +# i.e. Address() +#################################################### + sub HMCCU_GetCCUObjectAttribute ($$) { my ($object, $attr) = @_; @@ -1820,7 +2090,7 @@ sub HMCCU_GetHash ($@) } } - # Search for HMCCU device + # Search for first HMCCU device foreach my $dn (sort keys %defs) { return $defs{$dn} if ($defs{$dn}->{TYPE} eq 'HMCCU'); } @@ -1846,6 +2116,8 @@ sub HMCCU_GetAttribute ($$$$) #################################################### # Get channels and datapoints from attributes # statechannel, statedatapoint and controldatapoint. +# Return attribute values. Attribute controldatapoint +# is splittet into controlchannel and datapoint name. #################################################### sub HMCCU_GetSpecialDatapoints ($$$$$) @@ -1872,7 +2144,7 @@ sub HMCCU_GetSpecialDatapoints ($$$$$) } #################################################### -# Clear RPC queue +# Clear RPC queue(s) #################################################### sub HMCCU_ResetRPCQueue ($) @@ -1885,9 +2157,16 @@ sub HMCCU_ResetRPCQueue ($) my @portlist = split (',', $rpcport); foreach my $port (@portlist) { - my $queue = new RPCQueue (File => $rpcqueue."_".$port, Mode => 0666); - $queue->reset(); - $queue->close(); + my $clkey = 'rpccl'.$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(); } } @@ -1899,7 +2178,6 @@ sub HMCCU_ReadRPCQueue ($) { my ($hash) = @_; my $name = $hash->{NAME}; - my $maxevents = 20; my $eventno = 0; my $f = 0; my @newdevices; @@ -1908,18 +2186,32 @@ sub HMCCU_ReadRPCQueue ($) my $newcount = 0; my $delcount = 0; + 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 $maxevents = $rpcinterval*10; + $maxevents = 50 if ($maxevents > 50); + $maxevents = 10 if ($maxevents < 10); my @portlist = split (',', $rpcport); foreach my $port (@portlist) { - my $queue = new RPCQueue (File => $rpcqueue."_".$port, Mode => 0666); + my $clkey = 'rpccl'.$port; + next if (!exists ($hash->{hmccu}{rpc}{$clkey}{queue})); + my $queuename = $hash->{hmccu}{rpc}{$clkey}{queue}; + if (!HMCCU_QueueOpen ($hash, $queuename)) { + Log3 $name, 2, "HMCCU: Can't open file queue $queuename"; + next; + } + # my $queue = new RPCQueue (File => $rpcqueue."_".$port, Mode => 0666); - my $element = $queue->deq(); + my $element = HMCCU_QueueDeq ($hash); + # my $element = $queue->deq(); while ($element) { +# Log3 $name, 2, "HMCCU: queueelement = $element"; + $hash->{hmccu}{eventtime} = time () if ($eventno == 0); my @Tokens = split (/\|/, $element); if ($Tokens[0] eq 'EV') { @@ -1975,10 +2267,12 @@ sub HMCCU_ReadRPCQueue ($) Log3 $name, 2, "HMCCU: Unknown RPC event type ".$Tokens[0]; } - $element = $queue->deq(); + $element = HMCCU_QueueDeq ($hash); + # $element = $queue->deq(); } - $queue->close(); + HMCCU_QueueClose ($hash); + # $queue->close(); } # Check if RPC server still running if events from CCU timed out @@ -2007,7 +2301,7 @@ sub HMCCU_ReadRPCQueue ($) my $nhm_pids = scalar (@hm_pids); my $nex_pids = scalar (@ex_pids); - if ($nex_pids > 0) { + 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"; } @@ -2142,9 +2436,9 @@ sub HMCCU_GetDatapoint ($@) } if (($reading =~ /\.$statedpt$/ || $reading eq $statedpt) && $ccureadings) { if ($statechn eq '' || $statechn eq $chn) { - HMCCU_SetState ($hash, $value); + HMCCU_SetState ($hash, $value); } - } + } } return (1, $value); @@ -2164,14 +2458,18 @@ sub HMCCU_SetDatapoint ($$$) my ($hash, $param, $value) = @_; my $type = $hash->{TYPE}; - my $hmccu_hash = HMCCU_GetHash ($hash);; + my $hmccu_hash = HMCCU_GetHash ($hash); return -3 if (!defined ($hmccu_hash)); return -4 if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'Deleted'); + my $name = $hmccu_hash->{NAME}; + + my $ccutrace = AttrVal ($name, 'ccutrace', ''); 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); if ($flags == $HMCCU_FLAGS_IACD) { $url .= $int.'.'.$add.':'.$chn.'.'.$dpt.'").State('.$value.')'; + $nam = HMCCU_GetChannelName ($add.":".$chn, ''); } elsif ($flags == $HMCCU_FLAGS_NCD) { $url .= $nam.'").DPByHssDP("'.$dpt.'").State('.$value.')'; @@ -2180,8 +2478,14 @@ sub HMCCU_SetDatapoint ($$$) else { return -1; } - + my $addr = $add.":".$chn; + 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 = \n".$url; + } return -2 if (!defined ($response) || $response =~ /null{NAME}; my $type = $cl_hash->{TYPE}; + my $disable = AttrVal ($name, 'disable', 0); + return 1 if ($disable == 1); + my $hmccu_hash = HMCCU_GetHash ($cl_hash); return -3 if (!defined ($hmccu_hash)); return -4 if ($type ne 'HMCCU' && $cl_hash->{ccudevstate} eq 'Deleted'); @@ -2357,6 +2664,8 @@ sub HMCCU_UpdateDeviceReadings ($$) my $uc = 0; my $cn = $cl_hash->{NAME}; + my $disable = AttrVal ($cn, 'disable', 0); + return 0 if ($disable == 1); my $ccureadings = AttrVal ($cn, 'ccureadings', 1); return -6 if ($ccureadings == 0); # my $ccureadingfilter = AttrVal ($cn, 'ccureadingfilter', '.*'); @@ -2645,6 +2954,117 @@ sub HMCCU_RPCSetConfig ($$$) return 0; } +sub HMCCU_QueueOpen ($$) +{ + my ($hash, $queue_file) = @_; + + my $idx_file = $queue_file . '.idx'; + $queue_file .= '.dat'; + my $mode = '0666'; + + $hash->{hmccu}{queue}{block_size} = 64; + $hash->{hmccu}{queue}{seperator} = "\n"; + $hash->{hmccu}{queue}{sep_length} = length $hash->{hmccu}{queue}{seperator}; + + $hash->{hmccu}{queue}{queue_file} = $queue_file; + $hash->{hmccu}{queue}{idx_file} = $idx_file; + + $hash->{hmccu}{queue}{queue} = new IO::File $queue_file, O_CREAT | O_RDWR, $mode or return 0; + $hash->{hmccu}{queue}{idx} = new IO::File $idx_file, O_CREAT | O_RDWR, $mode or return 0; + + ### Default ptr to 0, replace it with value in idx file if one exists + $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET); + $hash->{hmccu}{queue}{idx}->sysread($hash->{hmccu}{queue}{ptr}, 1024); + $hash->{hmccu}{queue}{ptr} = '0' unless $hash->{hmccu}{queue}{ptr}; + + if($hash->{hmccu}{queue}{ptr} > -s $queue_file) + { + $hash->{hmccu}{queue}{idx}->truncate(0) or return 0; + $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET); + $hash->{hmccu}{queue}{idx}->syswrite('0') or return 0; + } + + return 1; +} + +sub HMCCU_QueueClose ($) +{ + my ($hash) = @_; + + if (exists ($hash->{hmccu}{queue})) { + $hash->{hmccu}{queue}{idx}->close(); + $hash->{hmccu}{queue}{queue}->close(); + delete $hash->{hmccu}{queue}; + } +} + +sub HMCCU_QueueReset ($) +{ + my ($hash) = @_; + + $hash->{hmccu}{queue}{idx}->truncate(0) or return 0; + $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET); + $hash->{hmccu}{queue}{idx}->syswrite('0') or return 0; + + $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr} = 0, SEEK_SET); + + return 1; +} + +sub HMCCU_QueueEnq ($$) +{ + my ($hash, $element) = @_; + + return 0 if (!exists ($hash->{hmccu}{queue})); + + $hash->{hmccu}{queue}{queue}->sysseek(0, SEEK_END); + $element =~ s/$hash->{hmccu}{queue}{separator}//g; + $hash->{hmccu}{queue}{queue}->syswrite($element.$hash->{hmccu}{queue}{seperator}) or return 0; + + return 1; +} + +sub HMCCU_QueueDeq ($) +{ + my ($hash) = @_; + my $element; + + return undef if (!exists ($hash->{hmccu}{queue})); + + $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr}, SEEK_SET); + + my $i; + while($hash->{hmccu}{queue}{queue}->sysread($_, $hash->{hmccu}{queue}{block_size})) { + $i = index($_, $hash->{hmccu}{queue}{seperator}); + if($i != -1) { + $element .= substr($_, 0, $i); + $hash->{hmccu}{queue}{ptr} += $i + $hash->{hmccu}{queue}{sep_length}; + $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr}, SEEK_SET); + last; + } + 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}{queue}->sysseek($hash->{hmccu}{queue}{ptr}, SEEK_SET); + } + } + + ## If queue seek pointer is at the EOF, truncate the queue file + if($hash->{hmccu}{queue}{queue}->sysread($_, 1) == 0) + { + $hash->{hmccu}{queue}{queue}->truncate(0) or return undef; + $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr} = 0, SEEK_SET); + } + + ## Set idx file contents to point to the current seek position in queue file + $hash->{hmccu}{queue}{idx}->truncate(0) or return undef; + $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET); + $hash->{hmccu}{queue}{idx}->syswrite($hash->{hmccu}{queue}{ptr}) or return undef; + + return $element; +} + #################################################### # Aggregate readings. Valid operations are 'and', # 'or' or 'cnt'. @@ -2715,6 +3135,27 @@ sub HMCCU_Dewpoint ($$$$) return sprintf "%.1f", $td; } +#################################################### +# *** External process part *** +#################################################### + +# Initialize hash +# my %rpchash; +# $rpchash{hmccu}{pid} = $$; +# $rpchash{hmccu}{name} = $0; +# my $pash = \%rpchash; + +# foreach my $carg (@ARGV) { +# print "Arg=$carg\n"; +# } + +# 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; @@ -2868,7 +3309,7 @@ sub HMCCU_Dewpoint ($$$$)
  • parfile <filename>
    Define parameter file for command 'get parfile'.

  • -
  • rpcinterval <3 | 5 | 10>
    +
  • rpcinterval <Seconds>
    Specifiy how often RPC queue is read. Default is 5 seconds.

  • rpcport <value[,...]>
    diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm index 7b3564654..4d9927466 100644 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm @@ -4,7 +4,7 @@ # # $Id:$ # -# Version 2.7 +# Version 3.0 # # (c) 2016 zap (zap01 t-online de) # @@ -31,6 +31,7 @@ # attr ccureadingformat { name | address | datapoint } # attr ccuverify { 0 | 1 } # attr controldatapoint +# attr disable { 0 | 1 } # attr statevals :[,...] # attr substitute [;...] # @@ -65,7 +66,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 statedatapoint statevals substitute stripnumber:0,1,2 ". $readingFnAttributes; + $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; } ##################################### @@ -86,14 +87,14 @@ sub HMCCUCHN_Define ($@) return "Invalid or unknown CCU channel name or address" if (! HMCCU_IsValidDevice ($devspec)); - if ($devspec =~ /^(.+)\.([A-Z]{3,3}[0-9]{7,7}:[0-9]+)$/) { + if (HMCCU_IsChnAddr ($devspec, 1)) { # CCU Channel address with interface $hash->{ccuif} = $1; $hash->{ccuaddr} = $2; $hash->{ccuname} = HMCCU_GetChannelName ($hash->{ccuaddr}, ''); return "CCU device name not found for channel address $devspec" if ($hash->{ccuname} eq ''); } - elsif ($devspec =~ /^[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) { + elsif (HMCCU_IsChnAddr ($devspec, 0)) { # CCU Channel address $hash->{ccuaddr} = $devspec; $hash->{ccuif} = HMCCU_GetDeviceInterface ($hash->{ccuaddr}, 'BidCos-RF'); @@ -101,7 +102,7 @@ sub HMCCUCHN_Define ($@) return "CCU device name not found for channel address $devspec" if ($hash->{ccuname} eq ''); } else { - # CCU Device name + # CCU Channel name $hash->{ccuname} = $devspec; my ($add, $chn) = HMCCU_GetAddress ($devspec, '', ''); return "Channel address not found for channel name $devspec" if ($add eq '' || $chn eq ''); @@ -171,15 +172,13 @@ sub HMCCUCHN_Set ($@) my $name = shift @a; my $opt = shift @a; - if (!defined ($hash->{IODev})) { - return HMCCUCHN_SetError ($hash, "No IO device defined"); - } - if ($hash->{statevals} eq 'readonly' && $opt ne 'config') { - return undef; - } + return HMCCU_SetError ($hash, -3) if (!defined ($hash->{IODev})); + return undef if ($hash->{statevals} eq 'readonly' && $opt ne 'config'); + + my $disable = AttrVal ($name, "disable", 0); + return undef if ($disable == 1); my $hmccu_hash = $hash->{IODev}; - if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) { return undef if ($opt eq '?'); return "HMCCUCHN: CCU busy"; @@ -197,32 +196,33 @@ sub HMCCUCHN_Set ($@) my $objname = shift @a; my $objvalue = join ('%20', @a); - if (!defined ($objname) || !defined ($objvalue)) { - return HMCCUCHN_SetError ($hash, "Usage: set datapoint [...]"); - } + return HMCCU_SetError ($hash, "Usage: set $name datapoint {datapoint} {value} [...]") + if (!defined ($objname) || !defined ($objvalue)); + return HMCCU_SetError ($hash, -8) if (!HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, + $hash->{ccuaddr}, $objname, 2)); + $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); # Build datapoint address $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$objname; $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); if ($ccuverify) { usleep (100000); ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); } - + HMCCU_SetState ($hash, "OK"); return undef; } elsif ($opt eq 'control') { - return HMCCUCHN_SetError ($hash, "Attribute control datapoint not set") if ($controldatapoint eq ''); + return HMCCU_SetError ($hash, "Attribute controldatapoint not set") if ($controldatapoint eq ''); my $objvalue = shift @a; my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$controldatapoint; $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); - + return HMCCU_SetError ($hash, $rc) if ($rc < 0); HMCCU_SetState ($hash, "OK"); return undef; } @@ -230,27 +230,26 @@ sub HMCCUCHN_Set ($@) my $cmd = $1; my $objvalue = ($cmd ne 'devstate') ? $cmd : join ('%20', @a); - if (!defined ($objvalue)) { - return HMCCUCHN_SetError ($hash, "Usage: set devstate "); - } + return HMCCU_SetError ($hash, "Usage: set $name devstate {value}") if (!defined ($objvalue)); + $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); # Build datapoint address my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$statedatapoint; $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); if ($ccuverify) { usleep (100000); ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); } - + HMCCU_SetState ($hash, "OK"); return undef; } - elsif ($opt eq 'toggle') { - return HMCCUCHN_SetError ($hash, "Attribute statevals not set") + elsif ($opt eq 'toggle') { + return HMCCU_SetError ($hash, "Attribute statevals not set") if ($statevals eq '' || !exists($hash->{statevals})); my $tstates = $hash->{statevals}; @@ -260,7 +259,7 @@ sub HMCCUCHN_Set ($@) my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$statedatapoint; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); my $objvalue = ''; my $st = 0; @@ -274,22 +273,22 @@ sub HMCCUCHN_Set ($@) } } - return HMCCUCHN_SetError ($hash, "Current device state doesn't match statevals") + return HMCCU_SetError ($hash, "Current device state doesn't match statevals") if ($objvalue eq ''); $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); - + return HMCCU_SetError ($hash, $rc) if ($rc < 0); HMCCU_SetState ($hash, "OK"); return undef; } elsif ($opt eq 'config') { - return HMCCUCHN_SetError ($hash, "Usage: set $name config {parameter}={value} [...]") if (@a < 1);; + return HMCCU_SetError ($hash, "Usage: set $name config {parameter}={value} [...]") if (@a < 1);; - my $rc = HMCCU_RPCSetConfig ($hash, $hash->{ccuaddr}, \@a); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); - return undef; + my $rc = HMCCU_RPCSetConfig ($hash, $hash->{ccuaddr}, \@a); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); + HMCCU_SetState ($hash, "OK"); + return undef; } else { my $retmsg = "HMCCUCHN: Unknown argument $opt, choose one of config datapoint devstate"; @@ -319,12 +318,12 @@ sub HMCCUCHN_Get ($@) my $name = shift @a; my $opt = shift @a; - if (!defined ($hash->{IODev})) { - return HMCCUCHN_SetError ($hash, "No IO device defined"); - } + return HMCCU_SetError ($hash, -3) if (!defined ($hash->{IODev})); + + my $disable = AttrVal ($name, "disable", 0); + return undef if ($disable == 1); my $hmccu_hash = $hash->{IODev}; - if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) { return undef if ($opt eq '?'); return "HMCCUCHN: CCU busy"; @@ -339,16 +338,18 @@ sub HMCCUCHN_Get ($@) if ($opt eq 'devstate') { my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$statedatapoint; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); return $ccureadings ? undef : $result; } elsif ($opt eq 'datapoint') { my $objname = shift @a; - return HMCCUCHN_SetError ($hash, "Usage: get datapoint ") if (!defined ($objname)); + return HMCCU_SetError ($hash, "Usage: get $name datapoint {datapoint}") if (!defined ($objname)); + return HMCCU_SetError ($hash, -8) if (!HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, + $hash->{ccuaddr}, $objname, 1)); $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$objname; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); return $ccureadings ? undef : $result; } elsif ($opt eq 'channel') { @@ -357,17 +358,17 @@ sub HMCCUCHN_Get ($@) $objname .= '.'.$dptexpr if (defined ($dptexpr)); my @chnlist = ($objname); ($rc, $result) = HMCCU_GetChannel ($hash, \@chnlist); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); return $ccureadings ? undef : $result; } elsif ($opt eq 'update') { my $ccuget = shift @a; $ccuget = 'Attr' if (!defined ($ccuget)); if ($ccuget !~ /^(Attr|State|Value)$/) { - return HMCCUCHN_SetError ($hash, "Usage: get $name update [{'State'|'Value'}]"); + return HMCCU_SetError ($hash, "Usage: get $name update [{'State'|'Value'}]"); } $rc = HMCCU_GetUpdate ($hash, $hash->{ccuaddr}, $ccuget); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); return undef; } elsif ($opt eq 'config') { @@ -376,7 +377,7 @@ sub HMCCUCHN_Get ($@) $port = 2001 if (!defined ($port)); my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset", $port); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); return $ccureadings ? undef : $res; } elsif ($opt eq 'configdesc') { @@ -385,11 +386,19 @@ sub HMCCUCHN_Get ($@) $port = 2001 if (!defined ($port)); my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription", $port); - return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); return $res; } else { - return "HMCCUCHN: Unknown argument $opt, choose one of devstate:noArg datapoint channel update:noArg config:noArg configdesc:noArg"; + my $retmsg = "HMCCUCHN: Unknown argument $opt, choose one of devstate:noArg datapoint"; + + my ($a, $c) = split(":", $hash->{ccuaddr}); + my @valuelist; + my $valuecount = HMCCU_GetValidDatapoints ($hash, $hash->{ccutype}, $c, 1, \@valuelist); + $retmsg .= ":".join(",",@valuelist) if ($valuecount > 0); + $retmsg .= " channel update:noArg config:noArg configdesc:noArg"; + + return $retmsg; } } @@ -403,12 +412,12 @@ sub HMCCUCHN_SetError ($$) my $name = $hash->{NAME}; my $msg; my %errlist = ( - -1 => 'Channel name or address invalid', - -2 => 'Execution of CCU script failed', - -3 => 'Cannot detect IO device', - -4 => 'Device deleted in CCU', - -5 => 'No response from CCU', - -6 => 'Update of readings disabled. Set attribute ccureadings first' + -1 => 'Channel name or address invalid', + -2 => 'Execution of CCU script failed', + -3 => 'Cannot detect IO device', + -4 => 'Device deleted in CCU', + -5 => 'No response from CCU', + -6 => 'Update of readings disabled. Set attribute ccureadings first' ); if (exists ($errlist{$text})) { @@ -492,22 +501,18 @@ sub HMCCUCHN_SetError ($$) Get

      -
    • get <name> devstate -
      +
    • get <name> devstate
      Get state of CCU device. Default datapoint STATE can be changed by setting attribute 'statedatapoint'.

    • -
    • get <name> datapoint <datapoint> -
      +
    • get <name> datapoint <datapoint>
      Get value of a CCU device datapoint.

    • -
    • get <name> config [<rpcport>] -
      +
    • get <name> config [<rpcport>]
      Get configuration parameters of CCU channel. If attribute ccureadings is 0 results will be displayed in browser window.

    • -
    • get <name> configdesc [<rpcport>] -
      +
    • get <name> configdesc [<rpcport>]
      Get description of configuration parameters of CCU channel.

    • get <name> update [{'State'|'Value'}]
      @@ -544,6 +549,9 @@ sub HMCCUCHN_SetError ($$) attr mydev webCmd control attr mydev widgetOverride control:slider,10,1,25

    • +
    • disable <0 | 1>
      + Disable client device. +

    • statedatapoint <datapoint>
      Set datapoint for devstate commands.

    • diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm index 74feab95f..83bece125 100644 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm @@ -4,7 +4,7 @@ # # $Id:$ # -# Version 2.8 +# Version 3.0 # # (c) 2016 zap (zap01 t-online de) # @@ -13,12 +13,13 @@ # define HMCCUDEV {|virtual} [statechannel] [readonly] # [{group={|}[,...]|groupexp=}] # +# set config [] [] = [...] # set control # set datapoint . # set devstate +# set on-for-timer # set # set toggle -# set config [] [] = [...] # # get devstate # get datapoint . @@ -33,6 +34,7 @@ # attr ccureadingfilter [,...] # attr ccuverify { 0 | 1 } # attr controldatapoint . +# attr disable { 0 | 1 } # attr mapdatapoints .=.[,...] # attr statechannel # attr statedatapoint @@ -71,7 +73,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 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 disable:0,1 mapdatapoints:textField-long statevals substitute statechannel statedatapoint controldatapoint stripnumber:0,1,2 ". $readingFnAttributes; } ##################################### @@ -263,14 +265,14 @@ sub HMCCUDEV_Set ($@) my $name = shift @a; my $opt = shift @a; - if (!exists ($hash->{IODev})) { - return HMCCUDEV_SetError ($hash, "No IO device defined"); - } + return HMCCU_SetError ($hash, -3) if (!exists ($hash->{IODev})); return undef if ($hash->{statevals} eq 'readonly'); + my $disable = AttrVal ($name, "disable", 0); + return undef if ($disable == 1); + my $hmccu_hash = $hash->{IODev}; my $hmccu_name = $hash->{IODev}->{NAME}; - if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) { return undef if ($opt eq '?'); return "HMCCUDEV: CCU busy"; @@ -290,31 +292,35 @@ sub HMCCUDEV_Set ($@) my $objvalue = join ('%20', @a); if (!defined ($objname) || $objname !~ /^[0-9]+\..+$/ || !defined ($objvalue)) { - return HMCCUDEV_SetError ($hash, "Usage: set datapoint . [...]"); + return HMCCU_SetError ($hash, "Usage: set $name datapoint {channel-number}.{datapoint} {value} [...]"); } + return HMCCU_SetError ($hash, -8) if (!HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, + $hash->{ccuaddr}, $objname, 2)); + $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); # Build datapoint address $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$objname; $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); - if ($ccuverify) { + if ($ccuverify && HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, $hash->{ccuaddr}, + $objname, 1)) { usleep (100000); ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); } HMCCU_SetState ($hash, "OK"); return undef; } elsif ($opt eq 'control') { - return HMCCUDEV_SetError ($hash, "Attribute control datapoint not set") if ($controldatapoint eq ''); + return HMCCU_SetError ($hash, "Attribute controldatapoint not set") if ($controldatapoint eq ''); my $objvalue = shift @a; my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$controldatapoint; $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); HMCCU_SetState ($hash, "OK"); return undef; @@ -323,8 +329,8 @@ sub HMCCUDEV_Set ($@) my $cmd = $1; my $objvalue = ($cmd ne 'devstate') ? $cmd : join ('%20', @a); - return HMCCUDEV_SetError ($hash, "No state channel specified") if ($statechannel eq ''); - return HMCCUDEV_SetError ($hash, "Usage: set devstate [...]") if (!defined ($objvalue)); + return HMCCU_SetError ($hash, "No state channel specified") if ($statechannel eq ''); + return HMCCU_SetError ($hash, "Usage: set $name devstate {value} [...]") if (!defined ($objvalue)); $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); @@ -332,20 +338,21 @@ sub HMCCUDEV_Set ($@) my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$statechannel.'.'.$statedatapoint; $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); if ($ccuverify) { usleep (100000); ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); } HMCCU_SetState ($hash, "OK"); return undef; } elsif ($opt eq 'toggle') { - return HMCCUDEV_SetError ($hash, "Attribute statevals not set") + return HMCCU_SetError ($hash, "Attribute statevals not set") if ($statevals eq '' || !exists($hash->{statevals})); + return HMCCU_SetError ($hash, "No state channel specified") if ($statechannel eq ''); my $tstates = $hash->{statevals}; $tstates =~ s/devstate\|//; @@ -354,7 +361,7 @@ sub HMCCUDEV_Set ($@) my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$statechannel.'.'.$statedatapoint; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); my $objvalue = ''; my $st = 0; @@ -368,23 +375,50 @@ sub HMCCUDEV_Set ($@) } } - return HMCCUDEV_SetError ($hash, "Current device state doesn't match statevals") + return HMCCU_SetError ($hash, "Current device state doesn't match statevals") if ($objvalue eq ''); $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); HMCCU_SetState ($hash, "OK"); return undef; } + elsif ($opt eq 'on-for-timer') { + return HMCCU_SetError ($hash, "Attribute statevals not set") + if ($statevals eq '' || !exists($hash->{statevals})); + return HMCCU_SetError ($hash, "No state channel specified") if ($statechannel eq ''); + return HMCCU_SetError ($hash, "No state value for 'on' defined") + if ("on" !~ /($hash->{statevals})/); + return HMCCU_SetError ($hash, "Parameter ON_TIME not available in channel $statechannel") + if (!HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, $statechannel, "ON_TIME", 2)); + + my $timespec = shift @a; + return HMCCU_SetError ($hash, "Usage: set $name on-timer {seconds}") if (!defined ($timespec)); + + # Set on time + my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$statechannel.'.ON_TIME'; + $rc = HMCCU_SetDatapoint ($hash, $objname, $timespec); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); + + # Set state + $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$statechannel.'.'.$statedatapoint; + my $objvalue = HMCCU_Substitute ("on", $statevals, 1, ''); + $rc = HMCCU_SetDatapoint ($hash, $objname, $timespec); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); + + HMCCU_SetState ($hash, "OK"); + return undef; + } elsif ($opt eq 'config') { - return HMCCUDEV_SetError ($hash, "Usage: set $name config [{channel-number}] {parameter}={value} [...]") if (@a < 1);; + return HMCCU_SetError ($hash, "Usage: set $name config [{channel-number}] {parameter}={value} [...]") + if (@a < 1); my $objname = $hash->{ccuaddr}; $objname .= ':'.shift @a if ($a[0] =~ /^[0-9]$/); my $rc = HMCCU_RPCSetConfig ($hash, $objname, \@a); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); HMCCU_SetState ($hash, "OK"); return undef; @@ -403,6 +437,8 @@ sub HMCCUDEV_Set ($@) $retmsg .= ' '.$sv.':noArg'; } $retmsg .= " toggle:noArg"; + $retmsg .= " on-for-timer" if ($statechannel ne '' && + HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, $statechannel, "ON_TIME", 2)); } } @@ -420,12 +456,12 @@ sub HMCCUDEV_Get ($@) my $name = shift @a; my $opt = shift @a; - if (!defined ($hash->{IODev})) { - return HMCCUDEV_SetError ($hash, "No IO device defined"); - } + return HMCCU_SetError ($hash, -3) if (!defined ($hash->{IODev})); + + my $disable = AttrVal ($name, "disable", 0); + return undef if ($disable == 1); my $hmccu_hash = $hash->{IODev}; - if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) { return undef if ($opt eq '?'); return "HMCCUDEV: CCU busy"; @@ -443,26 +479,26 @@ sub HMCCUDEV_Get ($@) } if ($opt eq 'devstate') { - if ($statechannel eq '') { - return HMCCUDEV_SetError ($hash, "No state channel specified"); - } + return HMCCU_SetError ($hash, "No state channel specified") if ($statechannel eq ''); my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$statechannel.'.'.$statedatapoint; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); return $ccureadings ? undef : $result; } elsif ($opt eq 'datapoint') { my $objname = shift @a; if (!defined ($objname) || $objname !~ /^[0-9]+\..*$/) { - return HMCCUDEV_SetError ($hash, "Usage: get datapoint ."); + return HMCCU_SetError ($hash, "Usage: get $name datapoint {channel-number}.{datapoint}"); } + return HMCCU_SetError ($hash, -8) if (!HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, + $hash->{ccuaddr}, $objname, 1)); $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$objname; ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); return $ccureadings ? undef : $result; @@ -472,10 +508,10 @@ sub HMCCUDEV_Get ($@) foreach my $objname (@a) { last if (!defined ($objname)); if ($objname =~ /^([0-9]+)/ && exists ($hash->{channels})) { - return HMCCUDEV_SetError ($hash, "Invalid channel number: $objname") if ($1 >= $hash->{channels}); + return HMCCU_SetError ($hash, -7) if ($1 >= $hash->{channels}); } else { - return HMCCUDEV_SetError ($hash, "Invalid channel number: $objname"); + return HMCCU_SetError ($hash, -7); } if ($objname =~ /^[0-9]{1,2}.*=/) { $objname =~ s/=/ /; @@ -483,11 +519,11 @@ sub HMCCUDEV_Get ($@) push (@chnlist, $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$objname); } if (@chnlist == 0) { - return HMCCUDEV_SetError ($hash, "Usage: get $name channel {channel-number}[.{datapoint-expr}] [...]"); + return HMCCU_SetError ($hash, "Usage: get $name channel {channel-number}[.{datapoint-expr}] [...]"); } ($rc, $result) = HMCCU_GetChannel ($hash, \@chnlist); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); return $ccureadings ? undef : $result; @@ -496,12 +532,12 @@ sub HMCCUDEV_Get ($@) my $ccuget = shift @a; $ccuget = 'Attr' if (!defined ($ccuget)); if ($ccuget !~ /^(Attr|State|Value)$/) { - return HMCCUDEV_SetError ($hash, "Usage: get $name update [{'State'|'Value'}]"); + return HMCCU_SetError ($hash, "Usage: get $name update [{'State'|'Value'}]"); } if ($hash->{ccuname} ne 'none') { $rc = HMCCU_GetUpdate ($hash, $hash->{ccuaddr}, $ccuget); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); } # Update other devices belonging to group @@ -509,7 +545,7 @@ sub HMCCUDEV_Get ($@) my @vdevs = split (",", $hash->{ccugroup}); foreach my $vd (@vdevs) { $rc = HMCCU_GetUpdate ($hash, $vd, $ccuget); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); } } @@ -519,56 +555,67 @@ sub HMCCUDEV_Get ($@) my $ccuget = shift @a; $ccuget = 'Attr' if (!defined ($ccuget)); if ($ccuget !~ /^(Attr|State|Value)$/) { - return HMCCUDEV_SetError ($hash, "Usage: get $name deviceinfo [{'State'|'Value'}]"); + return HMCCU_SetError ($hash, "Usage: get $name deviceinfo [{'State'|'Value'}]"); } $result = HMCCU_GetDeviceInfo ($hash, $hash->{ccuaddr}, $ccuget); - return HMCCUDEV_SetError ($hash, -2) if ($result eq ''); - return $result; + return HMCCU_SetError ($hash, -2) if ($result eq ''); + return HMCCU_FormatDeviceInfo ($result); } elsif ($opt eq 'config') { my $channel = undef; my $port = undef; my $par = shift @a; - if ($par =~ /^[0-9]$/) { - $channel = $par; - $port = shift @a; - } - else { - $port = $par; + if (defined ($par)) { + if ($par =~ /^[0-9]$/) { + $channel = $par; + $port = shift @a; + } + else { + $port = $par; + } } my $ccuobj = $hash->{ccuaddr}; $ccuobj .= ':'.$channel if (defined ($channel)); $port = 2001 if (!defined ($port)); - my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset", $port); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset", $port); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); - return $ccureadings ? undef : $res; + return $ccureadings ? undef : $res; } elsif ($opt eq 'configdesc') { my $channel = undef; my $port = undef; my $par = shift @a; - if ($par =~ /^[0-9]$/) { - $channel = $par; - $port = shift @a; - } - else { - $port = $par; + if (defined ($par)) { + if ($par =~ /^[0-9]$/) { + $channel = $par; + $port = shift @a; + } + else { + $port = $par; + } } my $ccuobj = $hash->{ccuaddr}; $ccuobj .= ':'.$channel if (defined ($channel)); $port = 2001 if (!defined ($port)); - my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription", $port); - return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription", $port); + return HMCCU_SetError ($hash, $rc) if ($rc < 0); HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); - return $res; + return $res; } else { - my $retmsg = "HMCCUDEV: Unknown argument $opt, choose one of datapoint channel update:noArg config configdesc deviceinfo:noArg"; + my $retmsg = "HMCCUDEV: Unknown argument $opt, choose one of datapoint"; + + my @valuelist; + my $valuecount = HMCCU_GetValidDatapoints ($hash, $hash->{ccutype}, -1, 1, \@valuelist); + + $retmsg .= ":".join(",", @valuelist) if ($valuecount > 0); + $retmsg .= " channel update:noArg config configdesc deviceinfo:noArg"; + if ($statechannel ne '') { $retmsg .= ' devstate:noArg'; } @@ -584,22 +631,22 @@ sub HMCCUDEV_SetError ($$) { my ($hash, $text) = @_; my $name = $hash->{NAME}; - my $msg; - my %errlist = ( - -1 => 'Channel name or address invalid', - -2 => 'Execution of CCU script failed', - -3 => 'Cannot detect IO device', - -4 => 'Device deleted in CCU', - -5 => 'No response from CCU', - -6 => 'Update of readings disabled. Set attribute ccureadings first' - ); + my $msg; + my %errlist = ( + -1 => 'Channel name or address invalid', + -2 => 'Execution of CCU script failed', + -3 => 'Cannot detect IO device', + -4 => 'Device deleted in CCU', + -5 => 'No response from CCU', + -6 => 'Update of readings disabled. Set attribute ccureadings first' + ); - if (exists ($errlist{$text})) { - $msg = $errlist{$text}; - } - else { - $msg = $text; - } + if (exists ($errlist{$text})) { + $msg = $errlist{$text}; + } + else { + $msg = $text; + } $msg = "HMCCUDEV: ".$name." ". $msg; readingsSingleUpdate ($hash, "state", "Error", 1); @@ -645,6 +692,10 @@ sub HMCCUDEV_SetError ($$) Example:
      set light_entrance devstate on
      +
    • 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'. +

    • set <name> <statevalue>
      State of a CCU device channel is set to 'statevalue'. Channel must be defined as attribute 'statechannel'. Default datapoint STATE can be @@ -734,6 +785,9 @@ sub HMCCUDEV_SetError ($$) attr mydev webCmd control attr mydev widgetOverride control:slider,10,1,25

    • +
    • disable <0 | 1>
      + Disable client device. +

    • mapdatapoints <channel.datapoint>=<channel.datapoint>[,...] Map channel to other channel in virtual devices (groups). Readings will be duplicated.

    • diff --git a/fhem/contrib/HMCCU/FHEM/ccurpcd.pl b/fhem/contrib/HMCCU/FHEM/ccurpcd.pl index e0a62ae92..9ebbc5a5b 100755 --- a/fhem/contrib/HMCCU/FHEM/ccurpcd.pl +++ b/fhem/contrib/HMCCU/FHEM/ccurpcd.pl @@ -5,7 +5,7 @@ # # $Id: # -# Version 1.9 +# Version 2.0 # # FHEM RPC server for Homematic CCU. # @@ -24,8 +24,10 @@ # New device: ND|Address|Type # Updated device: UD|Address|Hint # Deleted device: DD|Address +# Replace device: RD|Address1|Address2 +# Readd device: RA|Address # Event: EV|Address|Attribute|Value -# Shutdown: EX|SHUTDOWN|PID +# Shutdown: EX|SHUTDOWN|pid ######################################################### use strict; @@ -162,7 +164,7 @@ sub CCURPC_Initialize ($$) return undef; } else { - Log "callback server created listening on port $callbackport"; + Log "Callback server created listening on port $callbackport"; } # Callback for events @@ -201,6 +203,24 @@ sub CCURPC_Initialize ($$) } ); + # Callback for replaced devices + Log "Adding callback for replaced devices"; + $server->add_method ( + { name=>"replaceDevice", + signature=>["string string string string"], + code=>\&CCURPC_ReplaceDeviceCB + } + ); + + # Callback for readded devices + Log "Adding callback for readded devices"; + $server->add_method ( + { name=>"replaceDevice", + signature=>["string string array"], + code=>\&CCURPC_ReaddDeviceCB + } + ); + # Dummy implementation, always return an empty array $server->add_method ( { name=>"listDevices", @@ -260,6 +280,36 @@ sub CCURPC_UpdateDeviceCB ($$$$) return; } +##################################### +# Callback for replaced devices +##################################### + +sub CCURPC_ReplaceDeviceCB ($$$$) +{ + my ($server, $cb, $devid1, $devid2) = @_; + + WriteQueue ("RD|".$devid1."|".$devid2); + + return; +} + +##################################### +# Callback for readded devices +##################################### + +sub CCURPC_ReaddDevicesCB ($$$) +{ + my ($server, $cb, $a) = @_; + + Log "ReaddDevice: received ".scalar(@$a)." device addresses"; + + for my $dev (@$a) { + WriteQueue ("RA|".$dev); + } + + return; +} + ##################################### # Callback for handling CCU events #####################################