diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm deleted file mode 100644 index 7bdeef9d0..000000000 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm +++ /dev/null @@ -1,4549 +0,0 @@ -######################################################################### -# -# 88_HMCCU.pm -# -# $Id$ -# -# Version 3.3 -# -# Module for communication between FHEM and Homematic CCU2. -# Supports BidCos-RF, BidCos-Wired, HmIP-RF. -# -# (c) 2016 zap (zap01 t-online de) -# -######################################################################### -# -# define HMCCU -# -# set config {|} = [...] -# set datapoint {|}. -# set devstate -# set execute -# set hmscript -# set rpcserver {on|off|restart} -# set var [...] -# -# get channel {|}[.][=] -# get config {|} -# get configdesc {|} -# get datapoint . [] -# get deviceinfo -# get devicelist [dump] -# 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,intrpc,extrpc,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 rpctimeout [,] -# attr statedatapoint [.] -# attr statevals :[,...] -# attr stripchar -# attr stripnumber { 0 | 1 | 2 } -# attr substitute -# attr updatemode { client | both | hmccu } -# -# 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; - -no if $] >= 5.017011, warnings => 'experimental::smartmatch'; - -use strict; -use warnings; -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 SetExtensions; -use SubProcess; -use HMCCUConf; - -# Import configuration data -my $HMCCU_DEV_DEFAULTS = \%HMCCUConf::HMCCU_DEV_DEFAULTS; - -# 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 -# -# 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 = 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 -my %HMCCU_Addresses; -# Last update of device list -# my $HMCCU_UpdateTime = 0; -# 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; -my $HMCCU_FLAG_DATAPOINT = 4; -my $HMCCU_FLAG_ADDRESS = 8; -my $HMCCU_FLAG_INTERFACE = 16; -my $HMCCU_FLAG_FULLADDR = 32; - -# Valid flag combinations -my $HMCCU_FLAGS_IACD = $HMCCU_FLAG_INTERFACE | $HMCCU_FLAG_ADDRESS | - $HMCCU_FLAG_CHANNEL | $HMCCU_FLAG_DATAPOINT; -my $HMCCU_FLAGS_IAC = $HMCCU_FLAG_INTERFACE | $HMCCU_FLAG_ADDRESS | - $HMCCU_FLAG_CHANNEL; -my $HMCCU_FLAGS_ACD = $HMCCU_FLAG_ADDRESS | $HMCCU_FLAG_CHANNEL | - $HMCCU_FLAG_DATAPOINT; -my $HMCCU_FLAGS_AC = $HMCCU_FLAG_ADDRESS | $HMCCU_FLAG_CHANNEL; -my $HMCCU_FLAGS_ND = $HMCCU_FLAG_NAME | $HMCCU_FLAG_DATAPOINT; -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 ($$$); -sub HMCCU_GetReadingName ($$$$$$); -sub HMCCU_FormatReadingValue ($$); -sub HMCCU_SetError ($$); -sub HMCCU_SetState ($$); -sub HMCCU_Substitute ($$$$); -sub HMCCU_SubstRule ($$$); -sub HMCCU_UpdateClients ($$$$); -sub HMCCU_UpdateClientReading ($@); -sub HMCCU_DeleteDevices ($); -sub HMCCU_RPCRegisterCallback ($); -sub HMCCU_RPCDeRegisterCallback ($); -sub HMCCU_ResetCounters ($); -sub HMCCU_StartExtRPCServer ($); -sub HMCCU_StartIntRPCServer ($); -sub HMCCU_StopRPCServer ($); -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 ($$); -sub HMCCU_SplitChnAddr ($); -sub HMCCU_GetCCUObjectAttribute ($$); -sub HMCCU_GetHash ($@); -sub HMCCU_GetAttribute ($$$$); -sub HMCCU_GetDatapointCount ($$$); -sub HMCCU_GetSpecialDatapoints ($$$$$); -sub HMCCU_IsValidDevice ($); -sub HMCCU_GetValidDatapoints ($$$$$); -sub HMCCU_GetSwitchDatapoint ($$$); -sub HMCCU_IsValidDatapoint ($$$$$); -sub HMCCU_GetMatchingDevices ($$$$); -sub HMCCU_GetDeviceName ($$); -sub HMCCU_GetChannelName ($$); -sub HMCCU_GetDeviceType ($$); -sub HMCCU_GetDeviceChannels ($); -sub HMCCU_GetDeviceInterface ($$); -sub HMCCU_ResetRPCQueue ($$); -sub HMCCU_ReadRPCQueue ($); -sub HMCCU_ProcessEvent ($$); -sub HMCCU_HMScript ($$); -sub HMCCU_UpdateSingleReading ($$$$$); -sub HMCCU_GetDatapoint ($@); -sub HMCCU_SetDatapoint ($$$); -sub HMCCU_ScaleValue ($$$$); -sub HMCCU_GetVariables ($$); -sub HMCCU_SetVariable ($$$); -sub HMCCU_GetUpdate ($$$); -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 ($$$$); -sub HMCCU_EncodeEPDisplay ($); - -# 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 -##################################### - -sub HMCCU_Initialize ($) -{ - my ($hash) = @_; - - $hash->{DefFn} = "HMCCU_Define"; - $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,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; -} - -##################################### -# Define device -##################################### - -sub HMCCU_Define ($$) -{ - my ($hash, $def) = @_; - my $name = $hash->{NAME}; - my @a = split("[ \t][ \t]*", $def); - - return "Define CCU hostname or IP address as a parameter" if(@a < 3); - - $hash->{host} = $a[2]; - $hash->{Clients} = ':HMCCUDEV:HMCCUCHN:'; - - $hash->{DevCount} = HMCCU_GetDeviceList ($hash); - $hash->{NewDevices} = 0; - $hash->{DelDevices} = 0; - $hash->{RPCState} = "stopped"; - - $hash->{hmccu}{evtime} = 0; - $hash->{hmccu}{evtimeout} = 0; - $hash->{hmccu}{updatetime} = 0; - $hash->{hmccu}{rpccount} = 0; - - readingsSingleUpdate ($hash, "state", "Initialized", 1); - - return undef; -} - -##################################### -# Set attribute -##################################### - -sub HMCCU_Attr ($@) -{ - my ($cmd, $name, $attrname, $attrval) = @_; - my $hash = $defs{$name}; - - 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 -##################################### - -sub HMCCU_Notify ($$) -{ - my ($hash, $dev) = @_; - my $name = $hash->{NAME}; - - my $disable = AttrVal ($name, 'disable', 0); - my $rpcserver = AttrVal ($name, 'rpcserver', 'off'); - my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); - - return if ($dev->{NAME} ne "global" || $disable); -# return if (!grep (m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}})); - return if (!grep (m/^INITIALIZED$/, @{$dev->{CHANGED}})); - - if ($rpcserver eq 'on') { - my $delay = $HMCCU_INIT_INTERVAL0; - Log3 $name, 0, "HMCCU: Autostart of RPC server after FHEM initialization in $delay seconds"; - if ($ccuflags =~ /extrpc/) { - InternalTimer (gettimeofday()+$delay, "HMCCU_StartExtRPCServer", $hash, 0); - } - else { - InternalTimer (gettimeofday()+$delay, "HMCCU_StartIntRPCServer", $hash, 0); - } - } - - return undef; -} - -##################################### -# Delete device -##################################### - -sub HMCCU_Undef ($$) -{ - my ($hash, $arg) = @_; - - # Shutdown RPC server - HMCCU_Shutdown ($hash); - - # Delete reference to IO module in client devices - my @keylist = sort keys %defs; - foreach my $d (@keylist) { - if (exists ($defs{$d}) && exists($defs{$d}{IODev}) && - $defs{$d}{IODev} == $hash) { - delete $defs{$d}{IODev}; - } - } - - return undef; -} - -##################################### -# Shutdown FHEM -##################################### - -sub HMCCU_Shutdown ($) -{ - my ($hash) = @_; - - # Shutdown RPC server - HMCCU_StopRPCServer ($hash); - RemoveInternalTimer ($hash); - - return undef; -} - -##################################### -# Set commands -##################################### - -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,restart restart"; - my $host = $hash->{host}; - - if ($opt ne 'rpcserver' && HMCCU_IsRPCStateBlocking ($hash)) { - 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 ($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'); - my $substitute = AttrVal ($name, "substitute", ''); - - if ($opt eq 'devstate' || $opt eq 'datapoint' || $opt eq 'var') { - my $objname = shift @a; - my $objvalue = shift @a; - my $result; - - if (!defined ($objname) || !defined ($objvalue)) { - return HMCCU_SetError ($hash, "Usage: set $name $opt {ccuobject} {value} [...]"); - } - - $objname =~ s/$stripchar$// if ($stripchar ne ''); - $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); - - if ($opt eq 'var') { - $result = HMCCU_SetVariable ($hash, $objname, $objvalue); - } - elsif ($opt eq 'devstate') { - $result = HMCCU_SetDatapoint ($hash, $objname.'.'.$statedatapoint, $objvalue); - } - else { - $result = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - } - - return HMCCU_SetError ($hash, $result) if ($result < 0); - return HMCCU_SetState ($hash, "OK"); - } - elsif ($opt eq "execute") { - my $program = shift @a; - my $response; - - return HMCCU_SetError ($hash, "Usage: set $name execute {program-name}") if (!defined ($program)); - - my $url = qq(http://$host:8181/do.exe?r1=dom.GetObject("$program").ProgramExecute()); - $response = GetFileFromURL ($url); - $response =~ m/(.*)<\/r1>/; - my $value = $1; - if (defined ($value) && $value ne '' && $value ne 'null') { - return HMCCU_SetState ($hash, "OK"); - } - else { - return HMCCU_SetError ($hash, "Program execution error"); - } - } - elsif ($opt eq 'hmscript') { - my $scrfile = shift @a; - my $script; - my $response; - - return HMCCU_SetError ($hash, "Usage: set $name hmscript {scriptfile}") if (!defined ($scrfile)); - if (open (SCRFILE, "<$scrfile")) { - my @lines = ; - $script = join ("\n", @lines); - close (SCRFILE); - } - else { - return HMCCU_SetError ($hash, "Can't open file $scrfile"); - } - - $response = HMCCU_HMScript ($hash, $script); - return HMCCU_SetError ($hash, -2) if ($response eq ''); - - HMCCU_SetState ($hash, "OK"); - return $response if (! $ccureadings); - - foreach my $line (split /\n/, $response) { - my @tokens = split /=/, $line; - next if (@tokens != 2); - my $reading; - my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($tokens[0], $HMCCU_FLAG_INTERFACE); - ($add, $chn) = HMCCU_GetAddress ($nam, '', '') if ($flags == $HMCCU_FLAGS_NCD); - if ($flags == $HMCCU_FLAGS_IACD || $flags == $HMCCU_FLAGS_NCD) { - $reading = HMCCU_GetReadingName ($int, $add, $chn, $dpt, $nam, $readingformat); - HMCCU_UpdateClientReading ($hash, $add, $chn, $dpt, $reading, $tokens[1]); - } - else { - my $Value = HMCCU_Substitute ($tokens[1], $substitute, 0, $tokens[0]); - readingsSingleUpdate ($hash, $tokens[0], $Value, 1); - } - } - - return undef; - } - elsif ($opt eq 'config') { - my $ccuobj = shift @a; - - return HMCCU_SetError ($hash, "Usage: set $name config {device|channel} {param=value} [...]") - if (!defined ($ccuobj) || @a < 1); - - my $rc = HMCCU_RPCSetConfig ($hash, $ccuobj, \@a); - - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - return HMCCU_SetState ($hash, "OK"); - } - elsif ($opt eq 'rpcserver') { - my $action = shift @a; - - return HMCCU_SetError ($hash, "Usage: set $name rpcserver {on|off}") - if (!defined ($action) || $action !~ /^(on|off|restart)$/); - - if ($action eq 'on') { - if ($ccuflags =~ /extrpc/) { - return HMCCU_SetError ($hash, "Start of RPC server failed") - if (!HMCCU_StartExtRPCServer ($hash)); - } - else { - return HMCCU_SetError ($hash, "Start of RPC server failed") - if (!HMCCU_StartIntRPCServer ($hash)); - } - } - elsif ($action eq 'off') { - 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"); - } - else { - return "HMCCU: Unknown argument $opt, choose one of ".$options; - } -} - -##################################### -# Get commands -##################################### - -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 rpcevents:noArg rpcstate:noArg deviceinfo"; - my $host = $hash->{host}; - - if ($opt ne 'rpcstate' && HMCCU_IsRPCStateBlocking ($hash)) { - 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 ($sc, $statedatapoint, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash, '', 'STATE', '', ''); - my $substitute = AttrVal ($name, 'substitute', ''); - my $rpcport = AttrVal ($name, 'rpcport', 2001); - - my $readname; - my $readaddr; - my $result = ''; - my $rc; - - if ($opt eq 'devstate') { - my $ccuobj = shift @a; - my $reading = shift @a; - - 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); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - return HMCCU_SetState ($hash, "OK"); - } - elsif ($opt eq 'datapoint') { - my $ccuobj = shift @a; - my $reading = shift @a; - - 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); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - 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)); - - ($rc, $result) = HMCCU_GetVariables ($hash, $varname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - HMCCU_SetState ($hash, "OK"); - return $ccureadings ? undef : $result; - } - elsif ($opt eq 'channel') { - my @chnlist; - - foreach my $objname (@a) { - last if (!defined ($objname)); - if ($objname =~ /^.*=/) { - $objname =~ s/=/ /; - } - push (@chnlist, $objname); - } - - 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); - - HMCCU_SetState ($hash, "OK"); - return $ccureadings ? undef : $result; - } - elsif ($opt eq 'update' || $opt eq 'updateccu') { - my $devexp = shift @a; - $devexp = '.*' if (!defined ($devexp)); - my $ccuget = shift @a; - $ccuget = 'Attr' if (!defined ($ccuget)); - if ($ccuget !~ /^(Attr|State|Value)$/) { - return HMCCU_SetError ($hash, "Usage: get $name $opt [device-expr [{'State'|'Value'}]]"); - } - - my ($c_ok, $c_err) = HMCCU_UpdateClients ($hash, $devexp, $ccuget, ($opt eq 'updateccu') ? 1 : 0); - - HMCCU_SetState ($hash, "OK"); - return "$c_ok client devices successfully updated. Update for $c_err client devices failed"; - } - elsif ($opt eq 'parfile') { - my $par_parfile = shift @a; - my @parameters; - my $parcount; - - if (defined ($par_parfile)) { - $parfile = $par_parfile; - } - else { - return HMCCU_SetError ($hash, "No parameter file specified") if ($parfile eq ''); - } - - # Read parameter file - if (open (PARFILE, "<$parfile")) { - @parameters = ; - $parcount = scalar @parameters; - close (PARFILE); - } - else { - return HMCCU_SetError ($hash, "Can't open file $parfile"); - } - - return HMCCU_SetError ($hash, "Empty parameter file") if ($parcount < 1); - - ($rc, $result) = HMCCU_GetChannel ($hash, \@parameters); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - HMCCU_SetState ($hash, "OK"); - return $ccureadings ? undef : $result; - } - elsif ($opt eq 'deviceinfo') { - my $device = shift @a; - - return HMCCU_SetError ($hash, "Usage: get $name deviceinfo {device} [{'State'|'Value'}]") - if (!defined ($device)); - - my $ccuget = shift @a; - $ccuget = 'Attr' if (!defined ($ccuget)); - 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 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); - } - 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); - 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') { - foreach my $add (sort keys %HMCCU_Devices) { - $result .= $HMCCU_Devices{$add}{name}."\n"; - } - return $result; - } - return "Read ".$hash->{DevCount}." devices/channels from CCU"; - } - elsif ($opt eq 'config') { - my $ccuobj = shift @a; - - return HMCCU_SetError ($hash, "Usage: get $name config {device|channel}") - if (!defined ($ccuobj)); - - my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset"); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - HMCCU_SetState ($hash, "OK"); - return $ccureadings ? undef : $res; - } - elsif ($opt eq 'configdesc') { - my $ccuobj = shift @a; - - return HMCCU_SetError ($hash, "Usage: get $name configdesc {device|channel}") - if (!defined ($ccuobj)); - - my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription"); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - HMCCU_SetState ($hash, "OK"); - return $res; - } - else { - return "HMCCU: Unknown argument $opt, choose one of ".$options; - } -} - -################################################################## -# Parse CCU object specification. -# Supports classic Homematic and Homematic-IP addresses. -# Supports team addresses with leading * for BidCos-RF. -# Supports CCU virtual remote addresses (BidCoS:ChnNo) -# -# Possible syntax for datapoints: -# Interface.Address:Channel.Datapoint -# Address:Channel.Datapoint -# Channelname.Datapoint -# -# Possible syntax for channels: -# Interface.Address:Channel -# Address:Channel -# Channelname -# -# If object name doesn't match the rules above object is treated -# as name. -# -# Return list of detected attributes: -# (Interface, Address, Channel, Datapoint, Name, Flags) -################################################################## - -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})\.(.+)$/ || - $object =~ /^(.+?)\.([0-9A-F]{14}):([0-9]{1,2})\.(.+)$/ || - $object =~ /^(.+?)\.(BidCoS-RF):([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}[0-9]{7}):([0-9]{1,2})$/ || - $object =~ /^(.+)\.([0-9A-F]{14}):([0-9]{1,2})$/ || - $object =~ /^(.+)\.(BidCoS-RF):([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}[0-9]{7}):([0-9]){1,2}\.(.+)$/ || - $object =~ /^([0-9A-F]{14}):([0-9]{1,2})\.(.+)$/ || - $object =~ /^(BidCoS-RF):([0-9]{1,2})\.(.+)$/) { - # - # Address:Channel.Datapoint [14=01110] - # - $f = $HMCCU_FLAGS_ACD; - ($a, $c, $d) = ($1, $2, $3); - } - elsif ($object =~ /^([\*]*[A-Z]{3}[0-9]{7}):([0-9]{1,2})$/ || - $object =~ /^([0-9A-Z]{14}):([0-9]{1,2})$/ || - $object =~ /^(BidCoS-RF):([0-9]{1,2})$/) { - # - # Address:Channel [10=01010] - # - $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})$/ || - $object =~ /^([0-9A-Z]{14})$/ || - $object eq 'BidCoS') { - # - # Address - # - $f = $HMCCU_FLAG_ADDRESS; - $a = $1; - } - elsif ($object =~ /^(.+?)\.(.+)$/) { - # - # Name.Datapoint - # - $f = $HMCCU_FLAGS_ND; - ($n, $d) = ($1, $2); - } - elsif ($object =~ /^.+$/) { - # - # Name [1=00001] - # - $f = $HMCCU_FLAG_NAME | ($flags & $HMCCU_FLAG_DATAPOINT); - ($n, $d) = ($object, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : ''); - } - else { - $f = 0; - } - - # Check if name is a valid channel name - if ($f & $HMCCU_FLAG_NAME) { - my ($add, $chn) = HMCCU_GetAddress ($n, '', ''); - if ($chn ne '') { - $f = $f | $HMCCU_FLAG_CHANNEL; - } - if ($flags & $HMCCU_FLAG_FULLADDR) { - ($i, $a, $c) = (HMCCU_GetDeviceInterface ($add, 'BidCos-RF'), $add, $chn); - $f |= $HMCCU_FLAG_INTERFACE; - $f |= $HMCCU_FLAG_ADDRESS if ($add ne ''); - $f |= $HMCCU_FLAG_CHANNEL if ($chn ne ''); - } - } - elsif ($f & $HMCCU_FLAG_ADDRESS && $i eq '' && - ($flags & $HMCCU_FLAG_FULLADDR || $flags & $HMCCU_FLAG_INTERFACE)) { - $i = HMCCU_GetDeviceInterface ($a, 'BidCos-RF'); - $f |= $HMCCU_FLAG_INTERFACE; - } - - return ($i, $a, $c, $d, $n, $f); -} - -################################################################## -# Filter reading by datapoint and optionally by channel name -# Parameters: hash, channel, datapoint -################################################################## - -sub HMCCU_FilterReading ($$$) -{ - my ($hash, $chn, $dpt) = @_; - my $name = $hash->{NAME}; - - my $rf = AttrVal ($name, 'ccureadingfilter', '.*'); - return 1 if ($rf eq '.*'); - - my $chnnam = HMCCU_IsChnAddr ($chn, 0) ? HMCCU_GetChannelName ($chn, '') : $chn; - - my @rules = split (",", $rf); - foreach my $r (@rules) { - my ($c, $f) = split ("!", $r); - if (defined ($f) && $chnnam ne '') { - if ($chnnam =~ /$c/) { - return ($dpt =~ /$f/) ? 1 : 0; - } - } - else { - return 1 if ($dpt =~ /$r/); - } - } - - return 0; -} - -################################################################## -# Build reading name -# -# Parameters: -# -# Interface,Address,ChannelNo,Datapoint,ChannelNam,ReadingFormat -# -# ReadingFormat := { name | datapoint | address } -# -# Valid combinations: -# -# ChannelNam,Datapoint -# Address,Datapoint -# Address,ChannelNo,Datapoint -################################################################## - -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 ''); - - if ($rf eq 'datapoint') { - $rn = $d; - } - elsif ($rf eq 'name') { - if ($n eq '') { - if ($a ne '' && $c ne '') { - $n = HMCCU_GetChannelName ($a.':'.$c, ''); - } - elsif ($a ne '' && $c eq '') { - $n = HMCCU_GetDeviceName ($a, ''); - } - else { - return ''; - } - } - - $n =~ s/\:/\./g; - $n =~ s/[^A-Za-z\d_\.-]+/_/g; - - $rn = $n ne '' ? $n.'.'.$d : ''; - } - elsif ($rf eq 'address') { - if ($a eq '' && $n ne '') { - ($a, $c) = HMCCU_GetAddress ($n, '', ''); - } - - if ($a ne '') { - my $t = $a; - $i = HMCCU_GetDeviceInterface ($a, '') if ($i eq ''); - $t = $i.'.'.$t if ($i ne ''); - $t = $t.'.'.$c if ($c ne ''); - - $rn = $t.'.'.$d; - } - } - - return $rn; -} - -################################################################## -# Format reading value depending attribute stripnumber. -################################################################## - -sub HMCCU_FormatReadingValue ($$) -{ - my ($hash, $value) = @_; - - my $stripnumber = AttrVal ($hash->{NAME}, 'stripnumber', 0); - - if ($stripnumber == 1) { - $value =~ s/(\.[0-9])[0-9]+/$1/; - } - elsif ($stripnumber == 2) { - $value =~ s/[0]+$//; - $value =~ s/\.$//; - } - - return $value; -} - -################################################################## -# Set error state and write log file message -################################################################## - -sub HMCCU_SetError ($$) -{ - my ($hash, $text) = @_; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - my $msg; - my %errlist = ( - -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', - -7 => 'Invalid channel number', - -8 => 'Invalid datapoint', - -9 => 'Interface does not support RPC calls', - -10 => 'No readable datapoints found' - ); - - $msg = exists ($errlist{$text}) ? $errlist{$text} : $text; - $msg = $type.": ".$name." ". $msg; - - HMCCU_SetState ($hash, "Error"); - Log3 $name, 1, $msg; - return $msg; -} - -################################################################## -# Set state -################################################################## - -sub HMCCU_SetState ($$) -{ - my ($hash, $text) = @_; - - if (defined ($hash) && defined ($text)) { - readingsSingleUpdate ($hash, "state", $text, 1); - } - - return ($text eq "busy") ? "HMCCU: CCU busy" : undef; -} - -################################################################## -# Substitute first occurrence of regular expressions or fixed -# string. Floating point values are ignored without datapoint -# specification. Integer values are compared with complete value. -# mode: 0=Substitute regular expression, 1=Substitute text -################################################################## - -sub HMCCU_Substitute ($$$$) -{ - my ($value, $substrule, $mode, $reading) = @_; - my $rc = 0; - my $newvalue; - - return $value if (!defined ($substrule) || $substrule eq ''); -# return $value if ($value !~ /^[+-]?\d+$/ && $value =~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/); - - $reading =~ s/.+\.(.+)$/$1/; - - my @rulelist = split (';', $substrule); - foreach my $rule (@rulelist) { - my @ruletoks = split ('!', $rule); - if (@ruletoks == 2 && $reading ne '' && $mode == 0) { - my @dptlist = split (',', $ruletoks[0]); - foreach my $dpt (@dptlist) { - if ($dpt eq $reading) { - ($rc, $newvalue) = HMCCU_SubstRule ($value, $ruletoks[1], $mode); - return $newvalue; - } - } - } - 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); - } - } - - return $value; -} - -################################################################## -# Execute substitution -################################################################## - -sub HMCCU_SubstRule ($$$) -{ - my ($value, $substitutes, $mode ) = @_; - my $rc = 0; - - my @sub_list = split /,/,$substitutes; - foreach my $s (@sub_list) { - my ($regexp, $text) = split /:/,$s; - next if (!defined ($regexp) || !defined($text)); - if ($mode == 0 && $value =~ /$regexp/ && $value !~ /^[+-]?\d+$/) { - $value =~ s/$regexp/$text/; - $rc = 1; - last; - } - elsif (($mode == 1 || $value =~/^[+-]?\d+$/) && $value =~ /^$regexp$/) { - $value =~ s/^$regexp$/$text/; - $rc = 1; - last; - } - } - - return ($rc, $value); -} - -################################################################## -# Update all datapoint/readings of all client devices. Update -# will fail if attribute ccureadings of a device is set to 0. -################################################################## - -sub HMCCU_UpdateClients ($$$$) -{ - my ($hash, $devexp, $ccuget, $fromccu) = @_; - my $fhname = $hash->{NAME}; - my $c_ok = 0; - my $c_err = 0; - - if ($fromccu) { - foreach my $name (sort keys %HMCCU_Addresses) { - next if ($name !~ /$devexp/ || !($HMCCU_Addresses{$name}{valid})); - - foreach my $d (keys %defs) { - my $ch = $defs{$d}; - next if ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN'); - next if ($ch->{ccudevstate} ne 'Active'); - next if ($ch->{ccuaddr} ne $HMCCU_Addresses{$name}{address}); - next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr})); - - my $rc = HMCCU_GetUpdate ($ch, $HMCCU_Addresses{$name}{address}, $ccuget); - if ($rc <= 0) { - 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 { - $c_ok++; - } - } - } - } - else { - foreach my $d (keys %defs) { - # Get hash of client device - my $ch = $defs{$d}; - next if ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN'); - next if ($ch->{ccudevstate} ne 'Active'); - next if ($ch->{NAME} !~ /$devexp/); - next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr})); - - my $rc = HMCCU_GetUpdate ($ch, $ch->{ccuaddr}, $ccuget); - if ($rc <= 0) { - 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 { - $c_ok++; - } - } - } - - return ($c_ok, $c_err); -} - -################################################################## -# Update HMCCU readings and client readings. -# -# Parameters: -# hash, devadd, channelno, datapoint, reading, value, [mode] -# -# Parameter devadd can be a device or a channel address. If -# devadd is a channel address parameter channel should be ''. -# Valid modes are: hmccu, rpcevent, client. -# Reading values are substituted if attribute substitute is set -# in client device. -################################################################## - -sub HMCCU_UpdateClientReading ($@) -{ - my ($hash, $devadd, $channel, $dpt, $reading, $value, $mode) = @_; - my $name = $hash->{NAME}; - - my $hmccu_substitute = AttrVal ($name, 'substitute', ''); - my $hmccu_updreadings = AttrVal ($name, 'ccureadings', 1); - my $updatemode = AttrVal ($name, 'updatemode', 'hmccu'); - - # Update mode can be: client, hmccu, both, rpcevent - $updatemode = $mode if (defined ($mode)); - - # Check syntax - return 0 if (!defined ($hash) || !defined ($devadd) || - !defined ($channel) || !defined ($reading) || !defined ($value)); - - my $chnadd = $channel ne '' ? $devadd.':'.$channel : $devadd; - my $hmccu_value = ''; -# my $dpt = ''; -# if ($reading =~ /.*\.(.+)$/) { -# $dpt = $1; -# } - - 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 '' || - HMCCU_FilterReading ($hash, $chnadd, $dpt))) { - readingsSingleUpdate ($hash, $reading, $hmccu_value, 1); - } - return $hmccu_value if ($updatemode eq 'hmccu'); - } - - # Update client readings - foreach my $d (keys %defs) { - # Get hash and name of client device - my $ch = $defs{$d}; - my $cn = $ch->{NAME}; - - 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})) { - # 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 $mapdatapoints = AttrVal ($cn, 'mapdatapoints', ''); - my $substitute = AttrVal ($cn, 'substitute', ''); - my ($sc, $st, $cc, $cd) = HMCCU_GetSpecialDatapoints ($ch, '', 'STATE', '', ''); - last if ($upd == 0 || $dis == 1); - next if (!HMCCU_FilterReading ($ch, $chnadd, $dpt)); - - my $clreading = HMCCU_GetReadingName ('', $devadd, $channel, $dpt, '', $crf); - next if ($clreading eq ''); - - $value = HMCCU_ScaleValue ($ch, $dpt, $value, 0); - - # Client substitute attribute has priority - my $cl_value; - if ($substitute ne '') { - $cl_value = HMCCU_Substitute ($value, $substitute, 0, $clreading); - } - else { - $cl_value = HMCCU_Substitute ($value, $hmccu_substitute, 0, $clreading); - } - $cl_value = HMCCU_FormatReadingValue ($ch, $cl_value); - - # Update reading and control/state readings - readingsSingleUpdate ($ch, $clreading, $cl_value, 1); - if ($cd ne '' && $dpt eq $cd && $channel eq $cc) { - readingsSingleUpdate ($ch, 'control', $cl_value, 1); - } - if (($clreading =~ /\.$st$/ || $clreading eq $st) && ($sc eq '' || $sc eq $channel)) { - HMCCU_SetState ($ch, $cl_value); - } - - # Map datapoints for virtual devices (groups) - if ($mapdatapoints ne '') { - foreach my $m (split (",", $mapdatapoints)) { - my @mr = split ("=", $m); - next if (@mr != 2); - my ($i1, $a1, $c1, $d1, $n1, $f1) = - HMCCU_ParseObject ($mr[0], $HMCCU_FLAG_FULLADDR); - my ($i2, $a2, $c2, $d2, $n2, $f2) = - HMCCU_ParseObject ($mr[1], $HMCCU_FLAG_FULLADDR); -# 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) { - readingsSingleUpdate ($ch, 'control', $cl_value, 1); - } - if (($mreading =~ /\.$st/ || $mreading eq $st) && ($sc eq '' || $sc eq $c2)) { - HMCCU_SetState ($ch, $cl_value); - } - } - } - } - - return $hmccu_value; -} - -#################################################### -# Mark client devices deleted in CCU as invalid -#################################################### - -sub HMCCU_DeleteDevices ($) -{ - my ($devlist) = @_; - - foreach my $a (@$devlist) { - my $cc = $HMCCU_Devices{$a}{channels}; - $HMCCU_Devices{$a}{valid} = 0; - $HMCCU_Addresses{$HMCCU_Devices{$a}{name}}{valid} = 0; - for (my $i=0; $i<$cc; $i++) { - $HMCCU_Devices{$a.':'.$i}{valid} = 0; - $HMCCU_Addresses{$HMCCU_Devices{$a.':'.$i}{name}}{valid} = 0; - } - foreach my $d (keys %defs) { - my $ch = $defs{$d}; - if ($ch->{TYPE} eq 'HMCCUDEV' && $ch->{ccuaddr} eq $a) { - $ch->{ccudevstate} = 'Deleted'; - readingsSingleUpdate ($ch, 'state', 'Deleted', 1); - } - elsif ($ch->{TYPE} eq 'HMCCUCHN' && $ch->{ccuaddr} =~ /^$a:[0-9]+/) { - $ch->{ccudevstate} = 'Deleted'; - readingsSingleUpdate ($ch, 'state', 'Deleted', 1); - } - } - } -} - -#################################################### -# Register RPC callbacks at CCU if RPC-Server -# already in server loop -#################################################### - -sub HMCCU_RPCRegisterCallback ($) -{ - my ($hash) = @_; - my $name = $hash->{NAME}; - my $serveraddr = $hash->{host}; - my $localaddr = $hash->{hmccu}{localaddr}; - - my $rpcport = AttrVal ($name, 'rpcport', 2001); - my $rpcinterval = AttrVal ($name, 'rpcinterval', $HMCCU_INIT_INTERVAL2); - my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); - - foreach my $port (split (',', $rpcport)) { - 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})); - 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"; - } - } - - # Schedule reading of RPC queue - InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0); -} - -#################################################### -# Deregister RPC callbacks at CCU -#################################################### - -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}; - - 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'); - 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 = ''; - - 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; - } - - my $rpcserver = $modpath."/FHEM/ccurpcd.pl"; - # Check if RPC server exists - 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 - if ($localaddr eq '') { - my $socket = IO::Socket::INET->new (PeerAddr => $serveraddr, PeerPort => $port); - if (!$socket) { - Log3 $name, 1, "Can't connect to CCU port $port"; - next; - } - $localaddr = $socket->sockhost (); - close ($socket); - } - - 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." ".$verbose); - - # When we reach this line start of RPC server failed and child process can exit - die; - } - - # Parent process - - # 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 $clkey started with pid ".$pid; - - $fork_cnt++; - } - else { - $hash->{hmccu}{rpc}{$clkey}{pid} = 0; - $hash->{hmccu}{rpc}{$clkey}{state} = "register"; - $hash->{hmccu}{rpc}{$clkey}{queue} = ''; - } - - $hash->{hmccu}{rpc}{$clkey}{cbport} = $callbackport; - $hash->{hmccu}{rpc}{$clkey}{loop} = 0; - } - - $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"); - - InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0); - } - else { - Log3 $name, 1, "HMCCU: No RPC process started"; - } - - return scalar (@hm_pids); -} - -#################################################### -# 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; - -# Log3 $name, 2, "HMCCU: Child socket snd buffer = ".$child->getsndbuffer ($child->child()); -# Log3 $name, 2, "HMCCU: Child socket rcv buffer = ".$child->getrcvbuffer ($child->child()); -# Log3 $name, 2, "HMCCU: Parent socket snd buffer = ".$child->getrcvbuffer ($child->parent()); -# Log3 $name, 2, "HMCCU: Parent socket rcv buffer = ".$child->getrcvbuffer ($child->parent()); -# Log3 $name, 2, "HMCCU: Parent socket snd lowat = ".$child->getsndlowat ($child->parent()); -# Log3 $name, 2, "HMCCU: Parent socket rcv lowat = ".$child->getrcvlowat ($child->parent()); - - # 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 - HMCCU_RPCDeRegisterCallback ($hash); - - # Send signal SIGINT to RPC server processes - foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) { - 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"; - } - - # Wait - sleep (1); - - # Check if processes were terminated - my @hm_pids; - my @ex_pids; - HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); - if (@hm_pids > 0) { - foreach my $pid (@hm_pids) { - Log3 $name, 0, "HMCCU: Stopping RPC server with PID $pid"; - kill ('INT', $pid); - } - } - if (@ex_pids > 0) { - Log3 $name, 0, "HMCCU: Externally launched RPC server detected."; - foreach my $pid (@ex_pids) { - kill ('INT', $pid); - } - } - - # 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) { - 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; -} - -#################################################### -# Check status of RPC server depending on internal -# RPCState. Return 1 if RPC server is stopping, -# starting or restarting. During this phases CCU -# react very slowly so any get or set command from -# HMCCU devices are disabled. -#################################################### - -sub HMCCU_IsRPCStateBlocking ($) -{ - my ($hash) = @_; - - if ($hash->{RPCState} eq "starting" || - $hash->{RPCState} eq "restarting" || - $hash->{RPCState} eq "stopping") { - return 1; - } - else { - return 0; - } -} - -#################################################### -# Check if RPC server is running. Return list of -# PIDs in referenced arrays. -# 1 = One or more RPC servers running. -# 0 = No RPC server running. -#################################################### - -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}); - } - - if ($ccuflags =~ /extrpc/) { - 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 { - 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)); - } - } - } - - return (@$hm_pids > 0 || @$ex_pids > 0) ? 1 : 0; -} - -#################################################### -# Get PID of RPC server process (0=not running) -#################################################### - -sub HMCCU_CheckProcess ($$) -{ - my ($hash, $port) = @_; - my $name = $hash->{NAME}; - - my $modpath = AttrVal ('global', 'modpath', '/opt/fhem'); - my $rpcserver = $modpath."/FHEM/ccurpcd.pl"; - - # Using BDS syntax. Supported by Debian, MacOS and FreeBSD - my $pdump = `ps ax | grep $rpcserver | grep -v grep`; - my @plist = split "\n", $pdump; - - foreach my $proc (@plist) { - # Remove leading blanks, fix for MacOS. Thanks to mcdeck - $proc =~ s/^\s+//; - my @procattr = split /\s+/, $proc; - return $procattr[0] if ($procattr[0] != $$ && $procattr[4] =~ /perl$/ && - $procattr[5] eq $rpcserver && $procattr[7] eq "$port"); - } - - return 0; -} - -#################################################### -# Get channels and datapoints of CCU device -#################################################### - -sub HMCCU_GetDeviceInfo ($$$) -{ - my ($hash, $device, $ccuget) = @_; - my $name = $hash->{NAME}; - my $devname = ''; - - my $hmccu_hash = HMCCU_GetHash ($hash); - return '' if (!defined ($hmccu_hash)); - - $ccuget = HMCCU_GetAttribute ($hmccu_hash, $hash, 'ccuget', 'Value') if ($ccuget eq 'Attr'); - my $ccutrace = AttrVal ($hmccu_hash->{NAME}, 'ccutrace', ''); - - my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($device, 0); - if ($flags == $HMCCU_FLAG_ADDRESS) { - $devname = HMCCU_GetDeviceName ($add, ''); - return '' if ($devname eq ''); - } - else { - $devname = $nam; - } - - my $script = qq( -string chnid; -string sDPId; -object odev = dom.GetObject ("$devname"); -if (odev) { - foreach (chnid, odev.Channels()) { - object ochn = dom.GetObject(chnid); - 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); - } - } - } - } -} - ); - - my $response = HMCCU_HMScript ($hmccu_hash, $script); - if ($ccutrace ne '' && ($device =~ /$ccutrace/ || $devname =~ /$ccutrace/)) { - Log3 $name, 2, "HMCCU: Device=$device Devname=$devname"; - Log3 $name, 2, "HMCCU: Script response = \n".$response; - Log3 $name, 2, "HMCCU: Script = ".$script; - } - 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. -#################################################### - -sub HMCCU_GetDeviceList ($) -{ - my ($hash) = @_; - my $count = 0; - - my $script = qq( -string devid; -string chnid; -foreach(devid, root.Devices().EnumUsedIDs()) { - object odev=dom.GetObject(devid); - string intid=odev.Interface(); - string intna=dom.GetObject(intid).Name(); - integer cc=0; - foreach (chnid, odev.Channels()) { - object ochn=dom.GetObject(chnid); - WriteLine("C;" # ochn.Address() # ";" # ochn.Name()); - cc=cc+1; - } - WriteLine("D;" # intna # ";" # odev.Address() # ";" # odev.Name() # ";" # odev.HssType() # ";" # cc); -} - ); - - my $response = HMCCU_HMScript ($hash, $script); - return -1 if ($response eq ''); - - %HMCCU_Devices = (); - %HMCCU_Addresses = (); - $hash->{hmccu}{updatetime} = time (); - - foreach my $hmdef (split /\n/,$response) { - my @hmdata = split /;/,$hmdef; - if ($hmdata[0] eq 'D') { - # 1=Interface 2=Device-Address 3=Device-Name 4=Device-Type 5=Channel-Count - $HMCCU_Devices{$hmdata[2]}{name} = $hmdata[3]; - $HMCCU_Devices{$hmdata[2]}{type} = $hmdata[4]; - $HMCCU_Devices{$hmdata[2]}{interface} = $hmdata[1]; - $HMCCU_Devices{$hmdata[2]}{channels} = $hmdata[5]; - $HMCCU_Devices{$hmdata[2]}{addtype} = 'dev'; - $HMCCU_Devices{$hmdata[2]}{valid} = 1; - $HMCCU_Addresses{$hmdata[3]}{address} = $hmdata[2]; - $HMCCU_Addresses{$hmdata[3]}{addtype} = 'dev'; - $HMCCU_Addresses{$hmdata[3]}{valid} = 1; - $count++; - } - elsif ($hmdata[0] eq 'C') { - # 1=Channel-Address 2=Channel-Name - $HMCCU_Devices{$hmdata[1]}{name} = $hmdata[2]; - $HMCCU_Devices{$hmdata[1]}{channels} = 1; - $HMCCU_Devices{$hmdata[1]}{addtype} = 'chn'; - $HMCCU_Devices{$hmdata[1]}{valid} = 1; - $HMCCU_Addresses{$hmdata[2]}{address} = $hmdata[1]; - $HMCCU_Addresses{$hmdata[2]}{addtype} = 'chn'; - $HMCCU_Addresses{$hmdata[2]}{valid} = 1; - $count++; - } - } - - HMCCU_GetDatapointList ($hash); - - # Update client devices - foreach my $d (keys %defs) { - # Get hash of client device - my $ch = $defs{$d}; - next if ($ch->{TYPE} ne 'HMCCUDEV' && $ch->{TYPE} ne 'HMCCUCHN'); - next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr})); - next if ($ch->{TYPE} eq 'HMCCUDEV' && $ch->{ccuif} eq "VirtualDevices" && - $ch->{ccuname} eq 'none'); - my $add = $ch->{ccuaddr}; - my $dadd = $add; - $dadd =~ s/:[0-9]+$//; - - # Update device or channel attributes if it has changed in CCU - $ch->{ccuname} = $HMCCU_Devices{$add}{name} - if (!defined ($ch->{ccuname}) || $ch->{ccuname} ne $HMCCU_Devices{$add}{name}); - $ch->{ccuif} = $HMCCU_Devices{$dadd}{interface} - if (!defined ($ch->{ccuif}) || $ch->{ccuif} ne $HMCCU_Devices{$dadd}{interface}); - $ch->{ccutype} = $HMCCU_Devices{$dadd}{type} - if (!defined ($ch->{ccutype}) || $ch->{ccutype} ne $HMCCU_Devices{$dadd}{type}); - $ch->{channels} = $HMCCU_Devices{$add}{channels} - if (!defined ($ch->{channels}) || $ch->{channels} != $HMCCU_Devices{$add}{channels}); - } - - $hash->{NewDevices} = 0; - $hash->{DelDevices} = 0; - - 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 ''); - -# 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++; -# } - - my $c = 0; - foreach my $dpspec (split /\n/,$response) { - my ($devt, $devc, $dptn, $dptt, $dpto) = split (";", $dpspec); - $hash->{hmccu}{dp}{$devt}{spc}{ontime} = $devc.".".$dptn if ($dptn eq "ON_TIME"); - $hash->{hmccu}{dp}{$devt}{spc}{ramptime} = $devc.".".$dptn if ($dptn eq "RAMP_TIME"); - $hash->{hmccu}{dp}{$devt}{spc}{submit} = $devc.".".$dptn if ($dptn eq "SUBMIT"); - $hash->{hmccu}{dp}{$devt}{ch}{$devc}{$dptn}{type} = $dptt; - $hash->{hmccu}{dp}{$devt}{ch}{$devc}{$dptn}{oper} = $dpto; - if (exists ($hash->{hmccu}{dp}{$devt}{cnt}{$dptn})) { - $hash->{hmccu}{dp}{$devt}{cnt}{$dptn}++; - } - else { - $hash->{hmccu}{dp}{$devt}{cnt}{$dptn} = 1; - } - $c++; - } - - return $c; -} - -#################################################### -# Check if device/channel name or address is valid -# and refers to an existing device or channel. -#################################################### - -sub HMCCU_IsValidDevice ($) -{ - my ($param) = @_; - - if (HMCCU_IsDevAddr ($param, 0) || HMCCU_IsChnAddr ($param, 0)) { - return 0 if (! exists ($HMCCU_Devices{$param})); - return $HMCCU_Devices{$param}{valid}; - } - else { - return 0 if (! exists ($HMCCU_Addresses{$param})); - return $HMCCU_Addresses{$param}{valid}; - } -} - -#################################################### -# 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); -} - -#################################################### -# Get channel number and datapoint name for on-for- -# timer or ramp-time. -#################################################### - -sub HMCCU_GetSwitchDatapoint ($$$) -{ - my ($hash, $devtype, $mode) = @_; - - my $hmccu_hash = HMCCU_GetHash ($hash); - - if (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{spc}{$mode})) { - return $hmccu_hash->{hmccu}{dp}{$devtype}{spc}{$mode}; - } - else { - return ''; - } -} - -#################################################### -# Check if datapoint is valid. -# Parameter chn can be a channel address or a channel -# number. Parameter dpt can contain 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); - 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/); - - 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. -#################################################### - -sub HMCCU_GetMatchingDevices ($$$$) -{ - my ($hash, $regexp, $mode, $listref) = @_; - my $c = 0; - - foreach my $name (sort keys %HMCCU_Addresses) { - next if ($name !~/$regexp/ || $HMCCU_Addresses{$name}{addtype} ne $mode || - $HMCCU_Addresses{$name}{valid} == 0); - push (@$listref, $HMCCU_Addresses{$name}{address}); - $c++; - } - - return $c; -} - -#################################################### -# Get name of a CCU device by address. -# Channel number will be removed if specified. -#################################################### - -sub HMCCU_GetDeviceName ($$) -{ - my ($addr, $default) = @_; - - if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) { - $addr =~ s/:[0-9]+$//; - if (exists ($HMCCU_Devices{$addr})) { - return $HMCCU_Devices{$addr}{name}; - } - } - - return $default; -} - -#################################################### -# Get name of a CCU device channel by address. -#################################################### - -sub HMCCU_GetChannelName ($$) -{ - my ($addr, $default) = @_; - - if (HMCCU_IsChnAddr ($addr, 0)) { - if (exists ($HMCCU_Devices{$addr})) { - return $HMCCU_Devices{$addr}{name}; - } - } - - return $default; -} - -#################################################### -# Get type of a CCU device by address. -# Channel number will be removed if specified. -#################################################### - -sub HMCCU_GetDeviceType ($$) -{ - my ($addr, $default) = @_; - - if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) { - $addr =~ s/:[0-9]+$//; - if (exists ($HMCCU_Devices{$addr})) { - return $HMCCU_Devices{$addr}{type}; - } - } - - return $default; -} - - -#################################################### -# Get number of channels of a CCU device. -# Channel number will be removed if specified. -#################################################### - -sub HMCCU_GetDeviceChannels ($) -{ - my ($addr, $default) = @_; - - if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) { - $addr =~ s/:[0-9]+$//; - if (exists ($HMCCU_Devices{$addr})) { - return $HMCCU_Devices{$addr}{channels}; - } - } - - return 0; -} - -#################################################### -# Get interface of a CCU device by address. -# Channel number will be removed if specified. -#################################################### - -sub HMCCU_GetDeviceInterface ($$) -{ - my ($addr, $default) = @_; - - if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) { - $addr =~ s/:[0-9]+$//; - if (exists ($HMCCU_Devices{$addr})) { - return $HMCCU_Devices{$addr}{interface}; - } - } - - return $default; -} - -#################################################### -# Get address of a CCU device or channel by name. -# Return array with device address and channel no. -#################################################### - -sub HMCCU_GetAddress ($$$) -{ - my ($name, $defadd, $defchn) = @_; - my $add = $defadd; - my $chn = $defchn; - - if (exists ($HMCCU_Addresses{$name})) { - # Address known by HMCCU - my $addr = $HMCCU_Addresses{$name}{address}; - if (HMCCU_IsChnAddr ($addr, 0)) { - ($add, $chn) = split (":", $addr); - } - elsif (HMCCU_IsDevAddr ($addr, 0)) { - $add = $addr; - } - } - else { - # Address not known. Query CCU - my $response = HMCCU_GetCCUObjectAttribute ($name, "Address()"); - if (defined ($response)) { - if (HMCCU_IsChnAddr ($response, 0)) { - ($add, $chn) = split (":", $response); - $HMCCU_Addresses{$name}{address} = $response; - $HMCCU_Addresses{$name}{addtype} = 'chn'; - } - elsif (HMCCU_IsDevAddr ($response, 0)) { - $add = $response; - $HMCCU_Addresses{$name}{address} = $response; - $HMCCU_Addresses{$name}{addtype} = 'dev'; - } - } - } - - return ($add, $chn); -} - -#################################################### -# Check if parameter is a channel address (syntax) -# f=1: Interface required. -#################################################### - -sub HMCCU_IsChnAddr ($$) -{ - my ($id, $f) = @_; - - if ($f) { - return ($id =~ /^.+\.[\*]*[A-Z]{3}[0-9]{7}:[0-9]{1,2}$/ || - $id =~ /^.+\.[0-9A-F]{14}:[0-9]{1,2}$/ || - $id =~ /^.+\.BidCoS-RF:[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}$/ || - $id =~ /^BidCoS-RF:[0-9]{1,2}$/) ? 1 : 0; - } -} - -#################################################### -# Check if parameter is a device address (syntax) -# f=1: Interface required. -#################################################### - -sub HMCCU_IsDevAddr ($$) -{ - my ($id, $f) = @_; - - if ($f) { - return ($id =~ /^.+\.[\*]*[A-Z]{3}[0-9]{7}$/ || - $id =~ /^.+\.[0-9A-F]{14}$/ || - $id =~ /^.+\.BidCoS-RF$/) ? 1 : 0; - } - else { - return ($id =~ /^[\*]*[A-Z]{3}[0-9]{7}$/ || - $id =~ /^[0-9A-F]{14}$/ || - $id eq 'BidCoS-RF') ? 1 : 0; - } -} - -#################################################### -# Split channel address into device address and -# channel number -#################################################### - -sub HMCCU_SplitChnAddr ($) -{ - my ($addr) = @_; - - if (HMCCU_IsChnAddr ($addr, 0)) { - return split (":", $addr); - } - elsif (HMCCU_IsDevAddr ($addr, 0)) { - return ($addr, ''); - } - - return ('', ''); -} - -#################################################### -# Query object attribute from CCU. Attribute must -# be a valid method for specified object, -# i.e. Address() -#################################################### - -sub HMCCU_GetCCUObjectAttribute ($$) -{ - my ($object, $attr) = @_; - - my $hash = HMCCU_GetHash (0); - my $url = 'http://'.$hash->{host}.':8181/do.exe?r1=dom.GetObject("'.$object.'").'.$attr; - my $response = GetFileFromURL ($url); - if (defined ($response) && $response !~ /null(.+)<\/r1>/) { - return $1; - } - } - - return undef; -} - -#################################################### -# Get hash of HMCCU IO device. Useful for client -# devices. Accepts hash of HMCCU, HMCCUDEV or -# HMCCUCHN device as parameter. -#################################################### - -sub HMCCU_GetHash ($@) -{ - my ($hash) = @_; - - if (defined ($hash) && $hash != 0) { - if ($hash->{TYPE} eq 'HMCCUDEV' || $hash->{TYPE} eq 'HMCCUCHN') { - return $hash->{IODev} if (exists ($hash->{IODev})); - } - elsif ($hash->{TYPE} eq 'HMCCU') { - return $hash; - } - } - - # Search for first HMCCU device - foreach my $dn (sort keys %defs) { - return $defs{$dn} if ($defs{$dn}->{TYPE} eq 'HMCCU'); - } - - return undef; -} - -#################################################### -# Get attribute of client device with fallback to -# attribute of IO device. -#################################################### - -sub HMCCU_GetAttribute ($$$$) -{ - my ($hmccu_hash, $cl_hash, $attr_name, $attr_def) = @_; - - my $value = AttrVal ($cl_hash->{NAME}, $attr_name, ''); - $value = AttrVal ($hmccu_hash->{NAME}, $attr_name, $attr_def) if ($value eq ''); - - return $value; -} - -#################################################### -# Get number of occurrences of datapoint. -# Return 0 if datapoint does not exist. -#################################################### - -sub HMCCU_GetDatapointCount ($$$) -{ - my ($hash, $devtype, $dpt) = @_; - - if (exists ($hash->{hmccu}{dp}{$devtype}{cnt}{$dpt})) { - return $hash->{hmccu}{dp}{$devtype}{cnt}{$dpt}; - } - else { - return 0; - } -} - -#################################################### -# Get channels and datapoints from attributes -# 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, $defsc, $defsd, $defcc, $defcd) = @_; - my ($hash, $sc, $sd, $cc, $cd) = @_; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - - 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; - } - } - $sc = $statechannel if ($statechannel ne ''); - - 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 -#################################################### - -sub HMCCU_ResetRPCQueue ($$) -{ - my ($hash, $port) = @_; - my $name = $hash->{NAME}; - - my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue'); - my $clkey = 'CB'.$port; - - 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, $t[2], $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; -} - -#################################################### -# Timer function for reading RPC queue -#################################################### - -sub HMCCU_ReadRPCQueue ($) -{ - my ($hash) = @_; - my $name = $hash->{NAME}; - my $eventno = 0; - my $f = 0; - my @newdevices; - my @deldevices; - my @termpids; - my $newcount = 0; - my $delcount = 0; - - my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); - my $rpcinterval = AttrVal ($name, 'rpcinterval', 5); - my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue'); - my $rpcport = AttrVal ($name, 'rpcport', 2001); - my $rpctimeout = AttrVal ($name, 'rpcevtimeout', 300); - my $maxevents = $rpcinterval*10; - $maxevents = 50 if ($maxevents > 50); - $maxevents = 10 if ($maxevents < 10); - - my @portlist = split (',', $rpcport); - foreach my $port (@portlist) { - my $clkey = 'CB'.$port; - next if (!exists ($hash->{hmccu}{rpc}{$clkey}{queue})); - my $queuename = $hash->{hmccu}{rpc}{$clkey}{queue}; - next if ($queuename eq ''); - if (!HMCCU_QueueOpen ($hash, $queuename)) { - Log3 $name, 1, "HMCCU: Can't open file queue $queuename"; - next; - } - - my $element = HMCCU_QueueDeq ($hash); - while ($element) { - my ($et, @par) = HMCCU_ProcessEvent ($hash, $element); - if (defined ($et)) { - if ($et eq 'EV') { - $eventno++; - last if ($eventno == $maxevents); - } - elsif ($et eq 'ND') { - $newcount++ if (!exists ($HMCCU_Devices{$par[0]})); - } - elsif ($et eq 'DD') { - push (@deldevices, $par[0]); - $delcount++; - } - elsif ($et eq 'SL') { - InternalTimer (gettimeofday()+$HMCCU_INIT_INTERVAL1, - 'HMCCU_RPCRegisterCallback', $hash, 0); - return; - } - elsif ($et eq 'EX') { - push (@termpids, $par[1]); - $f = $par[2]; - last; - } - } - - # Read next element from queue - $element = HMCCU_QueueDeq ($hash); - } - - HMCCU_QueueClose ($hash); - } - - # 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; - Log3 $name, 2, "HMCCU: Received no events from CCU since $rpctimeout seconds"; - DoTrigger ($name, "No events from CCU since $rpctimeout seconds"); - } - - # CCU devices deleted - $delcount = scalar @deldevices; - if ($delcount > 0) { - HMCCU_DeleteDevices (\@deldevices); - $hash->{DelDevices} = $delcount; - DoTrigger ($name, "$delcount devices deleted in CCU"); - } - - # CCU devices added - if ($newcount > 0) { - $hash->{NewDevices} += $newcount; - DoTrigger ($name, "$newcount devices added in CCU"); - } - - my @hm_pids; - my @ex_pids; - HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@ex_pids); - my $nhm_pids = scalar (@hm_pids); - my $nex_pids = scalar (@ex_pids); - - if ($nex_pids > 0) { - Log3 $name, 1, "HMCCU: Externally launched RPC server(s) detected. Kill process(es) manually with command kill -SIGINT pid for pids ".join (',', @ex_pids)." f=$f"; - } - - if ($f > 0) { - # At least one RPC server has been stopped. Update PID list - $hash->{RPCPID} = $nhm_pids > 0 ? join(',',@hm_pids) : '0'; - Log3 $name, 0, "HMCCU: RPC server(s) with PID(s) ".join(',',@termpids)." shut down. f=$f"; - - # 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 - return if (HMCCU_StartExtRPCServer ($hash)); - Log3 $name, 0, "HMCCU: Restart of RPC server failed"; - } - - if ($nhm_pids > 0) { - # Reschedule reading of RPC queues if at least one RPC server is running - InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0); - } - else { - # 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"; - - Log3 $name, 0, "HMCCU: All RPC servers stopped"; - readingsSingleUpdate ($hash, "rpcstate", "stopped", 1); - DoTrigger ($name, "All RPC servers stopped"); - } -} - -#################################################### -# Execute Homematic script on CCU -#################################################### - -sub HMCCU_HMScript ($$) -{ - # Hostname, Script-Code - my ($hash, $hmscript) = @_; - my $name = $hash->{NAME}; - my $host = $hash->{host}; - - my $url = "http://".$host.":8181/tclrega.exe"; - my $ua = new LWP::UserAgent (); - my $response = $ua->post($url, Content => $hmscript); - - if (! $response->is_success ()) { - Log3 $name, 1, "HMCCU: ".$response->status_line(); - return ''; - } - else { - my $output = $response->content; - $output =~ s/.*<\/xml>//; - $output =~ s/\r//g; - return $output; - } -} - -#################################################### -# 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_ScaleValue ($hash, $dpt, $value, 0); - $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. -#################################################### - -sub HMCCU_GetDatapoint ($@) -{ - my ($hash, $param, $reading) = @_; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - my $hmccu_hash; - my $value = ''; - - $hmccu_hash = HMCCU_GetHash ($hash); - return (-3, $value) if (!defined ($hmccu_hash)); - return (-4, $value) if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'Deleted'); - - 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', '', ''); - my $ccuget = HMCCU_GetAttribute ($hmccu_hash, $hash, 'ccuget', 'Value'); - my $ccutrace = AttrVal ($hmccu_hash->{NAME}, 'ccutrace', ''); - my $tf = ($ccutrace ne '' && $param =~ /$ccutrace/) ? 1 : 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); - if ($flags == $HMCCU_FLAGS_IACD) { - $url .= $int.'.'.$add.':'.$chn.'.'.$dpt.'").'.$ccuget.'()'; - } - elsif ($flags == $HMCCU_FLAGS_NCD) { - $url .= $nam.'").DPByHssDP("'.$dpt.'").'.$ccuget.'()'; - ($add, $chn) = HMCCU_GetAddress ($nam, '', ''); - } - else { - return (-1, $value); - } - - if ($tf) { - Log3 $name, 2, "HMCCU: GetDatapoint()"; - Log3 $name, 2, "HMCCU: URL=$url"; - Log3 $name, 2, "HMCCU: param=$param"; - Log3 $name, 2, "HMCCU: ccuget=$ccuget"; - } - - my $rawresponse = GetFileFromURL ($url); - my $response = $rawresponse; - $response =~ m/(.*)<\/r1>/; - $value = $1; - - Log3 ($name, 2, "HMCCU: Response = ".$rawresponse) if ($tf); - - if (defined ($value) && $value ne '' && $value ne 'null') { - if (!defined ($reading) || $reading eq '') { - $reading = HMCCU_GetReadingName ($int, $add, $chn, $dpt, $nam, $readingformat); - } - return (0, $value) if ($reading eq ''); - - if ($type eq 'HMCCU') { - $value = HMCCU_UpdateClientReading ($hmccu_hash, $add, $chn, $dpt, $reading, - $value); - } - else { - $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); - } - else { - Log3 $name, 1, "HMCCU: Error URL = ".$url; - return (-2, ''); - } -} - -#################################################### -# Set datapoint -#################################################### - -sub HMCCU_SetDatapoint ($$$) -{ - my ($hash, $param, $value) = @_; - my $type = $hash->{TYPE}; - - 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 $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); - return -1 if ($flags != $HMCCU_FLAGS_IACD && $flags != $HMCCU_FLAGS_NCD); - - if ($hash->{ccutype} eq 'HM-Dis-EP-WM55' && $dpt eq 'SUBMIT') { - $value = HMCCU_EncodeEPDisplay ($value); - } - else { - $value = HMCCU_ScaleValue ($hash, $dpt, $value, 1); - } - - 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.')'; - ($add, $chn) = HMCCU_GetAddress ($nam, '', ''); - } - - 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".(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; -} - -#################################################### -# Scale datapoint value -# Mode: 0 = Get/Divide, 1 = Set/Multiply -#################################################### - -sub HMCCU_ScaleValue ($$$$) -{ - my ($hash, $dpt, $value, $mode) = @_; - my $name = $hash->{NAME}; - - my $ccuscaleval = AttrVal ($name, 'ccuscaleval', ''); - if ($ccuscaleval ne '') { - my @sl = split (',', $ccuscaleval); - foreach my $sr (@sl) { - my ($d, $f) = split (':', $sr); - if (defined ($d) && defined ($f) && $d eq $dpt) { - $f = 1.0 if ($f == 0.0); - return ($mode == 0) ? $value/$f : $value*$f; - } - } - } - - return $value; -} - -#################################################### -# Get CCU system variables and update readings -#################################################### - -sub HMCCU_GetVariables ($$) -{ - my ($hash, $pattern) = @_; - my $count = 0; - my $result = ''; - - my $ccureadings = AttrVal ($hash->{NAME}, 'ccureadings', 1); - - my $script = qq( -object osysvar; -string ssysvarid; -foreach (ssysvarid, dom.GetObject(ID_SYSTEM_VARIABLES).EnumUsedIDs()) -{ - osysvar = dom.GetObject(ssysvarid); - WriteLine (osysvar.Name() # "=" # osysvar.Variable() # "=" # osysvar.Value()); -} - ); - - my $response = HMCCU_HMScript ($hash, $script); - return (-2, $result) if ($response eq ''); - - readingsBeginUpdate ($hash) if ($ccureadings); - - foreach my $vardef (split /\n/, $response) { - my @vardata = split /=/, $vardef; - next if (@vardata != 3); - next if ($vardata[0] !~ /$pattern/); - my $value = HMCCU_FormatReadingValue ($hash, $vardata[2]); - readingsBulkUpdate ($hash, $vardata[0], $value) if ($ccureadings); - $result .= $vardata[0].'='.$vardata[2]."\n"; - $count++; - } - - readingsEndUpdate ($hash, 1) if ($hash->{TYPE} ne 'HMCCU' && $ccureadings); - - return ($count, $result); -} - -#################################################### -# Set CCU system variable -#################################################### - -sub HMCCU_SetVariable ($$$) -{ - my ($hash, $param, $value) = @_; - my $name = $hash->{NAME}; - my $url = 'http://'.$hash->{host}.':8181/do.exe?r1=dom.GetObject("'.$param.'").State("'.$value.'")'; - - my $response = GetFileFromURL ($url); - 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'); - - my $nam = ''; - my $script; - - $ccuget = HMCCU_GetAttribute ($hmccu_hash, $cl_hash, 'ccuget', 'Value') if ($ccuget eq 'Attr'); - my $ccutrace = AttrVal ($hmccu_hash->{NAME}, 'ccutrace', ''); - - if (HMCCU_IsChnAddr ($addr, 0)) { - $nam = HMCCU_GetChannelName ($addr, ''); - return -1 if ($nam eq ''); - - $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) { - if (OPERATION_READ & oDP.Operations()) { - WriteLine (sChnName # "=" # oDP.Name() # "=" # oDP.$ccuget()); - c = c+1; - } - } - } - WriteLine (c); -} - ); - } - elsif (HMCCU_IsDevAddr ($addr, 0)) { - $nam = HMCCU_GetDeviceName ($addr, ''); - return -1 if ($nam eq ''); - - $script = qq( -string chnid; -string sDPId; -integer c = 0; -object odev = dom.GetObject ("$nam"); -if (odev) { - foreach (chnid, odev.Channels()) { - object ochn = dom.GetObject(chnid); - if (ochn) { - foreach(sDPId, ochn.DPs()) { - object oDP = dom.GetObject(sDPId); - if (oDP) { - if (OPERATION_READ & oDP.Operations()) { - WriteLine (ochn.Name() # "=" # oDP.Name() # "=" # oDP.$ccuget()); - c = c+1; - } - } - } - } - } - WriteLine (c); -} - ); - } - else { - return -1; - } - - my $response = HMCCU_HMScript ($hmccu_hash, $script); - 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".$script; - } - 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); - return $rc if ($rc < 0); - - # Update virtual devices - my ($da, $cno) = HMCCU_SplitChnAddr ($cl_hash->{ccuaddr}); - foreach my $dn (sort keys %defs) { - my $ch = $defs{$dn}; - next if ($ch->{TYPE} ne 'HMCCUDEV'); - next if ($ch->{ccuif} ne "VirtualDevices" || !exists ($ch->{ccugroup})); - my @vdevs = split (",", $ch->{ccugroup}); - if ((grep { $_ eq $da } @vdevs) || - ($cno ne '' && (grep { $_ eq $cl_hash->{ccuaddr} } @vdevs))) { - HMCCU_UpdateDeviceReadings ($ch, \@dpdef); - } - } - - return 1; -} - -#################################################### -# Update readings of client device. Parameter dp -# is a reference to an array of datapoint=value -# pairs. Returns number of updated readings. -#################################################### - -sub HMCCU_UpdateDeviceReadings ($$) -{ - my ($cl_hash, $dp) = @_; - - my $uc = 0; - - my $cn = $cl_hash->{NAME}; - my $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', '.*'); - my $readingformat = AttrVal ($cn, 'ccureadingformat', 'name'); - my $substitute = AttrVal ($cn, 'substitute', ''); - my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints ( - $cl_hash, '', 'STATE', '', ''); - - readingsBeginUpdate ($cl_hash); - - foreach my $dpdef (@$dp) { - my @dpdata = split /=/, $dpdef; - next if (@dpdata < 2); - my @adrtoks = split /\./, $dpdata[1]; - next if (@adrtoks != 3); -# next if ($adrtoks[2] !~ /$ccureadingfilter/); - - my ($add, $chn) = split /:/, $adrtoks[1]; - next if (!HMCCU_FilterReading ($cl_hash, $adrtoks[1], $adrtoks[2])); - my $reading = HMCCU_GetReadingName ($adrtoks[0], $add, $chn, $adrtoks[2], - $dpdata[0], $readingformat); - next if ($reading eq ''); - - my $value = (defined ($dpdata[2]) && $dpdata[2] ne '') ? $dpdata[2] : 'N/A'; - $value = HMCCU_ScaleValue ($cl_hash, $adrtoks[2], $value, 0); - $value = HMCCU_Substitute ($value, $substitute, 0, $reading); - $value = HMCCU_FormatReadingValue ($cl_hash, $value); - readingsBulkUpdate ($cl_hash, $reading, $value); - if ($controldpt ne '' && $adrtoks[2] eq $controldpt && $chn eq $controlchn) { - readingsBulkUpdate ($cl_hash, 'control', $value); - } - if ($reading =~ /\.$statedpt$/ && ($statechn eq '' || $statechn eq $chn)) { - readingsBulkUpdate ($cl_hash, "state", $value); - } - $uc++; - } - - readingsEndUpdate ($cl_hash, 1); - - return $uc; -} - -#################################################### -# Get multiple datapoints of channels and update -# readings. -# If hash points to client device only readings -# of client device will be updated. -# Returncodes: -1 = Invalid channel/datapoint -# -2 = CCU script execution failed -# -3 = Cannot detect IO device -# On success number of updated readings is returned. -#################################################### - -sub HMCCU_GetChannel ($$) -{ - my ($hash, $chnref) = @_; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - my $count = 0; - my %chnpars; - my $chnlist = ''; - my $result = ''; - - my $hmccu_hash = HMCCU_GetHash ($hash); - return (-3, $result) if (!defined ($hmccu_hash));; - return (-4, $result) if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'Deleted'); - - my $ccuget = HMCCU_GetAttribute ($hmccu_hash, $hash, 'ccuget', 'Value'); - my $ccureadings = AttrVal ($name, 'ccureadings', 1); - my $readingformat = AttrVal ($name, 'ccureadingformat', 'name'); - my $defsubstitute = AttrVal ($name, 'substitute', ''); - my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints ( - $hash, '', 'STATE', '', ''); - - # Build channel list - foreach my $chndef (@$chnref) { - my ($channel, $substitute) = split /\s+/, $chndef; - next if (!defined ($channel) || $channel =~ /^#/ || $channel eq ''); - $substitute = $defsubstitute if (!defined ($substitute)); - my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($channel, - $HMCCU_FLAG_INTERFACE | $HMCCU_FLAG_DATAPOINT); - if ($flags == $HMCCU_FLAGS_IACD || $flags == $HMCCU_FLAGS_NCD) { - if ($flags == $HMCCU_FLAGS_IACD) { - $nam = HMCCU_GetChannelName ($add.':'.$chn, ''); - } - - $chnlist = $chnlist eq '' ? $nam : $chnlist.','.$nam; - $chnpars{$nam}{sub} = $substitute; - $chnpars{$nam}{dpt} = $dpt; - } - else { - return (-1, $result); - } - } - - return (0, $result) if ($chnlist eq ''); - - # CCU script to query datapoints - my $script = qq( -string sDPId; -string sChannel; -string sChnList = "$chnlist"; -foreach (sChannel, sChnList.Split(",")) { - object oChannel = dom.GetObject (sChannel); - if (oChannel) { - foreach(sDPId, oChannel.DPs()) { - object oDP = dom.GetObject(sDPId); - if (oDP) { - WriteLine (sChannel # "=" # oDP.Name() # "=" # oDP.$ccuget()); - } - } - } -} - ); - - my $response = HMCCU_HMScript ($hmccu_hash, $script); - return (-2, $result) if ($response eq ''); - - readingsBeginUpdate ($hash) if ($type ne 'HMCCU' && $ccureadings); - - foreach my $dpdef (split /\n/, $response) { - my @dpdata = split /=/, $dpdef; - next if (@dpdata != 3); - my @adrtoks = split /\./, $dpdata[1]; - next if (@adrtoks != 3); - next if ($adrtoks[2] !~ /$chnpars{$dpdata[0]}{dpt}/); - - my ($add, $chn) = split /:/, $adrtoks[1]; - my $reading = HMCCU_GetReadingName ($adrtoks[0], $add, $chn, $adrtoks[2], - $dpdata[0], $readingformat); - next if ($reading eq ''); - - my $value = HMCCU_Substitute ($dpdata[2], $chnpars{$dpdata[0]}{sub}, 0, $reading); - if ($hash->{TYPE} eq 'HMCCU') { - HMCCU_UpdateClientReading ($hmccu_hash, $add, $chn, $adrtoks[2], $reading, $value); - } - else { - $value = HMCCU_ScaleValue ($hash, $adrtoks[2], $value, 0); - $value = HMCCU_FormatReadingValue ($hash, $value); - if ($ccureadings) { - readingsBulkUpdate ($hash, $reading, $value); - if ($controldpt ne '' && $adrtoks[2] eq $controldpt && $chn eq $controlchn) { - readingsBulkUpdate ($hash, 'control', $value); - } - if ($reading =~ /\.$statedpt$/ && ($statechn eq '' || $statechn eq $chn)) { - readingsBulkUpdate ($hash, "state", $value); - } - } - } - - $result .= $reading.'='.$value."\n"; - $count++; - } - - readingsEndUpdate ($hash, 1) if ($type ne 'HMCCU' && $ccureadings); - - return ($count, $result); -} - -#################################################### -# Get RPC paramSet or paramSetDescription -#################################################### - -sub HMCCU_RPCGetConfig ($$$) -{ - my ($hash, $param, $mode) = @_; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - - my $addr; - my $result = ''; - - my $ccureadings = AttrVal ($name, 'ccureadings', 1); - my $readingformat = AttrVal ($name, 'ccureadingformat', 'name'); - my $substitute = AttrVal ($name, 'substitute', ''); - - my $hmccu_hash = HMCCU_GetHash ($hash); - return (-3, $result) if (!defined ($hmccu_hash)); - return (-4, $result) if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'Deleted'); - - my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($param, $HMCCU_FLAG_FULLADDR); - return (-1, '') if (!($flags & $HMCCU_FLAG_ADDRESS)); - $addr = $add; - $addr .= ':'.$chn if ($flags & $HMCCU_FLAG_CHANNEL); - - 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 $res = $client->simple_request ($mode, $addr, "MASTER"); - if (! defined ($res)) { - return (-5, "Function not available"); - } - elsif (ref ($res)) { - my $parcount = scalar (keys %$res); - if (exists ($res->{faultString})) { - Log3 $name, 1, "HMCCU: ".$res->{faultString}; - return (-2, $res->{faultString}); - } - elsif ($parcount == 0) { - return (-5, "CCU returned no data"); - } - } - else { - return (-2, defined ($RPC::XML::ERROR) ? $RPC::XML::ERROR : ''); - } - - if ($mode eq 'getParamsetDescription') { - foreach my $key (sort keys %$res) { - my $oper = ''; - $oper .= 'R' if ($res->{$key}->{OPERATIONS} & 1); - $oper .= 'W' if ($res->{$key}->{OPERATIONS} & 2); - $oper .= 'E' if ($res->{$key}->{OPERATIONS} & 4); - $result .= $key.": ".$res->{$key}->{TYPE}." [".$oper."]\n"; - } - - return (0, $result); - } - - readingsBeginUpdate ($hash) if ($ccureadings); - - foreach my $key (sort keys %$res) { - my $value = $res->{$key}; - $result .= "$key=$value\n"; - - if ($ccureadings) { - my $reading = HMCCU_GetReadingName ($int, $add, $chn, $key, $nam, - $readingformat); - if ($reading ne '') { - $value = HMCCU_Substitute ($value, $substitute, 0, $reading); - $value = HMCCU_FormatReadingValue ($hash, $value); - $reading = "R-".$reading; - readingsBulkUpdate ($hash, $reading, $value); - } - } - } - - readingsEndUpdate ($hash, 1) if ($ccureadings); - - return (0, $result); -} - -#################################################### -# Set RPC paramSet -#################################################### - -sub HMCCU_RPCSetConfig ($$$) -{ - my ($hash, $param, $parref) = @_; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - - my $addr; - my %paramset; - - my $hmccu_hash = HMCCU_GetHash ($hash); - return -3 if (!defined ($hmccu_hash)); - return -4 if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'Deleted'); - - my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($param, $HMCCU_FLAG_FULLADDR); - return -1 if (!($flags & $HMCCU_FLAG_ADDRESS)); - $addr = $add; - $addr .= ':'.$chn if ($flags & $HMCCU_FLAG_CHANNEL); - - 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); - next if (!defined ($par) || !defined ($val)); - $paramset{$par} = $val; - } - - my $client = RPC::XML::Client->new ($url); - my $res = $client->simple_request ("putParamset", $addr, "MASTER", \%paramset); - if (! defined ($res)) { - return -5; - } - elsif (ref ($res)) { - if (exists ($res->{faultString})) { - Log3 $name, 1, "HMCCU: ".$res->{faultString}; - return -2; - } - } - - return 0; -} - -sub HMCCU_QueueOpen ($$) -{ - my ($hash, $queue_file) = @_; - - my $idx_file = $queue_file . '.idx'; - $queue_file .= '.dat'; - my $mode = '0666'; - - umask (0); - - $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, oct($mode) or return 0; - $hash->{hmccu}{queue}{idx} = new IO::File $idx_file, O_CREAT | O_RDWR, oct($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}{seperator}//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'. -# and: return v1 if all readings matching v1, -# otherwise return v2. -# or: return v1 if at least 1 reading matches v1, -# otherwise return v2. -# cnt: return number of readings matching v1. -# Ex 1: number of open windows: state, "cnt", "open", "" -# Ex 2: Status of windows: state, "and", "close", "open" -#################################################### - -sub HMCCU_AggReadings ($$$$$) -{ - my ($name, $readexp, $oper, $v1, $v2) = @_; - - return undef if (!exists ($defs{$name})); - - my $mc = 0; - my $c = 0; - - foreach my $r (keys %{$defs{$name}{READINGS}}) { - next if ($r !~ /$readexp/); - $c++; - $mc++ if ($defs{$name}{READINGS}{$r}{VAL} eq $v1); - } - - if ($oper eq 'and') { - return ($mc < $c) ? $v2 : $v1; - } - elsif ($oper eq 'or') { - return ($mc > 0) ? $v1 : $v2; - } - else { - return $mc; - } -} - -#################################################### -# Calculate dewpoint. Requires reading names of -# temperature and humidity as parameters. -#################################################### - -sub HMCCU_Dewpoint ($$$$) -{ - my ($name, $rtmp, $rhum, $defdp) = @_; - my $a; - my $b; - - my $tmp = ReadingsVal ($name, $rtmp, 100.0); - my $hum = ReadingsVal ($name, $rhum, 0.0); - return $defdp if ($tmp == 100.0 || $hum == 0.0); - - if ($tmp >= 0.0) { - $a = 7.5; - $b = 237.3; - } - else { - $a = 7.6; - $b = 240.7; - } - - my $sdd = 6.1078*(10.0**(($a*$tmp)/($b+$tmp))); - my $dd = $hum/100.0*$sdd; - my $v = log($dd/6.1078)/log(10.0); - my $td = $b*$v/($a-$v); - - return sprintf "%.1f", $td; -} - -#################################################### -# Encode command string for e-paper display -# -# Parameters: -# -# msg := parameter=value[,...] -# -# text1-3=Text -# icon1-3=Icon -# sound= -# signal= -# pause=1-160 -# repeat=0-15 -# -# Returns undef on error or encoded string on success -#################################################### - -sub HMCCU_EncodeEPDisplay ($) -{ - my ($msg) = @_; - - # set defaults - $msg = '' if (!defined ($msg)); - - my %disp_icons = ( - ico_off => '0x80', ico_on => '0x81', ico_open => '0x82', ico_closed => '0x83', - ico_error => '0x84', ico_ok => '0x85', ico_info => '0x86', ico_newmsg => '0x87', - ico_svcmsg => '0x88' - ); - - my %disp_sounds = ( - snd_off => '0xC0', snd_longlong => '0xC1', snd_longshort => '0xC2', - snd_long2short => '0xC3', snd_short => '0xC4', snd_shortshort => '0xC5', - snd_long => '0xC6' - ); - - my %disp_signals = ( - sig_off => '0xF0', sig_red => '0xF1', sig_green => '0xF2', sig_orange => '0xF3' - ); - - # Parse command string - my @text = ('', '', ''); - my @icon = ('', '', ''); - my %conf = (sound => 'snd_off', signal => 'sig_off', repeat => 1, pause => 10); - foreach my $tok (split (',', $msg)) { - my ($par, $val) = split ('=', $tok); - next if (!defined ($val)); - if ($par =~ /^text([1-3])$/) { - $text[$1-1] = substr ($val, 0, 12); - } - elsif ($par =~ /^icon([1-3])$/) { - $icon[$1-1] = $val; - } - elsif ($par =~ /^(sound|pause|repeat|signal)$/) { - $conf{$1} = $val; - } - } - - my $cmd = '0x02,0x0A'; - - for (my $c=0; $c<3; $c++) { - if ($text[$c] ne '' || $icon[$c] ne '') { - $cmd .= ',0x12'; - - # Hex code - if ($text[$c] =~ /^0x[0-9A-F]{2}$/) { - $cmd .= ','.$text[$c]; - } - # Predefined text code #0-9 - elsif ($text[$c] =~ /^#([0-9])$/) { - $cmd .= sprintf (",0x8%1X", $1); - } - # Convert string to hex codes - else { - foreach my $ch (split ('', $text[$c])) { - $cmd .= sprintf (",0x%02X", ord ($ch)); - } - } - - # Icon - if ($icon[$c] ne '' && exists ($disp_icons{$icon[$c]})) { - $cmd .= ',0x13,'.$disp_icons{$icon[$c]}; - } - } - - $cmd .= ',0x0A'; - } - - # Sound - my $snd = $disp_sounds{snd_off}; - $snd = $disp_sounds{$conf{sound}} if (exists ($disp_sounds{$conf{sound}})); - $cmd .= ',0x14,'.$snd.',0x1C'; - - # Repeat - my $rep = $conf{repeat} if ($conf{repeat} >= 0 && $conf{repeat} <= 15); - $rep = 1 if ($rep < 0); - $rep = 15 if ($rep > 15); - if ($rep == 0) { - $cmd .= ',0xDF'; - } - else { - $cmd .= sprintf (",0x%02X", 0xD0+$rep-1); - } - $cmd .= ',0x1D'; - - # Pause - my $pause = $conf{pause}; - $pause = 1 if ($pause < 1); - $pause = 160 if ($pause > 160); - $cmd .= sprintf (",0xE%1X,0x16", int(($pause-1)/10)); - - # Signal - my $sig = $disp_signals{sig_off}; - $sig = $disp_signals{$conf{signal}} if (exists ($disp_signals{$conf{signal}})); - $cmd .= ','.$sig.',0x03'; - - return '"'.$cmd.'"'; -} - - - -#################################################### -# *** Subprocess process part *** -#################################################### - -# Child process. Must be global to allow access by RPC callbacks -my $hmccu_child; - -# 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(); -} - - -1; - - -=pod -=begin html - - -

