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:
-- write - send a msg to KNX-bus - FHEM implementation: set cmd
-- read - send request for a msg to another KNX-device - FHEM implementation: get cmd
+- write - send a msg to a KNX-bus device - FHEM implementation: set cmd
+- read - send request for an answer from a KNX-bus device - FHEM implementation: get cmd
- 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.
-- dpt1 off, on, toggle - see Note 1
+- dpt1 off, on, blink, toggle, timer functions - Note 1
- dpt1.000 0, 1
-- dpt1.001 off, on, toggle
+- dpt1.001 off, on, blink, toggle, timer functions
- dpt1.002 false, true
- dpt1.003 disable, enable
- dpt1.004 no_ramp, ramp
@@ -2706,20 +2753,20 @@ Examples:
- dpt2 off, on, forceOff, forceOn
- dpt2.000 0,1,2,3
- dpt3 -100..+100
-- dpt3.007 -100..+100 %
-- dpt3.008 -100..+100 %
+- dpt3.007 -100..+100 %
+- dpt3.008 -100..+100 %
- dpt4 single ASCII char
- dpt4.001 single ASCII char
- dpt4.002 single ISO-8859-1 char
- dpt5 0..255
-- dpt5.001 0..100 %
-- dpt5.003 0..360 °
-- dpt5.004 0..255 %
+- dpt5.001 0..100 %
+- dpt5.003 0..360 °
+- dpt5.004 0..255 %
- dpt5.005 0..255 (decimal factor)
- dpt5.006 0..255 (tariff info)
- dpt5.010 0..255 p (pulsecount)
- dpt6 -128..+127
-- dpt6.001 -128 %..+127 %
+- dpt6.001 -128..+127 %
- dpt6.010 -128..+127 p (pulsecount)
- dpt7 0..65535
- dpt7.001 0..65535 p (pulsecount)
@@ -2741,7 +2788,7 @@ Examples:
- dpt8.005 -32768..32767 s
- dpt8.006 -32768..32767 min
- dpt8.007 -32768..32767 h
-- dpt8.010 -32768..32767 %
+- dpt8.010 -32768..32767 %
- dpt8.011 -32768..32767 °
- dpt8.012 -32768..32767 m
- dpt9 -671088.64..+670433.28
@@ -2751,7 +2798,7 @@ Examples:
- dpt9.004 0..+670433.28 lux
- dpt9.005 0..+670433.28 m/s
- dpt9.006 0..+670433.28 Pa
-- dpt9.007 0..+670433.28 %
+- dpt9.007 0..+670433.28 %
- dpt9.008 0..+670433.28 ppm
- dpt9.009 -671088.64..+670433.28 m³/h
- dpt9.010 -671088.64..+670433.28 s
@@ -2863,7 +2910,7 @@ Examples:
- dpt14.074 -1.4e-45..+1.7e+38 s (time)
- dpt14.075 -1.4e-45..+1.7e+38 Nm (torque)
- dpt14.076 -1.4e-45..+1.7e+38 m³ (volume)
-- dpt14.077 -1.4e-45..+1.7e+38 m³/s (volume flux)
+- dpt14.077 -1.4e-45..+1.7e+38 m³/s (volume flux)
- dpt14.078 -1.4e-45..+1.7e+38 N (weight)
- dpt14.079 -1.4e-45..+1.7e+38 J (work)
- dpt14.080 -1.4e-45..+1.7e+38 VA (apparent power)
@@ -2876,7 +2923,12 @@ Examples:
- dpt18.001 [(activate|learn),]1..64
- dpt19 DD.MM.YYYY_HH:MM:SS (Date&Time combined)
- dpt19.001 DD.MM.YYYY_HH:MM:SS (Date&Time combined)
+- dpt20.014 Windforce (Beaufort)
+- dpt20.021 Cloud Cover
- dpt20.102 HVAC mode
+- dpt21 status - bitstring (readonly)
+- dpt21.001 Z8 status info (readonly)
+- dpt21.002 Device control (readonly)
- dpt22.101 HVAC RHCC Status (readonly)
- dpt217.001 dpt version (readonly)
- dpt221 MFGCode.SerialNr (readony)