2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-05-02 00:48:53 +00:00

10_KNX.pm: rework logging, pls. check (Forum Thread #122582)

git-svn-id: https://svn.fhem.de/fhem/trunk@27405 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
erwin 2023-04-07 09:01:00 +00:00
parent f48f7f9462
commit 0ffaac9bb8
2 changed files with 135 additions and 114 deletions

View File

@ -48,6 +48,8 @@
# replace GP_Import: Devio,Tcpserver,HttpUtils with use stmts # replace GP_Import: Devio,Tcpserver,HttpUtils with use stmts
# 28/03/2023 cleanup # 28/03/2023 cleanup
# rework Logging, duplicate msg detection # rework Logging, duplicate msg detection
# 04/04/2023 limit retries for keepalive timeouts
# rework Logging
package KNXIO; ## no critic 'package' package KNXIO; ## no critic 'package'
@ -438,7 +440,7 @@ sub KNXIO_ReadH {
$hash->{PhyAddr} = KNXIO_addr2hex($phyaddr,2); # correct Phyaddr. $hash->{PhyAddr} = KNXIO_addr2hex($phyaddr,2); # correct Phyaddr.
# DoTrigger($name, 'CONNECTED'); # DoTrigger($name, 'CONNECTED');
readingsSingleUpdate($hash, 'state', 'connected', 1); readingsSingleUpdate($hash, 'state', 'connected', 1);
KNXIO_Log ($name, 3, qq{$name connected}); KNXIO_Log ($name, 3, q{connected});
$hash->{KNXIOhelper}->{SEQUENCECNTR} = 0; $hash->{KNXIOhelper}->{SEQUENCECNTR} = 0;
return InternalTimer(gettimeofday() + 60, \&KNXIO_keepAlive, $hash); # start keepalive return InternalTimer(gettimeofday() + 60, \&KNXIO_keepAlive, $hash); # start keepalive
} }
@ -737,7 +739,7 @@ sub KNXIO_openDev {
$selectlist{"$name.$param"} = $hash; $selectlist{"$name.$param"} = $hash;
my $retxt = ($reopen)?'reappeared':'opened'; my $retxt = ($reopen)?'reappeared':'opened';
KNXIO_Log ($name, 3, qq{device $retxt}); KNXIO_Log ($name, 3, qq{$retxt});
$ret = KNXIO_init($hash); $ret = KNXIO_init($hash);
} }
@ -747,7 +749,7 @@ sub KNXIO_openDev {
} }
if(defined($ret) && $ret) { if(defined($ret) && $ret) {
KNXIO_Log ($name, 1, q{Cannot open KNXIO-Device - ignoring it}); KNXIO_Log ($name, 1, q{Cannot open device - ignoring it});
KNXIO_closeDev($hash); KNXIO_closeDev($hash);
} }
@ -774,7 +776,7 @@ sub KNXIO_init {
else { else {
# DoTrigger($name, 'CONNECTED'); # DoTrigger($name, 'CONNECTED');
readingsSingleUpdate($hash, 'state', 'connected', 1); readingsSingleUpdate($hash, 'state', 'connected', 1);
KNXIO_Log ($name, 3, qq{connected}); KNXIO_Log ($name, 3, q{connected});
} }
return; return;
@ -891,7 +893,7 @@ sub KNXIO_disconnect {
::DevIo_Disconnected($hash); ::DevIo_Disconnected($hash);
KNXIO_Log ($name, 1, qq{disconnected, waiting to reappear}); KNXIO_Log ($name, 1, q{disconnected, waiting to reappear});
$readyfnlist{"$name.$param"} = $hash; # Start polling $readyfnlist{"$name.$param"} = $hash; # Start polling
$hash->{NEXT_OPEN} = gettimeofday() + $reconnectTO; $hash->{NEXT_OPEN} = gettimeofday() + $reconnectTO;
@ -955,7 +957,7 @@ sub KNXIO_decodeEMI {
KNXIO_Log ($name, 4, 'OpenGrpCon response received'); KNXIO_Log ($name, 4, 'OpenGrpCon response received');
# DoTrigger($name, 'CONNECTED'); # DoTrigger($name, 'CONNECTED');
readingsSingleUpdate($hash, 'state', 'connected', 1); readingsSingleUpdate($hash, 'state', 'connected', 1);
KNXIO_Log ($name, 3, qq{connected}); KNXIO_Log ($name, 3, q{connected});
} }
else { else {
KNXIO_Log ($name, 3, 'invalid message code ' . sprintf('%04x',$id)); KNXIO_Log ($name, 3, 'invalid message code ' . sprintf('%04x',$id));
@ -1075,12 +1077,16 @@ sub KNXIO_hex2addr {
} }
### keep alive for mode H - every minute ### keep alive for mode H - every minute
# triggered on conn-response & # triggered on conn-response & connstate response
# 2nd param is undef unless called from KNXIO_keepAliveTO
sub KNXIO_keepAlive { sub KNXIO_keepAlive {
my $hash = shift; my $hash = shift;
my $name = $hash->{NAME}; my $cntrTO = shift // 0; #retry counter
KNXIO_Log ($name, 4, 'expect ConnectionStateResponse'); my $name = $hash->{NAME};
$hash->{KNXIOhelper}->{CNTRTO} = $cntrTO;
KNXIO_Log ($name, 4, 'send conn state request - expect connection state response');
my $msg = pack('nnnCCnnnn',(0x0610,0x0207,16,$hash->{KNXIOhelper}->{CCID},0, 0x0801,0,0,0)); my $msg = pack('nnnCCnnnn',(0x0610,0x0207,16,$hash->{KNXIOhelper}->{CCID},0, 0x0801,0,0,0));
RemoveInternalTimer($hash,\&KNXIO_keepAlive); RemoveInternalTimer($hash,\&KNXIO_keepAlive);
@ -1092,11 +1098,16 @@ sub KNXIO_keepAlive {
### keep alive timeout ### keep alive timeout
sub KNXIO_keepAliveTO { sub KNXIO_keepAliveTO {
my $hash = shift; my $hash = shift;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $cntrTO = $hash->{KNXIOhelper}->{CNTRTO};
KNXIO_Log ($name, 3, 'timeout - retry'); $cntrTO++;
KNXIO_Log ($name, 3, qq{timeout - retry $cntrTO});
return KNXIO_keepAlive($hash); return KNXIO_keepAlive($hash,$cntrTO) if ($cntrTO < 3);
KNXIO_disconnect($hash); # nr of timeouts exceeded
return;
} }
### TO hit while sending... ### TO hit while sending...
@ -1129,14 +1140,17 @@ sub KNXIO_TunnelRequestTO {
### prependes device, subroutine, linenr. to Log msg ### prependes device, subroutine, linenr. to Log msg
### return undef ### return undef
sub KNXIO_Log { sub KNXIO_Log {
my $dev = shift || 'global'; my $dev = shift // 'global';
my $loglvl = shift; my $loglvl = shift // 5;
my $logtxt = shift; my $logtxt = shift;
my $sub = (caller(1))[3] || 'main'; my $name = ( ref($dev) eq 'HASH' ) ? $dev->{NAME} : $dev;
my $dloglvl = AttrVal($name,'verbose',undef) // AttrVal('global','verbose',3);
return if ($loglvl > $dloglvl); # shortcut performance
my $sub = (caller(1))[3] // 'main';
my $line = (caller(0))[2]; my $line = (caller(0))[2];
my $name = ( ref($dev) eq "HASH" ) ? $dev->{NAME} : $dev; $sub =~ s/^.+[:]+//xms;
$sub =~ s/.+[:]+//xms;
Log3 ($name, $loglvl, qq{$name [$sub $line]: $logtxt}); Log3 ($name, $loglvl, qq{$name [$sub $line]: $logtxt});
return; return;

View File

@ -122,7 +122,9 @@
# MH 20230328 syntax check on attr stateregex # MH 20230328 syntax check on attr stateregex
# reminder to migrate to KNXIO # reminder to migrate to KNXIO
# announce answerreading as deprecated # announce answerreading as deprecated
# replace cascading if..elsif with given # MH 202304xx implement KNX_Log
# simplyfy checkAndClean-sub
# todo replace cascading if..elsif with given
package KNX; ## no critic 'package' package KNX; ## no critic 'package'
@ -457,22 +459,20 @@ sub KNX_Define {
$svnid =~ s/.*\.pm\s(.+)Z.*/$1/ixms; $svnid =~ s/.*\.pm\s(.+)Z.*/$1/ixms;
$hash->{'.SVN'} = $svnid; # store svn info in dev hash $hash->{'.SVN'} = $svnid; # store svn info in dev hash
my $logtxt = qq{KNX_define ($name): }; # leading txt KNX_Log ($name, 5, join (q{ }, @a));
Log3 ($name, 5, $logtxt . join (q{ }, @a));
#too less arguments or no valid 1st gad #too less arguments or no valid 1st gad
return ($logtxt . q{wrong syntax or wrong group-format (0-31/0-7/0-255)} . return (qq{KNX_define: wrong syntax or wrong group-format (0-31/0-7/0-255)\n} .
qq{\n} . q{ "define <name> KNX <group:model[:GAD-name][:set|get|listenonly][:nosuffix]> } . qq{ "define $name KNX <group:model[:GAD-name][:set|get|listenonly][:nosuffix]> } .
q{[<group:model[:GAD-name][:set|get|listenonly][:nosuffix]>]"}) if (int(@a) < 3 || $a[2] !~ m/^(?:$PAT_GAD|$PAT_GAD_HEX)/ixms); q{[<group:model[:GAD-name][:set|get|listenonly][:nosuffix]>]"}) if (int(@a) < 3 || $a[2] !~ m/^(?:$PAT_GAD|$PAT_GAD_HEX)/ixms);
# check if the last arg matches any IO-Device - and discard it ! # check if the last arg matches any IO-Device - and discard it !
if ( $a[int(@a) - 1] !~ m/^(?:$PAT_GAD|$PAT_GAD_HEX)/ixms ) { if ( $a[int(@a) - 1] !~ m/^(?:$PAT_GAD|$PAT_GAD_HEX)/ixms ) {
my $iodevCandidate = pop(@a); # remove from array, but do nothing with it! my $iodevCandidate = pop(@a); # remove from array, but do nothing with it!
my $logtxtIO = qq{$logtxt specifying IODev $iodevCandidate is deprecated in define } . my $logtxtIO = qq{specifying IODev $iodevCandidate is deprecated in define } .
qq{- use "attr $name IODev $iodevCandidate"}; qq{- use "attr $name IODev $iodevCandidate"};
Log3 ($name, 2, $logtxtIO); KNX_Log ($name, 2, $logtxtIO);
return $logtxtIO if ($init_done); # allow durin start return qq{KNX_define $name: $logtxtIO} if ($init_done); # allow durin start
} }
$hash->{'.DEFLINE'} = join(q{ },@a); # temp store defs for define2... $hash->{'.DEFLINE'} = join(q{ },@a); # temp store defs for define2...
@ -490,8 +490,6 @@ sub KNX_Define2 {
my @a = split(/\s+/xms, $def); my @a = split(/\s+/xms, $def);
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
my $logtxt = qq{KNX_define2 ($name): }; # leading txt
# Add pulldown for attr IODev # Add pulldown for attr IODev
my $attrList = $modules{KNX}->{AttrList}; #get Attrlist from Module def my $attrList = $modules{KNX}->{AttrList}; #get Attrlist from Module def
my $IODevs = KNX_chkIODev($hash); # get list of valid IO's my $IODevs = KNX_chkIODev($hash); # get list of valid IO's
@ -516,24 +514,24 @@ sub KNX_Define2 {
my $gadNo = $i - 1; my $gadNo = $i - 1;
my $gadName = 'g' . $gadNo; # old syntax my $gadName = 'g' . $gadNo; # old syntax
Log3 ($name, 5, $logtxt . qq{gadNr= $gadNo def-string= $a[$i]}); KNX_Log ($name, 5, qq{gadNr= $gadNo def-string= $a[$i]});
my ($gad, $gadModel, @gadArgs) = split(/:/xms, $a[$i]); my ($gad, $gadModel, @gadArgs) = split(/:/xms, $a[$i]);
$gadCode = $gad // return $logtxt . qq{GAD not defined for group-number $gadNo}; $gadCode = $gad // return qq{KNX_define2: GAD not defined for group-number $gadNo};
return ($logtxt . qq{wrong GA format in group-number $gadNo} . return (qq{KNX_define2 $name: wrong GA format in group-number $gadNo} .
': specify as 0-31/0-7/0-255 or as hex-notation') if ($gad !~ m/^(?:$PAT_GAD|$PAT_GAD_HEX)$/ixms); ': specify as 0-31/0-7/0-255 or as hex-notation') if ($gad !~ m/^(?:$PAT_GAD|$PAT_GAD_HEX)$/ixms);
$gad = KNX_hexToName ($gad) if ($gad =~ m/^$PAT_GAD_HEX$/ixms); $gad = KNX_hexToName ($gad) if ($gad =~ m/^$PAT_GAD_HEX$/ixms);
$gadCode = KNX_nameToHex ($gad); #convert it vice-versa, just to be sure $gadCode = KNX_nameToHex ($gad); #convert it vice-versa, just to be sure
return ($logtxt . qq{no model defined for group-number $gadNo}) if(! defined($gadModel)); return (qq{KNX_define2 $name: no model defined for group-number $gadNo}) if(! defined($gadModel));
if ($gadModel eq $MODELERR) { #within autocreate no model is supplied - throw warning if ($gadModel eq $MODELERR) { #within autocreate no model is supplied - throw warning
Log3 ($name, 3, $logtxt . 'autocreate device will be disabled, correct def with valid dpt and enable device'); KNX_Log ($name, 3, q{autocreate device will be disabled, correct def with valid dpt and enable device});
$attr{$name}->{disable} = 1 if (AttrVal($name,'disable',0) != 1); $attr{$name}->{disable} = 1 if (AttrVal($name,'disable',0) != 1);
} }
elsif (!defined($dpttypes{$gadModel})) { #check model-type elsif (!defined($dpttypes{$gadModel})) { #check model-type
return $logtxt . qq{invalid model: $gadModel for group-number $gadNo} . return qq{KNX_define2 $name: invalid model $gadModel for group-number $gadNo} .
'. Please consult commandref - avaliable DPT for correct model definition.'; '. Please consult commandref - avaliable DPT for correct model definition.';
} }
elsif ($gadNo == 1) { # gadModel ok elsif ($gadNo == 1) { # gadModel ok
@ -542,12 +540,12 @@ sub KNX_Define2 {
if (scalar(@gadArgs)) { if (scalar(@gadArgs)) {
$gadNoSuffix = pop(@gadArgs) if ($gadArgs[-1] =~ /$PAT_GAD_SUFFIX/ixms); $gadNoSuffix = pop(@gadArgs) if ($gadArgs[-1] =~ /$PAT_GAD_SUFFIX/ixms);
$gadOption = pop(@gadArgs) if (@gadArgs && $gadArgs[-1] =~ /^($PAT_GAD_OPTIONS)$/ixms); $gadOption = pop(@gadArgs) if ($gadArgs[-1] =~ /^($PAT_GAD_OPTIONS)$/ixms);
$gadName = pop(@gadArgs) if (@gadArgs); $gadName = pop(@gadArgs) if (@gadArgs);
return $logtxt . qq{forbidden gad-name: $gadName} if ($gadName =~ /$PAT_GAD_NONAME$/ixms); return qq{KNX_define2 $name: forbidden gad-name $gadName} if ($gadName =~ /$PAT_GAD_NONAME$/ixms);
return ($logtxt . qq{invalid option for group-number $gadNo. Use one of: $PAT_GAD_OPTIONS}) if (defined($gadOption) && ($gadOption !~ m/^(?:$PAT_GAD_OPTIONS)$/ixms)); # return qq{KNX_define2 $name: invalid option for group-number $gadNo. Use one of: $PAT_GAD_OPTIONS} if (defined($gadOption) && ($gadOption !~ m/^(?:$PAT_GAD_OPTIONS)$/ixms));
return ($logtxt . qq{invalid suffix for group-number $gadNo. Use $PAT_GAD_SUFFIX}) if (defined($gadNoSuffix) && ($gadNoSuffix !~ m/$PAT_GAD_SUFFIX/ixms)); # return qq{KNX_define2 $name: invalid suffix for group-number $gadNo. Use $PAT_GAD_SUFFIX} if (defined($gadNoSuffix) && ($gadNoSuffix !~ m/$PAT_GAD_SUFFIX/ixms));
} }
###GADTABLE ###GADTABLE
@ -559,7 +557,7 @@ sub KNX_Define2 {
$hash->{GADTABLE} = $tableHashRef; $hash->{GADTABLE} = $tableHashRef;
} }
return ($logtxt . qq{GAD $gad may be supplied only once per device.}) if (defined($hash->{GADTABLE}->{$gadCode})); return qq{KNX_define2 $name: GAD $gad may be supplied only once per device.} if (defined($hash->{GADTABLE}->{$gadCode}));
$hash->{GADTABLE}->{$gadCode} = $gadName; #add key and value to GADTABLE $hash->{GADTABLE}->{$gadCode} = $gadName; #add key and value to GADTABLE
@ -577,9 +575,8 @@ sub KNX_Define2 {
$rdNamePut = 'putG' . $gadNo; $rdNamePut = 'putG' . $gadNo;
} }
my $log = $logtxt . qq{found GAD: $gad NAME: $gadName NO: $gadNo HEX: $gadCode DPT: $gadModel}; KNX_Log ($name, 5, qq{found GAD: $gad NAME: $gadName NO: $gadNo HEX: $gadCode DPT: $gadModel} .
$log .= qq{ OPTION: $gadOption} if (defined ($gadOption)); qq{ OPTION: $gadOption}) if (defined ($gadOption));
Log3 ($name, 5, $log);
#determine dpt-details #determine dpt-details
my $dptDetails = $dpttypes{$gadModel}; my $dptDetails = $dpttypes{$gadModel};
@ -600,8 +597,8 @@ sub KNX_Define2 {
$setlist = q{:} . $min . q{,} . $max; $setlist = q{:} . $min . q{,} . $max;
} }
Log3 ($name, 5, $logtxt . qq{Estimated reading-names: $rdNameSet , $rdNameGet , $rdNamePut}); KNX_Log ($name, 5, qq{Estimated reading-names: $rdNameSet , $rdNameGet , $rdNamePut} .
Log3 ($name, 5, $logtxt . qq{SetList: $setlist}) if (defined ($setlist)); qq{SetList: $setlist}) if (defined ($setlist));
#add details to hash #add details to hash
$hash->{GADDETAILS}->{$gadName} = {GROUP => $gad, CODE => $gadCode, MODEL => $gadModel, NO => $gadNo, OPTION => $gadOption, $hash->{GADDETAILS}->{$gadName} = {GROUP => $gad, CODE => $gadCode, MODEL => $gadModel, NO => $gadNo, OPTION => $gadOption,
@ -628,10 +625,10 @@ sub KNX_Define2 {
} }
$hash->{'.SETSTRING'} = $setString; $hash->{'.SETSTRING'} = $setString;
Log3 ($name, 5, qq{$logtxt setstring= $hash->{'.SETSTRING'}}); KNX_Log ($name, 5, qq{setstring= $hash->{'.SETSTRING'}});
} }
Log3 ($name, 5, $logtxt . 'define complete'); KNX_Log ($name, 5, q{define complete});
return; return;
} }
@ -642,8 +639,6 @@ sub KNX_Undef {
my $hash = shift; my $hash = shift;
my $name = shift; my $name = shift;
Log3 ($name, 5, qq{KNX_undef ($name): enter});
#delete all defptr entries for this device #delete all defptr entries for this device
KNX_delete_defptr($hash); # verify with: {PrintHash($modules{KNX}->{defptr},3) } on FHEM-cmdline KNX_delete_defptr($hash); # verify with: {PrintHash($modules{KNX}->{defptr},3) } on FHEM-cmdline
return; return;
@ -658,7 +653,7 @@ sub KNX_Get {
my $gadName = shift // KNX_gadNameByNO($hash,1); # use first defined GAD if no argument is supplied my $gadName = shift // KNX_gadNameByNO($hash,1); # use first defined GAD if no argument is supplied
return qq{KNX_Get ($name): gadName not defined} if (! defined($gadName)); return qq{KNX_Get ($name): gadName not defined} if (! defined($gadName));
Log3 ($name, 3, qq{KNX_Get ($name): too much arguments. Only one argument allowed (gadName). Other Arguments are discarded.}) if (defined(shift)); KNX_Log ($name, 3, q{too much arguments. Only one argument allowed (gadName). Other Arguments are discarded.}) if (defined(shift));
#FHEM asks with a ? at startup - no action, no log - if dev is disabled: no SET/GET pulldown ! #FHEM asks with a ? at startup - no action, no log - if dev is disabled: no SET/GET pulldown !
if ($gadName =~ m/\?/xms) { if ($gadName =~ m/\?/xms) {
@ -676,7 +671,7 @@ sub KNX_Get {
} }
return qq{KNX_Get ($name): is disabled} if (IsDisabled($name) == 1); return qq{KNX_Get ($name): is disabled} if (IsDisabled($name) == 1);
Log3 ($name, 5, qq{KNX_Get ($name): -enter: CMD= $gadName}); KNX_Log ($name, 5, qq{enter: CMD= $gadName});
#return, if unknown group #return, if unknown group
return qq{KNX_Get ($name): invalid gadName: $gadName} if(! exists($hash->{GADDETAILS}->{$gadName})); return qq{KNX_Get ($name): invalid gadName: $gadName} if(! exists($hash->{GADDETAILS}->{$gadName}));
@ -688,7 +683,7 @@ sub KNX_Get {
#exit if get is prohibited #exit if get is prohibited
return qq{KNX_Get ($name): did not request a value - "set" or "listenonly" option is defined.} if (defined ($option) and ($option =~ m/(set|listenonly)/ixms)); return qq{KNX_Get ($name): did not request a value - "set" or "listenonly" option is defined.} if (defined ($option) and ($option =~ m/(set|listenonly)/ixms));
Log3 ($name, 5, qq{KNX_Get ($name): request value for GAD: $group GAD-NAME: $gadName}); KNX_Log ($name, 5, qq{request value for GAD: $group GAD-NAME: $gadName});
IOWrite($hash, $TULid, 'r' . $groupc); #send read-request to the bus IOWrite($hash, $TULid, 'r' . $groupc); #send read-request to the bus
@ -702,10 +697,6 @@ sub KNX_Get {
sub KNX_Set { sub KNX_Set {
my ($hash, $name, $targetGadName, @arg) = @_; my ($hash, $name, $targetGadName, @arg) = @_;
my @ca = caller(0); #identify this sub
my $thisSub = $ca[3] =~ s/.+[:]+//grxms;
$thisSub .= qq{ ($name): };
#FHEM asks with a "?" at startup or any reload of the device-detail-view - if dev is disabled: no SET/GET pulldown ! #FHEM asks with a "?" at startup or any reload of the device-detail-view - if dev is disabled: no SET/GET pulldown !
if(defined($targetGadName) && ($targetGadName =~ m/\?/xms)) { if(defined($targetGadName) && ($targetGadName =~ m/\?/xms)) {
my $setter = exists($hash->{'.SETSTRING'})?$hash->{'.SETSTRING'}:q{}; my $setter = exists($hash->{'.SETSTRING'})?$hash->{'.SETSTRING'}:q{};
@ -713,24 +704,24 @@ sub KNX_Set {
return qq{unknown argument $targetGadName choose one of $setter}; return qq{unknown argument $targetGadName choose one of $setter};
} }
return $thisSub . 'is disabled' if (IsDisabled($name) == 1); return qq{$name is disabled} if (IsDisabled($name) == 1);
return $thisSub . 'no parameter(s) specified for set cmd' if((!defined($targetGadName)) || ($targetGadName eq q{})); #return, if no cmd specified return qq{$name no parameter(s) specified for set cmd} if((!defined($targetGadName)) || ($targetGadName eq q{})); #return, if no cmd specified
Log3 ($name, 5, $thisSub . qq{-enter: $targetGadName } . join(q{ }, @arg)); KNX_Log ($name, 5, qq{enter: $targetGadName } . join(q{ }, @arg));
$targetGadName =~ s/^\s+|\s+$//gxms; # gad-name or cmd (in old syntax) $targetGadName =~ s/^\s+|\s+$//gxms; # gad-name or cmd (in old syntax)
my $cmd = undef; my $cmd = undef;
if (defined ($hash->{GADDETAILS}->{$targetGadName})) { # #new syntax, if first arg is a valid gadName if (defined ($hash->{GADDETAILS}->{$targetGadName})) { #new syntax, if first arg is a valid gadName
$cmd = shift(@arg); #shift args as with newsyntax $arg[0] is cmd $cmd = shift(@arg); #shift args as with newsyntax $arg[0] is cmd
return $thisSub . 'no cmd found' if(!defined($cmd)); return qq{$name no cmd found} if(!defined($cmd));
} }
else { # process old syntax targetGadName contains command! else { # process old syntax targetGadName contains command!
(my $err, $targetGadName, $cmd) = KNX_Set_oldsyntax($hash,$targetGadName,@arg); (my $err, $targetGadName, $cmd) = KNX_Set_oldsyntax($hash,$targetGadName,@arg);
return $thisSub . $err if defined($err); return qq{$name $err} if defined($err);
} }
Log3 ($name, 5, $thisSub . qq{desired target is gad: $targetGadName , command: $cmd , args: } . join (q{ }, @arg)); KNX_Log ($name, 5, qq{desired target is gad: $targetGadName , command: $cmd , args: } . join (q{ }, @arg));
#get details #get details
my $groupCode = $hash->{GADDETAILS}->{$targetGadName}->{CODE}; my $groupCode = $hash->{GADDETAILS}->{$targetGadName}->{CODE};
@ -738,7 +729,7 @@ sub KNX_Set {
my $rdName = $hash->{GADDETAILS}->{$targetGadName}->{RDNAMESET}; my $rdName = $hash->{GADDETAILS}->{$targetGadName}->{RDNAMESET};
my $model = $hash->{GADDETAILS}->{$targetGadName}->{MODEL}; my $model = $hash->{GADDETAILS}->{$targetGadName}->{MODEL};
return $thisSub . q{did not set a value - "get" or "listenonly" option is defined.} if (defined ($option) and ($option =~ m/(?:get|listenonly)/ixms)); return $name . q{ did not set a value - "get" or "listenonly" option is defined.} if (defined ($option) and ($option =~ m/(?:get|listenonly)/ixms));
my $value = $cmd; #process set command with $value as output my $value = $cmd; #process set command with $value as output
#Text neads special treatment - additional args may be blanked words #Text neads special treatment - additional args may be blanked words
@ -753,14 +744,14 @@ sub KNX_Set {
} }
my $transval = KNX_checkAndClean($hash, $value, $targetGadName); #check and cast value my $transval = KNX_checkAndClean($hash, $value, $targetGadName); #check and cast value
return $thisSub . qq{invalid value= $value} if (!defined($transval)); #if cast not successful return $name . qq{ set invalid value= $value} if (!defined($transval)); #cast not successful
my $transvale = KNX_encodeByDpt($hash, $transval, $targetGadName); #process set command my $transvale = KNX_encodeByDpt($hash, $transval, $targetGadName); #process set command
return $thisSub . 'failed - no set cmd allowed for this dpt' if (!defined($transvale)); # encodeByDpt failed return $name . ' set failed - no set cmd allowed for this dpt' if (!defined($transvale)); # encodeByDpt failed
IOWrite($hash, $TULid, 'w' . $groupCode . $transvale); IOWrite($hash, $TULid, 'w' . $groupCode . $transvale);
Log3 ($name, 4, $thisSub . qq{cmd= $cmd , value= $value , translated= $transvale}); KNX_Log ($name, 4, qq{cmd= $cmd , value= $value , translated= $transvale});
# decode again for values that have been changed in encode process # decode again for values that have been changed in encode process
$transval = KNX_decodeByDpt($hash, $transvale, $targetGadName) if ($model =~ m/^(?:dpt3|dpt10|dpt11|dpt19)/ixms); $transval = KNX_decodeByDpt($hash, $transvale, $targetGadName) if ($model =~ m/^(?:dpt3|dpt10|dpt11|dpt19)/ixms);
@ -768,7 +759,7 @@ sub KNX_Set {
#apply post processing for state and set all readings #apply post processing for state and set all readings
KNX_SetReadings($hash, $targetGadName, $transval, $rdName, undef); KNX_SetReadings($hash, $targetGadName, $transval, $rdName, undef);
Log3 ($name, 5, $thisSub . '-exit'); KNX_Log ($name, 5, 'exit');
return; return;
} }
@ -786,14 +777,14 @@ sub KNX_Set_oldsyntax {
#select another group, if the last arg starts with a g #select another group, if the last arg starts with a g
if($na >= 1 && $arg[$na - 1] =~ m/$PAT_GNO/ixms) { if($na >= 1 && $arg[$na - 1] =~ m/$PAT_GNO/ixms) {
$groupnr = pop (@arg); $groupnr = pop (@arg);
Log3 ($name, 3, qq{KNX_Set_syntax2 ($name): you are still using old syntax, pls. change to "set $name $groupnr $cmd } . join(q{ },@arg) . q{"}); KNX_Log ($name, 3, qq{you are still using old syntax, pls. change to "set $name $groupnr $cmd } . join(q{ },@arg) . q{"});
$groupnr =~ s/^g//gixms; #remove "g" $groupnr =~ s/^g//gixms; #remove "g"
} }
# if cmd contains g1: the check for valid gadnames failed ! # if cmd contains g1: the check for valid gadnames failed !
# this is NOT oldsyntax, but a user-error! # this is NOT oldsyntax, but a user-error!
if ($cmd =~ /^g[\d]/ixms) { if ($cmd =~ /^g[\d]/ixms) {
Log3 ($name, 2, qq{KNX_Set_syntax2 ($name): an invalid gadName: $cmd was used in set-cmd}); KNX_Log ($name, 2, qq{an invalid gadName: $cmd was used in set-cmd});
return qq{an invalid gadName: $cmd was used in set-cmd}; return qq{an invalid gadName: $cmd was used in set-cmd};
} }
@ -860,7 +851,7 @@ sub KNX_Set_dpt1 {
if ($cmd =~ m/(?:(on|off)-for-timer)$/ixms) { if ($cmd =~ m/(?:(on|off)-for-timer)$/ixms) {
#get duration #get duration
my $duration = sprintf('%02d:%02d:%02d', $arg[0]/3600, ($arg[0]%3600)/60, $arg[0]%60); my $duration = sprintf('%02d:%02d:%02d', $arg[0]/3600, ($arg[0]%3600)/60, $arg[0]%60);
Log3 ($name, 5, qq{KNX_Set_dpt1 ($name): $cmd $duration}); KNX_Log ($name, 5, qq{cmd: $cmd ts: $duration});
$hash->{".TIMER_$groupCode"} = $duration; #create local marker $hash->{".TIMER_$groupCode"} = $duration; #create local marker
#place at-command for switching on / off #place at-command for switching on / off
@ -874,7 +865,7 @@ sub KNX_Set_dpt1 {
#do like (on|off)-until-overnight in at cmd ! #do like (on|off)-until-overnight in at cmd !
my $hms_til = sprintf('%02d:%02d:%02d', $hr, $min, $sec); my $hms_til = sprintf('%02d:%02d:%02d', $hr, $min, $sec);
Log3 ($name, 5, qq{KNX_Set_dpt1 ($name): $cmd $hms_til}); KNX_Log ($name, 5, qq{cmd: $cmd ts: $hms_til});
$hash->{".TIMER_$groupCode"} = $hms_til; #create local marker $hash->{".TIMER_$groupCode"} = $hms_til; #create local marker
#place at-command for switching on / off #place at-command for switching on / off
@ -898,7 +889,7 @@ sub KNX_Set_dpt1 {
} }
} }
Log3 ($name, 3, qq{KNX_Set_dpt1 ($name): current value for "set $name $targetGadName TOGGLE" is not "on" or "off" - } . KNX_Log ($name, 3, qq{current value for "set $name $targetGadName TOGGLE" is not "on" or "off" - } .
qq{$targetGadName will be switched off}) if ($toggleOldVal !~ /^(?:on|off)/ixms); qq{$targetGadName will be switched off}) if ($toggleOldVal !~ /^(?:on|off)/ixms);
$value = q{on} if ($toggleOldVal =~ m/^off/ixms); # value off is default $value = q{on} if ($toggleOldVal =~ m/^off/ixms); # value off is default
} }
@ -931,11 +922,23 @@ sub KNX_Attr {
my $value = undef; my $value = undef;
if ($cmd eq 'set' && $aName =~ m/(listenonly|readonly|slider)/ixms) { if ($cmd eq 'set' && $aName =~ m/(listenonly|readonly|slider)/ixms) {
KNX_Log ($name, 3, qq{Attribute "$aName" is not supported/have no function at all, pls. check cmdref for equivalent function.});
return qq{KNX_Attr ($name): Attribute "$aName" is not supported/have no function at all, pls. check cmdref for equivalent function.}; return qq{KNX_Attr ($name): Attribute "$aName" is not supported/have no function at all, pls. check cmdref for equivalent function.};
} }
if ($cmd eq 'set' && $aName eq 'answerReading') { # deprecate announcement if ($cmd eq 'set' && $aName eq 'answerReading') { # deprecate announcement
Log3 ($name, 3, qq{KNX_Attr ($name): Attribute "$aName" will be deprecated soon, consider using Attr "putCmd" instead}); if (defined(AttrVal($name,'putCmd',undef))) {
delete ($attr{$name}{$aName});
return qq{Attribute $aName will be deleted now! It has no function while Attr. "putCmd" is defined. };
} else {
KNX_Log ($name, 3, qq{Attribute "$aName" will be deprecated soon, consider using Attr "putCmd" instead});
=pod
# set attr putCmd
KNX_Log ($name, 3, qq{Attribute "$aName" is deprecated, Attr. is converted to "putCmd"}).
@_[2] = q{putCmd};
@_[3] = '{return $state;}';
=cut
}
} }
if ($cmd eq 'set' && $init_done) { if ($cmd eq 'set' && $init_done) {
@ -975,7 +978,7 @@ sub KNX_Attr {
next if ($def eq ReadingsVal($name,'IODev',undef)); # deprecated IOdev next if ($def eq ReadingsVal($name,'IODev',undef)); # deprecated IOdev
next if ($def =~ /:dpt\d+/ixms); next if ($def =~ /:dpt\d+/ixms);
Log3 ($name, 2, qq{KNX_Attr ($name): Attribut "disable" cannot be deleted for this device until you specify a valid dpt!}); KNX_Log ($name, 2, q{Attribut "disable" cannot be deleted for this device until you specify a valid dpt!});
return qq{Attribut "disable" cannot be deleted for device $name until you specify a valid dpt!}; return qq{Attribut "disable" cannot be deleted for device $name until you specify a valid dpt!};
} }
delete $hash->{RAWMSG}; # debug internal delete $hash->{RAWMSG}; # debug internal
@ -1009,7 +1012,7 @@ sub KNX_DbLog_split {
my $value = join(q{ },@strings); my $value = join(q{ },@strings);
$unit = q{} if (!defined($unit)); $unit = q{} if (!defined($unit));
Log3 ($device, 5, qq{KNX_DbLog_Split ($device): EVENT= $event READING= $reading VALUE= $value UNIT= $unit}); KNX_Log ($device, 5, qq{EVENT= $event READING= $reading VALUE= $value UNIT= $unit});
return ($reading, $value, $unit); return ($reading, $value, $unit);
} }
@ -1054,7 +1057,7 @@ sub KNX_Parse {
next; next;
} }
Log3 ($deviceName, 4, qq{KNX_Parse ($deviceName): -process gadName=$gadName cmd= $cmd}); KNX_Log ($deviceName, 4, qq{process gadName=$gadName cmd=$cmd});
#handle write and reply messages #handle write and reply messages
if ($cmd =~ /[w|p]/ixms) { if ($cmd =~ /[w|p]/ixms) {
@ -1063,11 +1066,10 @@ sub KNX_Parse {
my $transval = KNX_decodeByDpt ($deviceHash, $val, $gadName); my $transval = KNX_decodeByDpt ($deviceHash, $val, $gadName);
#message invalid #message invalid
if (! defined($transval) || ($transval eq q{})) { if (! defined($transval) || ($transval eq q{})) {
Log3 ($deviceName, 2, qq{KNX_Parse_wp ($deviceName): readingName=$getName message=$msg} . KNX_Log ($deviceName, 2, qq{readingName=$getName message=$msg could not be decoded} );
' could not be decoded');
next; next;
} }
Log3 ($deviceName, 4, qq{KNX_Parse_wp ($deviceName): readingName=$getName value=$transval}); KNX_Log ($deviceName, 4, qq{readingName=$getName value=$transval});
#apply post processing for state and set all readings #apply post processing for state and set all readings
KNX_SetReadings($deviceHash, $gadName, $transval, $getName, $src); KNX_SetReadings($deviceHash, $gadName, $transval, $getName, $src);
@ -1084,11 +1086,11 @@ sub KNX_Parse {
$value = ReadingsVal($deviceName, 'state', undef); #default - prepare for removal of answerreading $value = ReadingsVal($deviceName, 'state', undef); #default - prepare for removal of answerreading
$value = KNX_eval ($deviceHash, $gadName, $value, $cmdAttr); $value = KNX_eval ($deviceHash, $gadName, $value, $cmdAttr);
if (defined($value) && ($value ne q{}) && ($value ne 'ERROR')) { # answer only, if eval was successful if (defined($value) && ($value ne q{}) && ($value ne 'ERROR')) { # answer only, if eval was successful
Log3 ($deviceName, 5, qq{KNX_Parse_r ($deviceName): replaced by Attr putCmd=$cmdAttr VALUE=$value}); KNX_Log ($deviceName, 5, qq{replaced by Attr putCmd=$cmdAttr VALUE=$value});
readingsSingleUpdate($deviceHash, $putName, $value,0); readingsSingleUpdate($deviceHash, $putName, $value,0);
} }
else { else {
Log3 ($deviceName, 5, qq{KNX_Parse_r ($deviceName): gadName=$gadName - no reply sent!}); KNX_Log ($deviceName, 5, qq{gadName=$gadName - no reply sent!});
$value = undef; # dont send ! $value = undef; # dont send !
} }
} }
@ -1106,7 +1108,7 @@ sub KNX_Parse {
#send transval #send transval
if (defined($value)) { if (defined($value)) {
my $transval = KNX_encodeByDpt($deviceHash, $value, $gadName); my $transval = KNX_encodeByDpt($deviceHash, $value, $gadName);
Log3 ($deviceName, 4, qq{KNX_Parse_r ($deviceName): send answer: reading=$gadName VALUE=$transval}); KNX_Log ($deviceName, 4, qq{send answer: reading=$gadName VALUE=$transval});
IOWrite ($deviceHash, $TULid, 'p' . $gadCode . $transval); IOWrite ($deviceHash, $TULid, 'p' . $gadCode . $transval);
} }
} }
@ -1175,10 +1177,10 @@ sub KNX_SetReadings {
my $newstate = KNX_eval ($hash, $gadName, $state, $cmdAttr); my $newstate = KNX_eval ($hash, $gadName, $state, $cmdAttr);
if (defined($newstate) && ($newstate ne q{}) && ($newstate !~ m/ERROR/ixms)) { if (defined($newstate) && ($newstate ne q{}) && ($newstate !~ m/ERROR/ixms)) {
$state = $newstate; $state = $newstate;
Log3 ($name, 5, qq{KNX_SetReadings ($name): state replaced via stateCmd $cmdAttr - state: $state}); KNX_Log ($name, 5, qq{state replaced via stateCmd $cmdAttr - state: $state});
} }
else { else {
Log3 ($name, 3, qq{KNX_SetReadings ($name): GAD $gadName , error during stateCmd processing}); KNX_Log ($name, 3, qq{GAD $gadName , error during stateCmd processing});
} }
} }
readingsBulkUpdate($hash, 'state', $state); readingsBulkUpdate($hash, 'state', $state);
@ -1280,8 +1282,9 @@ sub KNX_nameToHex {
sub KNX_checkAndClean { sub KNX_checkAndClean {
my ($hash, $value, $gadName) = @_; my ($hash, $value, $gadName) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $orgValue = $value;
$value =~ s/^\s+|\s+$//gixms; #trim whitespaces at begin & end
# my $orgValue = $value;
my $model = $hash->{GADDETAILS}->{$gadName}->{MODEL}; my $model = $hash->{GADDETAILS}->{$gadName}->{MODEL};
#return unchecked, if this is a autocreate-device #return unchecked, if this is a autocreate-device
@ -1289,13 +1292,13 @@ sub KNX_checkAndClean {
my $pattern = $dpttypes{$model}->{PATTERN}; my $pattern = $dpttypes{$model}->{PATTERN};
#trim whitespaces at begin & end
$value =~ s/^\s+|\s+$//gixms;
#new code: match against model pattern -to be tested!!! #new code: match against model pattern -to be tested!!!
# my $pattern = $dpttypes{$model}->{PATTERN}); my $found = () = $value =~ /^$pattern$/ixms;
# return if ($value !~ m/$pattern/ix); if ($found == 0) {
KNX_Log ($name, 2, qq{gadName= $gadName value= $value does not match allowed values});
return;
}
=pod
my @tmp = ($value =~ m/$pattern/gixms); my @tmp = ($value =~ m/$pattern/gixms);
#loop through results #loop through results
my $found = 0; my $found = 0;
@ -1309,8 +1312,9 @@ sub KNX_checkAndClean {
} }
return if ($found == 0); return if ($found == 0);
Log3 ($name, 3, qq{KNX_checkAndClean ($name): gadName= $gadName value= $orgValue was casted to $value}) if ($orgValue ne $value); KNX_Log ($name, 3, qq{gadName= $gadName value= $orgValue was casted to $value}) if ($orgValue ne $value);
Log3 ($name, 5, qq{KNX_checkAndClean ($name): gadName= $gadName value= $value model= $model pattern= $pattern}); =cut
KNX_Log ($name, 5, qq{gadName= $gadName value= $value model= $model pattern= $pattern});
return $value; return $value;
} }
@ -1358,7 +1362,7 @@ sub KNX_replaceByRegex {
} }
last; last;
} }
Log3 ($name, 5, qq{KNX_replaceByRegex ($name): replaced $rdName value from: $input to $retVal}) if ($input ne $retVal); KNX_Log ($name, 5, qq{replaced $rdName value from: $input to $retVal}) if ($input ne $retVal);
return ($retVal eq 'undefined')?undef:$retVal; return ($retVal eq 'undefined')?undef:$retVal;
} }
@ -1400,7 +1404,7 @@ sub KNX_limit {
$retVal = $max if (defined ($max) && ($retVal > $max)); $retVal = $max if (defined ($max) && ($retVal > $max));
} }
Log3 ($name, 5, qq{KNX_limit ($name): DIR= $direction INPUT= $value OUTPUT= $retVal}); KNX_Log ($name, 5, qq{DIR= $direction INPUT= $value OUTPUT= $retVal});
return $retVal; return $retVal;
} }
@ -1416,7 +1420,7 @@ sub KNX_eval {
$retVal = 'ERROR' if (not defined ($retVal)); $retVal = 'ERROR' if (not defined ($retVal));
if ($retVal =~ /(^Forbidden|error)/ixms) { # eval error or forbidden by Authorize if ($retVal =~ /(^Forbidden|error)/ixms) { # eval error or forbidden by Authorize
Log3 ($name, 2, qq{KNX_Eval-error ($name): gadName= $gadName evalString= $evalString result= $retVal}); KNX_Log ($name, 2, qq{eval-error: gadName= $gadName evalString= $evalString result= $retVal});
$retVal = 'ERROR'; $retVal = 'ERROR';
} }
return $retVal; return $retVal;
@ -1436,16 +1440,16 @@ sub KNX_encodeByDpt {
return if ($model eq $MODELERR); #return unchecked, if this is a autocreate-device return if ($model eq $MODELERR); #return unchecked, if this is a autocreate-device
my $lvalue = KNX_limit ($hash, $value, $model, 'ENCODE'); my $lvalue = KNX_limit ($hash, $value, $model, 'ENCODE');
Log3 ($name, 4, qq{KNX_limit ($name): gadName= $gadName modified... Input= $value Output= $lvalue Model= $model}) if ($value ne $lvalue); KNX_Log ($name, 4, qq{gadName= $gadName modified... Input= $value Output= $lvalue Model= $model}) if ($value ne $lvalue);
if (ref($dpttypes{$code}->{ENC}) eq 'CODE') { if (ref($dpttypes{$code}->{ENC}) eq 'CODE') {
my $hexval = $dpttypes{$code}->{ENC}->($lvalue, $model); my $hexval = $dpttypes{$code}->{ENC}->($lvalue, $model);
Log3 ($name, 5, qq{KNX_encodeByDpt ($name): gadName= $gadName model= $model code= $code } . KNX_Log ($name, 5, qq{gadName= $gadName model= $model code= $code } .
qq{in-Value= $value out-value= $lvalue out-hexval= $hexval}); qq{in-Value= $value out-value= $lvalue out-hexval= $hexval});
return $hexval; return $hexval;
} }
else { else {
Log3 ($name, 2, qq{KNX_encodeByDpt ($name): gadName= $gadName model= $model not valid}); KNX_Log ($name, 2, qq{gadName= $gadName model= $model not valid});
} }
return; return;
} }
@ -1465,12 +1469,12 @@ sub KNX_decodeByDpt {
if (ref($dpttypes{$code}->{DEC}) eq 'CODE') { if (ref($dpttypes{$code}->{DEC}) eq 'CODE') {
my $state = $dpttypes{$code}->{DEC}->($value, $model, $hash); my $state = $dpttypes{$code}->{DEC}->($value, $model, $hash);
Log3 ($name, 5, qq{KNX_decodeByDpt ($name): gadName= $gadName model= $model code= $code value= $value length-value= } . KNX_Log ($name, 5, qq{gadName= $gadName model= $model code= $code value= $value length-value= } .
length($value) . qq{ state= $state}); length($value) . qq{ state= $state});
return $state; return $state;
} }
else { else {
Log3 ($name, 2, qq{KNX_decodeByDpt ($name): gadName= $gadName model= $model not valid}); KNX_Log ($name, 2, qq{gadName= $gadName model= $model not valid});
} }
return; return;
} }
@ -1878,14 +1882,17 @@ sub KNX_gadNameByNO {
### prependes device, subroutine, linenr. to Log msg ### prependes device, subroutine, linenr. to Log msg
### return undef ### return undef
sub KNX_Log { sub KNX_Log {
my $dev = shift || 'global'; my $dev = shift // 'global';
my $loglvl = shift; my $loglvl = shift // 5;
my $logtxt = shift; my $logtxt = shift;
my $sub = (caller(1))[3] || 'main'; my $name = ( ref($dev) eq 'HASH' ) ? $dev->{NAME} : $dev;
my $dloglvl = AttrVal($name,'verbose',undef) // AttrVal('global','verbose',3);
return if ($loglvl > $dloglvl); # shortcut performance
my $sub = (caller(1))[3] // 'main';
my $line = (caller(0))[2]; my $line = (caller(0))[2];
my $name = ( ref($dev) eq "HASH" ) ? $dev->{NAME} : $dev; $sub =~ s/^.+[:]+//xms;
$sub =~ s/.+[:]+//xms;
Log3 ($name, $loglvl, qq{$name [$sub $line]: $logtxt}); Log3 ($name, $loglvl, qq{$name [$sub $line]: $logtxt});
return; return;
@ -2140,7 +2147,7 @@ The answer from the bus-device updates the readings &lt;getName&gt; and state.</
If enabled, FHEM answers on read requests. The content of reading state is sent to the bus as answer. If enabled, FHEM answers on read requests. The content of reading state is sent to the bus as answer.
If defined, the content of the reading &lt;putName&gt; is used as value for the answer. This attribute has no effect if the putCmd attribute is set!<br/> If defined, the content of the reading &lt;putName&gt; is used as value for the answer. This attribute has no effect if the putCmd attribute is set!<br/>
<b>This attribute (and reading &lt;putName&gt;) will be deprecated soon,</b> as replacement you can use for example:<br/> <b>This attribute (and reading &lt;putName&gt;) will be deprecated soon,</b> as replacement you can use for example:<br/>
<code>attr &lt;device&gt; putCmd {return $state if ($gadName eq 'g1');} </code> </li> <code>attr &lt;device&gt; putCmd {return $state;} </code> </li>
<br/> <br/>
<a id="KNX-attr-stateRegex"></a><li>stateRegex<br/> <a id="KNX-attr-stateRegex"></a><li>stateRegex<br/>
You can pass n pairs of regex-patterns and strings to replace, seperated by a space. A regex-pair is always in the format /&lt;readingName&gt;[:&lt;value&gt;]/[2nd part]/. You can pass n pairs of regex-patterns and strings to replace, seperated by a space. A regex-pair is always in the format /&lt;readingName&gt;[:&lt;value&gt;]/[2nd part]/.
@ -2191,8 +2198,8 @@ The answer from the bus-device updates the readings &lt;getName&gt; and state.</
For details, pls see <a href="#FHEMWEB-attr-widgetOverride">FHEMWEB-attribute</a>.</li> For details, pls see <a href="#FHEMWEB-attr-widgetOverride">FHEMWEB-attribute</a>.</li>
<br/> <br/>
<!-- <!--
<a id="KNX-attr-answerReading"></a><li>answerReading - This attr is deprecated. As replacement you can use for example:<br/> <a id="KNX-attr-answerReading"></a><li>answerReading - This attr is deprecated. It will be converted to equivalent function using attr putCmd:<br/>
<code>attr &lt;device&gt; putCmd {return $state if ($gadName eq 'g1');}</code> </li> <code>attr &lt;device&gt; putCmd {return $state;}</code> </li>
--> -->
<a id="KNX-attr-listenonly"></a><li>listenonly - This attr is deprecated - use "listenonly" option in device definition</li> <a id="KNX-attr-listenonly"></a><li>listenonly - This attr is deprecated - use "listenonly" option in device definition</li>
<a id="KNX-attr-readonly"></a><li>readonly - This attr is deprecated - use "get" option in device definition</li> <a id="KNX-attr-readonly"></a><li>readonly - This attr is deprecated - use "get" option in device definition</li>