HMCCU

-
    - The module provides an easy get/set interface for Homematic CCU. It acts as an - 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 -
      -
      - define <name> HMCCU <HostOrIP> -

      - Example: -
      - define myccu HMCCU 192.168.1.10 -

      - HostOrIP - Hostname or IP address of Homematic CCU. -
      -
    -
    - - - Set
    -
      -
      -
    • set <name> config {<device>|<channel>} <parameter>=<value> [...]
      - Set configuration parameters of CCU device or channel. -

    • -
    • set <name> devstate {[<interface>.]<channel-address>|<channel-name>} <value> [...]
      - Set state of a CCU device. Specified CCU channel must have a datapoint STATE. -

      - Example:
      - set d_ccu devstate ST-WZ-Bass false
      - set d_ccu devstate BidCos-RF.LEQ1462664:1 false -

    • -
    • set <name> datapoint {[<interface>.]<channel-address>.<datapoint>|<channel-name>.<datapoint>} <value> -
      - Set value of a datapoint of a CCU device channel. -

      - Example:
      - set d_ccu datapoint THERMOSTAT_CHN2.SET_TEMPERATURE 21
      - set d_ccu datapoint LEQ1234567:2.SET_TEMPERATURE 21 -

    • -
    • set <name> var <variable>> <Value> [...]
      - Set CCU variable value. -

    • -
    • set <name> execute <program>
      - Execute CCU program. -

      - Example:
      - set d_ccu execute PR-TEST -

    • -
    • set <name> hmscript <script-file>
      - Execute HM script on CCU. If output of script contains lines in format - Object=Value readings will be set. Object can be the name of a CCU system - variable or a valid datapoint specification. -

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

    -
      -
      -
    • get <name> config {<device>|<channel>} - Get configuration parameters of CCU device or channel. If attribute ccureadings is 0 parameters will - be displayed in browser window. -

    • -
    • get <name> configdesc {<device>|<channel>} - Get configuration parameter description of CCU device or channel. -

    • -
    • get <name> devstate {[<interface>.]<channel-address>|<channel-name>} [<reading>]
      - Get state of a CCU device. Specified channel must have a datapoint STATE. If <reading> - is specified the value will be stored using this name. -

    • -
    • get <name> vars <regexp>
      - Get CCU system variables matching <regexp> and store them as readings. -

    • -
    • get <name> channel {[<interface>.]<channel-address>[.<datapoint-expr>]|<channel-name>[.<datapoint-expr>]}[=[regexp1:subst1[,...]]] [...] -
      - Get value of datapoint(s). If no datapoint is specified all datapoints of specified - channel are read. <datapoint> can be specified as a regular expression. -

    • -
    • get <name> deviceinfo <device-name> [{'State'|'Value'}]
      - List device channels and datapoints. -

    • -
    • get <name> devicelist [dump]
      - Read list of devices and channels from CCU. This command is executed automatically after device - definition. Must be executed after module HMCCU is reloaded. With option dump devices are displayed - in browser window. -

    • -
    • get <name> parfile [<parfile>]
      - Get values of all channels / datapoints specified in <parfile>. <parfile> can also - be defined as an attribute. The file must contain one channel / datapoint definition per line. - Datapoints are optional (for syntax see command 'get channel'). After the channel definition - a list of string substitution rules for datapoint values can be specified (like attribute - 'substitute').
      - The syntax of Parfile entries is: -

      - {[<interface>.]<channel-address>[.<datapoint-expr>]|<channel-name>[.<datapoint-expr>]} <regexp>:<subsstr>[,...] -

      - Empty lines or lines starting with a # are ignored. -

    • -
    • get <name> rpcstate
      - Check if RPC server process is running. -

    • -
    • get <name> update [<devexp> [<'State'|'Value'>]]
      - Update all datapoints / readings of client devices with FHEM device name matching <devexp> -

    • -
    • get <name> updateccu [<devexp> [<'State'|'Value'>]]
      - Update all datapoints / readings of client devices with CCU device name matching <devexp> -
    • -
    -
    - - - Attributes
    -
    -
      -
    • ccuget <State | Value>
      - Set read access method for CCU channel datapoints. Method 'State' is slower than 'Value' because - each request is sent to the device. With method 'Value' only CCU is queried. Default is 'Value'. -

    • -
    • ccureadingformat <name | address>
      - Format of reading names (channel name or channel address) -

    • -
    • ccureadings <0 | 1>
      - If set to 1 values read from CCU will be stored as readings. Otherwise output - is displayed in browser window. -

    • -
    • ccutrace <ccu-devname-exp|ccu-address-exp>
      - Turn on trace mode for devices matching specified expression. Will write extended - information into FHEM log (level 1). -

    • -
    • parfile <filename>
      - Define parameter file for command 'get parfile'. -

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

    • -
    • rpcport <value[,...]>
      - Specify list of RPC ports on CCU. Default is 2001. -

    • -
    • rpcqueue <queue-file>
      - Specify name of RPC queue file. This parameter is only a prefix for the - queue files with extension .idx and .dat. Default is /tmp/ccuqueue. -

    • -
    • rpcserver <on | off>
      - Specify if RPC server is automatically started on FHEM startup. -

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

    • -
    • statevals <text:substext[,...]>
      - Define substitutions for values in 'set devstate/datapoint' command. -

    • -
    • substitude <expression>:<substext>[,...]
      - Define substitions for reading values. Substitutions for parfile values must - be specified in parfiles. -

    • -
    • stripchar <character>
      - Strip the specified character from variable or device name in set commands. This - is useful if a variable should be set in CCU using the reading with trailing colon. -

    • -
    • updatemode { client | both | hmccu }
      - Set update mode for readings.
      - 'client' = update only readings of client devices
      - 'both' = update readings of client devices and IO device
      - 'hmccu' = update readings of IO device -
    • -
    -
