2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-02-25 03:44:52 +00:00

10_KNX.pm: new attr. readingNmap (Forum #122582)

git-svn-id: https://svn.fhem.de/fhem/trunk@29680 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
erwin 2025-02-20 13:54:27 +00:00
parent 3d0aa04c3d
commit 7b31e22d53
3 changed files with 188 additions and 135 deletions

View File

@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it # Do not insert empty lines here, update check depends on it
- feature: 10_KNX: new attribute readingNmap
- feature: 76_SolarForecast: max. possible inverters set to 4 - feature: 76_SolarForecast: max. possible inverters set to 4
- feature: 98_vitoconnect.pm: enhanced error mapping - feature: 98_vitoconnect.pm: enhanced error mapping
now also language dependent now also language dependent

View File

@ -82,9 +82,10 @@
# 25/04/2024 changed _open for mode S # 25/04/2024 changed _open for mode S
# replaced/removed experimental given/when # replaced/removed experimental given/when
# 19/08/2024 fix error-msg when mode S fails to open # 19/08/2024 fix error-msg when mode S fails to open
# xx/11/2024 replace getimeofday w. Time::HiRes::time # 07/11/2024 replace getimeofday w. Time::HiRes::time
# use AttrNum instead of AttrVal where possible # use AttrNum instead of AttrVal where possible
# PBP remove postfix if # PBP remove postfix if
# xx/01/2025 add a few responseIDs in readH fn.
package KNXIO; ## no critic 'package' package KNXIO; ## no critic 'package'
@ -415,7 +416,8 @@ sub KNXIO_ReadH {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if ( unpack('n',$buf) != 0x0610) { my ($header, $responseID, $total_length) = unpack('nnn',$buf);
if ($header != 0x0610) {
KNXIO_Log ($name, 3, 'invalid Frame Header received - discarded'); KNXIO_Log ($name, 3, 'invalid Frame Header received - discarded');
return; return;
} }
@ -425,23 +427,34 @@ sub KNXIO_ReadH {
my $rxseqcntr = 0; my $rxseqcntr = 0;
my $txseqcntr = 0; my $txseqcntr = 0;
my $errcode = 0; my $errcode = 0;
my $responseID = unpack('x2n',$buf);
my %resIDs = ( my %resIDs = (
0x0201 => sub { # search request
KNXIO_Log ($name, 4, 'SearchRequest received');
return;
},
0x0202 => sub { # Search response 0x0202 => sub { # Search response
KNXIO_Log ($name, 4, 'SearchResponse received'); KNXIO_Log ($name, 4, 'SearchResponse received');
my (@contolpointIp, $controlpointPort) = unpack('x6CCCn',$buf); my (@contolpointIp, $controlpointPort) = unpack('x6CCCn',$buf);
return; return;
}, },
0x0203 => sub { # Decription request
KNXIO_Log ($name, 4, 'DescriptionRequest received');
return;
},
0x0204 => sub { # Decription response 0x0204 => sub { # Decription response
KNXIO_Log ($name, 4, 'DescriptionResponse received'); KNXIO_Log ($name, 4, 'DescriptionResponse received');
return; return;
}, },
0x0206 => sub { # Connection response 0x0205 => sub { # Connect request
KNXIO_Log ($name, 4, 'ConnectionRequest received');
return;
},
0x0206 => sub { # Connect response
($hash->{KNXIOhelper}->{CCID},$errcode) = unpack('x6CC',$buf); # save Comm Channel ID,errcode ($hash->{KNXIOhelper}->{CCID},$errcode) = unpack('x6CC',$buf); # save Comm Channel ID,errcode
RemoveInternalTimer($hash,\&KNXIO_keepAlive); RemoveInternalTimer($hash,\&KNXIO_keepAlive);
if ($errcode > 0) { if ($errcode > 0) {
KNXIO_Log ($name, 3, q{ConnectionResponse received } . KNXIO_Log ($name, 3, q{ConnectResponse received } .
qq{CCID= $hash->{KNXIOhelper}->{CCID} Status=} . KNXIO_errCodes($errcode)); qq{CCID= $hash->{KNXIOhelper}->{CCID} Status=} . KNXIO_errCodes($errcode));
KNXIO_disconnect($hash,2); KNXIO_disconnect($hash,2);
return; return;
@ -454,6 +467,10 @@ sub KNXIO_ReadH {
InternalTimer(Time::HiRes::time() + 60, \&KNXIO_keepAlive, $hash); # start keepalive InternalTimer(Time::HiRes::time() + 60, \&KNXIO_keepAlive, $hash); # start keepalive
return; return;
}, },
0x0207 => sub { # ConnectionState request
KNXIO_Log ($name, 4, 'ConnectioStatenRequest received');
return;
},
0x0208 => sub { # ConnectionState response 0x0208 => sub { # ConnectionState response
($hash->{KNXIOhelper}->{CCID}, $errcode) = unpack('x6CC',$buf); ($hash->{KNXIOhelper}->{CCID}, $errcode) = unpack('x6CC',$buf);
RemoveInternalTimer($hash,\&KNXIO_keepAlive); RemoveInternalTimer($hash,\&KNXIO_keepAlive);
@ -1047,8 +1064,8 @@ sub KNXIO_dispatch2 {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
$hash->{'msg_count'}++; $hash->{'MSGCNT'}++;
$hash->{'msg_time'} = TimeNow(); $hash->{'MSGTIME'} = TimeNow();
Dispatch($hash, $buf); Dispatch($hash, $buf);
@ -1132,8 +1149,8 @@ sub KNXIO_closeDev {
delete $hash->{stacktrace}; # clean delete $hash->{stacktrace}; # clean
delete $hash->{nextOpenDelay}; delete $hash->{nextOpenDelay};
delete $hash->{'msg_count'}; delete $hash->{'MSGCNT'};
delete $hash->{'msg_time'}; delete $hash->{'MSGTIME'};
#NO! delete $hash->{'.CCID'}; #NO! delete $hash->{'.CCID'};
delete $hash->{KNXIOhelper}->{SEQUENCECNTR}; delete $hash->{KNXIOhelper}->{SEQUENCECNTR};
@ -1479,7 +1496,8 @@ __END__
<li><b>S</b> Socket mode - communicate via KNXD's UNIX-socket on localhost. default Socket-path&colon; <li><b>S</b> Socket mode - communicate via KNXD's UNIX-socket on localhost. default Socket-path&colon;
<code>/var/run/knx</code><br/> <code>/var/run/knx</code><br/>
Path might be different, depending on knxd-version or -config specification! Path might be different, depending on knxd-version or -config specification!
This mode is tested ok with KNXD version 0.14.30. It does NOT work with ver. 0.10.0! This mode is tested ok with KNXD version 0.14.30. It does NOT work with ver. 0.10.0!</br>
If you run FHEM and KNXD in Docker, this is the preferred mode to run both in network-mode bridge - see wiki!.
</li> </li>
<li><b>X</b> Special mode - for details see KNXIO-wiki! <li><b>X</b> Special mode - for details see KNXIO-wiki!
</li> </li>

View File

@ -193,8 +193,10 @@
# dpt18 "learn" now implemented for dpt18.001, experimental dpt18.099 removed # dpt18 "learn" now implemented for dpt18.001, experimental dpt18.099 removed
# PBP: replace unless w. if not..., fix postfix if # PBP: replace unless w. if not..., fix postfix if
# cmd-ref update # cmd-ref update
# MH 20250201 fix dpt16 dblog-split fn
# feature: new attr readingNmap - modify readingnames on the fly
# internal code change: _define2, _attr, _set, _encodeByDpt
# #
# postponed new attr readingRegex - allow manipulating reading-names on the fly
# todo-4/2024 remove support for oldsyntax cmd's: raw,value,string,rgb # todo-4/2024 remove support for oldsyntax cmd's: raw,value,string,rgb
@ -241,7 +243,6 @@ BEGIN {
AnalyzePerlCommand AnalyzeCommandChain EvalSpecials AnalyzePerlCommand AnalyzeCommandChain EvalSpecials
fhemTimeLocal) fhemTimeLocal)
); );
# addToDevAttrList # removed 11/2024
} }
#string constants #string constants
@ -546,9 +547,9 @@ my %dpttypes = (
'dpt17.001' => {CODE=>'dpt17', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,2}/xms, MIN=>0, MAX=>63}, 'dpt17.001' => {CODE=>'dpt17', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,2}/xms, MIN=>0, MAX=>63},
# Scene, 1-64 # Scene, 1-64
'dpt18' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,2}/xms, OFFSET=>1, MIN=>1, MAX=>64, 'dpt18' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/(activate[,]|learn[,])?[+]?\d{1,2}/xms, OFFSET=>1, MIN=>1, MAX=>64,
DEC=>\&dec_dpt18,ENC=>\&enc_dpt18,}, DEC=>\&dec_dpt18,ENC=>\&enc_dpt18,},
'dpt18.001' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/(activate|learn)?[,]?[+]?\d{1,2}/xms, OFFSET=>1, MIN=>1, MAX=>64, SETLIST=>'widgetList,3,select,activate,learn,1,textField'}, 'dpt18.001' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/(activate[,]|learn[,])?[+]?\d{1,2}/xms, OFFSET=>1, MIN=>1, MAX=>64, SETLIST=>'widgetList,3,select,activate,learn,1,textField'},
#date and time #date and time
'dpt19' => {CODE=>'dpt19', UNIT=>q{}, PATTERN=>qr/($PAT_DATE$PAT_DTSEP$PAT_TIME|now)/ixms, MIN=>undef, MAX=>undef, 'dpt19' => {CODE=>'dpt19', UNIT=>q{}, PATTERN=>qr/($PAT_DATE$PAT_DTSEP$PAT_TIME|now)/ixms, MIN=>undef, MAX=>undef,
@ -616,7 +617,7 @@ sub Initialize {
'showtime:0,1 ' . #show event-time instead of value in device overview 'showtime:0,1 ' . #show event-time instead of value in device overview
'stateRegex:textField-long ' . #modifies state value 'stateRegex:textField-long ' . #modifies state value
'stateCmd:textField-long ' . #modify state value 'stateCmd:textField-long ' . #modify state value
#not yet 'readingRegex:textField-long ' . #modify reading name 'readingNmap:textField-long ' . #modify reading names
'putCmd:textField-long ' . #enable FHEM to answer KNX read telegrams 'putCmd:textField-long ' . #enable FHEM to answer KNX read telegrams
'format ' . #supplies post-string 'format ' . #supplies post-string
'KNX_toggle:textField ' . #toggle source <device>:<reading> 'KNX_toggle:textField ' . #toggle source <device>:<reading>
@ -684,8 +685,7 @@ sub KNX_Define2 {
foreach my $gaddef (@a) { foreach my $gaddef (@a) {
my $gadOption = undef; my $gadOption = undef;
my $gadNoSuffix = undef; my $gadNoSuffix = undef;
my $gadName = 'g' . $gadNo; # old syntax
my $gadName = 'g' . $gadNo; # old syntax
KNX_Log ($name, 5, qq{gadNr= $gadNo def-string= $gaddef}); KNX_Log ($name, 5, qq{gadNr= $gadNo def-string= $gaddef});
@ -695,6 +695,10 @@ sub KNX_Define2 {
push(@logarr,qq{GAD not defined or wrong format for group-number $gadNo, specify as 0-31/0-7/0-255}); push(@logarr,qq{GAD not defined or wrong format for group-number $gadNo, specify as 0-31/0-7/0-255});
next; next;
} }
elsif (defined($hash->{GADTABLE}->{$gadCode})) {
push(@logarr,qq{GAD $gad may be supplied only once per device});
next;
}
if (! defined($gadModel)) { if (! defined($gadModel)) {
push(@logarr,qq{no model defined for group-number $gadNo}); push(@logarr,qq{no model defined for group-number $gadNo});
@ -714,6 +718,7 @@ sub KNX_Define2 {
$hash->{model} = $dpttypes{$gadModel}->{CODE}; $hash->{model} = $dpttypes{$gadModel}->{CODE};
} }
# optional param
if (scalar(@gadArgs)) { if (scalar(@gadArgs)) {
if ($gadArgs[-1] =~ /$PAT_GAD_SUFFIX/ixms) {$gadNoSuffix = lc(pop(@gadArgs));} if ($gadArgs[-1] =~ /$PAT_GAD_SUFFIX/ixms) {$gadNoSuffix = lc(pop(@gadArgs));}
if (@gadArgs && $gadArgs[-1] =~ /^($PAT_GAD_OPTIONS)$/ixms) {$gadOption = lc(pop(@gadArgs));} if (@gadArgs && $gadArgs[-1] =~ /^($PAT_GAD_OPTIONS)$/ixms) {$gadOption = lc(pop(@gadArgs));}
@ -729,29 +734,10 @@ sub KNX_Define2 {
$gadName = q{g} . $gadNo; $gadName = q{g} . $gadNo;
} }
if (defined($hash->{GADTABLE}->{$gadCode})) {
push(@logarr,qq{GAD $gad may be supplied only once per device});
next;
}
$hash->{GADTABLE}->{$gadCode} = $gadName; #add key and value to GADTABLE $hash->{GADTABLE}->{$gadCode} = $gadName; #add key and value to GADTABLE
#cache suffixes # create readingnames
my ($suffixGet, $suffixSet) = qw(-get -set); my ($rdNameSet, $rdNameGet) = KNX_makeRdNames($hash, $gadName, $gadNo, $gadNoSuffix);
if (defined($gadNoSuffix)) {($suffixGet, $suffixSet) = (q{},q{});}
# new syntax readingNames
my $rdNameGet = $gadName . $suffixGet;
my $rdNameSet = $gadName . $suffixSet;
if (($gadName eq 'g' . $gadNo) && (! defined($gadNoSuffix))) { # old syntax
$rdNameGet = 'getG' . $gadNo;
$rdNameSet = 'setG' . $gadNo;
}
if (defined ($gadOption)) {
KNX_Log ($name, 5, qq{found GAD: $gad NAME: $gadName NO: $gadNo HEX: $gadCode DPT: $gadModel} .
qq{ OPTION: $gadOption});
}
#determine dpt-details #determine dpt-details
my $dptDetails = $dpttypes{$gadModel}; my $dptDetails = $dpttypes{$gadModel};
@ -768,7 +754,8 @@ sub KNX_Define2 {
$setlist = q{:} . $min . q{,} . $max; $setlist = q{:} . $min . q{,} . $max;
} }
KNX_Log ($name, 5, qq{Reading-names: $rdNameSet, $rdNameGet SetList: $setlist}); KNX_Log ($name, 5, qq{define GAD: $gad NAME: $gadName NO: $gadNo HEX: $gadCode DPT: $gadModel} .
qq{ RD-NAMES: $rdNameSet $rdNameGet SETLIST: $setlist} );
#add details to hash #add details to hash
$hash->{GADDETAILS}->{$gadName} = {CODE => $gadCode, MODEL => $gadModel, NO => $gadNo, OPTION => $gadOption, $hash->{GADDETAILS}->{$gadName} = {CODE => $gadCode, MODEL => $gadModel, NO => $gadNo, OPTION => $gadOption,
@ -920,12 +907,8 @@ sub KNX_Set {
return $err if defined($err); return $err if defined($err);
} }
#Text neads special treatment - additional args may be blanked words - truncate to 14 char #special treatment for dpt 10,16,18 done in KNX_encodeByDpt
elsif ($model =~ m/^dpt16(?:[.][\d]{3})?$/xms) { my $transval = KNX_encodeByDpt($hash, $targetGadName, $value, @arg); #process set command
if (scalar(@arg) > 0) {$value .= q{ } . join (q{ }, @arg);}
}
my $transval = KNX_encodeByDpt($hash, $value, $targetGadName); #process set command
return qq{"set $name $targetGadName $value" failed, see Log-Messages} if (!defined($transval)); # encodeByDpt failed return qq{"set $name $targetGadName $value" failed, see Log-Messages} if (!defined($transval)); # encodeByDpt failed
IOWrite($hash, $KNXID, 'w' . $groupCode . $transval); IOWrite($hash, $KNXID, 'w' . $groupCode . $transval);
@ -933,7 +916,9 @@ sub KNX_Set {
KNX_Log ($name, 5, qq{cmd= $cmd , value= $value , translated= $transval}); KNX_Log ($name, 5, qq{cmd= $cmd , value= $value , translated= $transval});
# decode again for values that have been changed in encode process # decode again for values that have been changed in encode process
if ($model =~ m/^dpt(?:3|10|11|16|18|19)(?:[.][\d]{3})?$/xms) {$value = KNX_decodeByDpt($hash, $transval, $targetGadName);} if ($model =~ m/^dpt(?:3|10|11|16|18|19)(?:[.][\d]{3})?$/xms) {
$value = KNX_decodeByDpt($hash, $targetGadName, $transval);
}
#apply post processing for state and set all readings #apply post processing for state and set all readings
KNX_SetReadings($hash, $targetGadName, $value, undef, undef); KNX_SetReadings($hash, $targetGadName, $value, undef, undef);
@ -975,6 +960,7 @@ sub KNX_Set_oldsyntax {
# pass thru -for-timer,-until,blink cmds... # pass thru -for-timer,-until,blink cmds...
return (undef, $targetGadName, $cmd, @arg) if ($cmd =~ m/(?:-till|-until|-till-overnight|-for-timer|$BLINK)$/ixms); return (undef, $targetGadName, $cmd, @arg) if ($cmd =~ m/(?:-till|-until|-till-overnight|-for-timer|$BLINK)$/ixms);
## prepare for deletion of deprecated cmds
my $code = $hash->{GADDETAILS}->{$targetGadName}->{MODEL}; my $code = $hash->{GADDETAILS}->{$targetGadName}->{MODEL};
my $value = shift(@arg); my $value = shift(@arg);
my $rtxt = qq{"set $name $cmd $value }; # log text my $rtxt = qq{"set $name $cmd $value }; # log text
@ -1000,10 +986,14 @@ sub KNX_Set_oldsyntax {
return q{invalid cmd: } . $rtxt . join(q{ },@arg) . q{" issued - ignored}; return q{invalid cmd: } . $rtxt . join(q{ },@arg) . q{" issued - ignored};
} }
KNX_Log ($name, 3, q{This cmd will be deprecated by 1/2024: } . $rtxt . join(q{ },@arg) . KNX_Log ($name, 2, q{This cmd is deprecated since 1/2024: } . $rtxt . join(q{ },@arg) .
qq{" - use: "set $name $targetGadName $value } . join(q{ },@arg) . q{"} ); qq{" - use: "set $name $targetGadName $value } . join(q{ },@arg) . q{"} );
return (undef, $targetGadName, $value, @arg); return (undef, $targetGadName, $value, @arg);
## prepare for deletion of deprecated cmds
## my $rtxt = q{invalid cmd: "set } . qq{$name $cmd $value } . join(q{ },@arg) . q{" issued - ignored};
## KNX_Log ($name, 2, $rtxt);
## return $rtxt;
} }
# process special dpt1, dpt1.001 set # process special dpt1, dpt1.001 set
@ -1060,7 +1050,6 @@ sub KNX_Set_dpt1 {
my $hms_til = ::FmtDateTime($endTS) =~ s/[\s]/T/rxms; # fhem.pl alternativ use full datespec my $hms_til = ::FmtDateTime($endTS) =~ s/[\s]/T/rxms; # fhem.pl alternativ use full datespec
$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
# CommandDefMod(undef, '-temporary ' . $name . qq{_TIMER_$groupCode at $hms_til set $name $targetGadName $tvalue});
CommandDefMod(undef, '-silent ' . $name . qq{_TIMER_$groupCode at $hms_til set $name $targetGadName $tvalue}); CommandDefMod(undef, '-silent ' . $name . qq{_TIMER_$groupCode at $hms_til set $name $targetGadName $tvalue});
return (undef,$value); return (undef,$value);
} }
@ -1131,12 +1120,14 @@ sub KNX_State {
sub KNX_Attr { sub KNX_Attr {
my ($cmd,$name,$aName,$aVal) = @_; my ($cmd,$name,$aName,$aVal) = @_;
my $hash = $defs{$name}; return KNX_Attr_readingNmap($cmd, $name, $aVal) if ($aName eq q{readingNmap});
my $hash = $defs{$name};
my $value = undef; my $value = undef;
if ($cmd eq 'set' && $init_done) { if ($cmd eq 'set' && $init_done) {
# check valid IODev # check valid IODev
if ($aName eq 'IODev') { return KNX_chkIODev($hash,$aVal); } return KNX_chkIODev($hash,$aVal) if ($aName eq 'IODev');
if ($aName eq 'KNX_toggle') { # validate device/reading if ($aName eq 'KNX_toggle') { # validate device/reading
my ($srcDev,$srcReading) = split(qr/:/xms,$aVal); # format: <device>:<reading> my ($srcDev,$srcReading) = split(qr/:/xms,$aVal); # format: <device>:<reading>
@ -1152,26 +1143,22 @@ sub KNX_Attr {
return qq{syntax check failed for $aName: \n $err} if($err); return qq{syntax check failed for $aName: \n $err} if($err);
} }
elsif ($aName =~ /(stateRegex|readingRegex)/xms) { # test for syntax errors elsif ($aName eq 'stateRegex') { # test for syntax errors
# elsif ($aName eq 'stateRegex') { # test for syntax errors
my @aValS = split(/\s+|\//xms,$aVal); my @aValS = split(/\s+|\//xms,$aVal);
foreach (@aValS) { foreach (@aValS) {
next if ($_ eq q{} ); next if ($_ eq q{} );
my $err = main::CheckRegexp($_,'Attr ' . $aName); # fhem.pl my $err = main::CheckRegexp($_,'Attr ' . $aName); # fhem.pl
# my $err = main::CheckRegexp($_,'Attr stateRegex'); # fhem.pl
return qq{syntax check failed for $aName: \n $err} if (defined($err)); return qq{syntax check failed for $aName: \n $err} if (defined($err));
} }
} }
} # /set } # /set
if ($cmd eq 'del') { elsif ($cmd eq 'del') {
if ($aName eq 'disable') { if ($aName eq 'disable') {
my @defentries = split(/\s/ixms,$hash->{DEF}); my @defentries = split(/[\s\t\n]+/xms,$hash->{DEF});
foreach my $def (@defentries) { # check all entries foreach my $def (@defentries) { # check all entries
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+]|RAW)/xms); next if ($def =~ /:dpt(?:[\d+]|RAW)/xms);
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
@ -1180,14 +1167,57 @@ sub KNX_Attr {
return; return;
} }
### special Handling Attr readingNmap
#############################
sub KNX_Attr_readingNmap {
my ($cmd, $name, $aVal) = @_;
if(ref($cmd) eq 'HASH') { # called from timer
($cmd, $name, $aVal) = ($cmd->{cmd}, $cmd->{name}, $cmd->{aVal});
}
my $hash = $defs{$name};
if ($cmd eq 'set' ) {
if (! $init_done) { #postpone attr def
my $param = {cmd=>$cmd, name=>$name, aVal=>$aVal};
InternalTimer(Time::HiRes::time() + 3, \&KNX_Attr_readingNmap, $param);
return;
}
my @aValS = split(/\s+|\//xms,$aVal);
delete $hash->{Helper}->{RDNAMEMAP};
foreach my $pairs (@aValS) {
my ($gadName, $newrdName) = split(/[:]/xms,$pairs);
if (! (defined($gadName) && (exists( $hash->{GADDETAILS}->{$gadName})))) {
return qq{invalid first parameter $gadName, use a valid <gadName>};
}
next if (! defined($newrdName));
#delete affected readings
::readingsDelete($hash, $hash->{GADDETAILS}->{$gadName}->{RDNAMESET}); # fhem.pl
::readingsDelete($hash, $hash->{GADDETAILS}->{$gadName}->{RDNAMEGET}); # fhem.pl
$hash->{Helper}->{RDNAMEMAP}->{$gadName} = $newrdName;
}
}
elsif ($cmd eq 'del') { # delete mapped readings
foreach my $gadName (keys %{$hash->{Helper}->{RDNAMEMAP}}) {
::readingsDelete($hash, $hash->{GADDETAILS}->{$gadName}->{RDNAMESET}); # fhem.pl
::readingsDelete($hash, $hash->{GADDETAILS}->{$gadName}->{RDNAMEGET}); # fhem.pl
}
delete $hash->{Helper}->{RDNAMEMAP};
}
CommandDefMod(undef, '-silent ' . $name . ' KNX ' . $hash->{DEF});
return;
}
#Split reading for DBLOG #Split reading for DBLOG
############################# #############################
sub KNX_DbLog_split { sub KNX_DbLog_split {
my $event = shift; my $event = shift;
my $device = shift; my $device = shift;
my $reading = 'state'; # default my $reading = q{state}; # default
my $unit = q{}; # default my $unit = q{}; # default
my $dpt16flag = 0; # is it a dpt16 ? my $dpt16flag = 0; # is it a dpt16 ?
# split event into pieces # split event into pieces
@ -1202,17 +1232,21 @@ sub KNX_DbLog_split {
#numeric value? and last value non numeric? - assume unit - except for dpt16 #numeric value? and last value non numeric? - assume unit - except for dpt16
my $devhash = $defs{$device}; my $devhash = $defs{$device};
foreach my $key (keys %{$devhash->{GADDETAILS}}) { foreach my $key (keys %{$devhash->{GADDETAILS}}) {
next if ($key ne $reading);
next if ($devhash->{GADDETAILS}->{$key}->{MODEL} !~ /^dpt16/xms); next if ($devhash->{GADDETAILS}->{$key}->{MODEL} !~ /^dpt16/xms);
$dpt16flag = 1; next if ($reading ne q{state} &&
$reading ne $devhash->{GADDETAILS}->{$key}->{RDNAMESET} &&
$reading ne $devhash->{GADDETAILS}->{$key}->{RDNAMEGET});
$dpt16flag = 1;
last; last;
} }
if (($dpt16flag == 0) && Scalar::Util::looks_like_number($strings[0]) && (! Scalar::Util::looks_like_number($strings[scalar(@strings)-1]))) { if (($dpt16flag == 0) &&
Scalar::Util::looks_like_number($strings[0]) &&
(! Scalar::Util::looks_like_number($strings[scalar(@strings)-1]))) {
$unit = pop(@strings); $unit = pop(@strings);
} }
my $value = join(q{ },@strings); my $value = join(q{ },@strings);
if (!defined($unit)) {$unit = q{};} # if (!defined($unit)) {$unit = q{};}
KNX_Log ($device, 5, qq{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);
@ -1253,7 +1287,6 @@ sub KNX_Parse {
my $gadName = $deviceHash->{GADTABLE}->{$gadCode}; my $gadName = $deviceHash->{GADTABLE}->{$gadCode};
if (IsDevice($deviceName)) { push(@foundMsgs, $deviceName);} # save to list even if dev is disabled - dev must exist! if (IsDevice($deviceName)) { push(@foundMsgs, $deviceName);} # save to list even if dev is disabled - dev must exist!
# push(@foundMsgs, $deviceName); # save to list even if dev is disabled
if (IsDisabled($deviceName) == 1) { if (IsDisabled($deviceName) == 1) {
$deviceHash->{RAWMSG} = qq{gadName=$gadName cmd=$cmd, hexvalue=$val}; # for debugging $deviceHash->{RAWMSG} = qq{gadName=$gadName cmd=$cmd, hexvalue=$val}; # for debugging
@ -1262,8 +1295,7 @@ sub KNX_Parse {
=begin comment =begin comment
# ignore input from "wrong" IO-dev # ignore input from "wrong" IO-dev
my $IODevAttr = AttrVal($deviceName,'IODev',$ioName); if (AttrVal($deviceName,'IODev',$ioName) ne $ioName) {
if ($IODevAttr ne $ioName) {
KNX_Log ($deviceName, 2, qq{msg for gad-name: $gadName from wrong IO-device: $ioName - ignored}); KNX_Log ($deviceName, 2, qq{msg for gad-name: $gadName from wrong IO-device: $ioName - ignored});
next; next;
} }
@ -1294,7 +1326,7 @@ sub KNX_Parse {
#handle GroupValueWrite and GroupValueResponse messages #handle GroupValueWrite and GroupValueResponse messages
if ($cmd =~ /[w|p]/ixms) { if ($cmd =~ /[w|p]/ixms) {
#decode message #decode message
my $transval = KNX_decodeByDpt ($deviceHash, $val, $gadName); my $transval = KNX_decodeByDpt ($deviceHash, $gadName, $val);
#message invalid #message invalid
if (! defined($transval)) { if (! defined($transval)) {
KNX_Log ($deviceName, 2, qq{readingName=$getName message=$msg could not be decoded}); KNX_Log ($deviceName, 2, qq{readingName=$getName message=$msg could not be decoded});
@ -1344,7 +1376,7 @@ sub KNX_Parse {
KNX_Log ($deviceName, 5, qq{replaced by Attr putCmd=$cmdAttr VALUE=$value}); KNX_Log ($deviceName, 5, qq{replaced by Attr putCmd=$cmdAttr VALUE=$value});
#send transval #send transval
my $transval = KNX_encodeByDpt($deviceHash, $value, $gadName); my $transval = KNX_encodeByDpt($deviceHash, $gadName, $value);
if (defined($transval)) { if (defined($transval)) {
KNX_Log ($deviceName, 4, qq{putCmd send answer: readingName=$putName value=$value sendvalue=$transval}); KNX_Log ($deviceName, 4, qq{putCmd send answer: readingName=$putName value=$value sendvalue=$transval});
readingsSingleUpdate($deviceHash, $putName, $value,0); readingsSingleUpdate($deviceHash, $putName, $value,0);
@ -1409,9 +1441,6 @@ sub KNX_SetReadings {
$rdName = $hash->{GADDETAILS}->{$gadName}->{RDNAMEGET}; $rdName = $hash->{GADDETAILS}->{$gadName}->{RDNAMEGET};
} }
### test remapping of readings
# $rdName = KNX_readingRegex($hash,$rdName); # modify reading name
#execute stateRegex #execute stateRegex
my $state = KNX_replaceByRegex ($hash, $rdName, $value); my $state = KNX_replaceByRegex ($hash, $rdName, $value);
@ -1446,9 +1475,9 @@ sub doKNX_Set {
my @param = @_; my @param = @_;
my ($hash, $gadName, $cmd) = ($param[0]->{hash}, $param[0]->{gadName}, $param[0]->{cmd}); my ($hash, $gadName, $cmd) = ($param[0]->{hash}, $param[0]->{gadName}, $param[0]->{cmd});
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my @arg = (); my @arg = ();
$arg[0] = $cmd; $arg[0] = $cmd;
return KNX_Set($hash, $name, $gadName, @arg); return KNX_Set($hash, $name, $gadName, @arg);
} }
@ -1587,39 +1616,34 @@ sub KNX_replaceByRegex {
return ($retVal eq 'undefined')?undef:$retVal; return ($retVal eq 'undefined')?undef:$retVal;
} }
### replace reading Name by regex ### make and replace reading names
### called from KNX_SetReadings ### called from KNX_define2
### input: hash, readingname ### input: hash, gadName, gadNo, nosuffix
### return: new readingname ### return: rdNameset, rdNameget
sub KNX_readingRegex { sub KNX_makeRdNames {
my $hash = shift; my $hash = shift;
my $rName = shift; my $gadName = shift;
my $gadNo = shift; # // $hash->{GADDETAILS}->{$gadName}->{NO}; # sttartup?
my $nosuffix = shift;
my $name = $hash->{NAME}; my $rdNameSet = $gadName;
my $regAttr = AttrVal($name, 'readingRegex', undef); if (exists ($hash->{Helper}->{RDNAMEMAP}->{$gadName})) {
return $rName if (! defined($regAttr)); $rdNameSet = $hash->{Helper}->{RDNAMEMAP}->{$gadName};
#get array of given attributes
my @reg = split(/\s\//xms, $regAttr);
my $newrName = $rName; # default if no match
#loop over all regex
foreach my $regex (@reg) {
#trim leading and trailing slashes
$regex =~ s/^\/|\/$//gixms;
# get pairs
my @regPair = split(/\//xms, $regex);
#KNX_Log ($name, 1, qq{r0=$regPair[0] r1=$regPair[1] rName=$rName});
next if ((! defined($regPair[0])) || ($rName !~ /$regPair[0]/xms) ); # no match
if ($regPair[0] =~ /[(].*[)]/xms) { # we have a capture group
my $g1 = $rName =~ s/$regPair[0]/$1/rxms; # extract 1st capture group
$regPair[1] =~ s/\$1/$g1/xms;
}
$newrName =~ s/$regPair[0]/$regPair[1]/xms;
KNX_Log ($name, 4, qq{replaced $rName with $newrName});
} }
return $newrName;
my $rdNameGet = $rdNameSet; # assume nosuffix
if (! defined($nosuffix)) {
if ($rdNameSet eq 'g' . $gadNo) { # old syntax
$rdNameSet = q{setG} . $gadNo;
$rdNameGet = q{getG} . $gadNo;
}
else {
$rdNameSet .= q{-set};
$rdNameGet .= q{-get};
}
}
return ($rdNameSet, $rdNameGet);
} }
### limit numeric values. Valid directions: encode, decode ### limit numeric values. Valid directions: encode, decode
@ -1693,9 +1717,7 @@ sub KNX_eval {
### encode KNX-Message according DPT ### encode KNX-Message according DPT
# on return: hex string to be sent to bus / undef on error # on return: hex string to be sent to bus / undef on error
sub KNX_encodeByDpt { sub KNX_encodeByDpt {
my $hash = shift; my ($hash, $gadName, $value, @arg) = @_;
my $value = shift;
my $gadName = shift;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $model = $hash->{GADDETAILS}->{$gadName}->{MODEL}; my $model = $hash->{GADDETAILS}->{$gadName}->{MODEL};
@ -1704,10 +1726,14 @@ 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
# special handling # special handling
if ($model !~ /^dpt16/xms) { # ...except for txt! if ($code eq 'dpt16') { # $arg contains additional words
if (scalar(@arg) > 0) {$value .= q{ } . join(q{ }, @arg);}
}
else { # strip off whitespace and unit
$value =~ s/^\s+|\s+$//gxms; # white space at begin/end $value =~ s/^\s+|\s+$//gxms; # white space at begin/end
$value = (split(/\s+/xms,$value))[0]; # strip off unit $value = (split(/\s+/xms,$value))[0]; # strip off unit
} }
# compatibility with widgetoverride :time # compatibility with widgetoverride :time
if ($code eq 'dpt10' && $value =~ /^[\d]{2}:[\d]{2}$/xms) {$value .= ':00';} if ($code eq 'dpt10' && $value =~ /^[\d]{2}:[\d]{2}$/xms) {$value .= ':00';}
@ -1715,9 +1741,16 @@ sub KNX_encodeByDpt {
my $arg1 = undef; my $arg1 = undef;
my $arg2 = undef; my $arg2 = undef;
if ($code eq 'dpt18') { if ($code eq 'dpt18') {
($arg1, $arg2) = split(/[,]/xms,$value); if ($value =~ /(activate|learn)$/xms) {
$value = (defined($arg2))?$arg2:$arg1; $value = shift(@arg); # blank separated
$arg1 = $1;
}
else {
($arg1, $arg2) = split(/[,]/xms,$value); # widget or just number?
$value = (defined($arg2))?$arg2:$arg1;
}
} }
# match against model pattern # match against model pattern
my $pattern = $dpttypes{$model}->{PATTERN}; my $pattern = $dpttypes{$model}->{PATTERN};
if ($value !~ /^$pattern$/ixms) { if ($value !~ /^$pattern$/ixms) {
@ -1746,12 +1779,12 @@ sub KNX_encodeByDpt {
# on return: decoded value from bus / on error: undef # on return: decoded value from bus / on error: undef
sub KNX_decodeByDpt { sub KNX_decodeByDpt {
my $hash = shift; my $hash = shift;
my $value = shift;
my $gadName = shift; my $gadName = shift;
my $value = shift;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $model = $hash->{GADDETAILS}->{$gadName}->{MODEL}; my $model = $hash->{GADDETAILS}->{$gadName}->{MODEL};
my $code = $dpttypes{$model}->{CODE}; my $code = $dpttypes{$model}->{CODE};
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
@ -2417,12 +2450,12 @@ The optional parameteter &lt;gadName&gt; may contain an alias for the GAD. The f
state, on, off, on-for-timer, on-until, off-for-timer, off-until, toggle, raw, rgb, string, value, set, get, listenonly, nosuffix state, on, off, on-for-timer, on-until, off-for-timer, off-until, toggle, raw, rgb, string, value, set, get, listenonly, nosuffix
- because of conflict with cmds &amp; parameters.<br/> - because of conflict with cmds &amp; parameters.<br/>
If you supply &lt;gadName&gt; this name is used instead as cmd prefix. The reading-names are &lt;gadName&gt;-get and &lt;gadName&gt;-set. If you supply &lt;gadName&gt; this name is used instead as cmd prefix. The reading-names are &lt;gadName&gt;-get and &lt;gadName&gt;-set.
The synonyms &lt;getName&gt; and &lt;setName&gt; are used in this documentation. <br/> The synonyms &lt;getName&gt; and &lt;setName&gt; are used in this documentation.
Reading-names may be modified by <a href="#KNX-attr-readingNmap">attribute readingNmap</a>, see examples.<br/>
If you add the option "nosuffix", &lt;getName&gt; and &lt;setName&gt; have the identical name - &lt;gadName&gt;. If you add the option "nosuffix", &lt;getName&gt; and &lt;setName&gt; have the identical name - &lt;gadName&gt;.
Both sent and received bus messages will be stored in the same reading &lt;gadName&gt;<br/> Both sent and received bus messages will be stored in the same reading &lt;gadName&gt;.<br/>
If you want to restrict the GAD, use the options "get", "set", or "listenonly". The usage is described in Set/Get-cmd chapter. If you want to restrict the GAD, use the options "get", "set", or "listenonly". The usage is described in Set/Get-cmd chapter.
It is not possible to combine the options.<br/> It is not possible to combine the options.
<!-- Reading-names may be modified by <a href="#KNX-attr-readingRegex">attribute readingRegex</a>, see examples. -->
</p> </p>
<p><b>Specifying an IO-Device in define is deprecated!</b> Use attribute <a href="#KNX-attr-IODev">IODev</a> instead, <p><b>Specifying an IO-Device in define is deprecated!</b> Use attribute <a href="#KNX-attr-IODev">IODev</a> instead,
but only if absolutely required!</p> but only if absolutely required!</p>
@ -2525,9 +2558,9 @@ Examples&colon;
<li><a href="#showtime">showtime</a></li> <li><a href="#showtime">showtime</a></li>
<li><a href="#FHEMWEB-attr-sortby">sortby</a></li> <li><a href="#FHEMWEB-attr-sortby">sortby</a></li>
<li><a href="#readingFnAttributes">stateFormat</a></li> <li><a href="#readingFnAttributes">stateFormat</a></li>
<li><a href="#suppressReading">suppressReading</a></li>
<li><a href="#readingFnAttributes">timestamp-on-change-reading</a></li> <li><a href="#readingFnAttributes">timestamp-on-change-reading</a></li>
<li><a href="#readingFnAttributes">userReadings</a></li> <li><a href="#readingFnAttributes">userReadings</a></li>
<li><a href="#suppressReading">suppressReading</a></li>
<li><a href="#userattr">userattr</a></li> <li><a href="#userattr">userattr</a></li>
<li><a href="#verbose">verbose</a></li> <li><a href="#verbose">verbose</a></li>
<li><a href="#FHEMWEB-attr-webCmd">webCmd</a></li> <li><a href="#FHEMWEB-attr-webCmd">webCmd</a></li>
@ -2539,23 +2572,24 @@ Examples&colon;
<li><strong>Special attributes</strong><br/> <li><strong>Special attributes</strong><br/>
<ul> <ul>
<!-- not yet <li><a id="KNX-attr-readingNmap"></a><b>readingNmap</b><br/>
<li><a id="KNX-attr-readingRegex"></a><b>readingRegex</b><br/> This function modifies the reading name (not the value!) if the first part exactly match the <b>&lt;gadName&gt;</b>,
This function modifies the reading name (not the value!) if the first part of the regex exactly match the original reading name. the new reading name will be the second part of the mapping. Parts are separated by a colon.
You can pass mutiple pairs of regex-patterns and strings to replace, separated by a space. You can pass mutiple pairs of mapping-patterns, separated by a space.
If no match, the "original" reading-name will get updated. If no match, the "original" reading-name will be used / updated.
Setting/changing this Attribute deletes previously defined readings of the device.
<pre> <pre>
Examples&colon; Examples&colon;
<code> attr &lt;device&gt; readingRegex /desired-(.*)-get/$1/ # use word in () as the new reading name <code> attr &lt;device&gt; readingNmap desired-temp-get:WunschThemperatur # desired-temp-get -> WunschThemperatur
attr &lt;device&gt; readingRegex /(position|pct)/desired-$1/ # replace "position" or "pct" by "desired-position" or "desired-pct" attr &lt;device&gt; readingNmap position:desired-pos # replace "position" by "desired-pos"
attr &lt;device&gt; readingRegex /.*-pos-.*/Postion/ # reading Name is now "Position". attr &lt;device&gt; readingNmap pct:Postion g1:Position # both gadNames store their values into reading "Position".
</code></pre> </code></pre>
This is useful for e.g. setting the actual blind-Position (as received from actor) into a reading "desired-position" This is useful for e.g. setting the actual blind-Position (as received from actor) into a reading "desired-position"
or "desired-pct" to update a cmd-slider to the current position of the blind.<br/> or "desired-pct" to update a cmd-slider to the current position of the blind.<br/>
Can also be used to make the reading-name independent from the GadName / cmd, to simplify integration of external systems. Can also be used to make the reading-name independent from the gadName / cmd, to simplify integration of external systems.
This attr does <b>not change</b> the value of the reading. This attr does <b>not change</b> the value of the reading.<br/>
This attr is processed ahead of other attributes, you have to use the mapped reading-name in stateRegex,stateCmd,toggle attributes.
<br/></li> <br/></li>
-->
<li><a id="KNX-attr-stateRegex"></a><b>stateRegex</b><br/> <li><a id="KNX-attr-stateRegex"></a><b>stateRegex</b><br/>
This function modifies the value of reading state. This function modifies the value of reading state.
It is executed directly after replacing the reading-names and processing "format" Attr, but before stateCmd.<br/> It is executed directly after replacing the reading-names and processing "format" Attr, but before stateCmd.<br/>
@ -2639,11 +2673,11 @@ Examples&colon;
<br/></li> <br/></li>
<li><a id="KNX-events"></a><strong>Events</strong><br/> <li><a id="KNX-events"></a><strong>Events</strong><br/>
<p>Events are generated for each reading sent or received to/from KNX-Bus unless restricted by <code>event-xxx</code> attributes <p>Events are generated for each message sent or received to/from KNX-Bus unless restricted by <code>event-xxx</code> attributes
or modified by <code>eventMap, stateRegex</code> attributes. or modified by <code>readingNmap, eventMap, stateRegex</code> attributes.
<br/>KNX-events have this format&colon;</p> <br/>KNX-events have this format&colon;</p>
<pre> <code>&lt;device&gt; &lt;readingName&gt;&colon; &ltvalue&gt; # reading event <pre> <code>&lt;device&gt; &lt;readingName&gt;&colon; &ltvalue&gt; [&lt;unit&gt;] # reading event, unit optional
&lt;device&gt; &lt;value&gt; # state event</code></pre> &lt;device&gt; &lt;value&gt; [&lt;unit&gt;] # state event</code></pre>
</li> </li>
<li><a id="KNX-dpt"></a><strong>DPT - data-point-types</strong><br/> <li><a id="KNX-dpt"></a><strong>DPT - data-point-types</strong><br/>