###################################################################### # # 88_HMCCUCHN.pm # # $Id: 88_HMCCUCHN.pm 18552 2019-02-10 11:52:28Z zap $ # # Version 4.4.039 # # (c) 2021 zap (zap01 t-online de) # ###################################################################### # Client device for Homematic channels. # Requires module 88_HMCCU.pm ###################################################################### package main; use strict; use warnings; use SetExtensions; require "$attr{global}{modpath}/FHEM/88_HMCCU.pm"; sub HMCCUCHN_Initialize ($); sub HMCCUCHN_Define ($@); sub HMCCUCHN_InitDevice ($$); sub HMCCUCHN_Undef ($$); sub HMCCUCHN_Rename ($$); sub HMCCUCHN_Set ($@); sub HMCCUCHN_Get ($@); sub HMCCUCHN_Attr ($@); ###################################################################### # Initialize module ###################################################################### sub HMCCUCHN_Initialize ($) { my ($hash) = @_; $hash->{DefFn} = 'HMCCUCHN_Define'; $hash->{UndefFn} = 'HMCCUCHN_Undef'; $hash->{RenameFn} = 'HMCCUCHN_Rename'; $hash->{SetFn} = 'HMCCUCHN_Set'; $hash->{GetFn} = 'HMCCUCHN_Get'; $hash->{AttrFn} = 'HMCCUCHN_Attr'; $hash->{parseParams} = 1; $hash->{AttrList} = 'IODev ccucalculate '. 'ccuflags:multiple-strict,ackState,logCommand,noReadings,trace,showMasterReadings,showLinkReadings,showDeviceReadings,showServiceReadings '. 'ccureadingfilter:textField-long statedatapoint controldatapoint '. 'ccureadingformat:name,namelc,address,addresslc '. 'ccureadingname:textField-long ccuSetOnChange ccuReadingPrefix '. 'ccuscaleval ccuverify:0,1,2 ccuget:State,Value '. 'disable:0,1 hmstatevals:textField-long statevals substitute:textField-long '. 'substexcl stripnumber peer:textField-long traceFilter '. $readingFnAttributes; } ###################################################################### # Define device ###################################################################### sub HMCCUCHN_Define ($@) { my ($hash, $a, $h) = @_; my $name = $hash->{NAME}; my $usage = "Usage: define $name HMCCUCHN {device} ['readonly'] ['noDefaults'|'defaults'] [iodev={iodevname}]"; return $usage if (@$a < 3); my ($devname, $devtype, $devspec) = splice (@$a, 0, 3); my $ioHash; my $existDev = HMCCU_ExistsClientDevice ($devspec, $devtype); return "FHEM device $existDev for CCU device $devspec already exists" if ($existDev ne ''); # Store some definitions for delayed initialization $hash->{hmccu}{devspec} = $devspec; # Defaults $hash->{readonly} = 'no'; $hash->{hmccu}{channels} = 1; $hash->{hmccu}{nodefaults} = $init_done ? 0 : 1; $hash->{hmccu}{semDefaults} = 0; # Parse optional command line parameters my $n = 0; while (my $arg = shift @$a) { return $usage if ($n == 3); if ($arg eq 'readonly') { $hash->{readonly} = 'yes'; } elsif (lc($arg) eq 'nodefaults' && $init_done) { $hash->{hmccu}{nodefaults} = 1; } elsif (lc($arg) eq 'defaults' && $init_done) { $hash->{hmccu}{nodefaults} = 0; } else { return $usage; } $n++; } # IO device can be set by command line parameter iodev, otherwise try to detect IO device if (exists($h->{iodev})) { return "Device $h->{iodev} does not exist" if (!exists($defs{$h->{iodev}})); return "Type of device $h->{iodev} is not HMCCU" if ($defs{$h->{iodev}}->{TYPE} ne 'HMCCU'); $ioHash = $defs{$h->{iodev}}; } else { # The following call will fail during FHEM start if CCU is not ready $ioHash = HMCCU_FindIODevice ($devspec); } if ($init_done) { # Interactive define command while CCU not ready or no IO device defined if (!defined($ioHash)) { my ($ccuactive, $ccuinactive) = HMCCU_IODeviceStates (); return $ccuinactive > 0 ? 'CCU and/or IO device not ready. Please try again later' : 'Cannot detect IO device or CCU device not found'; } } else { # CCU not ready during FHEM start if (!defined($ioHash) || $ioHash->{ccustate} ne 'active') { HMCCU_Log ($hash, 3, 'Cannot detect IO device, maybe CCU not ready. Trying later ...'); $hash->{ccudevstate} = 'pending'; return undef; } } # Initialize FHEM device, set IO device my $rc = HMCCUCHN_InitDevice ($ioHash, $hash); return 'Invalid or unknown CCU channel name or address' if ($rc == 1); return "Can't assign I/O device $ioHash->{NAME}" if ($rc == 2); 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 = Invalid channel name or address # 2 = Cannot assign IO device ###################################################################### sub HMCCUCHN_InitDevice ($$) { my ($ioHash, $devHash) = @_; my $name = $devHash->{NAME}; my $devspec = $devHash->{hmccu}{devspec}; return 1 if (!HMCCU_IsValidChannel ($ioHash, $devspec, 7)); my ($di, $da, $dn, $dt, $dc) = HMCCU_GetCCUDeviceParam ($ioHash, $devspec); return 1 if (!defined($da)); # Inform HMCCU device about client device return 2 if (!HMCCU_AssignIODevice ($devHash, $ioHash->{NAME})); $devHash->{ccuif} = $di; $devHash->{ccuaddr} = $da; $devHash->{ccuname} = $dn; $devHash->{ccutype} = $dt; $devHash->{ccudevstate} = 'active'; if ($init_done) { my $detect = HMCCU_DetectDevice ($ioHash, $da, $di); # Interactive device definition HMCCU_SetSCAttributes ($ioHash, $devHash, $detect); HMCCU_AddDevice ($ioHash, $di, $da, $devHash->{NAME}); HMCCU_UpdateDevice ($ioHash, $devHash); HMCCU_UpdateDeviceRoles ($ioHash, $devHash); return -2 if (!defined($detect) || $detect->{level} == 0); # Device not detected if (!HMCCU_SetDefaultSCDatapoints ($ioHash, $devHash, $detect)) { HMCCU_Log ($devHash, 2, "Cannot set default state- and control datapoints"); } HMCCU_SetInitialAttributes ($ioHash, $name); if (!exists($devHash->{hmccu}{nodefaults}) || $devHash->{hmccu}{nodefaults} == 0) { if (!HMCCU_SetDefaultAttributes ($devHash)) { HMCCU_SetDefaults ($devHash); } } HMCCU_GetUpdate ($devHash, $da, 'Value'); } return 0; } ###################################################################### # Delete device ###################################################################### sub HMCCUCHN_Undef ($$) { my ($hash, $arg) = @_; if (defined($hash->{IODev})) { HMCCU_RemoveDevice ($hash->{IODev}, $hash->{ccuif}, $hash->{ccuaddr}, $hash->{NAME}); } return undef; } ###################################################################### # Rename device ###################################################################### sub HMCCUCHN_Rename ($$) { my ($newName, $oldName) = @_; my $clHash = $defs{$newName}; my $ioHash = defined($clHash) ? $clHash->{IODev} : undef; HMCCU_RenameDevice ($ioHash, $clHash, $oldName); } ###################################################################### # Set attribute ###################################################################### sub HMCCUCHN_Attr ($@) { my ($cmd, $name, $attrname, $attrval) = @_; my $clHash = $defs{$name}; my $ioHash = HMCCU_GetHash ($clHash); if ($cmd eq 'set') { return 'Missing attribute value' if (!defined($attrval)); if ($attrname eq 'IODev') { $clHash->{IODev} = $defs{$attrval}; } elsif ($attrname eq 'statevals') { return 'Device is read only' if ($clHash->{readonly} eq 'yes'); } elsif ($attrname =~ /^(state|control)datapoint$/) { my $role = HMCCU_GetChannelRole ($clHash); return "Invalid value $attrval" if (!HMCCU_SetSCDatapoints ($clHash, $attrname, $attrval, $role)); } } elsif ($cmd eq 'del') { if ($attrname =~ /^(state|control)datapoint$/) { # Reset value HMCCU_SetSCDatapoints ($clHash, $attrname); delete $clHash->{hmccu}{roleCmds} if (exists($clHash->{hmccu}{roleCmds}) && (!exists($clHash->{hmccu}{control}{chn}) || $clHash->{hmccu}{control}{chn} eq '')); if ($init_done) { if (!HMCCU_SetDefaultSCDatapoints ($ioHash, $clHash)) { HMCCU_Log ($clHash, 2, "Cannot set default state- and control datapoints"); } } } } return undef; } ###################################################################### # Set commands ###################################################################### sub HMCCUCHN_Set ($@) { my ($hash, $a, $h) = @_; my $name = shift @$a; my $opt = shift @$a // return 'No set command specified'; my $lcopt = lc($opt); # Check device state return "Device state doesn't allow set commands" if (!defined($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' || !defined($hash->{IODev}) || ($hash->{readonly} eq 'yes' && $lcopt !~ /^(\?|clear|config|defaults)$/) || AttrVal ($name, 'disable', 0) == 1); my $ioHash = $hash->{IODev}; my $ioName = $ioHash->{NAME}; return ($opt eq '?' ? undef : 'Cannot perform set commands. CCU busy') if (HMCCU_IsRPCStateBlocking ($ioHash)); # Build set command syntax my $syntax = 'clear defaults:reset,update'; # Command readingFilter depends on readable datapoints my ($add, $chn) = split(":", $hash->{ccuaddr}); my @dpRList = (); my $dpRCount = HMCCU_GetValidDatapoints ($hash, $hash->{ccutype}, $chn, 5, \@dpRList); $syntax .= ' readingFilter:multiple-strict,'.join(',', @dpRList) if ($dpRCount > 0); # Commands only available in read/write mode if ($hash->{readonly} ne 'yes') { $syntax .= ' config'; my $dpWCount = HMCCU_GetValidDatapoints ($hash, $hash->{ccutype}, $chn, 2); $syntax .= ' datapoint' if ($dpWCount > 0); my $addCmds = $hash->{hmccu}{cmdlist}{set} // ''; $syntax .= " $addCmds" if ($addCmds ne ''); } # Log commands HMCCU_Log ($hash, 3, "set $name $opt ".join (' ', @$a)) if ($opt ne '?' && (HMCCU_IsFlag ($name, 'logCommand') || HMCCU_IsFlag ($ioName, 'logCommand'))); if ($lcopt eq 'control') { return HMCCU_ExecuteSetControlCommand ($hash, $a, $h); } elsif ($lcopt eq 'datapoint') { return HMCCU_ExecuteSetDatapointCommand ($hash, $a, $h); } elsif ($lcopt eq 'toggle') { return HMCCU_ExecuteToggleCommand ($hash); } elsif (exists($hash->{hmccu}{roleCmds}{set}{$opt})) { return HMCCU_ExecuteRoleCommand ($ioHash, $hash, 'set', $opt, $a, $h); } elsif ($opt eq 'clear') { return HMCCU_ExecuteSetClearCommand ($hash, $a); } elsif ($lcopt =~ /^(config|values)$/) { return HMCCU_ExecuteSetParameterCommand ($ioHash, $hash, $lcopt, $a, $h); } elsif ($lcopt =~ 'readingfilter') { my $filter = shift @$a // return HMCCU_SetError ($hash, "Usage: set $name readingFilter {datapointList}"); $filter =~ s/,/\|/g; $filter = '^('.$filter.')$'; return CommandAttr (undef, "$name ccureadingfilter $filter"); } elsif ($lcopt eq 'defaults') { my $mode = shift @$a // 'update'; my $rc = HMCCU_SetDefaultAttributes ($hash, { mode => $mode, role => undef, roleChn => undef }); $rc = HMCCU_SetDefaults ($hash) if (!$rc); HMCCU_RefreshReadings ($hash) if ($rc); return HMCCU_SetError ($hash, $rc == 0 ? "No default attributes found" : "OK"); } else { return "Unknown argument $opt choose one of $syntax"; } } ###################################################################### # Get commands ###################################################################### sub HMCCUCHN_Get ($@) { my ($hash, $a, $h) = @_; my $name = shift @$a; my $opt = shift @$a // return 'No get command specified'; my $lcopt = lc($opt); return undef if (!defined ($hash->{ccudevstate}) || $hash->{ccudevstate} eq 'pending' || !defined ($hash->{IODev}) || AttrVal ($name, "disable", 0) == 1); my $ioHash = $hash->{IODev}; my $ioName = $ioHash->{NAME}; return $opt eq '?' ? undef : 'Cannot perform get command. CCU busy' if (HMCCU_IsRPCStateBlocking ($ioHash)); my $ccutype = $hash->{ccutype}; my $ccuaddr = $hash->{ccuaddr}; my $ccuif = $hash->{ccuif}; my $ccuflags = AttrVal ($name, 'ccuflags', 'null'); # Build set command syntax my $syntax = 'update:noArg config:noArg paramsetDesc:noArg deviceInfo:noArg values:noArg'; # Command datapoint depends on readable datapoints my ($add, $chn) = split(":", $hash->{ccuaddr}); my @dpRList; my $dpRCount = HMCCU_GetValidDatapoints ($hash, $ccutype, $chn, 1, \@dpRList); $syntax .= ' datapoint:'.join(",", @dpRList) if ($dpRCount > 0); # Additional device specific commands my $addCmds = $hash->{hmccu}{cmdlist}{get} // ''; $syntax .= " $addCmds" if ($addCmds ne ''); # Log commands HMCCU_Log ($hash, 3, "get $name $opt ".join (' ', @$a)) if ($opt ne '?' && $ccuflags =~ /logCommand/ || HMCCU_IsFlag ($ioName, 'logCommand')); if ($lcopt eq 'datapoint') { my $objname = shift @$a // return HMCCU_SetError ($hash, "Usage: get $name datapoint {datapoint}"); return HMCCU_SetError ($hash, -8, $objname) if (!HMCCU_IsValidDatapoint ($hash, $ccutype, $ccuaddr, $objname, 1)); $objname = $ccuif.'.'.$ccuaddr.'.'.$objname; my ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname, 0); return $rc < 0 ? HMCCU_SetError ($hash, $rc, $result) : $result; } elsif ($lcopt eq 'deviceinfo') { my $extended = shift @$a; my ($devAddr, undef) = HMCCU_SplitChnAddr ($ccuaddr); return HMCCU_ExecuteGetDeviceInfoCommand ($ioHash, $hash, $devAddr, defined($extended) ? 1 : 0); } elsif ($lcopt =~ /^(config|values|update)$/) { my ($devAddr, undef) = HMCCU_SplitChnAddr ($ccuaddr); my @addList = ($devAddr, "$devAddr:0", $ccuaddr); my $result = HMCCU_ExecuteGetParameterCommand ($ioHash, $hash, $lcopt, \@addList); return HMCCU_SetError ($hash, "Can't get device description") if (!defined($result)); return HMCCU_DisplayGetParameterResult ($ioHash, $hash, $result); } elsif ($lcopt eq 'paramsetdesc') { my $result = HMCCU_ParamsetDescToStr ($ioHash, $hash); return defined($result) ? $result : HMCCU_SetError ($hash, "Can't get device model"); } elsif (exists($hash->{hmccu}{roleCmds}{get}{$opt})) { return HMCCU_ExecuteRoleCommand ($ioHash, $hash, 'get', $opt, $a, $h); } else { return "Unknown argument $opt choose one of $syntax"; } } 1; =pod =item device =item summary controls HMCCU client devices for Homematic CCU2/3 - FHEM integration =begin html

HMCCUCHN

=end html =cut