- -=end html -=cut - diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm deleted file mode 100644 index 22dac050e..000000000 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm +++ /dev/null @@ -1,571 +0,0 @@ -################################################################ -# -# 88_HMCCUCHN.pm -# -# $Id:$ -# -# Version 3.3 -# -# (c) 2016 zap (zap01 t-online de) -# -################################################################ -# -# define HMCCUCHN [readonly] -# -# set control -# set datapoint -# set devstate -# set -# set toggle -# set config = [...] -# -# get devstate -# get datapoint -# get channel -# get config -# get configdesc -# get update -# -# attr ccureadings { 0 | 1 } -# attr ccureadingfilter -# attr ccureadingformat { name | address | datapoint } -# attr ccuverify { 0 | 1 | 2 } -# attr controldatapoint -# attr disable { 0 | 1 } -# attr statedatapoint -# attr statevals :[,...] -# attr substitute [;...] -# -################################################################ -# Requires module 88_HMCCU.pm -################################################################ - -package main; - -use strict; -use warnings; -use SetExtensions; - -use Time::HiRes qw( gettimeofday usleep ); - -sub HMCCUCHN_Define ($@); -sub HMCCUCHN_Set ($@); -sub HMCCUCHN_Get ($@); -sub HMCCUCHN_Attr ($@); -sub HMCCUCHN_SetError ($$); - -##################################### -# Initialize module -##################################### - -sub HMCCUCHN_Initialize ($) -{ - my ($hash) = @_; - - $hash->{DefFn} = "HMCCUCHN_Define"; - $hash->{SetFn} = "HMCCUCHN_Set"; - $hash->{GetFn} = "HMCCUCHN_Get"; - $hash->{AttrFn} = "HMCCUCHN_Attr"; - - $hash->{AttrList} = "IODev ccureadingfilter ccureadingformat:name,address,datapoint ccureadings:0,1 ccuscaleval ccuverify:0,1,2 ccuget:State,Value controldatapoint disable:0,1 statedatapoint statevals substitute stripnumber:0,1,2 ". $readingFnAttributes; -} - -##################################### -# Define device -##################################### - -sub HMCCUCHN_Define ($@) -{ - my ($hash, $def) = @_; - my $name = $hash->{NAME}; - my @a = split("[ \t][ \t]*", $def); - - return "Specifiy the CCU device name or address as parameters" if (@a < 3); - - my $devname = shift @a; - my $devtype = shift @a; - my $devspec = shift @a; - - return "Invalid or unknown CCU channel name or address" if (! HMCCU_IsValidDevice ($devspec)); - - 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 (HMCCU_IsChnAddr ($devspec, 0)) { - # CCU Channel address - $hash->{ccuaddr} = $devspec; - $hash->{ccuif} = HMCCU_GetDeviceInterface ($hash->{ccuaddr}, 'BidCos-RF'); - $hash->{ccuname} = HMCCU_GetChannelName ($devspec, ''); - return "CCU device name not found for channel address $devspec" if ($hash->{ccuname} eq ''); - } - else { - # 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 ''); - $hash->{ccuaddr} = $add.':'.$chn; - $hash->{ccuif} = HMCCU_GetDeviceInterface ($hash->{ccuaddr}, 'BidCos-RF'); - } - - $hash->{ccutype} = HMCCU_GetDeviceType ($hash->{ccuaddr}, ''); - $hash->{channels} = 1; - $hash->{statevals} = 'devstate'; - - my $arg = shift @a; - if (defined ($arg) && $arg eq 'readonly') { - $hash->{statevals} = $arg; - } - - # Inform HMCCU device about client device - AssignIoPort ($hash); - - readingsSingleUpdate ($hash, "state", "Initialized", 1); - $hash->{ccudevstate} = 'Active'; - - return undef; -} - -##################################### -# Set attribute -##################################### - -sub HMCCUCHN_Attr ($@) -{ - my ($cmd, $name, $attrname, $attrval) = @_; - my $hash = $defs{$name}; - - if ($cmd eq "set") { - return "Missing attribute value" if (!defined ($attrval)); - if ($attrname eq 'IODev') { - $hash->{IODev} = $defs{$attrval}; - } - elsif ($attrname eq 'statevals') { - return "Device is read only" if ($hash->{statevals} eq 'readonly'); - $hash->{statevals} = "devstate"; - my @states = split /,/,$attrval; - foreach my $st (@states) { - my @statesubs = split /:/,$st; - return "value := text:substext[,...]" if (@statesubs != 2); - $hash->{statevals} .= '|'.$statesubs[0]; - } - } - } - elsif ($cmd eq "del") { - if ($attrname eq 'statevals') { - $hash->{statevals} = "devstate"; - } - } - - return undef; -} - -##################################### -# Set commands -##################################### - -sub HMCCUCHN_Set ($@) -{ - my ($hash, @a) = @_; - my $name = shift @a; - my $opt = shift @a; - - 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"; - } - - my $statevals = AttrVal ($name, "statevals", ''); -# my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); -# my $controldatapoint = AttrVal ($name, "controldatapoint", ''); - my ($sc, $statedatapoint, $cc, $controldatapoint) = HMCCU_GetSpecialDatapoints ( - $hash, '', 'STATE', '', ''); - - my $result = ''; - my $rc; - - if ($opt eq 'datapoint') { - my $objname = shift @a; -# my $objvalue = join ('%20', @a); - my $objvalue = shift @a; - - 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 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; - $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - HMCCU_SetState ($hash, "OK"); - return undef; - } - elsif ($opt =~ /^($hash->{statevals})$/) { - my $cmd = $1; - my $objvalue = ($cmd ne 'devstate') ? $cmd : shift @a; - - 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 HMCCU_SetError ($hash, $rc) if ($rc < 0); - - HMCCU_SetState ($hash, "OK"); - return undef; - } - elsif ($opt eq 'toggle') { - return HMCCU_SetError ($hash, "Attribute statevals not set") - if ($statevals eq '' || !exists($hash->{statevals})); - - my $tstates = $hash->{statevals}; - $tstates =~ s/devstate\|//; - my @states = split /\|/, $tstates; - my $sc = scalar (@states); - - my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$statedatapoint; - ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - my $objvalue = ''; - my $st = 0; - while ($st < $sc) { - if ($states[$st] eq $result) { - $objvalue = ($st == $sc-1) ? $states[0] : $states[$st+1]; - last; - } - else { - $st++; - } - } - - 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 HMCCU_SetError ($hash, $rc) if ($rc < 0); - HMCCU_SetState ($hash, "OK"); - return undef; - } - elsif ($opt eq 'config') { - return HMCCU_SetError ($hash, "Usage: set $name config {parameter}={value} [...]") if (@a < 1);; - - 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"; - return undef if ($hash->{statevals} eq 'readonly'); - - if ($hash->{statevals} ne '') { - my @cmdlist = split /\|/,$hash->{statevals}; - shift @cmdlist; - $retmsg .= ':'.join(',',@cmdlist) if (@cmdlist > 0); - foreach my $sv (@cmdlist) { - $retmsg .= ' '.$sv.':noArg'; - } - $retmsg .= " toggle:noArg"; - } - - return $retmsg; - } -} - -##################################### -# Get commands -##################################### - -sub HMCCUCHN_Get ($@) -{ - my ($hash, @a) = @_; - my $name = shift @a; - my $opt = shift @a; - - 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"; - } - -# my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); - my ($sc, $statedatapoint, $cc, $cd) = HMCCU_GetSpecialDatapoints ($hash, '', 'STATE', '', ''); - my $ccureadings = AttrVal ($name, "ccureadings", 1); - - my $result = ''; - my $rc; - - if ($opt eq 'devstate') { - my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$statedatapoint; - ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - return $ccureadings ? undef : $result; - } - elsif ($opt eq 'datapoint') { - my $objname = shift @a; - 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 HMCCU_SetError ($hash, $rc) if ($rc < 0); - return $ccureadings ? undef : $result; - } - elsif ($opt eq 'channel') { - my $dptexpr = shift @a; - my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}; - $objname .= '.'.$dptexpr if (defined ($dptexpr)); - my @chnlist = ($objname); - ($rc, $result) = HMCCU_GetChannel ($hash, \@chnlist); - 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 HMCCU_SetError ($hash, "Usage: get $name update [{'State'|'Value'}]"); - } - $rc = HMCCU_GetUpdate ($hash, $hash->{ccuaddr}, $ccuget); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - return undef; - } - elsif ($opt eq 'config') { - my $ccuobj = $hash->{ccuaddr}; - - my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset"); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - return $ccureadings ? undef : $res; - } - elsif ($opt eq 'configdesc') { - my $ccuobj = $hash->{ccuaddr}; - - my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription"); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - return $res; - } - else { - 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; - } -} - -##################################### -# Set error status -##################################### - -sub HMCCUCHN_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' - ); - - if (exists ($errlist{$text})) { - $msg = $errlist{$text}; - } - else { - $msg = $text; - } - - $msg = "HMCCUCHN: ".$name." ". $msg; - readingsSingleUpdate ($hash, "state", "Error", 1); - Log3 $name, 1, $msg; - return $msg; -} - -1; - -=pod -=begin html - - -

