From e23ea0f01db9e60a4d681a444fb2d3d85ea9585d Mon Sep 17 00:00:00 2001 From: erwin Date: Fri, 24 Oct 2025 12:49:28 +0000 Subject: [PATCH] 10_KNX.pm: maintenance update & a few new dpts, (Forum #122582) git-svn-id: https://svn.fhem.de/fhem/trunk@30443 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/10_KNX.pm | 250 ++++++++++++++++++++++++++------------------ 1 file changed, 151 insertions(+), 99 deletions(-) diff --git a/fhem/FHEM/10_KNX.pm b/fhem/FHEM/10_KNX.pm index e8845774c..ab4dc6e7e 100644 --- a/fhem/FHEM/10_KNX.pm +++ b/fhem/FHEM/10_KNX.pm @@ -196,8 +196,15 @@ # MH 20250201 fix dpt16 dblog-split fn # feature: new attr readingNmap - modify readingnames on the fly # internal code change: _define2, _attr, _set, _encodeByDpt -# MH 202505xx modify blink cmd logic - support widgetList -# +# MH 20250727 modify blink cmd logic - support widgetList +# MH 20251025 modify dpt10, dpt11 encode +# test for duplicate gadNames - modify on the fly +# fix dpt19 regex $PAT_DATE +# add dpt21, add sub-dpts for dpt20, cmd-ref +# modify DBLog_split unit logic +# modified dpt18 encode +# prepare for removal of $MODELERR +# # todo-4/2024 remove support for oldsyntax cmd's: raw,value,string,rgb @@ -263,7 +270,7 @@ my $SVNID = '$Id$'; ## no critic (Policy::ValuesAndExpressions::RequireInt #pattern for group-adress my $PAT_GAD = '(3[01]|[012][\d]|[\d])\/([0-7])\/(2[0-4][\d]|25[0-5]|(?:[01])?[\d]{1,2})'; # 0-31/0-7/0-255 #pattern for group-adress in hex-format -my $PAT_GAD_HEX = '[01][0-9a-f][0-7][0-9a-f]{2}'; # max is 1F7FF -> 31/7/255 5 digits +my $PAT_GADHEX = '[01][0-9a-f][0-7][0-9a-f]{2}'; # max is 1F7FF -> 31/7/255 5 digits #pattern for group-no my $PAT_GNO = qr/^g[1-9][\d]?$/xms; #pattern for GAD-Options @@ -271,16 +278,15 @@ my $PAT_GAD_OPTIONS = 'get|set|listenonly'; #pattern for GAD-suffixes my $PAT_GAD_SUFFIX = 'nosuffix'; #pattern for forbidden GAD-Names -my $PAT_GAD_NONAME = 'on|off|on-for-timer|on-until|off-for-timer|off-until|toggle|raw|rgb|string|value|get|set|listenonly|nosuffix'; +my $PAT_GAD_NONAME = qr/^((?:on|off)(?:-for-timer|-until|-till)?|toggle|blink|raw|rgb|string|value|get|set|listenonly|nosuffix)$/xms; ## no critic (RegularExpressions::ProhibitComplexRegexes) #pattern for DPT -my $PAT_GAD_DPT = 'dpt\d+\.?\d*'; +my $PAT_DPT = qr/dpt(?:\d+(?:[.]\d{3,4})?|RAW)/mxs; #pattern for dpt1 (standard) my $PAT_DPT1_PAT = 'on|off|[01]'; #pattern for date -my $PAT_DTSEP = qr/(?:_)/ixms; # date/time separator my $PAT_DATEdm = qr/^(3[01]|[1-2]\d|0?[1-9])[.](1[0-2]|0?[1-9])/ixms; # day/month -my $PAT_DATE = qr/$PAT_DATEdm[.]((?:19|20|21)\d{2})/ixms; # dpt19 year range: 1900-2155 ! -my $PAT_DATE2 = qr/$PAT_DATEdm[.](199\d|20[0-8]\d)/ixms; # dpt11 year range: 1990-2089 ! +my $PAT_DATE19 = qr/$PAT_DATEdm[.]((?:19|20|)\d{2}|21[0-4]\d|215[0-5])/xms; # dpt19 year range: 1900-2155 ! +my $PAT_DATE11 = qr/$PAT_DATEdm[.](199\d|20[0-8]\d)/ixms; # dpt11 year range: 1990-2089 ! #pattern for time my $PAT_TIME = qr/(2[0-4]|[01]{0,1}\d):([0-5]{0,1}\d):([0-5]{0,1}\d)/ixms; my $PAT_DPT16_CLR = qr/>CLR {CODE=>'dpt10', UNIT=>q{}, PATTERN=>qr/($PAT_TIME|now)/ixms, MIN=>undef, MAX=>undef}, # Date - 'dpt11' => {CODE=>'dpt11', UNIT=>q{}, PATTERN=>qr/($PAT_DATE2|now)/ixms, MIN=>undef, MAX=>undef, + 'dpt11' => {CODE=>'dpt11', UNIT=>q{}, PATTERN=>qr/($PAT_DATE11|now)/ixms, MIN=>undef, MAX=>undef, DEC=>\&dec_dpt11,ENC=>\&enc_dpt11,}, # year range 1990-2089 ! - 'dpt11.001' => {CODE=>'dpt11', UNIT=>q{}, PATTERN=>qr/($PAT_DATE2|now)/ixms, MIN=>undef, MAX=>undef}, + 'dpt11.001' => {CODE=>'dpt11', UNIT=>q{}, PATTERN=>qr/($PAT_DATE11|now)/ixms, MIN=>undef, MAX=>undef}, # 4-Octet unsigned value 'dpt12' => {CODE=>'dpt12', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,10}/xms, MIN=>0, MAX=>4294967295, @@ -548,22 +554,33 @@ my %dpttypes = ( 'dpt17.001' => {CODE=>'dpt17', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,2}/xms, MIN=>0, MAX=>63}, # Scene, 1-64 - 'dpt18' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/(activate[,]|learn[,])?[+]?\d{1,2}/xms, OFFSET=>1, MIN=>1, MAX=>64, + 'dpt18' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/(activate[,\s]|learn[,\s])?[+]?\d{1,2}/xms, OFFSET=>1, MIN=>1, MAX=>64, 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[,\s]|learn[,\s])?[+]?\d{1,2}/xms, OFFSET=>1, MIN=>1, MAX=>64, SETLIST=>'widgetList,3,select,activate,learn,1,textField'}, #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_DATE19[_]$PAT_TIME|now)/ixms, MIN=>undef, MAX=>undef, DEC=>\&dec_dpt19,ENC=>\&enc_dpt19,}, - 'dpt19.001' => {CODE=>'dpt19', UNIT=>q{}, PATTERN=>qr/($PAT_DATE$PAT_DTSEP$PAT_TIME|now)/ixms, MIN=>undef, MAX=>undef}, + 'dpt19.001' => {CODE=>'dpt19', UNIT=>q{}, PATTERN=>qr/($PAT_DATE19[_]$PAT_TIME|now)/ixms, MIN=>undef, MAX=>undef}, - # HVAC mode, 1Byte + # 1 Byte HVAC mode, Windspeed (Bf), Cloudstate 'dpt20' => {CODE=>'dpt20', UNIT=>q{}, PATTERN=>qr/(auto|comfort|standby|(economy|night)|(protection|frost|heat))/ixms, ## no critic (RegularExpressions::ProhibitComplexRegexes) MIN=>undef, MAX=>undef, SETLIST=>'Auto,Comfort,Standby,Economy,Protection', DEC=>\&dec_dpt20,ENC=>\&enc_dpt20,}, + 'dpt20.014' => {CODE=>'dpt20', UNIT=>q{}, PATTERN=>qr/(calm|light_air|(light|gentle|moderate|fresh|strong)_breeze|(near|fresh|strong)_gale|storm|violent_storm|hurricane)/ixms, ## no critic (RegularExpressions::ProhibitComplexRegexes) + MIN=>0, MAX=>12, + SETLIST=>'calm,light_air,light_breeze,gentle_breeze,moderate_breeze,fresh_breeze,strong_breeze,near_gale,fresh_gale,strong_gale,storm,violent_storm,hurricane'}, + 'dpt20.021' => {CODE=>'dpt20', UNIT=>q{}, PATTERN=>qr/(cloudless|sunny|sunshiny|lightly_cloudy|scattered_cloudy|cloudy|most_cloudy|most_overcast|overcast|sky_obstructed)/xms, ## no critic (RegularExpressions::ProhibitComplexRegexes) + MIN=>0, MAX=>9, SETLIST=>'cloudless,sunny,sunshiny,lightly_cloudy,scattered_cloudy,cloudy,most_cloudy,most_overcast,overcast,sky_obstructed'}, 'dpt20.102' => {CODE=>'dpt20', UNIT=>q{}, PATTERN=>qr/(auto|comfort|standby|(economy|night)|(protection|frost|heat))/ixms, ## no critic (RegularExpressions::ProhibitComplexRegexes) MIN=>undef, MAX=>undef, SETLIST=>'Auto,Comfort,Standby,Economy,Protection'}, + # Z8 Status info - receive only + 'dpt21' => {CODE=>'dpt21', UNIT=>q{}, PATTERN=>qr/noset/xms,MIN=>0, MAX=>31,SETLIST=>'noset', + DEC=>\&dec_dpt21,}, + 'dpt21.001' => {CODE=>'dpt21', UNIT=>q{}, PATTERN=>qr/noset/xms,MIN=>0, MAX=>31,SETLIST=>'noset',}, + 'dpt21.002' => {CODE=>'dpt21', UNIT=>q{}, PATTERN=>qr/noset/xms,MIN=>0, MAX=>7,SETLIST=>'noset',}, + # HVAC mode RHCC Status, 2Byte - receive only!!! 'dpt22' => {CODE=>'dpt22', UNIT=>q{}, PATTERN=>qr/noset/ixms, MIN=>undef, MAX=>undef, SETLIST=>'noset', DEC=>\&dec_dpt22,}, @@ -635,13 +652,22 @@ sub KNX_Define { my $hash = shift // return; my $def = shift; +### prepare for removal of $MODELERR + # replace all instances of MODELERR with dptRAW +# if ($def =~ /[:]MODEL_NOT_DEFINED/xms) { +# $def =~ s/[:]MODEL_NOT_DEFINED/[:]dptRAW/gxms; +# KNX_Log ($hash, 2, q{replaced all instances of 'MODEL_NOT_DEFINED' with 'dptRAW', pls. save config!}); +# } +### my @a = split(/[ \t\n]+/xms, $def); #enable newline within define with \ my $name = $a[0]; $hash->{NAME} = $name; $hash->{'.SVN'} = $SVNID =~ s/.+[.]pm\s(\S+\s+\S+).+/$1/rxms; #too less arguments or no valid 1st gad - if (int(@a) < 3 || $a[2] !~ m/^(?:$PAT_GAD|$PAT_GAD_HEX)[:](?:dpt|$MODELERR)/ixms) { # at least the first gad must be valid + if (scalar(@a) < 3 || $a[2] !~ m/^(?:$PAT_GAD|$PAT_GADHEX)[:](?:$PAT_DPT|$MODELERR)/ixms) { # at least the first gad must be valid +### prepare for removal of $MODELERR +# if (scalar(@a) < 3 || $a[2] !~ m/^(?:$PAT_GAD|$PAT_GADHEX)[:]$PAT_DPT/ixms) { # at least the first gad must be valid return (qq{KNX_define: wrong syntax or wrong group-format (0-31/0-7/0-255)\n} . qq{ "define $name KNX [:set|get|listenonly][:nosuffix] } . q{[[:set|get|listenonly][:nosuffix]]"}); @@ -702,6 +728,8 @@ sub KNX_Define2 { next; } +### prepare for removal of $MODELERR +# remove if cond if ($gadModel eq $MODELERR) { #within autocreate no model is supplied - throw warning KNX_Log ($name, 3, q{autocreate device will be disabled, correct def with valid dpt and enable device}); if (AttrNum($name,'disable',0) != 1) {$attr{$name}->{disable} = 1;} @@ -726,11 +754,20 @@ sub KNX_Define2 { push(@logarr,q{syntax or parameter error in options definition: } . join(q{:},@gadArgs)); } - if (($gadName =~ /^($PAT_GAD_NONAME)$/xms) || ($gadName eq q{state} && defined($gadNoSuffix))) { # allow mixed case + if (($gadName =~ /$PAT_GAD_NONAME/xms) || ($gadName eq q{state} && defined($gadNoSuffix))) { # allow mixed case push(@logarr,qq{forbidden gadName: $gadName - modified to: g} . $gadNo); $gadName = q{g} . $gadNo; } + # test for duplicate gadname + foreach my $gval (values %{$hash->{GADTABLE}}) { + next if ($gval ne $gadName); + $gadName .= q{_} . $gadNo; + push(@logarr,qq{duplicate gadName: $gval - modified to: $gadName}); + $hash->{DEF} =~ s/($gad[:]$gadModel[:])$gval/$1$gadName/xms; # alter def + last; + } + $hash->{GADTABLE}->{$gadCode} = $gadName; #add key and value to GADTABLE # create readingnames @@ -932,7 +969,8 @@ sub KNX_Set_oldsyntax { #select another group, if the last arg starts with a g if($na >= 1 && $arg[$na - 1] =~ m/$PAT_GNO/xms) { $groupnr = pop (@arg); - KNX_Log ($name, 3, q{you are still using old syntax, pls. change to "set } . qq{$name $groupnr $cmd } . q{"}); + KNX_Log ($name, 3, q{you are still using old syntax, pls. change to "set } . + qq{$name $groupnr $cmd } . join(q{\s},@arg) . q{"}); $groupnr =~ s/^[g]//ixms; #remove "g" $na--; } @@ -1151,8 +1189,7 @@ sub KNX_Attr { if ($aName eq 'disable') { my @defentries = split(/[\s\t\n]+/xms,$hash->{DEF}); foreach my $def (@defentries) { # check all entries -# next if ($def eq ReadingsVal($name,'IODev',undef)); # deprecated IOdev - next if ($def =~ /:dpt(?:[\d+]|RAW)/xms); + next if ($def =~ /$PAT_DPT/xms); return qq{Attribut "disable" cannot be deleted for device $name until you specify a valid dpt!}; } delete $hash->{RAWMSG}; # debug internal @@ -1227,11 +1264,10 @@ sub KNX_DbLog_split { my $devhash = $defs{$device}; foreach my $key (keys %{$devhash->{GADDETAILS}}) { next if ($devhash->{GADDETAILS}->{$key}->{MODEL} !~ /^dpt16/xms); - next if ($reading ne q{state} && - $reading ne $devhash->{GADDETAILS}->{$key}->{RDNAMESET} && - $reading ne $devhash->{GADDETAILS}->{$key}->{RDNAMEGET}); - $dpt16flag = 1; - last; + if ($reading =~ /state|$devhash->{GADDETAILS}->{$key}->{RDNAMESET}|$devhash->{GADDETAILS}->{$key}->{RDNAMEGET}/xms) { + $dpt16flag = 1; + last; + } } if (($dpt16flag == 0) && Scalar::Util::looks_like_number($strings[0]) && @@ -1403,7 +1439,9 @@ sub KNX_autoCreate { my $igntypes = AttrVal($acdev,'ignoreTypes',q{}); return q{} if($newDevName =~ /$igntypes/xms); } +### prepare for removal of $MODELERR return qq{UNDEFINED $newDevName KNX $gad} . q{:} . $MODELERR; +# return qq{UNDEFINED $newDevName KNX $gad} . q{:dptRAW}; } ### KNX_SetReadings is called from KNX_Set and KNX_Parse @@ -1554,7 +1592,7 @@ sub KNX_hex2Name { sub KNX_name2gadCode { my $gad = shift // return; - if ($gad =~ m/^$PAT_GAD_HEX$/ixms) {$gad = KNX_hex2Name($gad);} # called with hex-option + if ($gad =~ m/^$PAT_GADHEX$/ixms) {$gad = KNX_hex2Name($gad);} # called with hex-option if ($gad =~ m/^$PAT_GAD$/xms) { return sprintf('%02x%01x%02x',$1,$2,$3); } @@ -1620,7 +1658,7 @@ sub KNX_makeRdNames { my $nosuffix = shift; my $rdNameSet = $gadName; - if (exists ($hash->{Helper}->{RDNAMEMAP}->{$gadName})) { + if (exists ($hash->{Helper}->{RDNAMEMAP}) && exists ($hash->{Helper}->{RDNAMEMAP}->{$gadName})) { $rdNameSet = $hash->{Helper}->{RDNAMEMAP}->{$gadName}; } @@ -1731,16 +1769,12 @@ sub KNX_encodeByDpt { if ($code eq 'dpt10' && $value =~ /^[\d]{2}:[\d]{2}$/xms) {$value .= ':00';} # support dpt18 learn - my $arg1 = undef; - my $arg2 = undef; + my $dpt18flag = 0; if ($code eq 'dpt18') { - if ($value =~ /(activate|learn)$/xms) { - $value = shift(@arg); # blank separated - $arg1 = $1; - } - else { - ($arg1, $arg2) = split(/[,]/xms,$value); # widget or just number? - $value = (defined($arg2))?$arg2:$arg1; + if (defined($arg[0])) {$value .= q{ } . $arg[0];} # blank separated + if ($value =~ /(?:(activate|learn)[,\s])?(\d+)$/xms) { + $value = $2; + if (defined($1) && ($1 eq q{learn})) {$dpt18flag = 1}; } } @@ -1757,7 +1791,8 @@ sub KNX_encodeByDpt { if (ref($dpttypes{$code}->{ENC}) eq 'CODE') { my $hexval = $dpttypes{$code}->{ENC}->($lvalue, $model); - if ($code eq 'dpt18' && $arg1 =~ /^learn/xms) {$hexval = sprintf('00%.2x',hex($hexval) + 0x80);} +# if ($code eq 'dpt18' && $arg1 =~ /^learn/xms) {$hexval = sprintf('00%.2x',hex($hexval) + 0x80);} + if ($dpt18flag == 1) {$hexval = sprintf('00%.2x',hex($hexval) + 0x80);} KNX_Log ($name, 5, qq{gadName= $gadName model= $model } . qq{in-Value= $value out-Value= $lvalue out-ValueHex= $hexval}); return $hexval; @@ -1872,44 +1907,35 @@ sub enc_dpt9 { #2-Octet Float value sub enc_dpt10 { #Time of Day my $value = shift; - my $numval = 0; my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); # default now if ($value =~ /$PAT_TIME/ixms) { ($hours,$mins,$secs) = split(/[:]/ixms,$value); my $ts = fhemTimeLocal($secs, $mins, $hours, $mday, $mon, $year); ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($ts); } - #add offsets - $year += 1900; - $mon++; # calculate offset for weekday if ($wday == 0) {$wday = 7;} - $hours += 32 * $wday; - $numval = $secs + ($mins << 8) + ($hours << 16); - - return sprintf('00%.6x',$numval); + $hours += ($wday << 5); + return sprintf('00%.2x%.2x%.2x',$hours,$mins,$secs); } sub enc_dpt11 { #Date year range is 1990-2089 {0 => 2000 , 89 => 2089, 90 => 1990} my $value = shift; - my $numval = 0; + my ($day, $month, $year); if ($value =~ m/now/ixms) { #get actual time - my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + my ($secs,$mins,$hours,$mday,$mon,$yr,$wday,$yday,$isdst) = localtime(time); #add offsets - $year+=1900; - $mon++; - # calculate offset for weekday - if ($wday == 0) {$wday = 7;} - $numval = ($year - 2000) + ($mon << 8) + ($mday << 16); + $year = $yr - 100; # based on 1900 + $month = $mon + 1; + $day = $mday; } else { - my ($dd, $mm, $yyyy) = split (/[.]/xms, $value); - $yyyy -= 2000; - if ($yyyy < 0) {$yyyy += 100;} - $numval = ($yyyy) + ($mm << 8) + ($dd << 16); + ($day, $month, $year) = split (/[.]/xms, $value); + $year -= 2000; + if ($year < 0) {$year += 100;} } - return sprintf('00%.6x',$numval); + return sprintf('00%.2x%.2x%.2x',$day,$month,$year); } sub enc_dpt12 { #4-Octet unsigned value @@ -1945,7 +1971,7 @@ sub enc_dpt16 { #14-Octet String sub enc_dpt18 { # scene 10/2024 allow activate & learn my $value = shift; - $value = ($value & 0x3f); +# $value = ($value & 0x3f); return sprintf('00%.2x',$value); } @@ -1953,7 +1979,7 @@ sub enc_dpt19 { #DateTime my $value = shift; my $ts = time; # default or when "now" is given # if no match we assume now and use current date/time - if ($value =~ m/^$PAT_DATE$PAT_DTSEP$PAT_TIME/xms) { + if ($value =~ m/^$PAT_DATE19[_]$PAT_TIME/xms) { $ts = fhemTimeLocal($6, $5, $4, $1, $2-1, $3 - 1900); } my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($ts); @@ -1967,12 +1993,16 @@ sub enc_dpt19 { #DateTime return sprintf('00%02x%02x%02x%02x%02x%02x%02x%02x',$year,$mon,$mday,$hours,$mins,$secs,$status1,$status0); } -sub enc_dpt20 { # HVAC 1Byte +sub enc_dpt20 { # HVAC, Wind (Bf), Clouds - 1Byte my $value = shift; - my $dpt20list = {auto => 0, comfort => 1, standby => 2, economy => 3, protection => 4,}; - my $numval = $dpt20list->{lc($value)}; - if (! defined($numval)) {$numval = 5;} - return sprintf('00%.2x',$numval); + my $model = shift; + my @dpt_txt = split(/[,]/xms,$dpttypes{$model}->{SETLIST}); + my $i = 0; + foreach (@dpt_txt) { + last if ($_ eq $value); + $i++; + } + return sprintf('00%.2x',$i); } sub enc_dpt232 { #RGB-Code @@ -2172,12 +2202,30 @@ sub dec_dpt19 { #DateTime return sprintf('%02d.%02d.%04d_%02d:%02d:%02d', $day, $month, $year, $hours, $mins, $secs); } -sub dec_dpt20 { #HVAC +sub dec_dpt20 { #HVAC, Wind, Clouds my $numval = hex (shift); - $numval = ($numval & 0x07); # mask any non-std bits! - my @dpt20_102txt = qw(Auto Comfort Standby Economy Protection reserved); - if ($numval > 4) {$numval = 5;} # dpt20.102 - return $dpt20_102txt[$numval]; + my $model = shift; + my @dpt_txt = split(/[,]/xms,$dpttypes{$model}->{SETLIST}); + return q{reserved} if ($numval >= scalar(@dpt_txt)); + return $dpt_txt[$numval]; +} + +sub dec_dpt21 { # Z8 status info 5bit + my $numval = hex (shift); + my $model = shift; + if ($model eq 'dpt21') {return sprintf('%.8b',$numval)}; # binary bits + my $state = q{}; + $numval = $numval & 0x1f; # 5 bits + return q{ok} if ($numval == 0); + my @dpt_txt = qw(invalid_dpt); + if ($model eq 'dpt21.001') {@dpt_txt = qw(OutofService Fault Overridden InAlarm AlarmUnack);} + elsif ($model eq 'dpt21.002') {@dpt_txt = qw(UserStopped OwnIA VerifyMode);} + foreach (@dpt_txt) { + if (($numval & 0x01) == 0x01) {$state .= $_ . q{,} }; + $numval = $numval >> 1; + } + $state =~ s/[,]$//xms; + return $state; } sub dec_dpt22 { #HVAC dpt22.101 only @@ -2226,17 +2274,13 @@ sub dec_dptRAW { # entry: $hash, desired gadNO # return: undef on error / gadName sub KNX_gadNameByNO { - my $hash = shift; - my $groupnr = shift // 1; # default: search for g1 + my $hash = shift; + my $groupnr = shift // 1; # default: search for g1 - my $targetGadName = undef; - foreach my $key (keys %{$hash->{GADDETAILS}}) { - if ($hash->{GADDETAILS}->{$key}->{NO} == $groupnr) { - $targetGadName = $key; - last; - } + foreach my $targetGadName (keys %{$hash->{GADDETAILS}}) { + return $targetGadName if ($hash->{GADDETAILS}->{$targetGadName}->{NO} == $groupnr); } - return $targetGadName; + return; } ### unified Log handling @@ -2297,6 +2341,7 @@ sub main::KNX_scan { foreach my $key (keys %{$devhash->{GADDETAILS}}) { last if (! defined($key)); next if($devhash->{GADDETAILS}->{$key}->{MODEL} eq $MODELERR); +# next if($devhash->{GADDETAILS}->{$key}->{MODEL} eq q{dptRAW}); my $option = $devhash->{GADDETAILS}->{$key}->{OPTION}; next if (defined($option) && $option =~ /(?:set|listenonly)/ixms); $k++; @@ -2415,13 +2460,13 @@ This module provides a basic set of operations (on, off, toggle, on-until, on-fo correct value and optional unit.

The KNX-Bus protocol defines three basic commands, any KNX-device, including FHEM, has to support:

    -
  1. write - send a msg to KNX-bus - FHEM implementation: set cmd
  2. -
  3. read  - send request for a msg to another KNX-device - FHEM implementation: get cmd
  4. +
  5. write - send a msg to a KNX-bus device - FHEM implementation: set cmd
  6. +
  7. read  - send request for an answer from a KNX-bus device - FHEM implementation: get cmd
  8. reply - send answer when receiving a read-cmd from KNX-Bus - FHEM implementation: putCmd attribute

-

For each received message there will be a reading containing the received value and the sender address.
-For every set, there will be a reading containing the sent value.
+

For each received message or get-cmd there will be a reading containing the received value and the sender address.
+For every set-cmd, there will be a reading containing the sent value.
The reading <state> will be updated with the last sent or received value. 

A (german) wiki page is avaliable here: FHEM Wiki

@@ -2440,9 +2485,10 @@ The reading <state> will be updated with the last sent or received value.&

If <gadName> not specified, the default is "g<number>". The corresponding reading-names are getG<number> and setG<number>.
The optional parameteter <gadName> may contain an alias for the GAD. The following gadNames are not allowed: - 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, blink, raw, rgb, string, value, set, get, listenonly, nosuffix - because of conflict with cmds & parameters.
-If you supply <gadName> this name is used instead as cmd prefix. The reading-names are <gadName>-get and <gadName>-set. +If you supply <gadName> this name is used instead as cmd prefix. <gadName> has to be unique within a device definition.
+ The reading-names are <gadName>-get and <gadName>-set. The synonyms <getName> and <setName> are used in this documentation. Reading-names may be modified by attribute readingNmap, see examples.
If you add the option "nosuffix", <getName> and <setName> have the identical name - <gadName>. @@ -2595,9 +2641,10 @@ Examples: having more fun with icons, ...

 Examples:
-   attr <device> stateRegex /position:0/aus/ # on update of reading "position" and value=0, reading state value will be "aus"
-   attr <device> stateRegex /position/pos-/  # on update of reading "position" reading state value will be prepended with "pos-".
-   attr <device> stateRegex /desired-pos//   # reading state will NOT get updated when reading "desired-pos" is updated.
+   attr <device> stateRegex /position:0/aus/   # on update of reading "position" and value=0, reading state value will be "aus"
+   attr <device> stateRegex /position:(.*)/$1/ # on update of reading "position" reading state is updated with reading position value".
+   attr <device> stateRegex /position/pos-/    # on update of reading "position" reading state value will be prepended with "pos-".
+   attr <device> stateRegex /desired-pos//     # reading state will NOT get updated when reading "desired-pos" is updated.
 
  • stateCmd
    @@ -2675,11 +2722,11 @@ Examples:
  • DPT - data-point-types

    The following dpt are implemented and have to be assigned within the device definition. - The values right to the dpt define the valid range of Set-command values and Get-command return values and units.

    + The values right to the dpt define the valid range or min/max Set-command values and Get-command return values and units.

      -
    1. dpt1 off, on, toggle - see Note 1
    2. +
    3. dpt1 off, on, blink, toggle, timer functions - Note 1
    4. dpt1.000 0, 1
    5. -
    6. dpt1.001 off, on, toggle
    7. +
    8. dpt1.001 off, on, blink, toggle, timer functions
    9. dpt1.002 false, true
    10. dpt1.003 disable, enable
    11. dpt1.004 no_ramp, ramp
    12. @@ -2706,20 +2753,20 @@ Examples:
    13. dpt2 off, on, forceOff, forceOn
    14. dpt2.000 0,1,2,3
    15. dpt3 -100..+100
    16. -
    17. dpt3.007 -100..+100 %
    18. -
    19. dpt3.008 -100..+100 %
    20. +
    21. dpt3.007 -100..+100 %
    22. +
    23. dpt3.008 -100..+100 %
    24. dpt4 single ASCII char
    25. dpt4.001 single ASCII char
    26. dpt4.002 single ISO-8859-1 char
    27. dpt5 0..255
    28. -
    29. dpt5.001 0..100 %
    30. -
    31. dpt5.003 0..360 °
    32. -
    33. dpt5.004 0..255 %
    34. +
    35. dpt5.001 0..100 %
    36. +
    37. dpt5.003 0..360 °
    38. +
    39. dpt5.004 0..255 %
    40. dpt5.005 0..255 (decimal factor)
    41. dpt5.006 0..255 (tariff info)
    42. dpt5.010 0..255 p (pulsecount)
    43. dpt6 -128..+127
    44. -
    45. dpt6.001 -128 %..+127 %
    46. +
    47. dpt6.001 -128..+127 %
    48. dpt6.010 -128..+127 p (pulsecount)
    49. dpt7 0..65535
    50. dpt7.001 0..65535 p (pulsecount)
    51. @@ -2741,7 +2788,7 @@ Examples:
    52. dpt8.005 -32768..32767 s
    53. dpt8.006 -32768..32767 min
    54. dpt8.007 -32768..32767 h
    55. -
    56. dpt8.010 -32768..32767 %
    57. +
    58. dpt8.010 -32768..32767 %
    59. dpt8.011 -32768..32767 °
    60. dpt8.012 -32768..32767 m
    61. dpt9 -671088.64..+670433.28
    62. @@ -2751,7 +2798,7 @@ Examples:
    63. dpt9.004 0..+670433.28 lux
    64. dpt9.005 0..+670433.28 m/s
    65. dpt9.006 0..+670433.28 Pa
    66. -
    67. dpt9.007 0..+670433.28 %
    68. +
    69. dpt9.007 0..+670433.28 %
    70. dpt9.008 0..+670433.28 ppm
    71. dpt9.009 -671088.64..+670433.28 m³/h
    72. dpt9.010 -671088.64..+670433.28 s
    73. @@ -2863,7 +2910,7 @@ Examples:
    74. dpt14.074 -1.4e-45..+1.7e+38 s (time)
    75. dpt14.075 -1.4e-45..+1.7e+38 Nm (torque)
    76. dpt14.076 -1.4e-45..+1.7e+38 m³ (volume)
    77. -
    78. dpt14.077 -1.4e-45..+1.7e+38 m³/s (volume flux)
    79. +
    80. dpt14.077 -1.4e-45..+1.7e+38 m³/s (volume flux)
    81. dpt14.078 -1.4e-45..+1.7e+38 N (weight)
    82. dpt14.079 -1.4e-45..+1.7e+38 J (work)
    83. dpt14.080 -1.4e-45..+1.7e+38 VA (apparent power)
    84. @@ -2876,7 +2923,12 @@ Examples:
    85. dpt18.001 [(activate|learn),]1..64
    86. dpt19 DD.MM.YYYY_HH:MM:SS (Date&Time combined)
    87. dpt19.001 DD.MM.YYYY_HH:MM:SS (Date&Time combined)
    88. +
    89. dpt20.014 Windforce (Beaufort)
    90. +
    91. dpt20.021 Cloud Cover
    92. dpt20.102 HVAC mode
    93. +
    94. dpt21 status - bitstring (readonly)
    95. +
    96. dpt21.001 Z8 status info (readonly)
    97. +
    98. dpt21.002 Device control (readonly)
    99. dpt22.101 HVAC RHCC Status (readonly)
    100. dpt217.001 dpt version (readonly)
    101. dpt221 MFGCode.SerialNr (readony)