############################################################################## # # 88_HMCCU.pm # # $Id$ # # Version 4.3.020 # # 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) 2019 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.3.020'; # 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_RPCGetConfig ($$$$); sub HMCCU_RPCSetConfig ($$$); 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_CreateDevice ($$$$$); sub HMCCU_DeleteDevice ($); sub HMCCU_FormatDeviceInfo ($); sub HMCCU_GetAddress ($$$$); sub HMCCU_GetAffectedAddresses ($); sub HMCCU_GetCCUDeviceParam ($$); sub HMCCU_GetChannelName ($$$); sub HMCCU_GetDefaultInterface ($); sub HMCCU_GetDeviceChannels ($$$); sub HMCCU_GetDeviceInfo ($$$); sub HMCCU_GetDeviceInterface ($$$); sub HMCCU_GetDeviceList ($); 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_UpdateDeviceTable ($$); # Handle datapoints sub HMCCU_FindDatapoint ($$$$$); sub HMCCU_GetChannel ($$); 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_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_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,extrpc,intrpc,procrpc,dptnocheck,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 parfile 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 =~ /extrpc/) { HMCCU_Log ($hash, 1, "RPC server mode extrpc will be ignored. Using procrpc instead"); } elsif ($attrval =~ /intrpc/) { HMCCU_Log ($hash, 1, "RPC server mode intrpc is deprecated. Please use procrpc instead"); } } 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 =