HMCCUCHN

-
    - The module implements client devices for HMCCU. A HMCCU device must exist - before a client device can be defined. -

    - - Define -
      -
      - define <name> HMCCUCHN {<channel-name>|<channel-address>} [readonly] -

      - If readonly parameter is specified no set command will be available. -

      - Examples:
      - define window_living HMCCUCHN WIN-LIV-1 readonly
      - define temp_control HMCCUCHN BidCos-RF.LEQ1234567:1 -
      -
    -
    - - - Set
    -
      -
      -
    • set <name> devstate <value> [...]
      - Set state of a CCU device channel. Channel datapoint must be defined - by setting attribute 'statedatapoint'. -

      - Example:
      - set light_entrance devstate on -

    • -
    • set <name> <statevalue>
      - State of a CCU device channel is set to StateValue. State datapoint - must be defined as attribute statedatapoint. State values can be replaced - by setting attribute statevals. -

      - Example:
      - - attr myswitch statedatapoint TEST
      - attr myswitch statevals on:true,off:false
      - set myswitch on -
      -

    • -
    • set <name> toggle
      - Toggles between values defined by attribute 'statevals'. -

    • -
    • set <name> datapoint <datapoint> <value>
      - Set value of a datapoint of a CCU device channel. -

      - Example:
      - set temp_control datapoint SET_TEMPERATURE 21 -

    • -
    • set <name> config [<rpcport>] <parameter>=<value>] [...]
      - Set config parameters of CCU channel. -
    • -
    -
    - - - Get
    -
      -
      -
    • get <name> devstate
      - Get state of CCU device. Default datapoint STATE can be changed by setting - attribute 'statedatapoint'. -

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

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

    • -
    • get <name> configdesc
      - Get description of configuration parameters of CCU channel. -

    • -
    • get <name> update [{'State'|'Value'}]
      - Update all datapoints / readings of channel. -
    • -
    -
    - - - Attributes
    -
    -
      -
    • ccuget <State | Value>
      - Set read access method for CCU channel datapoints. Method 'State' is slower than 'Value' because - each request is sent to the device. With method 'Value' only CCU is queried. Default is 'Value'. -

    • -
    • ccureadings <0 | 1>
      - If set to 1 values read from CCU will be stored as readings. Default is 1. -

    • -
    • ccureadingfilter <filter-rule[,...]>
      - Only datapoints matching specified expression are stored as readings.
      - Syntax for filter rule is: [channel-no:]RegExp
      - If channel-no is specified the following rule applies only to this channel. -

    • -
    • ccuscaleval <datapoint>:<factor>[,...]
      - Scale datapoint values before executing set datapoint commands or after executing get - datapoint commands. During get the value read from CCU is devided by factor. During set - the value is multiplied by factor. -

    • -
    • 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 - setting control datapoint. For example if datapoint of thermostat control is - SET_TEMPERATURE one can define a slider for setting the destination temperature with - following attributes:

      - attr mydev controldatapoint SET_TEMPERATURE - 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. -

    • -
    • statevals <text>:<text>[,...]
      - Define substitution for set commands values. The parameters <text> - are available as set commands. Example:
      - attr my_switch statevals on:true,off:false
      - set my_switch on -

    • -
    • substitude <subst-rule>[;...]
      - Define substitions for reading values. Substitutions for parfile values must - be specified in parfiles. Syntax of subst-rule is

      - [datapoint!]<regexp1>:<text1>[,...] -
    • -
    -
