diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm new file mode 100644 index 000000000..dc137c5c8 --- /dev/null +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCU.pm @@ -0,0 +1,9006 @@ +############################################################################## +# +# 88_HMCCU.pm +# +# $Id: 88_HMCCU.pm 18745 2019-02-26 17:33:23Z zap $ +# +# Version 4.4.000 +# +# Module for communication between FHEM and Homematic CCU2/3. +# +# Supports BidCos-RF, BidCos-Wired, HmIP-RF, virtual CCU channels, +# CCU group devices, HomeGear, CUxD, Osram Lightify, Homematic Virtual Layer +# and Philips Hue (not tested) +# +# (c) 2020 by zap (zap01 t-online de) +# +############################################################################## +# +# 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 Time::HiRes qw(usleep); +use IO::File; +use Fcntl 'SEEK_END', 'SEEK_SET', 'O_CREAT', 'O_RDWR'; +use RPC::XML::Client; +use RPC::XML::Server; +use HttpUtils; +use SetExtensions; +use SubProcess; +use HMCCUConf; + +# Import configuration data +my $HMCCU_CHN_DEFAULTS = \%HMCCUConf::HMCCU_CHN_DEFAULTS; +my $HMCCU_DEV_DEFAULTS = \%HMCCUConf::HMCCU_DEV_DEFAULTS; +my $HMCCU_SCRIPTS = \%HMCCUConf::HMCCU_SCRIPTS; + +# Custom configuration data +my %HMCCU_CUST_CHN_DEFAULTS; +my %HMCCU_CUST_DEV_DEFAULTS; + +# HMCCU version +my $HMCCU_VERSION = '4.4.000'; + +# Constants and default values +my $HMCCU_MAX_IOERRORS = 100; +my $HMCCU_MAX_QUEUESIZE = 500; +my $HMCCU_TIME_WAIT = 100000; +my $HMCCU_TIME_TRIGGER = 10; + +# RPC ping interval for default interface, should be smaller than HMCCU_TIMEOUT_EVENT +my $HMCCU_TIME_PING = 300; + +my $HMCCU_TIMEOUT_CONNECTION = 10; +my $HMCCU_TIMEOUT_WRITE = 0.001; +my $HMCCU_TIMEOUT_ACCEPT = 1; +my $HMCCU_TIMEOUT_EVENT = 600; +my $HMCCU_STATISTICS = 500; +my $HMCCU_TIMEOUT_REQUEST = 4; + +# ReGa Ports +my %HMCCU_REGA_PORT = ( + 'http' => 8181, 'https' => '48181' +); + +# RPC interface priority +my @HMCCU_RPC_PRIORITY = ('BidCos-RF', 'HmIP-RF', 'BidCos-Wired'); + +# RPC port name by port number +my %HMCCU_RPC_NUMPORT = ( + 2000 => 'BidCos-Wired', 2001 => 'BidCos-RF', 2010 => 'HmIP-RF', 9292 => 'VirtualDevices', + 2003 => 'Homegear', 8701 => 'CUxD', 7000 => 'HVL' +); + +# RPC port number by port name +my %HMCCU_RPC_PORT = ( + 'BidCos-Wired', 2000, 'BidCos-RF', 2001, 'HmIP-RF', 2010, 'VirtualDevices', 9292, + 'Homegear', 2003, 'CUxD', 8701, 'HVL', 7000 +); + +# RPC flags +my %HMCCU_RPC_FLAG = ( + 2000 => 'forceASCII', 2001 => 'forceASCII', 2003 => '_', 2010 => 'forceASCII', + 7000 => 'forceInit', 8701 => 'forceInit', 9292 => '_' +); + +my %HMCCU_RPC_SSL = ( + 2000 => 1, 2001 => 1, 2010 => 1, 9292 => 1, + 'BidCos-Wired' => 1, 'BidCos-RF' => 1, 'HmIP-RF' => 1, 'VirtualDevices' => 1 +); + +# 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; + +# Default values for delayed initialization during FHEM startup +my $HMCCU_CCU_PING_TIMEOUT = 1; +my $HMCCU_CCU_BOOT_DELAY = 180; +my $HMCCU_CCU_DELAYED_INIT = 59; +my $HMCCU_CCU_RPC_OFFSET = 20; + +# Number of arguments in RPC events +my %rpceventargs = ( + "EV", 3, # Datapoint value updated + "ND", 6, # New device created + "DD", 1, # Device deleted + "RD", 2, # Device renamed + "RA", 1, # Device readded + "UD", 2, # Device updated + "IN", 3, # RPC init + "EX", 3, # Exit RPC server + "SL", 2, # RPC server loop + "ST", 10 # Status +); + +# 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; + +# Flags for address/name checks +my $HMCCU_FL_STADDRESS = 1; +my $HMCCU_FL_NAME = 2; +my $HMCCU_FL_EXADDRESS = 4; +my $HMCCU_FL_ADDRESS = 5; +my $HMCCU_FL_ALL = 7; + +# Default values +my $HMCCU_DEF_HMSTATE = '^0\.UNREACH!(1|true):unreachable;^[0-9]\.LOW_?BAT!(1|true):warn_battery'; + +# Placeholder for external addresses (i.e. HVL) +my $HMCCU_EXT_ADDR = 'ZZZ0000000'; + +# Binary RPC data types +my $BINRPC_INTEGER = 1; +my $BINRPC_BOOL = 2; +my $BINRPC_STRING = 3; +my $BINRPC_DOUBLE = 4; +my $BINRPC_BASE64 = 17; +my $BINRPC_ARRAY = 256; +my $BINRPC_STRUCT = 257; + +# Declare functions + +# FHEM standard functions +sub HMCCU_Initialize ($); +sub HMCCU_Define ($$); +sub HMCCU_InitDevice ($); +sub HMCCU_Undef ($$); +sub HMCCU_DelayedShutdown ($); +sub HMCCU_Shutdown ($); +sub HMCCU_Set ($@); +sub HMCCU_Get ($@); +sub HMCCU_Attr ($@); +sub HMCCU_AttrInterfacesPorts ($$$); +sub HMCCU_Notify ($$); +sub HMCCU_Detail ($$$$); + +# Aggregation +sub HMCCU_AggregateReadings ($$); +sub HMCCU_AggregationRules ($$); + +# Handling of default attributes +sub HMCCU_DetectDefaults ($$); +sub HMCCU_ExportDefaults ($$); +sub HMCCU_ExportDefaultsCSV ($$); +sub HMCCU_ImportDefaults ($); +sub HMCCU_FindDefaults ($$); +sub HMCCU_GetDefaults ($$); +sub HMCCU_SetDefaults ($); + +# Status and logging functions +sub HMCCU_Trace ($$$$); +sub HMCCU_Log ($$$;$); +sub HMCCU_LogError ($$$); +sub HMCCU_SetError ($@); +sub HMCCU_SetState ($@); +sub HMCCU_SetRPCState ($@); + +# Filter and modify readings +sub HMCCU_FilterReading ($$$); +sub HMCCU_FormatReadingValue ($$$); +sub HMCCU_GetReadingName ($$$$$$$); +sub HMCCU_ScaleValue ($$$$$); +sub HMCCU_Substitute ($$$$$); +sub HMCCU_SubstRule ($$$); +sub HMCCU_SubstVariables ($$$); + +# Update client device readings +sub HMCCU_BulkUpdate ($$$$); +sub HMCCU_GetUpdate ($$$); +sub HMCCU_UpdateCB ($$$); +sub HMCCU_UpdateClients ($$$$$$); +sub HMCCU_UpdateInternalValues ($$$$); +sub HMCCU_UpdateMultipleDevices ($$); +sub HMCCU_UpdatePeers ($$$$); +sub HMCCU_UpdateSingleDatapoint ($$$$); +sub HMCCU_UpdateSingleDevice ($$$$); + +# RPC functions +sub HMCCU_EventsTimedOut ($); +sub HMCCU_GetRPCCallbackURL ($$$$$); +sub HMCCU_GetRPCDevice ($$$); +sub HMCCU_GetRPCInterfaceList ($); +sub HMCCU_GetRPCPortList ($); +sub HMCCU_GetRPCServerInfo ($$$); +sub HMCCU_IsRPCServerRunning ($$$); +sub HMCCU_IsRPCType ($$$); +sub HMCCU_IsRPCStateBlocking ($); +sub HMCCU_ResetCounters ($); +sub HMCCU_RPCDeRegisterCallback ($); +sub HMCCU_RPCRegisterCallback ($); +sub HMCCU_RPCRequest ($$$$$;$); +sub HMCCU_StartExtRPCServer ($); +sub HMCCU_StartIntRPCServer ($); +sub HMCCU_StopExtRPCServer ($;$); +sub HMCCU_StopRPCServer ($); + +# Parse and validate names and addresses +sub HMCCU_ParseObject ($$$); +sub HMCCU_IsDevAddr ($$); +sub HMCCU_IsChnAddr ($$); +sub HMCCU_SplitChnAddr ($); +sub HMCCU_SplitDatapoint ($;$); + +# FHEM device handling functions +sub HMCCU_AssignIODevice ($$$); +sub HMCCU_FindClientDevices ($$$$); +sub HMCCU_FindIODevice ($); +sub HMCCU_GetHash ($@); +sub HMCCU_GetAttribute ($$$$); +sub HMCCU_GetFlags ($); +sub HMCCU_GetAttrReadingFormat ($$); +sub HMCCU_GetAttrStripNumber ($); +sub HMCCU_GetAttrSubstitute ($$); +sub HMCCU_IODeviceStates (); +sub HMCCU_IsFlag ($$); + +# Handle interfaces, devices and channels +sub HMCCU_AddDeviceDesc ($$$$); +sub HMCCU_AddDeviceModel ($$$$$$); +sub HMCCU_CreateDevice ($$$$$); +sub HMCCU_DeleteDevice ($); +sub HMCCU_ExistsDeviceModel ($$$;$); +sub HMCCU_FormatDeviceInfo ($); +sub HMCCU_GetAddress ($$$$); +sub HMCCU_GetAffectedAddresses ($); +sub HMCCU_GetCCUDeviceParam ($$); +sub HMCCU_GetChannelName ($$$); +sub HMCCU_GetClientDeviceModel ($;$); +sub HMCCU_GetDefaultInterface ($); +sub HMCCU_GetDeviceAddresses ($;$$); +sub HMCCU_GetDeviceChannels ($$$); +sub HMCCU_GetDeviceDesc ($;$$); +sub HMCCU_GetDeviceInfo ($$$); +sub HMCCU_GetDeviceInterface ($$$); +sub HMCCU_GetDeviceList ($); +sub HMCCU_GetDeviceModel ($$$;$); +sub HMCCU_GetDeviceName ($$$); +sub HMCCU_GetDeviceType ($$$); +sub HMCCU_GetFirmwareVersions ($$); +sub HMCCU_GetGroupMembers ($$); +sub HMCCU_GetMatchingDevices ($$$$); +sub HMCCU_IsValidChannel ($$$); +sub HMCCU_IsValidDevice ($$$); +sub HMCCU_IsValidDeviceOrChannel ($$$); +sub HMCCU_ResetDeviceTables ($$); +sub HMCCU_UpdateDeviceTable ($$); + +# Handle datapoints +sub HMCCU_FindDatapoint ($$$$$); +sub HMCCU_GetDatapoint ($@); +sub HMCCU_GetDatapointAttr ($$$$$); +sub HMCCU_GetDatapointCount ($$$); +sub HMCCU_GetDatapointList ($$$); +sub HMCCU_GetSpecialDatapoints ($$$$$); +sub HMCCU_GetSwitchDatapoint ($$$); +sub HMCCU_GetValidDatapoints ($$$$$); +sub HMCCU_IsValidDatapoint ($$$$$); +# sub HMCCU_SetDatapoint ($$$); +sub HMCCU_SetMultipleDatapoints ($$); +sub HMCCU_SetMultipleParameters ($$$); + +# Internal RPC server functions +sub HMCCU_ResetRPCQueue ($$); +sub HMCCU_ReadRPCQueue ($); +sub HMCCU_ProcessEvent ($$); + +# Homematic script and variable functions +sub HMCCU_GetVariables ($$); +sub HMCCU_HMCommand ($$$); +sub HMCCU_HMCommandCB ($$$); +sub HMCCU_HMCommandNB ($$$); +sub HMCCU_HMScriptExt ($$$$$); +sub HMCCU_SetVariable ($$$$$); +sub HMCCU_UpdateVariables ($); + +# File queue functions +sub HMCCU_QueueOpen ($$); +sub HMCCU_QueueClose ($); +sub HMCCU_QueueReset ($); +sub HMCCU_QueueEnq ($$); +sub HMCCU_QueueDeq ($); + +# Helper functions +sub HMCCU_BitsToStr ($$); +sub HMCCU_BuildURL ($$); +sub HMCCU_CalculateReading ($$); +sub HMCCU_CorrectName ($); +sub HMCCU_Encrypt ($); +sub HMCCU_Decrypt ($); +sub HMCCU_DeleteReadings ($$); +sub HMCCU_EncodeEPDisplay ($); +sub HMCCU_ExprMatch ($$$); +sub HMCCU_ExprNotMatch ($$$); +sub HMCCU_GetDutyCycle ($); +sub HMCCU_GetHMState ($$$); +sub HMCCU_GetIdFromIP ($$); +sub HMCCU_GetTimeSpec ($); +sub HMCCU_FlagsToStr ($$$;$$); +sub HMCCU_MaxHashEntries ($$); +sub HMCCU_RefToString ($); +sub HMCCU_ResolveName ($$); +sub HMCCU_TCPConnect ($$); +sub HMCCU_TCPPing ($$$); + +# Subprocess functions of internal RPC server +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->{DelayedShutdownFn} = "HMCCU_DelayedShutdown"; + $hash->{FW_detailFn} = "HMCCU_Detail"; + $hash->{parseParams} = 1; + + $hash->{AttrList} = "stripchar stripnumber ccuaggregate:textField-long". + " ccudefaults rpcinterfaces:multiple-strict,".join(',',sort keys %HMCCU_RPC_PORT). + " ccudef-hmstatevals:textField-long ccudef-substitute:textField-long". + " ccudef-readingname:textField-long ccudef-readingfilter:textField-long". + " ccudef-readingformat:name,namelc,address,addresslc,datapoint,datapointlc". + " ccudef-stripnumber". + " ccuflags:multiple-strict,procrpc,dptnocheck,logCommand,noagg,nohmstate,". + "logEvents,noEvents,noInitialUpdate,noReadings,nonBlocking,reconnect,logPong,trace". + " ccuReqTimeout ccuGetVars rpcinterval:2,3,5,7,10 rpcqueue rpcPingCCU". + " rpcport:multiple-strict,".join(',',sort keys %HMCCU_RPC_NUMPORT). + " rpcserver:on,off rpcserveraddr rpcserverport rpctimeout rpcevtimeout substitute". + " ccuget:Value,State ". + $readingFnAttributes; +} + +###################################################################### +# Define device +###################################################################### + +sub HMCCU_Define ($$) +{ + my ($hash, $a, $h) = @_; + my $name = $hash->{NAME}; + + return "Specify CCU hostname or IP address as a parameter" if (scalar (@$a) < 3); + + # Setup http or ssl connection + if ($$a[2] =~ /^(https?):\/\/(.+)/) { + $hash->{prot} = $1; + $hash->{host} = $2; + } + else { + $hash->{prot} = 'http'; + $hash->{host} = $$a[2]; + } + + $hash->{Clients} = ':HMCCUDEV:HMCCUCHN:HMCCURPC:HMCCURPCPROC:'; + $hash->{hmccu}{ccu}{delay} = exists ($h->{ccudelay}) ? $h->{ccudelay} : $HMCCU_CCU_BOOT_DELAY; + $hash->{hmccu}{ccu}{timeout} = exists ($h->{waitforccu}) ? $h->{waitforccu} : $HMCCU_CCU_PING_TIMEOUT; + $hash->{hmccu}{ccu}{delayed} = 0; + + # Check if TCL-Rega process is running on CCU (CCU reachable) + if (exists ($h->{delayedinit}) && $h->{delayedinit} > 0) { + return "Value for delayed initialization must be greater than $HMCCU_CCU_DELAYED_INIT" + if ($h->{delayedinit} <= $HMCCU_CCU_DELAYED_INIT); + $hash->{hmccu}{ccu}{delay} = $h->{delayedinit}; + $hash->{ccustate} = 'unreachable'; + HMCCU_Log ($hash, 1, "Forced delayed initialization"); + } + else { + if (HMCCU_TCPPing ($hash->{host}, $HMCCU_REGA_PORT{$hash->{prot}}, $hash->{hmccu}{ccu}{timeout})) { + $hash->{ccustate} = 'active'; + } + else { + $hash->{ccustate} = 'unreachable'; + HMCCU_Log ($hash, 1, "CCU port ".$HMCCU_REGA_PORT{$hash->{prot}}." is not reachable"); + } + } + + # Get CCU IP address + $hash->{ccuip} = HMCCU_ResolveName ($hash->{host}, 'N/A'); + + # Get CCU number (if more than one) + if (scalar (@$a) >= 4) { + return "CCU number must be in range 1-9" if ($$a[3] < 1 || $$a[3] > 9); + $hash->{CCUNum} = $$a[3]; + } + else { + # Count CCU devices + my $ccucount = 0; + foreach my $d (keys %defs) { + my $ch = $defs{$d}; + next if (!exists ($ch->{TYPE})); + $ccucount++ if ($ch->{TYPE} eq 'HMCCU' && $ch != $hash); + } + $hash->{CCUNum} = $ccucount+1; + } + + $hash->{version} = $HMCCU_VERSION; + $hash->{ccutype} = 'CCU2/3'; + $hash->{RPCState} = "inactive"; + $hash->{NOTIFYDEV} = "global,TYPE=(HMCCU|HMCCUDEV|HMCCUCHN)"; + $hash->{hmccu}{defInterface} = $HMCCU_RPC_PRIORITY[0]; + $hash->{hmccu}{defPort} = $HMCCU_RPC_PORT{$hash->{hmccu}{defInterface}}; + $hash->{hmccu}{rpcports} = undef; + + HMCCU_Log ($hash, 1, "Initialized version $HMCCU_VERSION"); + + my $rc = 0; + if ($hash->{ccustate} eq 'active') { + # If CCU is alive read devices, channels, interfaces and groups + HMCCU_Log ($hash, 1, "HMCCU: Initializing device"); + $rc = HMCCU_InitDevice ($hash); + } + + if ($hash->{ccustate} ne 'active' || $rc > 0) { + # Schedule update of CCU assets if CCU is not active during FHEM startup + if (!$init_done) { + $hash->{hmccu}{ccu}{delayed} = 1; + HMCCU_Log ($hash, 1, "Scheduling delayed initialization in ".$hash->{hmccu}{ccu}{delay}." seconds"); + InternalTimer (gettimeofday()+$hash->{hmccu}{ccu}{delay}, "HMCCU_InitDevice", $hash); + } + } + + $hash->{hmccu}{evtime} = 0; + $hash->{hmccu}{evtimeout} = 0; + $hash->{hmccu}{updatetime} = 0; + $hash->{hmccu}{rpccount} = 0; + + readingsBeginUpdate ($hash); + readingsBulkUpdate ($hash, "state", "Initialized"); + readingsBulkUpdate ($hash, "rpcstate", "inactive"); + readingsEndUpdate ($hash, 1); + + $attr{$name}{stateFormat} = "rpcstate/state"; + + return undef; +} + +###################################################################### +# Initialization of FHEM device. +# Called during Define() or by HMCCU after CCU ready. +# Return 0 on successful initialization or >0 on error: +# 1 = CCU port 8181 or 48181 is not reachable. +# 2 = Error while reading device list from CCU. +###################################################################### + +sub HMCCU_InitDevice ($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + if ($hash->{hmccu}{ccu}{delayed} == 1) { + HMCCU_Log ($hash, 1, "HMCCU: Initializing devices"); + if (!HMCCU_TCPPing ($hash->{host}, $HMCCU_REGA_PORT{$hash->{prot}}, $hash->{hmccu}{ccu}{timeout})) { + $hash->{ccustate} = 'unreachable'; + HMCCU_Log ($hash, 1, "HMCCU: CCU port ".$HMCCU_REGA_PORT{$hash->{prot}}." is not reachable"); + return 1; + } + } + + my ($devcnt, $chncnt, $ifcount, $prgcount, $gcount) = HMCCU_GetDeviceList ($hash); + if ($devcnt >= 0) { + HMCCU_Log ($hash, 1, "HMCCU: Read $devcnt devices with $chncnt channels from CCU ".$hash->{host}); + HMCCU_Log ($hash, 1, "HMCCU: Read $ifcount interfaces from CCU ".$hash->{host}); + HMCCU_Log ($hash, 1, "HMCCU: Read $prgcount programs from CCU ".$hash->{host}); + HMCCU_Log ($hash, 1, "HMCCU: Read $gcount virtual groups from CCU ".$hash->{host}); + return 0; + } + else { + HMCCU_Log ($hash, 1, "HMCCU: Error while reading device list from CCU ".$hash->{host}); + return 2; + } +} + +###################################################################### +# Set or delete attribute +###################################################################### + +sub HMCCU_Attr ($@) +{ + my ($cmd, $name, $attrname, $attrval) = @_; + my $hash = $defs{$name}; + my $rc = 0; + + if ($cmd eq 'set') { + if ($attrname eq 'ccudefaults') { + $rc = HMCCU_ImportDefaults ($attrval); + return HMCCU_SetError ($hash, -16) if ($rc == 0); + if ($rc < 0) { + $rc = -$rc; + return HMCCU_SetError ($hash, + "Syntax error in default attribute file $attrval line $rc"); + } + } + elsif ($attrname eq 'ccuaggregate') { + $rc = HMCCU_AggregationRules ($hash, $attrval); + return HMCCU_SetError ($hash, "Syntax error in attribute ccuaggregate") if ($rc == 0); + } + elsif ($attrname eq 'ccuackstate') { + return "HMCCU: Attribute ccuackstate is depricated. Use ccuflags with 'ackState' instead"; + } + elsif ($attrname eq 'ccureadings') { + return "HMCCU: Attribute ccureadings is depricated. Use ccuflags with 'noReadings' instead"; + } + elsif ($attrname eq 'ccuflags') { + my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); + my @flags = ($attrval =~ /(intrpc|extrpc|procrpc)/g); + return "Flags extrpc, procrpc and intrpc cannot be combined" if (scalar (@flags) > 1); +# if ($attrval =~ /(extrpc|intrpc|procrpc)/) { +# my $rpcmode = $1; +# if ($ccuflags !~ /$rpcmode/) { +# return "Stop RPC server before switching RPC server" +# if (HMCCU_IsRPCServerRunning ($hash, undef, undef)); +# } +# } + if ($attrval =~ /(intrpc|extrpc)/) { + HMCCU_Log ($hash, 1, "RPC server mode $1 no longer supported. Using procrpc instead"); + $attrval =~ s/(extrpc|intrpc)/procrpc/; + $_[3] = $attrval; + } + } + elsif ($attrname eq 'ccuGetVars') { + my ($interval, $pattern) = split /:/, $attrval; + $pattern = '.*' if (!defined ($pattern)); + $hash->{hmccu}{ccuvarspat} = $pattern; + $hash->{hmccu}{ccuvarsint} = $interval; + RemoveInternalTimer ($hash, "HMCCU_UpdateVariables"); + if ($interval > 0) { + HMCCU_Log ($hash, 2, "Updating CCU system variables every $interval seconds"); + InternalTimer (gettimeofday()+$interval, "HMCCU_UpdateVariables", $hash); + } + } + elsif ($attrname eq 'rpcdevice') { + return "HMCCU: Attribute rpcdevice is depricated. Please remove it"; + } + elsif ($attrname eq 'rpcinterfaces' || $attrname eq 'rpcport') { + if ($hash->{hmccu}{ccu}{delayed} == 0) { + my $msg = HMCCU_AttrInterfacesPorts ($hash, $attrname, $attrval); + return $msg if ($msg ne ''); + } + } + } + elsif ($cmd eq 'del') { + if ($attrname eq 'ccuaggregate') { + HMCCU_AggregationRules ($hash, ''); + } + elsif ($attrname eq 'ccuGetVars') { + RemoveInternalTimer ($hash, "HMCCU_UpdateVariables"); + } + elsif ($attrname eq 'rpcdevice') { + delete $hash->{RPCDEV} if (exists ($hash->{RPCDEV})); + } + elsif ($attrname eq 'rpcport' || $attrname eq 'rpcinterfaces') { + my ($defInterface, $defPort) = HMCCU_GetDefaultInterface ($hash); + $hash->{hmccu}{rpcports} = undef; + delete $attr{$name}{'rpcinterfaces'} if ($attrname eq 'rpcport'); + delete $attr{$name}{'rpcport'} if ($attrname eq 'rpcinterfaces'); + } + } + + return undef; +} + +###################################################################### +# Set attributes rpcinterfaces and rpcport. +# Return empty string on success or error message on error. +###################################################################### + +sub HMCCU_AttrInterfacesPorts ($$$) +{ + my ($hash, $attr, $attrval) = @_; + my $name = $hash->{NAME}; + + if ($attr eq 'rpcinterfaces') { + my @ilist = split (',', $attrval); + my @plist = (); + foreach my $p (@ilist) { + my ($pn, $dc) = HMCCU_GetRPCServerInfo ($hash, $p, 'port,devcount'); + return "HMCCU: Illegal RPC interface $p" if (!defined ($pn)); + return "HMCCU: No devices assigned to interface $p" if ($dc == 0); + push (@plist, $pn); + } + return "No RPC interface specified" if (scalar (@plist) == 0); + $hash->{hmccu}{rpcports} = join (',', @plist); + $attr{$name}{"rpcport"} = $hash->{hmccu}{rpcports}; + } + elsif ($attr eq 'rpcport') { + my @plist = split (',', $attrval); + my @ilist = (); + foreach my $p (@plist) { + my ($in, $dc) = HMCCU_GetRPCServerInfo ($hash, $p, 'name,devcount'); + return "HMCCU: Illegal RPC port $p" if (!defined ($in)); + return "HMCCU: No devices assigned to interface $in" if ($dc == 0); + push (@ilist, $in); + } + return "No RPC port specified" if (scalar (@ilist) == 0); + $hash->{hmccu}{rpcports} = $attrval; + $attr{$name}{"rpcinterfaces"} = join (',', @ilist); + } + + return ''; +} + +###################################################################### +# Parse aggregation rules for readings. +# Syntax of aggregation rule is: +# FilterSpec[;...] +# FilterSpec := {Name|Filt|Read|Cond|Else|Pref|Coll|Html}[,...] +# Name := name:Name +# Filt := filter:{name|type|group|room|alias}=Regexp[!Regexp] +# Read := read:Regexp +# Cond := if:{any|all|min|max|sum|avg|gt|lt|ge|le}=Value +# Else := else:Value +# Pref := prefix:{RULE|Prefix} +# Coll := coll:{NAME|Attribute} +# Html := html:Template +###################################################################### + +sub HMCCU_AggregationRules ($$) +{ + my ($hash, $rulestr) = @_; + my $name = $hash->{NAME}; + + # Delete existing aggregation rules + if (exists ($hash->{hmccu}{agg})) { + delete $hash->{hmccu}{agg}; + } + return if ($rulestr eq ''); + + my @pars = ('name', 'filter', 'if', 'else'); + + # Extract aggregation rules + my $cnt = 0; + my @rules = split (/[;\n]+/, $rulestr); + foreach my $r (@rules) { + $cnt++; + + # Set default rule parameters. Can be modified later + my %opt = ( 'read' => 'state', 'prefix' => 'RULE', 'coll' => 'NAME' ); + + # Parse aggregation rule + my @specs = split (',', $r); + foreach my $spec (@specs) { + if ($spec =~ /^(name|filter|read|if|else|prefix|coll|html):(.+)$/) { + $opt{$1} = $2; + } + } + + # Check if mandatory parameters are specified + foreach my $p (@pars) { + return HMCCU_Log ($hash, 1, "Parameter $p is missing in aggregation rule $cnt.") + if (!exists ($opt{$p})); + } + + my $fname = $opt{name}; + my ($fincl, $fexcl) = split ('!', $opt{filter}); + my ($ftype, $fexpr) = split ('=', $fincl); + return 0 if (!defined ($fexpr)); + my ($fcond, $fval) = split ('=', $opt{if}); + return 0 if (!defined ($fval)); + my ($fcoll, $fdflt) = split ('!', $opt{coll}); + $fdflt = 'no match' if (!defined ($fdflt)); + my $fhtml = exists ($opt{'html'}) ? $opt{'html'} : ''; + + # Read HTML template (optional) + if ($fhtml ne '') { + my %tdef; + my @html; + + # Read template file + if (open (TEMPLATE, "<$fhtml")) { + @html =