############################################################################## # # 88_HMCCU.pm # # $Id: 88_HMCCU.pm 18745 2019-02-26 17:33:23Z zap $ # # Version 4.4.068 # # 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) 2021 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 IO::File; use Encode qw(decode encode); use RPC::XML::Client; use RPC::XML::Server; use HttpUtils; use SetExtensions; use HMCCUConf; # Import configuration data my $HMCCU_CONFIG_VERSION = $HMCCUConf::HMCCU_CONFIG_VERSION; my $HMCCU_DEF_ROLE = \%HMCCUConf::HMCCU_DEF_ROLE; my $HMCCU_STATECONTROL = \%HMCCUConf::HMCCU_STATECONTROL; my $HMCCU_READINGS = \%HMCCUConf::HMCCU_READINGS; my $HMCCU_ROLECMDS = \%HMCCUConf::HMCCU_ROLECMDS; my $HMCCU_GETROLECMDS = \%HMCCUConf::HMCCU_GETROLECMDS; my $HMCCU_ATTR = \%HMCCUConf::HMCCU_ATTR; my $HMCCU_CONVERSIONS = \%HMCCUConf::HMCCU_CONVERSIONS; 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.068'; # Timeout for CCU requests (seconds) 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 ); # Default values for delayed initialization during FHEM startup my $HMCCU_INIT_INTERVAL0 = 12; my $HMCCU_CCU_PING_TIMEOUT = 1; my $HMCCU_CCU_PING_SLEEP = 1; my $HMCCU_CCU_BOOT_DELAY = 180; my $HMCCU_CCU_DELAYED_INIT = 59; my $HMCCU_CCU_RPC_OFFSET = 20; # 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'; # 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_Notify ($$); sub HMCCU_Detail ($$$$); sub HMCCU_PostInit ($); # Aggregation sub HMCCU_AggregateReadings ($$); sub HMCCU_AggregationRules ($$); # Handling of default attributes sub HMCCU_ExportDefaults ($$); sub HMCCU_ImportDefaults ($); sub HMCCU_FindDefaults ($$); sub HMCCU_GetDefaults ($;$); sub HMCCU_SetDefaults ($); # Status and logging functions sub HMCCU_Trace ($$$); sub HMCCU_Log ($$$;$); sub HMCCU_LogDisplay ($$$;$); 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_StripNumber ($$;$); sub HMCCU_Substitute ($$$$$;$$); sub HMCCU_SubstRule ($$$); sub HMCCU_SubstVariables ($$$); # Update client device readings sub HMCCU_BulkUpdate ($$$;$); sub HMCCU_GetUpdate ($$$); sub HMCCU_RefreshReadings ($); sub HMCCU_UpdateCB ($$$); sub HMCCU_UpdateClients ($$$$;$$); sub HMCCU_UpdateInternalValues ($$$$$); sub HMCCU_UpdateMultipleDevices ($$); sub HMCCU_UpdatePeers ($$$$); sub HMCCU_UpdateParamsetReadings ($$$;$); sub HMCCU_UpdateSingleDatapoint ($$$$); # RPC functions sub HMCCU_CreateRPCDevice ($$$$); sub HMCCU_EventsTimedOut ($); sub HMCCU_GetRPCCallbackURL ($$$$$); sub HMCCU_GetRPCDevice ($$$); sub HMCCU_GetRPCInterfaceList ($;$); sub HMCCU_GetRPCServerInfo ($$$); sub HMCCU_IsRPCServerRunning ($;$); sub HMCCU_IsRPCType ($$$); sub HMCCU_IsRPCStateBlocking ($); sub HMCCU_RPCRequest ($$$$;$$); sub HMCCU_StartExtRPCServer ($); sub HMCCU_StopExtRPCServer ($;$); # 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_ExistsClientDevice ($$); 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_AddDevice ($$$;$); sub HMCCU_AddDeviceDesc ($$$$); sub HMCCU_AddDeviceModel ($$$$$$); sub HMCCU_AddPeers ($$$); sub HMCCU_CheckParameter ($$;$$$); sub HMCCU_DetectDevice ($$$); sub HMCCU_IdentifyRole ($$$$$); sub HMCCU_GetSCInfo ($$;$); sub HMCCU_DeviceDescToStr ($$); sub HMCCU_ExecuteRoleCommand ($@); sub HMCCU_ExecuteGetDeviceInfoCommand ($@); sub HMCCU_ExecuteGetParameterCommand ($@); sub HMCCU_ExecuteSetClearCommand ($@); sub HMCCU_ExecuteSetControlCommand ($@); sub HMCCU_ExecuteSetDatapointCommand ($@); sub HMCCU_ExecuteSetParameterCommand ($@); sub HMCCU_DisplayGetParameterResult ($$$); sub HMCCU_DisplayWeekProgram ($$$;$$); sub HMCCU_ExistsDeviceModel ($$$;$); sub HMCCU_FindParamDef ($$$); sub HMCCU_FormatDeviceInfo ($); sub HMCCU_GetAddress ($$;$$); sub HMCCU_GetAffectedAddresses ($); sub HMCCU_GetCCUDeviceParam ($$); sub HMCCU_GetChannelName ($$;$); sub HMCCU_GetChannelRole ($;$); sub HMCCU_GetDeviceRoles ($$$;$); sub HMCCU_GetClientDeviceModel ($;$); sub HMCCU_GetDefaultInterface ($); sub HMCCU_GetDeviceAddresses ($;$$); sub HMCCU_GetDeviceConfig ($); sub HMCCU_GetDeviceDesc ($$;$); sub HMCCU_GetDeviceIdentifier ($$;$$); sub HMCCU_GetDeviceInfo ($$;$); sub HMCCU_GetDeviceInterface ($$;$); sub HMCCU_GetInterfaceList ($); sub HMCCU_GetDeviceList ($); sub HMCCU_GetDeviceModel ($$$;$); sub HMCCU_GetDeviceName ($$;$); sub HMCCU_GetDeviceType ($$$); sub HMCCU_GetFirmwareVersions ($$); sub HMCCU_GetGroupMembers ($$); sub HMCCU_GetMatchingDevices ($$$$); sub HMCCU_GetParamDef ($$$;$); sub HMCCU_GetParamValueConversion ($$$$$); sub HMCCU_GetReceivers ($$$); sub HMCCU_IsValidChannel ($$$); sub HMCCU_IsValidDevice ($$$); sub HMCCU_IsValidDeviceOrChannel ($$$); sub HMCCU_IsValidParameter ($$$$;$); sub HMCCU_IsValidReceiver ($$$$); sub HMCCU_ParamsetDescToStr ($$); sub HMCCU_RemoveDevice ($$$;$); sub HMCCU_RenameDevice ($$$); sub HMCCU_ResetDeviceTables ($;$$); sub HMCCU_SetSCAttributes ($$;$); sub HMCCU_UpdateDevice ($$); sub HMCCU_UpdateDeviceRoles ($$;$$); sub HMCCU_UpdateDeviceTable ($$); sub HMCCU_UpdateRoleCommands ($$;$); sub HMCCU_UpdateAdditionalCommands ($$;$$); # Handle datapoints sub HMCCU_FindDatapoint ($$$$$); sub HMCCU_GetDatapoint ($@); sub HMCCU_GetDatapointAttr ($$$$$); sub HMCCU_GetDatapointList ($;$$); sub HMCCU_GetSCDatapoints ($); sub HMCCU_SetSCDatapoints ($$;$$); sub HMCCU_GetStateValues ($;$$); sub HMCCU_GetValidDatapoints ($$$$;$); sub HMCCU_IsValidDatapoint ($$$$$); sub HMCCU_SetInitialAttributes ($$); sub HMCCU_SetDefaultAttributes ($;$); sub HMCCU_SetMultipleDatapoints ($$); sub HMCCU_SetMultipleParameters ($$$;$); # 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 ($); # Helper functions sub HMCCU_BitsToStr ($$); sub HMCCU_BuildURL ($$); sub HMCCU_CalculateReading ($$); sub HMCCU_CorrectName ($); sub HMCCU_Encrypt ($); sub HMCCU_Decrypt ($); sub HMCCU_DefStr ($;$$); sub HMCCU_DeleteReadings ($$); sub HMCCU_EncodeEPDisplay ($); sub HMCCU_ExprMatch ($$$); sub HMCCU_ExprNotMatch ($$$); sub HMCCU_FlagsToStr ($$$;$$); sub HMCCU_UpdateDeviceStates ($); sub HMCCU_GetDutyCycle ($); sub HMCCU_GetHMState ($$;$); sub HMCCU_GetIdFromIP ($$); sub HMCCU_GetTimeSpec ($); sub HMCCU_IsFltNum ($;$); sub HMCCU_IsIntNum ($); sub HMCCU_ISO2UTF ($); sub HMCCU_Max ($$); sub HMCCU_MaxHashEntries ($$); sub HMCCU_Min ($$); sub HMCCU_MinMax ($$$); sub HMCCU_RefToString ($); sub HMCCU_ResolveName ($$); sub HMCCU_TCPConnect ($$;$); sub HMCCU_TCPPing ($$$); sub HMCCU_UpdateReadings ($$;$); ################################################## # 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'. ' ccudef-hmstatevals:textField-long ccudef-substitute:textField-long'. ' ccudef-readingformat:name,namelc,address,addresslc,datapoint,datapointlc'. ' ccudef-stripnumber ccudef-attributes ccuReadingPrefix'. ' ccuflags:multiple-strict,procrpc,dptnocheck,logCommand,noagg,nohmstate,updGroupMembers,'. 'logEvents,noEvents,noInitialUpdate,noReadings,nonBlocking,reconnect,logPong,trace,logEnhanced'. ' ccuReqTimeout ccuGetVars rpcPingCCU'. ' 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}; my $usage = "Usage: define $name HMCCU {NameOrIP} [{ccunum}] [nosync] ccudelay={time} waitforccu={time} delayedinit={time}"; return $usage 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:HMCCURPCPROC:'; $hash->{hmccu}{ccu}{delay} = $h->{ccudelay} // $HMCCU_CCU_BOOT_DELAY; $hash->{hmccu}{ccu}{timeout} = $h->{waitforccu} // $HMCCU_CCU_PING_TIMEOUT; $hash->{hmccu}{ccu}{delayed} = 0; $hash->{hmccu}{ccu}{sync} = 1; if (exists($h->{delayedinit}) && $h->{delayedinit} > 0) { if (!$init_done) { # Forced delayed initialization 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 { HMCCU_LogDisplay ($hash, 2, 'Forced delayed initialization is done during FHEM start'); } } else { # Check if TCL-Rega process is running on CCU (CCU is reachable) if (HMCCU_TCPPing ($hash->{host}, $HMCCU_REGA_PORT{$hash->{prot}}, $hash->{hmccu}{ccu}{timeout})) { $hash->{ccustate} = 'active'; HMCCU_Log ($hash, 1, 'CCU port '.$HMCCU_REGA_PORT{$hash->{prot}}.' is reachable'); } else { $hash->{ccustate} = 'unreachable'; HMCCU_LogDisplay ($hash, 1, 'CCU port '.$HMCCU_REGA_PORT{$hash->{prot}}.' is not reachable'); } } # Get CCU IP address $hash->{ccuip} = HMCCU_ResolveName ($hash->{host}, 'N/A'); # Parse optional command line parameters for (my $i=3; $i 9); $hash->{CCUNum} = $$a[$i]; } elsif (lc($$a[$i]) eq 'nosync') { $hash->{hmccu}{ccu}{sync} = 0; } else { return $usage; } } # Get CCU number (if there is more than one) if (!exists($hash->{CCUNum})) { # Count CCU devices $hash->{CCUNum} = 1; foreach my $d (keys %defs) { my $ch = $defs{$d}; $hash->{CCUNum}++ if (exists($ch->{TYPE}) && $ch->{TYPE} eq 'HMCCU' && $ch != $hash); } } $hash->{version} = $HMCCU_VERSION; $hash->{config} = $HMCCU_CONFIG_VERSION; $hash->{ccutype} = 'CCU2/3'; $hash->{RPCState} = 'inactive'; $hash->{NOTIFYDEV} = 'global,TYPE=(HMCCU|HMCCUDEV|HMCCUCHN)'; $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, 'Initializing device'); $rc = HMCCU_InitDevice ($hash); } if (($hash->{ccustate} ne 'active' || $rc > 0) && !$init_done) { # Schedule update of CCU assets if CCU is not active during FHEM startup $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}{$_} = 0 for ('evtime', 'evtimeout', 'updatetime', 'rpccount', 'defaults'); HMCCU_UpdateReadings ($hash, { 'state' => 'Initialized', 'rpcstate' => 'inactive' }); if ($init_done) { $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}; my $host = $hash->{host}; if ($hash->{hmccu}{ccu}{delayed} == 1) { HMCCU_Log ($hash, 1, 'Initializing devices'); if (!HMCCU_TCPPing ($host, $HMCCU_REGA_PORT{$hash->{prot}}, $hash->{hmccu}{ccu}{timeout})) { $hash->{ccustate} = 'unreachable'; return HMCCU_Log ($hash, 1, "CCU port ".$HMCCU_REGA_PORT{$hash->{prot}}." is not reachable", 1); } } my ($devcnt, $chncnt, $ifcount, $prgcount, $gcount) = HMCCU_GetDeviceList ($hash); if ($ifcount > 0) { setDevAttrList ($name, $modules{HMCCU}{AttrList}.' rpcinterfaces:multiple-strict,'.$hash->{ccuinterfaces}); HMCCU_Log ($hash, 1, [ "Read $devcnt devices with $chncnt channels from CCU $host", "Read $prgcount programs from CCU $host", "Read $gcount virtual groups from CCU $host" ]); # Interactive device definition if ($init_done && $hash->{hmccu}{ccu}{delayed} == 0) { # Force sync with CCU during interactive device definition if ($hash->{hmccu}{ccu}{sync} == 1) { HMCCU_Log ($hash, 1, 'Reading device config from CCU. This may take a couple of seconds ...'); my ($cDev, $cPar, $cLnk) = HMCCU_GetDeviceConfig ($hash); HMCCU_Log ($hash, 2, "Read RPC device configuration: devices/channels=$cDev parametersets=$cPar links=$cLnk"); } } } else { return HMCCU_Log ($hash, 1, "No RPC interfaces found on CCU $host", 2); } } ###################################################################### # Tasks to be executed after all devices have been defined. Executed # as timer function after FHEM has been initialized and startup is # complete. # Read device configuration from CCU # Start RPC servers ###################################################################### sub HMCCU_PostInit ($) { my ($hash) = @_; my $host = $hash->{host}; if ($hash->{ccustate} eq 'active') { my $rpcServer = AttrVal ($hash->{NAME}, 'rpcserver', 'off'); HMCCU_Log ($hash, 1, 'Reading device config from CCU. This may take a couple of seconds ...'); my ($cDev, $cPar, $cLnk) = HMCCU_GetDeviceConfig ($hash); HMCCU_Log ($hash, 2, "Read device configuration: devices/channels=$cDev parametersets=$cPar links=$cLnk"); HMCCU_StartExtRPCServer ($hash) if ($rpcServer eq 'on'); } else { HMCCU_Log ($hash, 1, 'CCU not active. Post FHEM start initialization failed'); } } ###################################################################### # 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); return HMCCU_SetError ($hash, 'Syntax error in default attribute file $attrval line '.(-$rc)) if ($rc < 0); } 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 =~ /(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 'rpcport') { return 'HMCCU: Attribute rpcport is no longer supported. Use rpcinterfaces instead'; } } elsif ($cmd eq 'del') { if ($attrname eq 'ccuaggregate') { HMCCU_AggregationRules ($hash, ''); } elsif ($attrname eq 'ccuGetVars') { RemoveInternalTimer ($hash, "HMCCU_UpdateVariables"); } } return undef; } ###################################################################### # 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}; return 0 if ($rulestr eq ''); # Delete existing aggregation rules if (exists($hash->{hmccu}{agg})) { delete $hash->{hmccu}{agg}; } my @pars = ('name', 'filter', 'if', 'else'); # Extract aggregation rules my $cnt = 0; foreach my $r (split(/[;\n]+/, $rulestr)) { $cnt++; # Set default rule parameters. Can be modified later my %opt = ('read' => 'state', 'prefix' => 'RULE', 'coll' => 'NAME'); # Parse aggregation rule foreach my $spec (split(',', $r)) { 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.", 0) if (!exists($opt{$p})); } my $fname = $opt{name}; my ($fincl, $fexcl) = split ('!', $opt{filter}); my ($ftype, $fexpr) = split ('=', $fincl); my ($fcond, $fval) = split ('=', $opt{if}); my ($fcoll, $fdflt) = split ('!', $opt{coll}); return 0 if (!defined($fexpr) || !defined($fval)); $fdflt //= 'no match'; 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 =