- -=end html -=cut - diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm deleted file mode 100644 index e1b5951e6..000000000 --- a/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm +++ /dev/null @@ -1,830 +0,0 @@ -##################################################################### -# -# 88_HMCCUDEV.pm -# -# $Id:$ -# -# Version 3.3 -# -# (c) 2016 zap (zap01 t-online de) -# -##################################################################### -# -# define HMCCUDEV {|virtual} [statechannel] [readonly] -# [{group={|}[,...]|groupexp=}] -# -# set config [] = [...] -# set control -# set datapoint . -# set defaults -# set devstate -# set on-for-timer -# set -# set toggle -# -# get devstate -# get datapoint . -# get defaults -# get channel [.] -# get config [] -# get configdesc [] -# get update -# -# attr ccuget { State | Value } -# attr ccureadings { 0 | 1 } -# attr ccureadingformat { address | name } -# attr ccureadingfilter [,...] -# attr ccuscaleval :[,...] -# attr ccuverify { 0 | 1 | 2} -# attr controldatapoint . -# attr disable { 0 | 1 } -# attr mapdatapoints .=.[,...] -# attr statechannel -# attr statedatapoint [.] -# attr statevals :[,...] -# attr substitute :[,...] -# -##################################################################### -# Requires module 88_HMCCU -##################################################################### - -package main; - -use strict; -use warnings; -use SetExtensions; -# use Data::Dumper; - -use Time::HiRes qw( gettimeofday usleep ); - -sub HMCCUDEV_Define ($@); -sub HMCCUDEV_Set ($@); -sub HMCCUDEV_Get ($@); -sub HMCCUDEV_Attr ($@); -sub HMCCUDEV_SetError ($$); - -##################################### -# Initialize module -##################################### - -sub HMCCUDEV_Initialize ($) -{ - my ($hash) = @_; - - $hash->{DefFn} = "HMCCUDEV_Define"; - $hash->{SetFn} = "HMCCUDEV_Set"; - $hash->{GetFn} = "HMCCUDEV_Get"; - $hash->{AttrFn} = "HMCCUDEV_Attr"; - - $hash->{AttrList} = "IODev ccureadingfilter:textField-long ccureadingformat:name,address ccureadings:0,1 ccuget:State,Value ccuscaleval ccuverify:0,1,2 disable:0,1 mapdatapoints:textField-long statevals substitute statechannel statedatapoint controldatapoint stripnumber:0,1,2 ". $readingFnAttributes; -} - -##################################### -# Define device -##################################### - -sub HMCCUDEV_Define ($@) -{ - my ($hash, $def) = @_; - my $name = $hash->{NAME}; - my @a = split("[ \t][ \t]*", $def); - - my $usage = "Usage: define $name HMCCUDEV {device|'virtual'} [state-channel] ['readonly'] [{groupexp=regexp|group={device|channel}[,...]]"; - return $usage if (@a < 3); - - my $devname = shift @a; - my $devtype = shift @a; - my $devspec = shift @a; - - my $hmccu_hash = undef; - - if ($devspec ne 'virtual') { - return "Invalid or unknown CCU device name or address" if (!HMCCU_IsValidDevice ($devspec)); - } - - if ($devspec eq 'virtual') { - # Virtual device FHEM only - my $no = 0; - foreach my $d (sort keys %defs) { - my $ch = $defs{$d}; - $hmccu_hash = $ch if ($ch->{TYPE} eq 'HMCCU' && !defined ($hmccu_hash)); - next if ($ch->{TYPE} ne 'HMCCUDEV'); - next if ($d eq $name); - next if ($ch->{ccuif} ne 'VirtualDevices' || $ch->{ccuname} ne 'none'); - $no++; - } - return "No IO device found" if (!defined ($hmccu_hash)); - $hash->{ccuif} = "VirtualDevices"; - $hash->{ccuaddr} = sprintf ("VIR%07d", $no+1); - $hash->{ccuname} = "none"; - } - elsif (HMCCU_IsDevAddr ($devspec, 1)) { - # CCU Device address with interface - $hash->{ccuif} = $1; - $hash->{ccuaddr} = $2; - $hash->{ccuname} = HMCCU_GetDeviceName ($hash->{ccuaddr}, ''); - } - elsif (HMCCU_IsDevAddr ($devspec, 0)) { - # CCU Device address without interface - $hash->{ccuaddr} = $devspec; - $hash->{ccuname} = HMCCU_GetDeviceName ($devspec, ''); - $hash->{ccuif} = HMCCU_GetDeviceInterface ($hash->{ccuaddr}, 'BidCos-RF'); - } - else { - # CCU Device name - $hash->{ccuname} = $devspec; - my ($add, $chn) = HMCCU_GetAddress ($devspec, '', ''); - return "Name is a channel name" if ($chn ne ''); - $hash->{ccuaddr} = $add; - $hash->{ccuif} = HMCCU_GetDeviceInterface ($hash->{ccuaddr}, 'BidCos-RF'); - } - - return "CCU device address not found for $devspec" if ($hash->{ccuaddr} eq ''); - return "CCU device name not found for $devspec" if ($hash->{ccuname} eq ''); - - $hash->{ccutype} = HMCCU_GetDeviceType ($hash->{ccuaddr}, ''); - $hash->{channels} = HMCCU_GetDeviceChannels ($hash->{ccuaddr}); - - if ($hash->{ccuif} eq "VirtualDevices" && $hash->{ccuname} eq 'none') { - $hash->{statevals} = 'readonly'; - } - else { - $hash->{statevals} = 'devstate'; - } - - my $n = 0; - my $arg = shift @a; - while (defined ($arg)) { - return $usage if ($n == 3); - if ($arg eq 'readonly') { - $hash->{statevals} = $arg; - $n++; - } - elsif ($arg =~ /^groupexp=/ && $hash->{ccuif} eq "VirtualDevices") { - my ($g, $gdev) = split ("=", $arg); - return $usage if (!defined ($gdev)); - my @devlist; - my $cnt = HMCCU_GetMatchingDevices ($hmccu_hash, $gdev, 'dev', \@devlist); - return "No matching CCU devices found" if ($cnt == 0); - $hash->{ccugroup} = shift @devlist; - foreach my $gd (@devlist) { - $hash->{ccugroup} .= ",".$gd; - } - } - elsif ($arg =~ /^group=/ && $hash->{ccuif} eq "VirtualDevices") { - my ($g, $gdev) = split ("=", $arg); - return $usage if (!defined ($gdev)); - my @gdevlist = split (",", $gdev); - $hash->{ccugroup} = '' if (@gdevlist > 0); - foreach my $gd (@gdevlist) { - my ($gda, $gdc, $gdo) = ('', '', '', ''); - - return "Invalid device or channel $gd" - if (!HMCCU_IsValidDevice ($gd)); - - if (HMCCU_IsDevAddr ($gd, 0) || HMCCU_IsChnAddr ($gd, 1)) { - $gdo = $gd; - } - else { - ($gda, $gdc) = HMCCU_GetAddress ($gd, '', ''); - $gdo = $gda; - $gdo .= ':'.$gdc if ($gdc ne ''); - } - - if (exists ($hash->{ccugroup}) && $hash->{ccugroup} ne '') { - $hash->{ccugroup} .= ",".$gdo; - } - else { - $hash->{ccugroup} = $gdo; - } - } - } - elsif ($arg =~ /^[0-9]+$/) { - $attr{$name}{statechannel} = $arg; - $n++; - } - else { - return $usage; - } - $arg = shift @a; - } - - return "No devices in group" if ($hash->{ccuif} eq "VirtualDevices" && ( - !exists ($hash->{ccugroup}) || $hash->{ccugroup} eq '')); - - # Inform HMCCU device about client device - AssignIoPort ($hash); - - readingsSingleUpdate ($hash, "state", "Initialized", 1); - $hash->{ccudevstate} = 'Active'; - - return undef; -} - -##################################### -# Set attribute -##################################### - -sub HMCCUDEV_Attr ($@) -{ - my ($cmd, $name, $attrname, $attrval) = @_; - my $hash = $defs{$name}; - - if ($cmd eq "set") { - return "Missing attribute value" if (!defined ($attrval)); - if ($attrname eq 'IODev') { - $hash->{IODev} = $defs{$attrval}; - } - elsif ($attrname eq "statevals") { - return "Device is read only" if ($hash->{statevals} eq 'readonly'); - $hash->{statevals} = 'devstate'; - my @states = split /,/,$attrval; - foreach my $st (@states) { - my @statesubs = split /:/,$st; - return "value := text:substext[,...]" if (@statesubs != 2); - $hash->{statevals} .= '|'.$statesubs[0]; - } - } - elsif ($attrname eq "mapdatapoints") { - return "Not a virtual device" if ($hash->{ccuif} ne "VirtualDevices"); - } - } - elsif ($cmd eq "del") { - if ($attrname eq "statevals") { - $hash->{statevals} = "devstate"; - } - } - - return undef; -} - -##################################### -# Set commands -##################################### - -sub HMCCUDEV_Set ($@) -{ - my ($hash, @a) = @_; - my $name = shift @a; - my $opt = shift @a; - - 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"; - } - -# my $statechannel = AttrVal ($name, "statechannel", ''); -# my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); - my $statevals = AttrVal ($name, "statevals", ''); -# my $controldatapoint = AttrVal ($name, "controldatapoint", ''); - my ($statechannel, $statedatapoint, $controlchannel, $controldatapoint) = - HMCCU_GetSpecialDatapoints ($hash, '', 'STATE', '', ''); - - my $result = ''; - my $rc; - - if ($opt eq 'datapoint') { - my $objname = shift @a; -# my $objvalue = join ('%20', @a); - my $objvalue = shift @a; - - if (!defined ($objname) || $objname !~ /^[0-9]+\..+$/ || !defined ($objvalue)) { - 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 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}.':'.$controlchannel.'.'.$controldatapoint; - $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - HMCCU_SetState ($hash, "OK"); - return undef; - } - elsif ($opt =~ /^($hash->{statevals})$/) { - my $cmd = $1; - my $objvalue = ($cmd ne 'devstate') ? $cmd : shift @a; - - 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, ''); - - # Build datapoint address - my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$statechannel.'.'.$statedatapoint; - - $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - HMCCU_SetState ($hash, "OK"); - return undef; - } - elsif ($opt eq 'toggle') { - 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\|//; - my @states = split /\|/, $tstates; - my $sc = scalar (@states); - - my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$statechannel.'.'.$statedatapoint; - ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - my $objvalue = ''; - my $st = 0; - while ($st < $sc) { - if ($states[$st] eq $result) { - $objvalue = ($st == $sc-1) ? $states[0] : $states[$st+1]; - last; - } - else { - $st++; - } - } - - 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 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})/); - - my $timespec = shift @a; - return HMCCU_SetError ($hash, "Usage: set $name on-for-timer {on-time} [{ramp-time}]") - if (!defined ($timespec)); - - my $swrtdpt = ''; - my $ramptime = shift @a; - if (defined ($ramptime)) { - $swrtdpt = HMCCU_GetSwitchDatapoint ($hash, $hash->{ccutype}, 'ramptime'); - return HMCCU_SetError ($hash, "Can't find ramp-time datapoint for device type") - if ($swrtdpt eq ''); - } - - my $swotdpt = HMCCU_GetSwitchDatapoint ($hash, $hash->{ccutype}, 'ontime'); - return HMCCU_SetError ($hash, "Can't find on-time datapoint for device type") - if ($swotdpt eq ''); - - # Set on time - my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$swotdpt; - $rc = HMCCU_SetDatapoint ($hash, $objname, $timespec); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - # Set ramp time - if ($swrtdpt ne '') { - my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$swrtdpt; - $rc = HMCCU_SetDatapoint ($hash, $objname, $ramptime); - 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, $objvalue); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - HMCCU_SetState ($hash, "OK"); - return undef; - } - elsif ($opt eq 'config') { - 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 HMCCU_SetError ($hash, $rc) if ($rc < 0); - - 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 defaults:noArg"; - return undef if ($hash->{statevals} eq 'readonly'); - - if ($statechannel ne '') { - $retmsg .= " devstate"; - if ($hash->{statevals} ne '') { - my @cmdlist = split /\|/,$hash->{statevals}; - shift @cmdlist; - $retmsg .= ':'.join(',',@cmdlist) if (@cmdlist > 0); - foreach my $sv (@cmdlist) { - $retmsg .= ' '.$sv.':noArg'; - } - $retmsg .= " toggle:noArg"; - $retmsg .= " on-for-timer" if ($statechannel ne '' && - HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, $statechannel, "ON_TIME", 2)); - } - } - - return $retmsg; - } -} - -##################################### -# Get commands -##################################### - -sub HMCCUDEV_Get ($@) -{ - my ($hash, @a) = @_; - my $name = shift @a; - my $opt = shift @a; - - 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"; - } - -# 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; - - if ($hash->{ccuif} eq "VirtualDevices" && $hash->{ccuname} eq "none" && $opt ne 'update') { - return "HMCCUDEV: Unknown argument $opt, choose one of update:noArg"; - } - - if ($opt eq 'devstate') { - 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 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 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 HMCCU_SetError ($hash, $rc) if ($rc < 0); - - HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); - return $ccureadings ? undef : $result; - } - elsif ($opt eq 'channel') { - my @chnlist; - foreach my $objname (@a) { - last if (!defined ($objname)); - if ($objname =~ /^([0-9]+)/ && exists ($hash->{channels})) { - return HMCCU_SetError ($hash, -7) if ($1 >= $hash->{channels}); - } - else { - return HMCCU_SetError ($hash, -7); - } - if ($objname =~ /^[0-9]{1,2}.*=/) { - $objname =~ s/=/ /; - } - push (@chnlist, $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$objname); - } - if (@chnlist == 0) { - return HMCCU_SetError ($hash, "Usage: get $name channel {channel-number}[.{datapoint-expr}] [...]"); - } - - ($rc, $result) = HMCCU_GetChannel ($hash, \@chnlist); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - - HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); - return $ccureadings ? undef : $result; - } - elsif ($opt eq 'update') { - my $ccuget = shift @a; - $ccuget = 'Attr' if (!defined ($ccuget)); - if ($ccuget !~ /^(Attr|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 HMCCU_SetError ($hash, $rc) if ($rc < 0); - } - - # Update other devices belonging to group - if ($hash->{ccuif} eq "VirtualDevices" && exists ($hash->{ccugroup})) { - my @vdevs = split (",", $hash->{ccugroup}); - foreach my $vd (@vdevs) { - $rc = HMCCU_GetUpdate ($hash, $vd, $ccuget); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - } - } - - return undef; - } - elsif ($opt eq 'deviceinfo') { - my $ccuget = shift @a; - $ccuget = 'Attr' if (!defined ($ccuget)); - if ($ccuget !~ /^(Attr|State|Value)$/) { - return HMCCU_SetError ($hash, "Usage: get $name deviceinfo [{'State'|'Value'}]"); - } - $result = HMCCU_GetDeviceInfo ($hash, $hash->{ccuaddr}, $ccuget); - return HMCCU_SetError ($hash, -2) if ($result eq ''); - return HMCCU_FormatDeviceInfo ($result); - } - elsif ($opt eq 'config') { - my $channel = undef; - my $par = shift @a; - if (defined ($par)) { - $channel = $par if ($par =~ /^[0-9]{1,2}$/); - } - - my $ccuobj = $hash->{ccuaddr}; - $ccuobj .= ':'.$channel if (defined ($channel)); - - my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset"); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); - return $ccureadings ? undef : $res; - } - elsif ($opt eq 'configdesc') { - my $channel = undef; - my $par = shift @a; - if (defined ($par)) { - $channel = $par if ($par =~ /^[0-9]{1,2}$/); - } - - my $ccuobj = $hash->{ccuaddr}; - $ccuobj .= ':'.$channel if (defined ($channel)); - - my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription"); - return HMCCU_SetError ($hash, $rc) if ($rc < 0); - 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"; - - my @valuelist; - my $valuecount = HMCCU_GetValidDatapoints ($hash, $hash->{ccutype}, -1, 1, \@valuelist); - - $retmsg .= ":".join(",", @valuelist) if ($valuecount > 0); - $retmsg .= " defaults:noArg channel update:noArg config configdesc deviceinfo:noArg"; - - if ($statechannel ne '') { - $retmsg .= ' devstate:noArg'; - } - return $retmsg; - } -} - -##################################### -# Set error status -##################################### - -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' - ); - - if (exists ($errlist{$text})) { - $msg = $errlist{$text}; - } - else { - $msg = $text; - } - - $msg = "HMCCUDEV: ".$name." ". $msg; - readingsSingleUpdate ($hash, "state", "Error", 1); - Log3 $name, 1, $msg; - return $msg; -} - -1; - -=pod -=begin html - - -

HMCCUDEV

-
    - The module implements client devices for HMCCU. A HMCCU device must exist - before a client device can be defined. -

    - - Define -
      -
      - define <name> HMCCUDEV {<device-name>|<device-address>} [<statechannel>] [readonly] [{group={device|channel}[,...]|groupexp=regexp] -

      - If readonly parameter is specified no set command will be available. -

      - Examples:
      - define window_living HMCCUDEV WIN-LIV-1 readonly
      - define temp_control HMCCUDEV BidCos-RF.LEQ1234567 1 -
      -
    -
    - - - Set
    -
      -
      -
    • set <name> devstate <value> [...]
      - Set state of a CCU device channel. Channel must be defined as attribute - 'statechannel'. Default datapoint can be modfied by setting attribute - 'statedatapoint'. -

      - Example:
      - set light_entrance devstate on -

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

    • -
    • set <name> on-for-timer <seconds> <seconds>
      - Switch device on for specified time. Requires that device contains a datapoint - ON_TIME and optionally RAMP_TIME. Attribute '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 - modified by setting attribute 'statedatapoint'. Values for statevalue - are defined by setting attribute 'statevals'. -

      - Example:
      - - attr myswitch statechannel 1
      - attr myswitch statevals on:true,off:false
      - set myswitch on -
      -

    • -
    • set <name> toggle
      - Toggles between values defined by attribute 'statevals'. -

    • -
    • set <name> datapoint <channel-number>.<datapoint> <value> [...]
      - Set value of a datapoint of a CCU device channel. -

      - Example:
      - set temp_control datapoint 1.SET_TEMPERATURE 21 -

    • -
    • set <name> config [<channel-number>] <parameter>=<value> [...]
      - Set configuration parameter of CCU device or channel. -
    • -
    -
    - - - Get
    -
      -
      -
    • get <name> devstate
      - Get state of CCU device. Attribute 'statechannel' must be set. -

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

    • -
    • get <name> config [<channel-number>]
      - Get configuration parameters of CCU device. If attribute ccureadings is set to 0 - parameters are displayed in browser window (no readings set). -

    • -
    • 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. -

    • -
    • get <name> deviceinfo [{'State'|'Value'}]
      - Display all channels and datapoints of device. -
    • -
    -
    - - - Attributes
    -
    -
      -
    • ccuget <State | Value>
      - Set read access method for CCU channel datapoints. Method 'State' is slower than 'Value' because - each request is sent to the device. With method 'Value' only CCU is queried. Default is 'Value'. -

    • -
    • ccureadings <0 | 1>
      - If set to 1 values read from CCU will be stored as readings. Default is 1. -

    • -
    • ccureadingfilter <filter-rule[,...]>
      - Only datapoints matching specified expression are stored as readings. - Syntax for filter rule is: [channel-no:]RegExp
      - If channel-no is specified the following rule applies only to this channel. -

    • -
    • ccureadingformat <address | name>
      - Set format of readings. Default is 'name'. -

    • -
    • ccuscaleval <datapoint>:<factor>[,...]
      - Scale datapoint values before executing set datapoint commands or after executing get - datapoint commands. During get the value read from CCU is devided by factor. During set - the value is multiplied by factor. -

    • -
    • 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 - setting control datapoint. For example if datapoint of thermostat control is - 2.SET_TEMPERATURE one can define a slider for setting the destination temperature with - following attributes:

      - attr mydev controldatapoint 2.SET_TEMPERATURE - 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. -

    • -
    • statechannel <channel-number>
      - Channel for setting device state by devstate command. -

    • -
    • statedatapoint <datapoint>
      - Datapoint for setting device state by devstate command. -

    • -
    • statevals <text>:<text>[,...]
      - Define substitution for set commands values. The parameters <text> - are available as set commands. Example:
      - attr my_switch statevals on:true,off:false
      - set my_switch on -

    • -
    • substitute <subst-rule>[;...]
      - Define substitutions for reading values. Substitutions for parfile values must - be specified in parfiles. Syntax of subst-rule is

      - [datapoint!]<regexp>:<text>[,...] -

    • -
    -
- -=end html -=cut - diff --git a/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm b/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm deleted file mode 100644 index f3afea26e..000000000 --- a/fhem/contrib/HMCCU/FHEM/HMCCUConf.pm +++ /dev/null @@ -1,122 +0,0 @@ -######################################################################### -# -# 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 deleted file mode 100644 index 93acbdf8f..000000000 --- a/fhem/contrib/HMCCU/FHEM/RPCQueue.pm +++ /dev/null @@ -1,186 +0,0 @@ -package RPCQueue; - -use strict; -use IO::File; -use Fcntl 'SEEK_END', 'SEEK_SET', 'O_CREAT', 'O_RDWR'; -use Carp qw(carp croak); - -our $VERSION = '1.01'; - -sub new -{ - my $class = shift; - my $mi = $class . '->new()'; - - croak "$mi requires an even number of parameters" if (@_ & 1); - my %params = @_; - - # convert to lower case - my @keylist = keys %params; - foreach my $key (@keylist) { - my $val = $params{$key}; - delete $params{$key}; - $params{ lc($key) } = $val; - } - - croak "$mi needs an File parameter" unless exists $params{file}; - my $queue_file = delete $params{file}; - my $idx_file = $queue_file . '.idx'; - $queue_file .= '.dat'; - - my $self; - my $mode = delete $params{mode} || '0600'; - $self->{block_size} = delete $params{blocksize} || 64; - $self->{seperator} = delete $params{seperator} || "\n"; - $self->{sep_length} = length $self->{seperator}; - - croak "Seperator length cannot be greater than BlockSize" if ($self->{sep_length} > $self->{block_size}); - - $self->{queue_file} = $queue_file; - $self->{idx_file} = $idx_file; - - $self->{queue} = new IO::File $queue_file, O_CREAT | O_RDWR, $mode or croak $!; - $self->{idx} = new IO::File $idx_file, O_CREAT | O_RDWR, $mode or croak $!; - - ### Default ptr to 0, replace it with value in idx file if one exists - $self->{idx}->sysseek(0, SEEK_SET); - $self->{idx}->sysread($self->{ptr}, 1024); - $self->{ptr} = '0' unless $self->{ptr}; - - if($self->{ptr} > -s $queue_file) - { - carp "Ptr is greater than queue file size, resetting ptr to '0'"; - - $self->{idx}->truncate(0) or croak "Could not truncate idx: $!"; - $self->{idx}->sysseek(0, SEEK_SET); - $self->{idx}->syswrite('0') or croak "Could not syswrite to idx: $!"; - } - - bless $self, $class; - return $self; -} - -sub enq -{ - my ($self, $element) = @_; - - $self->{queue}->sysseek(0, SEEK_END); - - if(ref $element) - { - croak 'Cannot handle references'; - } - - if($element =~ s/$self->{seperator}//g) - { - carp "Removed illegal seperator(s) from $element"; - } - - $self->{queue}->syswrite("$element$self->{seperator}") or croak "Could not syswrite to queue: $!"; -} - -sub deq -{ - my $self = shift; - my $element; - - $self->{queue}->sysseek($self->{ptr}, SEEK_SET); - - my $i; - while($self->{queue}->sysread($_, $self->{block_size})) - { - - $i = index($_, $self->{seperator}); - if($i != -1) - { - $element .= substr($_, 0, $i); - $self->{ptr} += $i + $self->{sep_length}; - $self->{queue}->sysseek($self->{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, -$self->{sep_length}, ''); - $self->{ptr} += $self->{block_size} - $self->{sep_length}; - $self->{queue}->sysseek($self->{ptr}, SEEK_SET); - } - } - - ## If queue seek pointer is at the EOF, truncate the queue file - if($self->{queue}->sysread($_, 1) == 0) - { - $self->{queue}->truncate(0) or croak "Could not truncate queue: $!"; - $self->{queue}->sysseek($self->{ptr} = 0, SEEK_SET); - } - - ## Set idx file contents to point to the current seek position in queue file - $self->{idx}->truncate(0) or croak "Could not truncate idx: $!"; - $self->{idx}->sysseek(0, SEEK_SET); - $self->{idx}->syswrite($self->{ptr}) or croak "Could not syswrite to idx: $!"; - - return $element; -} - -sub peek -{ - my ($self, $count) = @_; - croak "Invalid argument to peek ($count)" unless $count > 0; - - my $elements; - - $self->{queue}->sysseek($self->{ptr}, SEEK_SET); - - my (@items, $remainder); -GATHER: - while($self->{queue}->sysread($_, $self->{block_size})) - { - if(defined $remainder) - { - $_ = $remainder . $_; - } - - @items = split /$self->{seperator}/, $_, -1; - $remainder = pop @items; - - foreach (@items) - { - push @$elements, $_; - last GATHER if $count == @$elements; - } - } - - return $elements; -} - -sub reset -{ - my $self = shift; - - $self->{idx}->truncate(0) or croak "Could not truncate idx: $!"; - $self->{idx}->sysseek(0, SEEK_SET); - $self->{idx}->syswrite('0') or croak "Could not syswrite to idx: $!"; - - $self->{queue}->sysseek($self->{ptr} = 0, SEEK_SET); -} - -sub close -{ - my $self = shift; - - $self->{idx}->close(); - $self->{queue}->close(); -} - -sub delete -{ - my $self = shift; - - $self->close(); - - unlink $self->{queue_file}; - unlink $self->{idx_file}; -} - -1; diff --git a/fhem/contrib/HMCCU/FHEM/ccurpcd.pl b/fhem/contrib/HMCCU/FHEM/ccurpcd.pl deleted file mode 100755 index 284bbf14b..000000000 --- a/fhem/contrib/HMCCU/FHEM/ccurpcd.pl +++ /dev/null @@ -1,443 +0,0 @@ -#!/usr/bin/perl - -######################################################### -# ccurpcd.pl -# -# $Id: -# -# Version 2.1 -# -# FHEM RPC server for Homematic CCU. -# -# (C) 2016 by zap -#-------------------------------------------------------- -# Usage: -# -# ccurpcd.pl Hostname Port QueueFile LogFile -# ccurpcd.pl shutdown Hostname Port PID -#-------------------------------------------------------- -# Queue file entries: -# -# Code|Data[|...] -# -# Server Loop: SL|pid|Server -# Initialized: IN|INIT|1|Server -# 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|Server -######################################################### - -use strict; -use warnings; -# use File::Queue; -use RPC::XML::Server; -use RPC::XML::Client; -use IO::Socket::INET; -use FindBin qw($Bin); -use lib "$Bin"; -use RPCQueue; - -# Global variables -my $client; -my $server; -my $queue; -my $logfile; -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, 'SL', 0, 'IN', 0, 'EX', 0); - -# Functions -sub CheckProcess ($$); -sub Log ($); -sub WriteQueue ($); -sub CCURPC_Shutdown ($$$); -sub CCURPC_Initialize ($$); - - -##################################### -# Get PID of running RPC server or 0 -##################################### - -sub CheckProcess ($$) -{ - my ($prcname, $port) = @_; - - my $filename = $prcname; - my $pdump = `ps ax | grep $prcname | grep -v grep`; - my @plist = split "\n", $pdump; - foreach my $proc (@plist) { - # Remove leading blanks, fix for MacOS. Thanks to mcdeck - $proc =~ s/^\s+//; - my @procattr = split /\s+/, $proc; - if ($procattr[0] != $$ && $procattr[4] =~ /perl$/ && - ($procattr[5] eq $prcname || $procattr[5] =~ /\/ccurpcd\.pl$/) && - $procattr[7] eq "$port") { - Log "Process $proc is running connected to CCU port $port"; - return $procattr[0]; - } - } - - return 0; -} - -##################################### -# Write logfile entry -##################################### - -sub Log ($) -{ - my @messages = @_; - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime (); - - if (open (LOGFILE, '>>', $logfile)) { - printf LOGFILE "%02d.%02d.%04d %02d:%02d:%02d ", - $mday,$mon+1,$year+1900,$hour,$min,$sec; - foreach my $token (@messages) { - print LOGFILE $token; - } - print LOGFILE "\n"; - close LOGFILE; - } -} - -##################################### -# Write queue entry -##################################### - -sub WriteQueue ($) -{ - my ($message) = @_; - - $queue->enq ($message); - - return 1; -} - -##################################### -# Shutdown RPC connection -##################################### - -sub CCURPC_Shutdown ($$$) -{ - my ($serveraddr, $serverport, $pid) = @_; - - # Detect local IP - my $socket = IO::Socket::INET->new (PeerAddr => $serveraddr, PeerPort => $serverport); - if (!$socket) { - print "Can't connect to CCU port $serverport"; - return undef; - } - my $localaddr = $socket->sockhost (); - close ($socket); - - my $ccurpcport = 5400+$serverport; - my $callbackurl = "http://".$localaddr.":".$ccurpcport."/fh".$serverport; - - $client = RPC::XML::Client->new ("http://$serveraddr:$serverport/"); - - print "Trying to deregister RPC server $callbackurl\n"; - $client->send_request("init", $callbackurl); - sleep (3); - - print "Sending SIGINT to PID $pid\n"; - kill ('INT', $pid); - - return undef; -} - -##################################### -# Initialize RPC connection -##################################### - -sub CCURPC_Initialize ($$) -{ - my ($serveraddr, $serverport) = @_; - my $callbackport = 5400+$serverport; - - # Create RPC server - $server = RPC::XML::Server->new (port=>$callbackport); - if (!ref($server)) - { - Log "Can't create RPC callback server on port $callbackport. Port in use?"; - return undef; - } - else { - Log "Callback server created listening on port $callbackport"; - } - - # Callback for events - Log "Adding callback for events"; - $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=>\&CCURPC_EventCB - } - ); - - # Callback for new devices - Log "Adding callback for new devices"; - $server->add_method ( - { name=>"newDevices", - signature=>["string string array"], - code=>\&CCURPC_NewDevicesCB - } - ); - - # Callback for deleted devices - Log "Adding callback for deleted devices"; - $server->add_method ( - { name=>"deleteDevices", - signature=>["string string array"], - code=>\&CCURPC_DeleteDevicesCB - } - ); - - # Callback for modified devices - Log "Adding callback for modified devices"; - $server->add_method ( - { name=>"updateDevice", - signature=>["string string string int"], - code=>\&CCURPC_UpdateDeviceCB - } - ); - - # 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", - signature=>["array string"], - code=>\&CCURPC_ListDevicesCB - } - ); - - return "OK"; -} - -##################################### -# Callback for new devices -##################################### - -sub CCURPC_NewDevicesCB ($$$) -{ - my ($server, $cb, $a) = @_; - - Log "NewDevice: received ".scalar(@$a)." device specifications"; - - for my $dev (@$a) { - $ev{total}++; - $ev{ND}++; - WriteQueue ("ND|".$dev->{ADDRESS}."|".$dev->{TYPE}); - } - -# return RPC::XML::array->new(); - return; -} - -##################################### -# Callback for deleted devices -##################################### - -sub CCURPC_DeleteDevicesCB ($$$) -{ - my ($server, $cb, $a) = @_; - - Log "DeleteDevice: received ".scalar(@$a)." device addresses"; - - for my $dev (@$a) { - $ev{total}++; - $ev{DD}++; - WriteQueue ("DD|".$dev); - } - - return; -} - -##################################### -# Callback for modified devices -##################################### - -sub CCURPC_UpdateDeviceCB ($$$$) -{ - my ($server, $cb, $devid, $hint) = @_; - - $ev{total}++; - $ev{UD}++; - WriteQueue ("UD|".$devid."|".$hint); - - return; -} - -##################################### -# Callback for replaced devices -##################################### - -sub CCURPC_ReplaceDeviceCB ($$$$) -{ - my ($server, $cb, $devid1, $devid2) = @_; - - $ev{total}++; - $ev{RD}++; - 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) { - $ev{total}++; - $ev{RA}++; - WriteQueue ("RA|".$dev); - } - - return; -} - -##################################### -# Callback for handling CCU events -##################################### - -sub CCURPC_EventCB ($$$$$) -{ - my ($server,$cb,$devid,$attr,$val) = @_; - - $ev{total}++; - $ev{EV}++; - WriteQueue ("EV|".$devid."|".$attr."|".$val); - - $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! - return; -} - -##################################### -# Callback for list devices -##################################### - -sub CCURPC_ListDevicesCB () -{ - my ($server, $cb) = @_; - - $ev{total}++; - $ev{IN}++; - $cb = "unknown" if (!defined ($cb)); - Log "ListDevices $cb. Sending init to HMCCU"; - WriteQueue ("IN|INIT|1|$cb"); - - return RPC::XML::array->new(); -} - -##################################### -# MAIN -##################################### - -my $name = $0; - -# Process command line arguments -if ($#ARGV+1 < 4) { - print "Usage: $name CCU-Host Port QueueFile LogFile LogLevel\n"; - print " $name shutdown CCU-Host Port PID\n"; - exit 1; -} - -# -# Manually shutdown RPC server -# -if ($ARGV[0] eq 'shutdown') { - CCURPC_Shutdown ($ARGV[1], $ARGV[2], $ARGV[3]); - exit 0; -} - -# -# Start RPC server -# -my $ccuhost = $ARGV[0]; -my $ccuport = $ARGV[1]; -my $queuefile = $ARGV[2]; -$logfile = $ARGV[3]; -$loglevel = $ARGV[4] if ($#ARGV+1 == 5); - -my $pid = CheckProcess ($name, $ccuport); -if ($pid > 0) { - Log "Error: ccurpcd.pl is already running (PID=$pid) for CCU port $ccuport"; - die "Error: ccurpcd.pl is already running (PID=$pid) for CCU port $ccuport\n"; -} - -# Create or open queue -Log "Creating file queue"; -$queue = new RPCQueue (File => $queuefile, Mode => 0666); -if (!defined ($queue)) { - Log "Error: Can't create queue"; - die "Error: Can't create queue\n"; -} -else { - $queue->reset (); - while ($queue->deq ()) { } -} - -# Initialize RPC server -Log "Initializing RPC server"; -my $callbackurl = CCURPC_Initialize ($ccuhost, $ccuport); -if (!defined ($callbackurl)) { - Log "Error: Can't initialize RPC server"; - die "Error: Can't initialize RPC server\n"; -} - -# Server loop is interruptable bei signal SIGINT -Log "Entering server loop. Use kill -SIGINT $$ to terminate program"; -WriteQueue ("SL|$$|CB".$ccuport); -$server->server_loop; - -$totalcount++; -WriteQueue ("EX|SHUTDOWN|$$|CB".$ccuport); -$ev{total}++; -$ev{EX}++; -Log "RPC server terminated"; - -if ($loglevel == 2) { - foreach my $cnt (sort keys %ev) { - Log "Events $cnt = ".$ev{$cnt}; - } -} - diff --git a/fhem/contrib/HMCCU/controls_HMCCU.txt b/fhem/contrib/HMCCU/controls_HMCCU.txt deleted file mode 100644 index 65ea69949..000000000 --- a/fhem/contrib/HMCCU/controls_HMCCU.txt +++ /dev/null @@ -1,5 +0,0 @@ -UPD 2016-03-13_18:13:51 85357 FHEM/88_HMCCU.pm -UPD 2016-03-11_18:56:09 17459 FHEM/88_HMCCUCHN.pm -UPD 2016-03-11_18:56:09 24314 FHEM/88_HMCCUDEV.pm -UPD 2016-03-11_18:56:21 7857 FHEM/ccurpcd.pl -UPD 2016-03-02_17:28:28 4377 FHEM/RPCQueue.pm diff --git a/fhem/contrib/HMCCU/example.cfg b/fhem/contrib/HMCCU/example.cfg deleted file mode 100644 index 4e1aeb88c..000000000 --- a/fhem/contrib/HMCCU/example.cfg +++ /dev/null @@ -1,42 +0,0 @@ -# -# Example configuration for modules HMCCU and HMCCUDEV -# - -# Define device (hostname of CCU is "homematic") -define d_ccu HMCCU homematic - -# Generate readings for values read from CCU -attr d_ccu ccureadings 1 - -# Parameterfile with channel/datapoints to be read from CCU -# by command get parfile -attr d_ccu parfile /opt/fhem/scripts/hmvalues.txt - -# If CCU systemvariable name ends with a ":" this character -# will be removed. Applies to command set devstate only. -# Helpful if CCU variable should be set as a reaction on an -# event. -attr d_ccu stripchar : - -# Substitute values read from CCU before storing them in a -# reading. -attr d_ccu substitute false:closed,true:open - -# Subsitute values before setting them -attr d_ccu statevals on:true,off:false - -# Define client device for door/window sensor -define d_hm_dw_window HMCCUDEV TF-WZ-Window readonly -attr d_hm_dw_window ccureadings 1 -attr d_hm_dw_window substitute false:closed,true:open - -# Define client device for subwoofer with state channel 1 -define d_hm_st_sub HMCCUDEV ST-WZ-Sub 1 -attr d_hm_st_sub ccureadings 1 -attr d_hm_st_sub statevals on:true,off:false -attr d_hm_st_sub substitute true:on,false:off - -# Update CCU readings and client devices every 10 minutes -define at_ccu at +*00:10:00 get d_ccu parfile -attr at_ccu alignTime 00:05 - diff --git a/fhem/contrib/HMCCU/hmvalues.pat b/fhem/contrib/HMCCU/hmvalues.pat deleted file mode 100644 index 24466667d..000000000 --- a/fhem/contrib/HMCCU/hmvalues.pat +++ /dev/null @@ -1,35 +0,0 @@ -# -# Beispiel fuer eine HMCCU Parameterdatei -# Setzen mit attr parfile -# oder Angabe bei get parfile -# -# Tueren und Fenster -# Zustaende der CCU Geräte werden in den FHEM -# Readings ersetzt (false=closed,true=open). -# -TF-AZ-Fenster1:1.STATE false:closed,true:open -TF-AZ-Fenster2:1.STATE false:closed,true:open -TF-BE-Fenster:1.STATE false:closed,true:open -TF-BO-Fenster:1.STATE false:closed,true:open -TF-FL-Haustuer:1.STATE false:closed,true:open -TF-GA-Fenster:1.STATE false:closed,true:open -TF-GZ-Fenster1:1.STATE false:closed,true:open -TF-SZ-Fenster1:1.STATE false:closed,true:open -TF-SZ-Fenster2:1.STATE false:closed,true:open -TF-WZ-Balkon:1.STATE false:closed,true:open -TF-WZ-Terrasse:1.STATE false:closed,true:open -# -# Schalter -# -ST-HR-Pumpe:1.STATE false:off,true:on -ST-WZ-Bass:1.STATE false:off,true:on -# -# Temperatur und Luftfeuchte Sensoren -# Angabe Datenpunkt fehlt => Es werden alle Datenpunkte -# abgeholt (TEMPERATURE, HUMIDITY) -# -KL-SZ-THX:1 -KL-AZ-THX:1 -KL-GA-THX:1 -KL-BO-THX:1 -KL-WZ-THX-1:1 diff --git a/fhem/contrib/HMCCU/hmvalues.txt b/fhem/contrib/HMCCU/hmvalues.txt deleted file mode 100644 index 24466667d..000000000 --- a/fhem/contrib/HMCCU/hmvalues.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# Beispiel fuer eine HMCCU Parameterdatei -# Setzen mit attr parfile -# oder Angabe bei get parfile -# -# Tueren und Fenster -# Zustaende der CCU Geräte werden in den FHEM -# Readings ersetzt (false=closed,true=open). -# -TF-AZ-Fenster1:1.STATE false:closed,true:open -TF-AZ-Fenster2:1.STATE false:closed,true:open -TF-BE-Fenster:1.STATE false:closed,true:open -TF-BO-Fenster:1.STATE false:closed,true:open -TF-FL-Haustuer:1.STATE false:closed,true:open -TF-GA-Fenster:1.STATE false:closed,true:open -TF-GZ-Fenster1:1.STATE false:closed,true:open -TF-SZ-Fenster1:1.STATE false:closed,true:open -TF-SZ-Fenster2:1.STATE false:closed,true:open -TF-WZ-Balkon:1.STATE false:closed,true:open -TF-WZ-Terrasse:1.STATE false:closed,true:open -# -# Schalter -# -ST-HR-Pumpe:1.STATE false:off,true:on -ST-WZ-Bass:1.STATE false:off,true:on -# -# Temperatur und Luftfeuchte Sensoren -# Angabe Datenpunkt fehlt => Es werden alle Datenpunkte -# abgeholt (TEMPERATURE, HUMIDITY) -# -KL-SZ-THX:1 -KL-AZ-THX:1 -KL-GA-THX:1 -KL-BO-THX:1 -KL-WZ-THX-1:1