mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-25 16:05:19 +00:00
10_KNX.pm: multiple fixes/internal changes & dpt18 learn support, (Forum #122582)
git-svn-id: https://svn.fhem.de/fhem/trunk@29271 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
81c8f4e759
commit
deac5ece85
@ -179,6 +179,14 @@
|
|||||||
# enforce gadName rules
|
# enforce gadName rules
|
||||||
# prevent set- and get-cmd during fhem-start (e.g. in fhem.cfg)
|
# prevent set- and get-cmd during fhem-start (e.g. in fhem.cfg)
|
||||||
# change dpt16 encoding - fix dblogsplit for dpt16
|
# change dpt16 encoding - fix dblogsplit for dpt16
|
||||||
|
# MH 20241020 replace gettimeofday w. Time::HiRes::time
|
||||||
|
# replace encode & decode w. Encode::encode & Encode::decode
|
||||||
|
# replace looks_like_number w. Scalar::Util::looks_like_number
|
||||||
|
# prevent subsecond parameter in (on|off)-for-timer
|
||||||
|
# rework KNX_Set_dpt1 logic
|
||||||
|
# correct dpt1.009, dpt1.024, dpt1.100
|
||||||
|
# add dpt18 "learn" - still experimental
|
||||||
|
# cmd-ref update
|
||||||
#
|
#
|
||||||
# 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
|
||||||
|
|
||||||
@ -188,7 +196,7 @@ package KNX; ## no critic 'package'
|
|||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
use Encode qw(encode decode);
|
use Encode qw(encode decode);
|
||||||
use Time::HiRes qw(gettimeofday);
|
use Time::HiRes qw(time);
|
||||||
use Scalar::Util qw(looks_like_number);
|
use Scalar::Util qw(looks_like_number);
|
||||||
use GPUtils qw(GP_Import); # Package Helper Fn
|
use GPUtils qw(GP_Import); # Package Helper Fn
|
||||||
|
|
||||||
@ -248,7 +256,8 @@ my $PAT_GAD = '(?:3[01]|([012])?[0-9])\/(?:[0-7])\/(?:2[0-4][0-9]|25[0-5]|([01])
|
|||||||
#pattern for group-adress in hex-format
|
#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_GAD_HEX = '[01][0-9a-f][0-7][0-9a-f]{2}'; # max is 1F7FF -> 31/7/255 5 digits
|
||||||
#pattern for group-no
|
#pattern for group-no
|
||||||
my $PAT_GNO = 'g[1-9][0-9]?';
|
my $PAT_GNO = qr/^g[1-9][\d]?$/xms;
|
||||||
|
#my $PAT_GNO = 'g[1-9][0-9]?';
|
||||||
#pattern for GAD-Options
|
#pattern for GAD-Options
|
||||||
my $PAT_GAD_OPTIONS = 'get|set|listenonly';
|
my $PAT_GAD_OPTIONS = 'get|set|listenonly';
|
||||||
#pattern for GAD-suffixes
|
#pattern for GAD-suffixes
|
||||||
@ -258,7 +267,7 @@ my $PAT_GAD_NONAME = 'on|off|on-for-timer|on-until|off-for-timer|off-until|toggl
|
|||||||
#pattern for DPT
|
#pattern for DPT
|
||||||
my $PAT_GAD_DPT = 'dpt\d+\.?\d*';
|
my $PAT_GAD_DPT = 'dpt\d+\.?\d*';
|
||||||
#pattern for dpt1 (standard)
|
#pattern for dpt1 (standard)
|
||||||
my $PAT_DPT1_PAT = 'on|off|[01]$';
|
my $PAT_DPT1_PAT = 'on|off|[01]';
|
||||||
#pattern for date
|
#pattern for date
|
||||||
my $PAT_DTSEP = qr/(?:_)/ixms; # date/time separator
|
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_DATEdm = qr/^(3[01]|[1-2]\d|0?[1-9])[.](1[0-2]|0?[1-9])/ixms; # day/month
|
||||||
@ -303,8 +312,8 @@ my %dpttypes = (
|
|||||||
'dpt1.021' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|logical_or|logical_and)/ixms, MIN=>'logical_or', MAX=>'logical_and'},
|
'dpt1.021' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|logical_or|logical_and)/ixms, MIN=>'logical_or', MAX=>'logical_and'},
|
||||||
'dpt1.022' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|scene_A|scene_B)/ixms, MIN=>'scene_A', MAX=>'scene_B'},
|
'dpt1.022' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|scene_A|scene_B)/ixms, MIN=>'scene_A', MAX=>'scene_B'},
|
||||||
'dpt1.023' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|move_(up_down|and_step_mode))/ixms, MIN=>'move_up_down', MAX=>'move_and_step_mode'},
|
'dpt1.023' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|move_(up_down|and_step_mode))/ixms, MIN=>'move_up_down', MAX=>'move_and_step_mode'},
|
||||||
'dpt1.024' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|Day|Night)/ixms, MIN=>'Day', MAX=>'Night'},
|
'dpt1.024' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|Day|Night)/ixms, MIN=>'day', MAX=>'night'},
|
||||||
'dpt1.100' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|Heat|Cool)/ixms, MIN=>'Heat', MAX=>'Cool'},
|
'dpt1.100' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|heating|cooling)/ixms, MIN=>'cooling', MAX=>'heating'},
|
||||||
|
|
||||||
#Step value (two-bit)
|
#Step value (two-bit)
|
||||||
'dpt2' => {CODE=>'dpt2', UNIT=>q{}, PATTERN=>qr/(on|off|forceon|forceoff)/ixms, MIN=>undef, MAX=>undef, SETLIST=>'on,off,forceon,forceoff',
|
'dpt2' => {CODE=>'dpt2', UNIT=>q{}, PATTERN=>qr/(on|off|forceon|forceoff)/ixms, MIN=>undef, MAX=>undef, SETLIST=>'on,off,forceon,forceoff',
|
||||||
@ -526,14 +535,15 @@ my %dpttypes = (
|
|||||||
'dpt16.001' => {CODE=>'dpt16', UNIT=>q{}, PATTERN=>qr/(?:[[:ascii:]]|[\xC2-\xF4][\x80-\xBF]{1,3}){1,}/ixms, MIN=>undef, MAX=>undef, SETLIST=>'multiple,>CLR<'},
|
'dpt16.001' => {CODE=>'dpt16', UNIT=>q{}, PATTERN=>qr/(?:[[:ascii:]]|[\xC2-\xF4][\x80-\xBF]{1,3}){1,}/ixms, MIN=>undef, MAX=>undef, SETLIST=>'multiple,>CLR<'},
|
||||||
|
|
||||||
# Scene, 0-63
|
# Scene, 0-63
|
||||||
'dpt17' => {CODE=>'dpt5', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, MIN=>0, MAX=>63,
|
'dpt17' => {CODE=>'dpt17', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, MIN=>0, MAX=>63,
|
||||||
DEC=>\&dec_dpt5,ENC=>\&enc_dpt5,},
|
DEC=>\&dec_dpt18,ENC=>\&enc_dpt18,},
|
||||||
'dpt17.001' => {CODE=>'dpt5', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, MIN=>0, MAX=>63},
|
'dpt17.001' => {CODE=>'dpt17', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, MIN=>0, MAX=>63},
|
||||||
|
|
||||||
# Scene, 1-64
|
# Scene, 1-64
|
||||||
'dpt18' => {CODE=>'dpt5', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, OFFSET=>1, MIN=>1, MAX=>64,
|
'dpt18' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, OFFSET=>1, MIN=>1, MAX=>64,
|
||||||
DEC=>\&dec_dpt5,ENC=>\&enc_dpt5,},
|
DEC=>\&dec_dpt18,ENC=>\&enc_dpt18,},
|
||||||
'dpt18.001' => {CODE=>'dpt5', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, OFFSET=>1, MIN=>1, MAX=>64},
|
'dpt18.001' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, OFFSET=>1, MIN=>1, MAX=>64},
|
||||||
|
'dpt18.099' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/(activate|learn)?[,]?\d{1,3}/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,
|
||||||
@ -634,7 +644,7 @@ sub KNX_Define {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$hash->{'.DEFLINE'} = join(q{ },@a[2 .. $#a]); # temp store defs for define2...
|
$hash->{'.DEFLINE'} = join(q{ },@a[2 .. $#a]); # temp store defs for define2...
|
||||||
return InternalTimer(gettimeofday() + 3.0,\&KNX_Define2,$hash) if (! $init_done);
|
return InternalTimer(Time::HiRes::time() + 3.0,\&KNX_Define2,$hash) if (! $init_done);
|
||||||
return KNX_Define2($hash);
|
return KNX_Define2($hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -700,7 +710,7 @@ sub KNX_Define2 {
|
|||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
elsif ($gadNo == 1) { # gadModel ok - use first gad as mdl reference for fheminfo
|
elsif ($gadNo == 1) { # gadModel ok - use first gad as mdl reference for fheminfo
|
||||||
$hash->{model} = lc($gadModel) =~ s/^(dpt[\d]+)(?:[.].+)?/$1/rxms;
|
$hash->{model} = $dpttypes{$gadModel}->{CODE};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scalar(@gadArgs)) {
|
if (scalar(@gadArgs)) {
|
||||||
@ -751,7 +761,7 @@ sub KNX_Define2 {
|
|||||||
elsif (defined ($min) && $gadModel =~ /^(?:dpt5|dpt6)/xms) { # slider
|
elsif (defined ($min) && $gadModel =~ /^(?:dpt5|dpt6)/xms) { # slider
|
||||||
$setlist = ':slider,' . $min . q{,1,} . $max;
|
$setlist = ':slider,' . $min . q{,1,} . $max;
|
||||||
}
|
}
|
||||||
elsif (defined ($min) && (! looks_like_number($min))) { #on/off/...
|
elsif (defined ($min) && (! Scalar::Util::looks_like_number($min))) { #on/off/...
|
||||||
$setlist = q{:} . $min . q{,} . $max;
|
$setlist = q{:} . $min . q{,} . $max;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -883,7 +893,7 @@ sub KNX_Set {
|
|||||||
return qq{$name 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, @arg) = KNX_Set_oldsyntax($hash,$targetGadName,@arg);
|
||||||
return qq{$name $err} if defined($err);
|
return qq{$name $err} if defined($err);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -900,10 +910,6 @@ sub KNX_Set {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 - truncate to 14 char
|
|
||||||
if ($model =~ m/^dpt16/xms) {
|
|
||||||
$value .= q{ } . join (q{ }, @arg) if (scalar (@arg) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#Special commands for dpt1 and dpt1.001
|
#Special commands for dpt1 and dpt1.001
|
||||||
if ($model =~ m/^(?:dpt1|dpt1.001)$/xms) {
|
if ($model =~ m/^(?:dpt1|dpt1.001)$/xms) {
|
||||||
@ -911,6 +917,11 @@ 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
|
||||||
|
elsif ($model =~ m/^dpt16(?:[.][\d]{3})?$/xms) {
|
||||||
|
$value .= q{ } . join (q{ }, @arg) if (scalar (@arg) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
my $transval = KNX_encodeByDpt($hash, $value, $targetGadName); #process set command
|
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
|
||||||
|
|
||||||
@ -919,7 +930,7 @@ 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
|
||||||
$value = KNX_decodeByDpt($hash, $transval, $targetGadName) if ($model =~ m/^dpt(?:3|10|11|16|19)/xms);
|
$value = KNX_decodeByDpt($hash, $transval, $targetGadName) if ($model =~ m/^dpt(?:3|10|11|16|18|19)(?:[.][\d]{3})?$/xms);
|
||||||
|
|
||||||
#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);
|
||||||
@ -929,7 +940,7 @@ sub KNX_Set {
|
|||||||
|
|
||||||
# Process set command for old syntax
|
# Process set command for old syntax
|
||||||
# calling param: $hash, $cmd, arg array
|
# calling param: $hash, $cmd, arg array
|
||||||
# returns ($err, targetgadname, $cmd)
|
# returns ($err, targetgadname, $cmd, @arg - array might be modified)
|
||||||
sub KNX_Set_oldsyntax {
|
sub KNX_Set_oldsyntax {
|
||||||
my ($hash, $cmd, @arg) = @_;
|
my ($hash, $cmd, @arg) = @_;
|
||||||
|
|
||||||
@ -939,16 +950,16 @@ sub KNX_Set_oldsyntax {
|
|||||||
my $groupnr = 1; #default group
|
my $groupnr = 1; #default group
|
||||||
|
|
||||||
#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/xms) {
|
||||||
$groupnr = pop (@arg);
|
$groupnr = pop (@arg);
|
||||||
KNX_Log ($name, 3, qq{you are still using old syntax, pls. change to "set $name $groupnr $cmd } . join(q{ },@arg) . q{"});
|
KNX_Log ($name, 3, q{you are still using old syntax, pls. change to "set } . qq{$name $groupnr $cmd } . q{<value>"});
|
||||||
$groupnr =~ s/^[g]//ixms; #remove "g"
|
$groupnr =~ s/^[g]//ixms; #remove "g"
|
||||||
$na--;
|
$na--;
|
||||||
}
|
}
|
||||||
|
|
||||||
# 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 =~ /^$PAT_GNO/ixms) {
|
if ($cmd =~ /$PAT_GNO/xms) {
|
||||||
KNX_Log ($name, 2, qq{an invalid gadName: $cmd or invalid dpt used in set-cmd});
|
KNX_Log ($name, 2, qq{an invalid gadName: $cmd or invalid dpt used in set-cmd});
|
||||||
return qq{an invalid gadName: $cmd or invalid dpt used in set-cmd};
|
return qq{an invalid gadName: $cmd or invalid dpt used in set-cmd};
|
||||||
}
|
}
|
||||||
@ -959,42 +970,41 @@ sub KNX_Set_oldsyntax {
|
|||||||
# all of the following cmd's need at least 1 Argument (or more)
|
# all of the following cmd's need at least 1 Argument (or more)
|
||||||
return (undef, $targetGadName, $cmd) if ($na <= 0);
|
return (undef, $targetGadName, $cmd) if ($na <= 0);
|
||||||
# pass thru -for-timer,-until,blink cmds...
|
# pass thru -for-timer,-until,blink cmds...
|
||||||
return (undef, $targetGadName, $cmd) if ($cmd =~ m/(?:-until|-for-timer|$BLINK)$/ixms);
|
return (undef, $targetGadName, $cmd, @arg) if ($cmd =~ m/(?:-until|-till-overnight|-for-timer|$BLINK)$/ixms);
|
||||||
|
|
||||||
my $code = $hash->{GADDETAILS}->{$targetGadName}->{MODEL};
|
my $code = $hash->{GADDETAILS}->{$targetGadName}->{MODEL};
|
||||||
my $value = $cmd;
|
my $value = $arg[0];
|
||||||
|
|
||||||
|
if ($code =~ /^dpt16(?:[.]\d+)?$/xms) { #special case txt
|
||||||
|
$cmd = shift(@arg) if ($cmd =~ m/$STRING/xms); # cmd contains first word
|
||||||
|
return (undef, $targetGadName, $cmd, @arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $rtxt = qq{set $name $cmd };
|
||||||
|
|
||||||
if ($cmd =~ m/$RAW/ixms) {
|
if ($cmd =~ m/$RAW/ixms) {
|
||||||
#check for 1-16 hex-digits
|
#check for 1-16 hex-digits
|
||||||
return q{"raw" } . $arg[0] . ' has wrong syntax. Use hex-format only.' if ($arg[0] !~ m/[\da-f]{1,16}/ixms);
|
return $rtxt . $value . q{ has wrong syntax. Use hex-format only.} if ($value !~ m/[\da-f]{1,16}/ixms);
|
||||||
$value = $arg[0];
|
|
||||||
}
|
}
|
||||||
elsif ($cmd =~ m/$VALUE/ixms) {
|
elsif ($cmd =~ m/$VALUE/ixms) {
|
||||||
return q{"value" not allowed for dpt1, dpt16 and dpt232} if ($code =~ m/(dpt1$)|(dpt16$)|(dpt232$)/ixms);
|
return $rtxt . q{not allowed for dpt1, dpt16 and dpt232} if ($code =~ m/(dpt1|dpt16|dpt232)(?:[.]\d+)?$/ixms);
|
||||||
$value = $arg[0];
|
|
||||||
$value =~ s/,/\./gxms;
|
$value =~ s/,/\./gxms;
|
||||||
}
|
}
|
||||||
#set string <val1 val2 valn>
|
|
||||||
elsif ($cmd =~ m/$STRING/ixms) {
|
|
||||||
return q{"string" only allowed for dpt16} if ($code !~ m/dpt16/ixms);
|
|
||||||
$value = q{}; # will be joined in KNX_Set
|
|
||||||
}
|
|
||||||
#set RGB <RRGGBB>
|
#set RGB <RRGGBB>
|
||||||
elsif ($cmd =~ m/$RGB/ixms) {
|
elsif ($cmd =~ m/$RGB/ixms) {
|
||||||
return q{"rgb" only allowed for dpt232} if ($code !~ m/dpt232$/ixms);
|
return $rtxt . q{only allowed for dpt232} if ($code !~ m/dpt232/ixms);
|
||||||
#check for 6 hex-digits
|
#check for 6 hex-digits
|
||||||
return q{"rgb" } . $arg[0] . q{ has wrong syntax. Use 6 hex-digits only.} if ($arg[0] !~ m/[\da-f]{6}/ixms);
|
return $rtxt . $value . q{ has wrong syntax. Use 6 hex-digits only.} if ($value !~ m/[\da-f]{6}/ixms);
|
||||||
$value = lc($arg[0]);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
KNX_Log ($name, 2, qq{invalid cmd: "set $name $cmd" issued - ignored});
|
KNX_Log ($name, 2, q{invalid cmd: "} . $rtxt . q{" issued - ignored});
|
||||||
return qq{invalid cmd: "set $name $cmd } . join(q{ },@arg). q{" -ignored};
|
return q{invalid cmd: "} . $rtxt . join(q{ },@arg). q{" issued - ignored};
|
||||||
}
|
}
|
||||||
|
|
||||||
KNX_Log ($name, 3, qq{This cmd will be deprecated by 1/2024: "set $name $cmd } . join(q{ },@arg) .
|
KNX_Log ($name, 3, q{This cmd will be deprecated by 1/2024: "} . $rtxt . join(q{ },@arg) .
|
||||||
qq{" - use: "set $name $targetGadName $value"});
|
qq{" - use: "set $name $targetGadName $value"});
|
||||||
|
|
||||||
return (undef, $targetGadName, $value);
|
return (undef, $targetGadName, $value, @arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
# process special dpt1, dpt1.001 set
|
# process special dpt1, dpt1.001 set
|
||||||
@ -1012,61 +1022,73 @@ sub KNX_Set_dpt1 {
|
|||||||
delete $hash->{".TIMER_$groupCode"};
|
delete $hash->{".TIMER_$groupCode"};
|
||||||
}
|
}
|
||||||
|
|
||||||
my $value = 'off'; # default
|
my $value = ($cmd =~ m/^(on|1)/ixms)?'on':'off';
|
||||||
my $tvalue = 'on'; # default reversed value for timer ops
|
my $tvalue = ($value eq 'on')?'off':'on';
|
||||||
if ($cmd =~ m/(^on|1)/ixms) {
|
|
||||||
$value = 'on';
|
|
||||||
$tvalue = 'off';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (undef,$value) if ($cmd =~ m/(?:on|off)$/ixms); # shortcut
|
$cmd =~ s/[,]*$//xms; # remove empty widget parameters
|
||||||
|
return (undef,$value) if ($cmd =~ m/(?:$PAT_DPT1_PAT)$/ixms); # shortcut
|
||||||
|
|
||||||
#set on-for-timer / off-for-timer
|
#set on-for-timer / off-for-timer
|
||||||
|
my $hms_til = undef;
|
||||||
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);
|
return qq{KNX_Set_dpt1 ($name): parameter must be numeric} unless Scalar::Util::looks_like_number($arg[0]);
|
||||||
KNX_Log ($name, 5, qq{cmd: $cmd ts: $duration});
|
return qq{KNX_Set_dpt1 ($name): subsecond parameter not supported} if ($arg[0] < 1);
|
||||||
|
|
||||||
$hash->{".TIMER_$groupCode"} = $duration; #create local marker
|
my $myTS = Time::HiRes::time() + $arg[0];
|
||||||
#place at-command for switching on / off
|
$hms_til = (split(q{ },::FmtDateTime($myTS)))[1]; # fhem.pl
|
||||||
CommandDefMod(undef, '-temporary ' . $name . qq{_TIMER_$groupCode at +$duration set $name $targetGadName $tvalue});
|
|
||||||
}
|
}
|
||||||
#set on-until / off-until
|
#set on-until / off-until
|
||||||
elsif ($cmd =~ m/(?:(on|off)-until)$/ixms) {
|
elsif ($cmd =~ m/(?:(on|off)-(until|till-overnight))$/ixms) {
|
||||||
#get off-time
|
#get off-time
|
||||||
my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($arg[0]); # fhem.pl
|
my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($arg[0]); # fhem.pl
|
||||||
return qq{KNX_Set_dpt1 ($name): Error trying to parse timespec for $arg[0] : $err} if (defined($err));
|
return qq{KNX_Set_dpt1 ($name): Error trying to parse timespec for $arg[0] : $err} if (defined($err));
|
||||||
|
|
||||||
#do like (on|off)-till-overnight in at cmd !
|
#do like (on|off)-till-overnight in at cmd !
|
||||||
my $hms_til = sprintf('%02d:%02d:%02d', $hr, $min, $sec);
|
$hms_til = sprintf('%02d:%02d:%02d', $hr, $min, $sec);
|
||||||
KNX_Log ($name, 5, qq{cmd: $cmd ts: $hms_til});
|
}
|
||||||
|
if (defined($hms_til)) { # process both timer variants
|
||||||
$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, '-temporary ' . $name . qq{_TIMER_$groupCode at $hms_til set $name $targetGadName $tvalue});
|
||||||
|
return (undef,$value);
|
||||||
}
|
}
|
||||||
#toggle
|
###
|
||||||
elsif ($cmd =~ m/$TOGGLE/ixms) {
|
return KNX_set_dpt1_sp($hash, $targetGadName, $cmd, @arg);
|
||||||
my $toggleOldVal = 'dontknow';
|
}
|
||||||
|
|
||||||
|
# process special dpt1, dpt1.001 set blink & toggle
|
||||||
|
# return: $err, $value
|
||||||
|
sub KNX_set_dpt1_sp {
|
||||||
|
my ($hash, $targetGadName, $cmd, @arg) = @_;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
my $groupCode = $hash->{GADDETAILS}->{$targetGadName}->{CODE};
|
||||||
|
|
||||||
|
# get current value from reading(s)
|
||||||
|
my $toggleOldVal = undef;
|
||||||
|
|
||||||
my ($tDev, $togglereading) = split(qr/:/xms,AttrVal($name,'KNX_toggle',$name));
|
my ($tDev, $togglereading) = split(qr/:/xms,AttrVal($name,'KNX_toggle',$name));
|
||||||
if (defined($togglereading)) { # prio1: use Attr. KNX_toggle: format: <device>:<reading>
|
if (defined($togglereading)) { # prio1: use Attr. KNX_toggle: format: <device>:<reading>
|
||||||
$tDev = $name if ($tDev eq '$self'); ## no critic (Policy::ValuesAndExpressions::RequireInterpolationOfMetachars)
|
$tDev = $name if ($tDev eq '$self'); ## no critic (Policy::ValuesAndExpressions::RequireInterpolationOfMetachars)
|
||||||
$toggleOldVal = ReadingsVal($tDev, $togglereading, 'dontknow'); # switch off in case of non existent reading
|
$toggleOldVal = ReadingsVal($tDev, $togglereading, undef); # switch off in case of non existent reading
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$togglereading = $hash->{GADDETAILS}->{$targetGadName}->{RDNAMEGET}; #prio2: use get-reading
|
$togglereading = $hash->{GADDETAILS}->{$targetGadName}->{RDNAMEGET}; #prio2: use get-reading
|
||||||
$toggleOldVal = ReadingsVal($name, $togglereading, undef);
|
$toggleOldVal = ReadingsVal($name, $togglereading, undef);
|
||||||
|
}
|
||||||
if (! defined($toggleOldVal)) {
|
if (! defined($toggleOldVal)) {
|
||||||
$togglereading = $hash->{GADDETAILS}->{$targetGadName}->{RDNAMESET}; #prio3: use set-reading
|
$togglereading = $hash->{GADDETAILS}->{$targetGadName}->{RDNAMESET}; #prio3: use set-reading
|
||||||
$toggleOldVal = ReadingsVal($name, $togglereading, 'dontknow');
|
$toggleOldVal = ReadingsVal($name, $togglereading, 'dontknow');
|
||||||
}
|
}
|
||||||
}
|
my $value = ($toggleOldVal =~ m/^off/ixms)?'on':'off';
|
||||||
|
|
||||||
|
#toggle
|
||||||
|
if ($cmd =~ m/$TOGGLE/ixms) {
|
||||||
KNX_Log ($name, 3, qq{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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#blink - implemented with timer & toggle
|
#blink - implemented with timer & toggle
|
||||||
elsif ($cmd =~ m/$BLINK/ixms) {
|
elsif ($cmd =~ m/$BLINK/ixms) {
|
||||||
my $count = ($arg[0])?$arg[0] * 2 -1:1;
|
my $count = ($arg[0])?$arg[0] * 2 -1:1;
|
||||||
@ -1076,13 +1098,12 @@ sub KNX_Set_dpt1 {
|
|||||||
|
|
||||||
my $duration = sprintf('%02d:%02d:%02d', $dur/3600, ($dur%3600)/60, $dur%60);
|
my $duration = sprintf('%02d:%02d:%02d', $dur/3600, ($dur%3600)/60, $dur%60);
|
||||||
CommandDefMod(undef, '-temporary ' . $name . "_TIMERBLINK_$groupCode at +*{" . $count ."}$duration set $name $targetGadName toggle");
|
CommandDefMod(undef, '-temporary ' . $name . "_TIMERBLINK_$groupCode at +*{" . $count ."}$duration set $name $targetGadName toggle");
|
||||||
$value = q{toggle};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#no valid cmd
|
#no valid cmd
|
||||||
else {
|
else {
|
||||||
KNX_Log ($name, 2, qq{invalid cmd: "set $name $cmd $targetGadName $value" issued - ignored});
|
KNX_Log ($name, 2, qq{invalid cmd: "set $name $targetGadName $cmd" issued - ignored});
|
||||||
return qq{invalid cmd: "set $name $cmd $targetGadName $value" - ignored};
|
return qq{invalid cmd: "set $name $targetGadName $cmd" issued - ignored};
|
||||||
}
|
}
|
||||||
return (undef,$value);
|
return (undef,$value);
|
||||||
}
|
}
|
||||||
@ -1170,7 +1191,7 @@ sub KNX_DbLog_split {
|
|||||||
$dpt16flag = 1;
|
$dpt16flag = 1;
|
||||||
last;
|
last;
|
||||||
}
|
}
|
||||||
if (($dpt16flag == 0) && looks_like_number($strings[0]) && (! 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1222,23 +1243,27 @@ sub KNX_Parse {
|
|||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
=begin comment
|
||||||
# ignore input from "wrong" IO-dev
|
# ignore input from "wrong" IO-dev
|
||||||
# if ($iohash ne $deviceHash->{IODev}) {
|
my $IODevAttr = AttrVal($deviceName,'IODev',$ioName);
|
||||||
# KNX_Log ($deviceName, 2, qq{ioname mismatch device= $deviceName io= $ioName});
|
if ($IODevAttr ne $ioName) {
|
||||||
# next;
|
KNX_Log ($deviceName, 2, qq{msg for gad-name: $gadName from wrong IO-device: $ioName - ignored});
|
||||||
# }
|
next;
|
||||||
|
}
|
||||||
|
=end comment
|
||||||
|
=cut
|
||||||
|
|
||||||
my $getName = $deviceHash->{GADDETAILS}->{$gadName}->{RDNAMEGET};
|
my $getName = $deviceHash->{GADDETAILS}->{$gadName}->{RDNAMEGET};
|
||||||
|
|
||||||
KNX_Log ($deviceName, 4, qq{process ioName=$ioName gadName=$gadName cmd=$cmd readingName=$getName value=$val});
|
KNX_Log ($deviceName, 4, qq{process ioName=$ioName gadName=$gadName cmd=$cmd readingName=$getName value=$val});
|
||||||
|
|
||||||
my $trigger = 1; # default create events
|
my $trigger = 1; # default create events
|
||||||
|
|
||||||
=begin comment
|
=begin comment
|
||||||
#GroupValueResponse messages when not triggered by read from fhem
|
#GroupValueResponse messages when not triggered by read from fhem
|
||||||
# special experiment for Amenophis86
|
# special experiment for Amenophis86
|
||||||
if ($cmd =~ /[p]/ixms && exists($deviceHash->{GADDETAILS}->{$gadName}->{noreplyflag};
|
if ($cmd =~ /[p]/ixms && exists($deviceHash->{GADDETAILS}->{$gadName}->{noreplyflag};
|
||||||
my $nrts = $deviceHash->{GADDETAILS}->{$gadName}->{noreplyflag};
|
if (Time::HiRes:time() > $deviceHash->{GADDETAILS}->{$gadName}->{noreplyflag}) {
|
||||||
if (gettimeofday() > $nrts) {
|
|
||||||
delete $deviceHash->{GADDETAILS}->{$gadName}->{noreplyflag};
|
delete $deviceHash->{GADDETAILS}->{$gadName}->{noreplyflag};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1269,20 +1294,9 @@ sub KNX_Parse {
|
|||||||
my $cmdAttr = AttrVal($deviceName, 'putCmd', undef);
|
my $cmdAttr = AttrVal($deviceName, 'putCmd', undef);
|
||||||
next if (! defined($cmdAttr) || $cmdAttr eq q{});
|
next if (! defined($cmdAttr) || $cmdAttr eq q{});
|
||||||
|
|
||||||
# generate <putname>
|
=begin comment
|
||||||
my $putName = $getName =~ s/get/put/irxms;
|
## special experiment for Amenophis86
|
||||||
$putName .= ($putName eq $getName)?q{-put}:q{}; # nosuffix
|
if ($cmdAttr eq 'noReply') {
|
||||||
|
|
||||||
my $value = ReadingsVal($deviceName, 'state', undef); #default
|
|
||||||
$value = KNX_eval ($deviceHash, $gadName, $value, $cmdAttr);
|
|
||||||
next if (! defined($value) || $value eq q{}); # dont send!
|
|
||||||
if ($value eq q{ERROR}) {
|
|
||||||
KNX_Log ($deviceName, 2, qq{putCmd eval error gadName=$gadName - no reply sent!});
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
|
|
||||||
## special experiment for Amenophis86
|
|
||||||
elsif ($value eq 'noReply') {
|
|
||||||
if ($iohash->{PhyAddr} eq KNX_hex2Name($src,1)) { # match src-address with phy of IOdev
|
if ($iohash->{PhyAddr} eq KNX_hex2Name($src,1)) { # match src-address with phy of IOdev
|
||||||
# from fhem - delete ignore reply flag
|
# from fhem - delete ignore reply flag
|
||||||
delete $deviceHash->{GADDETAILS}->{$gadName}->{noreplyflag}; # allow when sent from fhem
|
delete $deviceHash->{GADDETAILS}->{$gadName}->{noreplyflag}; # allow when sent from fhem
|
||||||
@ -1290,11 +1304,25 @@ sub KNX_Parse {
|
|||||||
else {
|
else {
|
||||||
KNX_Log ($deviceName, 4, q{read msg from } . KNX_hex2Name($src,1) .
|
KNX_Log ($deviceName, 4, q{read msg from } . KNX_hex2Name($src,1) .
|
||||||
qq{ for $deviceName $gadName IODev= $iohash->{PhyAddr}});
|
qq{ for $deviceName $gadName IODev= $iohash->{PhyAddr}});
|
||||||
$deviceHash->{GADDETAILS}->{$gadName}->{noreplyflag} = gettimeofday() + 2;
|
$deviceHash->{GADDETAILS}->{$gadName}->{noreplyflag} = Time::HiRes::time() + 2;
|
||||||
}
|
}
|
||||||
next; # cannot use putCmd !
|
next; # cannot use putCmd !
|
||||||
}
|
}
|
||||||
## end special experiment for Amenophis86
|
=end comment
|
||||||
|
=cut
|
||||||
|
# generate <putname>
|
||||||
|
my $putName = $getName =~ s/get/put/irxms;
|
||||||
|
$putName .= ($putName eq $getName)?q{-put}:q{}; # nosuffix
|
||||||
|
|
||||||
|
my $value = ReadingsVal($deviceName, 'state', q{}); #default
|
||||||
|
$value = KNX_eval ($deviceHash, $gadName, $value, $cmdAttr);
|
||||||
|
if (! defined($value)) {
|
||||||
|
KNX_Log ($deviceName, 4, qq{putCmd no reply sent for gadName=$gadName});
|
||||||
|
next;
|
||||||
|
} elsif ($value eq q{} || $value eq q{ERROR}) {
|
||||||
|
KNX_Log ($deviceName, 2, qq{putCmd eval error gadName=$gadName - no reply sent!});
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
KNX_Log ($deviceName, 5, qq{replaced by Attr putCmd=$cmdAttr VALUE=$value});
|
KNX_Log ($deviceName, 5, qq{replaced by Attr putCmd=$cmdAttr VALUE=$value});
|
||||||
|
|
||||||
@ -1533,7 +1561,7 @@ sub KNX_limit {
|
|||||||
my ($hash, $value, $model, $direction) = @_;
|
my ($hash, $value, $model, $direction) = @_;
|
||||||
|
|
||||||
#continue only if numeric value
|
#continue only if numeric value
|
||||||
return $value if (! looks_like_number ($value));
|
return $value if (! Scalar::Util::looks_like_number ($value));
|
||||||
return $value if (! defined($direction));
|
return $value if (! defined($direction));
|
||||||
|
|
||||||
my $name = $hash->{NAME};
|
my $name = $hash->{NAME};
|
||||||
@ -1547,7 +1575,7 @@ sub KNX_limit {
|
|||||||
#get limits
|
#get limits
|
||||||
my $min = $dpttypes{$model}->{MIN};
|
my $min = $dpttypes{$model}->{MIN};
|
||||||
my $max = $dpttypes{$model}->{MAX};
|
my $max = $dpttypes{$model}->{MAX};
|
||||||
return $value if (! looks_like_number ($min)); # allow 0/1 for dpt1.002+
|
return $value if (! Scalar::Util::looks_like_number ($min)); # allow 0/1 for dpt1.002+
|
||||||
|
|
||||||
#determine direction of scaling, do only if defined
|
#determine direction of scaling, do only if defined
|
||||||
if ($direction =~ m/^encode/ixms) {
|
if ($direction =~ m/^encode/ixms) {
|
||||||
@ -1583,6 +1611,8 @@ sub KNX_eval {
|
|||||||
my $code = EvalSpecials($evalString,('%hash' => $hash, '%name' => $name, '%gadName' => $gadName, '%state' => $state));
|
my $code = EvalSpecials($evalString,('%hash' => $hash, '%name' => $name, '%gadName' => $gadName, '%state' => $state));
|
||||||
$retVal = AnalyzeCommandChain(undef, $code);
|
$retVal = AnalyzeCommandChain(undef, $code);
|
||||||
|
|
||||||
|
return if (! defined($retVal));
|
||||||
|
|
||||||
if ($retVal =~ /(^Forbidden|error)/ixms) { # eval error or forbidden by Authorize
|
if ($retVal =~ /(^Forbidden|error)/ixms) { # eval error or forbidden by Authorize
|
||||||
KNX_Log ($name, 2, qq{eval-error: gadName= $gadName evalString= $evalString result= $retVal});
|
KNX_Log ($name, 2, qq{eval-error: gadName= $gadName evalString= $evalString result= $retVal});
|
||||||
$retVal = 'ERROR';
|
$retVal = 'ERROR';
|
||||||
@ -1609,8 +1639,15 @@ sub KNX_encodeByDpt {
|
|||||||
$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
|
||||||
$value .= ':00' if ($model eq 'dpt10' && $value =~ /^[\d]{2}:[\d]{2}$/xms);
|
$value .= ':00' if ($code eq 'dpt10' && $value =~ /^[\d]{2}:[\d]{2}$/xms);
|
||||||
|
|
||||||
|
# support dpt18 learn
|
||||||
|
my $arg1 = undef;
|
||||||
|
my $arg2 = undef;
|
||||||
|
if ($code eq 'dpt18') {
|
||||||
|
($arg1, $arg2) = split(/[,]/xms,$value);
|
||||||
|
$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) {
|
||||||
@ -1624,6 +1661,7 @@ sub KNX_encodeByDpt {
|
|||||||
|
|
||||||
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);
|
||||||
|
$hexval = sprintf('00%.2x',hex($hexval) + 0x80) if ($code eq 'dpt18' && $arg1 =~ /^learn/xms);
|
||||||
KNX_Log ($name, 5, qq{gadName= $gadName model= $model } .
|
KNX_Log ($name, 5, qq{gadName= $gadName model= $model } .
|
||||||
qq{in-Value= $value out-Value= $lvalue out-ValueHex= $hexval});
|
qq{in-Value= $value out-Value= $lvalue out-ValueHex= $hexval});
|
||||||
return $hexval;
|
return $hexval;
|
||||||
@ -1649,7 +1687,7 @@ 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);
|
||||||
KNX_Log ($name, 5, qq{gadName= $gadName model= $model code= $code value= $value length-value= } .
|
KNX_Log ($name, 5, qq{gadName= $gadName model= $model code= $code hexvalue= $value length-value= } .
|
||||||
length($value) . qq{ state= $state});
|
length($value) . qq{ state= $state});
|
||||||
return $state;
|
return $state;
|
||||||
}
|
}
|
||||||
@ -1696,7 +1734,7 @@ sub enc_dpt3 { #Step value (four-bit)
|
|||||||
sub enc_dpt4 { #single ascii or iso-8859-1 char
|
sub enc_dpt4 { #single ascii or iso-8859-1 char
|
||||||
my $value = shift;
|
my $value = shift;
|
||||||
my $model = shift;
|
my $model = shift;
|
||||||
my $numval = encode('iso-8859-1', decode('utf8', $value)); #always convert to latin-1
|
my $numval = Encode::encode('iso-8859-1', Encode::decode('utf8', $value)); #always convert to latin-1
|
||||||
$numval =~ s/[\x80-\xff]/?/gxms if ($model ne 'dpt4.002'); #replace values >= 0x80 if ascii
|
$numval =~ s/[\x80-\xff]/?/gxms if ($model ne 'dpt4.002'); #replace values >= 0x80 if ascii
|
||||||
#convert to hex-string
|
#convert to hex-string
|
||||||
my $dat = unpack('H*', $numval);
|
my $dat = unpack('H*', $numval);
|
||||||
@ -1796,7 +1834,7 @@ sub enc_dpt14 { #4-Octet single precision float
|
|||||||
sub enc_dpt16 { #14-Octet String
|
sub enc_dpt16 { #14-Octet String
|
||||||
my $value = shift;
|
my $value = shift;
|
||||||
my $model = shift;
|
my $model = shift;
|
||||||
my $numval = encode('iso-8859-1', decode('utf8', $value)); #always convert to latin-1
|
my $numval = Encode::encode('iso-8859-1', Encode::decode('utf8', $value)); #always convert to latin-1
|
||||||
$numval =~ s/[\x80-\xff]/?/gxms if ($model ne 'dpt16.001'); #replace values >= 0x80 if ascii
|
$numval =~ s/[\x80-\xff]/?/gxms if ($model ne 'dpt16.001'); #replace values >= 0x80 if ascii
|
||||||
$numval = substr($numval,0,14); # limit to 14 char
|
$numval = substr($numval,0,14); # limit to 14 char
|
||||||
|
|
||||||
@ -1809,6 +1847,12 @@ sub enc_dpt16 { #14-Octet String
|
|||||||
return $hexval;
|
return $hexval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub enc_dpt18 { # scene 10/2024 allow activate & learn
|
||||||
|
my $value = shift;
|
||||||
|
$value = ($value & 0x3f);
|
||||||
|
return sprintf('00%.2x',$value);
|
||||||
|
}
|
||||||
|
|
||||||
sub enc_dpt19 { #DateTime
|
sub enc_dpt19 { #DateTime
|
||||||
my $value = shift;
|
my $value = shift;
|
||||||
my $ts = time; # default or when "now" is given
|
my $ts = time; # default or when "now" is given
|
||||||
@ -1883,13 +1927,12 @@ sub dec_dpt3 { #Step value (four-bit)
|
|||||||
return sprintf ('%d', $stepcode);
|
return sprintf ('%d', $stepcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub dec_dpt5 { #1-Octet unsigned value / also used for dpt17, dpt18
|
sub dec_dpt5 { #1-Octet unsigned value
|
||||||
my $numval = hex (shift);
|
my $numval = hex (shift);
|
||||||
my $model = shift;
|
my $model = shift;
|
||||||
my $hash = shift;
|
my $hash = shift;
|
||||||
$numval = ($numval & 0x3F) if ($model =~ /^(dpt17|dpt18)/xms);
|
|
||||||
my $state = KNX_limit ($hash, $numval, $model, 'DECODE');
|
my $state = KNX_limit ($hash, $numval, $model, 'DECODE');
|
||||||
return sprintf ('%.0f', $state);
|
return sprintf ('%d', $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub dec_dpt6 { #1-Octet signed value
|
sub dec_dpt6 { #1-Octet signed value
|
||||||
@ -1908,7 +1951,7 @@ sub dec_dpt7 { #2-Octet unsigned Value
|
|||||||
my $state = KNX_limit ($hash, $numval, $model, 'DECODE');
|
my $state = KNX_limit ($hash, $numval, $model, 'DECODE');
|
||||||
return sprintf ('%.2f', $state) if ($model eq 'dpt7.003');
|
return sprintf ('%.2f', $state) if ($model eq 'dpt7.003');
|
||||||
return sprintf ('%.1f', $state) if ($model eq 'dpt7.004');
|
return sprintf ('%.1f', $state) if ($model eq 'dpt7.004');
|
||||||
return sprintf ('%.0f', $state);
|
return sprintf ('%d', $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub dec_dpt8 { #2-Octet signed Value
|
sub dec_dpt8 { #2-Octet signed Value
|
||||||
@ -1958,12 +2001,12 @@ sub dec_dpt11 { #Date
|
|||||||
return sprintf('%02d.%02d.%04d',$day,$month,$year);
|
return sprintf('%02d.%02d.%04d',$day,$month,$year);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub dec_dpt12 { #4-Octet unsigned value (handled as dpt7)
|
sub dec_dpt12 { #4-Octet unsigned value
|
||||||
my $numval = hex (shift);
|
my $numval = hex (shift);
|
||||||
my $model = shift;
|
my $model = shift;
|
||||||
my $hash = shift;
|
my $hash = shift;
|
||||||
my $state = KNX_limit ($hash, $numval, $model, 'DECODE');
|
my $state = KNX_limit ($hash, $numval, $model, 'DECODE');
|
||||||
return sprintf ('%.0f', $state);
|
return sprintf ('%d', $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub dec_dpt13 { #4-Octet Signed Value
|
sub dec_dpt13 { #4-Octet Signed Value
|
||||||
@ -2002,12 +2045,21 @@ sub dec_dpt16 { #14-Octet String or dpt4: single Char string
|
|||||||
$value =~ s/\s*$//gxms; # strip trailing blanks
|
$value =~ s/\s*$//gxms; # strip trailing blanks
|
||||||
my $state = pack('H*',$value);
|
my $state = pack('H*',$value);
|
||||||
#convert from latin-1
|
#convert from latin-1
|
||||||
$state = encode ('utf8', decode('iso-8859-1',$state)) if ($model =~ m/^dpt(?:16.001|4.002)$/xms);
|
$state = Encode::encode ('utf8', Encode::decode('iso-8859-1',$state)) if ($model =~ m/^dpt(?:16.001|4.002)$/xms);
|
||||||
$state = q{} if ($state =~ m/^[\x00]+$/ixms); # case all zeros received
|
$state = q{} if ($state =~ m/^[\x00]+$/ixms); # case all zeros received
|
||||||
$state =~ s/[\x00-\x1F]+//gxms; # remove non printable chars
|
$state =~ s/[\x00-\x1F]+//gxms; # remove non printable chars
|
||||||
return $state;
|
return $state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub dec_dpt18 { #1-Octet scene - also used for dpt17
|
||||||
|
my $numval = hex (shift);
|
||||||
|
my $model = shift;
|
||||||
|
my $hash = shift;
|
||||||
|
$numval = ($numval & 0x3F);
|
||||||
|
my $state = KNX_limit ($hash, $numval, $model, 'DECODE');
|
||||||
|
return sprintf ('%d', $state);
|
||||||
|
}
|
||||||
|
|
||||||
sub dec_dpt19 { #DateTime
|
sub dec_dpt19 { #DateTime
|
||||||
my $numval = substr(shift,-16); # strip off 1st byte
|
my $numval = substr(shift,-16); # strip off 1st byte
|
||||||
my $time = hex (substr ($numval, 6, 6));
|
my $time = hex (substr ($numval, 6, 6));
|
||||||
@ -2186,7 +2238,7 @@ sub doKNX_scan {
|
|||||||
my ($devName,$gadName) = split(/\s/xms, shift(@{$iohash->{Helper}->{knxscan}}),2);
|
my ($devName,$gadName) = split(/\s/xms, shift(@{$iohash->{Helper}->{knxscan}}),2);
|
||||||
KNX_Get ($defs{$devName}, $devName, $gadName);
|
KNX_Get ($defs{$devName}, $devName, $gadName);
|
||||||
my $delay = ($count % 10 == 0)?1:0.35; # extra delay on each 10th request
|
my $delay = ($count % 10 == 0)?1:0.35; # extra delay on each 10th request
|
||||||
return InternalTimer(gettimeofday() + $delay,\&doKNX_scan,$iohash);
|
return InternalTimer(Time::HiRes::time() + $delay,\&doKNX_scan,$iohash);
|
||||||
}
|
}
|
||||||
delete $iohash->{Helper}->{knxscan};
|
delete $iohash->{Helper}->{knxscan};
|
||||||
Log3 ($iohash, 3, q{KNX_scan: finished});
|
Log3 ($iohash, 3, q{KNX_scan: finished});
|
||||||
@ -2275,21 +2327,20 @@ The reading <state> will be updated with the last sent or received value.&
|
|||||||
<li><a id="KNX-define"></a><strong>Define</strong><br/>
|
<li><a id="KNX-define"></a><strong>Define</strong><br/>
|
||||||
<p><code>define <name> KNX <group>:<dpt>[:[<gadName>]:[set|get|listenonly]:[nosuffix]]
|
<p><code>define <name> KNX <group>:<dpt>[:[<gadName>]:[set|get|listenonly]:[nosuffix]]
|
||||||
[<group>:<dpt> ..] <del>[IODev]</del></code></p>
|
[<group>:<dpt> ..] <del>[IODev]</del></code></p>
|
||||||
<p><strong>Important: a KNX device needs at least one valid DPT.</strong>
|
|
||||||
The system cannot en- or de-code messages without a valid <a href="#KNX-dpt">dpt</a> defined.<br/>
|
|
||||||
<strong>Devices defined by autocreate have to be configured with a correct dpt and the disable attribute deleted.
|
|
||||||
Otherwise they won't do anything.</strong></p>
|
|
||||||
<p>The <group> parameter is either a group name notation (0-31/0-7/0-255) or the hex representation of it
|
<p>The <group> parameter is either a group name notation (0-31/0-7/0-255) or the hex representation of it
|
||||||
([00-1f][0-7][00-ff]) (5 digits). All of the defined groups can be used for bus-communication.
|
([00-1f][0-7][00-ff]) (5 digits). All of the defined groups can be used for bus-communication.
|
||||||
It is not allowed to have the same group-address more then once in one device. You can have multiple FHEM-devices containing
|
It is not allowed to have the same group-address more then once in one device. You can have multiple FHEM-devices containing
|
||||||
the same group-adresses.<br/>
|
the same group-adresses.
|
||||||
As described above the parameter <dpt> has to contain the corresponding DPT - matching the dpt-spec of the KNX-Hardware.</p>
|
</p>
|
||||||
<p>The gadName default is "g<number>". The corresponding reading-names are getG<number>
|
<p><strong>Important: a KNX device needs at least one valid DPT </strong>matching the dpt-spec of the KNX-Hardware.
|
||||||
|
The system cannot en- or de-code messages without a valid <a href="#KNX-dpt">dpt</a> defined.<br/>
|
||||||
|
</p>
|
||||||
|
<p>If <gadName> not specified, the default is "g<number>". The corresponding reading-names are getG<number>
|
||||||
and setG<number>.<br/>
|
and setG<number>.<br/>
|
||||||
The optional parameteter <gadName> may contain an alias for the GAD. The following gadNames are <b>not allowed:</b>
|
The optional parameteter <gadName> may contain an alias for the GAD. The following gadNames are <b>not allowed:</b>
|
||||||
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 & parameters.<br/>
|
- because of conflict with cmds & parameters.<br/>
|
||||||
If you supply <gadName> this name is used instead. The readings are <gadName>-get and <gadName>-set.
|
If you supply <gadName> this name is used instead. The reading-names are <gadName>-get and <gadName>-set.
|
||||||
The synonyms <getName> and <setName> are used in this documentation.<br/>
|
The synonyms <getName> and <setName> are used in this documentation.<br/>
|
||||||
If you add the option "nosuffix", <getName> and <setName> have the identical name - <gadName>.
|
If you add the option "nosuffix", <getName> and <setName> have the identical name - <gadName>.
|
||||||
Both sent and received bus messages will be stored in the same reading <gadName><br/>
|
Both sent and received bus messages will be stored in the same reading <gadName><br/>
|
||||||
@ -2297,41 +2348,40 @@ If you want to restrict the GAD, you can use the options "get", "set", or "liste
|
|||||||
It is not possible to combine the options.</p>
|
It is not possible to combine the options.</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>
|
||||||
<p>The first group is used for sending by default. If you want to send to a different group, you have to address it.
|
|
||||||
E.g: <code>set <name> <gadName> <value> </code>
|
|
||||||
Without additional attributes, all incoming and outgoing messages are in addition copied into reading <state>.</p>
|
|
||||||
<p>If enabled, the module <a href="#autocreate">autocreate</a> is creating a new definition for each not already defined group-address.
|
<p>If enabled, the module <a href="#autocreate">autocreate</a> is creating a new definition for each not already defined group-address.
|
||||||
However, the new device will be disabled until you added a DPT to the definition and delete the
|
However, <strong>the new device will be disabled until you added a DPT to the definition </strong>and delete the
|
||||||
<a href="#KNX-attr-disable">disable</a> attribute. The device name will be KNX_<llaaddd> where ll is the line-,
|
<a href="#KNX-attr-disable">disable</a> attribute. The device name will be KNX_<llaaddd> where ll is the line-,
|
||||||
aa the area- and ddd the device-address.
|
aa the area- and ddd the device-address.
|
||||||
No FileLog or SVG definition is created for KNX-devices by autocreate. Use for example
|
No FileLog or SVG definition is created for KNX-devices by autocreate. Use for example:<br/>
|
||||||
<code>define <name> FileLog <filename> KNX_.*</code> to create a single FileLog-definition for all KNX-devices
|
<code>define <name> FileLog <filename> KNX_.*</code><br/>
|
||||||
created by autocreate.<br/>
|
to create a single FileLog-definition for all KNX-devices created by autocreate.
|
||||||
Another option is to disable autocreate for KNX-devices in production environments (when no changes / additions are expected)
|
Another option is to disable autocreate for KNX-devices in production environments (when no changes / additions are expected)
|
||||||
by using: <code>attr <autocreate> ignoreTypes KNX_.*</code></p>
|
by using: <code>attr <autocreate> ignoreTypes KNX_.*</code></p>
|
||||||
<pre>
|
<pre>
|
||||||
Examples:
|
Examples:
|
||||||
<code> define lamp1 KNX 0/10/11:dpt1
|
<code> define lamp1 KNX 0/7/11:dpt1
|
||||||
attr lamp1 webCmd on:off
|
attr lamp1 webCmd on:off
|
||||||
attr lamp1 devStateIcon on:li_wht_on:off off:li_wht_off:on
|
attr lamp1 devStateIcon on:li_wht_on:off off:li_wht_off:on
|
||||||
|
|
||||||
define lamp2 KNX 0/10/12:dpt1:steuern:set 0/10/13:dpt1.001:status:listenonly
|
define lamp2 KNX 0/7/12:dpt1:steuern:set 0/7/13:dpt1.001:status:listenonly
|
||||||
|
|
||||||
define lamp3 KNX 00A0D:dpt1.001
|
define lamp3 KNX 0070D:dpt1.001
|
||||||
</code></pre>
|
</code></pre>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li><a id="KNX-set"></a><strong>Set</strong><br/>
|
<li><a id="KNX-set"></a><strong>Set</strong><br/>
|
||||||
<p><code>set <deviceName> [<gadName>] on|off|toggle<br/>
|
<p><code>set <deviceName> [<gadName>] on|off|toggle<br/>
|
||||||
set <deviceName> <gadName> blink <nr of blinks> <duration seconds><br/>
|
set <deviceName> <gadName> blink <nr of blinks> <duration seconds><br/>
|
||||||
set <deviceName> <gadName> on-for-timer|off-for-timer <duration seconds><br/>
|
set <deviceName> <gadName> on-for-timer|off-for-timer <duration seconds> # seconds < 1 are NOT supported!<br/>
|
||||||
set <deviceName> <gadName> on-until|off-until <timespec (HH:MM[:SS])><br/>
|
set <deviceName> <gadName> on-until|off-until <timespec (HH:MM[:SS])><br/>
|
||||||
</code></p>
|
</code></p>
|
||||||
<p>Set sends the given value to the bus.<br/> If <gadName> is omitted, the first listed GAD of the device is used.
|
<p>Set sends the given value to the bus.<br/> If <gadName> is omitted, the first listed GAD of the device definition is used.
|
||||||
|
If you want to send to a different group, you have to address it (see Examples). Without additional attributes,
|
||||||
|
all incoming and outgoing messages are in addition copied into reading <state>.<br/>
|
||||||
If the GAD is restricted in the definition with "get" or "listenonly", the set-command will be refused.<br/>
|
If the GAD is restricted in the definition with "get" or "listenonly", the set-command will be refused.<br/>
|
||||||
For dpt1 and dpt1.001 valid values are on, off, toggle and blink. Also the timer-functions can be used.
|
For dpt1 and dpt1.001 valid values are on, off, toggle and blink. Also the timer-functions can be used.
|
||||||
A running timer-function will be cancelled if a new set cmd (on,off,on-for-,....) for this GAD is issued.<br/>
|
A running timer-function will be cancelled if a new set cmd (on,off,on-for-,....) for this GAD is issued.<br/>
|
||||||
For all other dpt1.<xxx> the min- and max-values can be used for en- and decoding alternatively to on/off.<br/>
|
For all other dpt1.<xxx> the min- and max-values can be used for en- and de-coding alternatively to on/off.<br/>
|
||||||
All DPTs: allowed values or range of values are specified here: <a href="#KNX-dpt">KNX-dpt</a><br/>
|
All DPTs: allowed values or range of values are specified here: <a href="#KNX-dpt">KNX-dpt</a><br/>
|
||||||
After successful sending, the value is stored in readings <setName> and state.<br>
|
After successful sending, the value is stored in readings <setName> and state.<br>
|
||||||
Do not use wildcards for <deviceName>, the KNX-GW/Bus might be not perfomant enough to handle that.
|
Do not use wildcards for <deviceName>, the KNX-GW/Bus might be not perfomant enough to handle that.
|
||||||
@ -2344,8 +2394,9 @@ Examples:
|
|||||||
set lamp2 off # gadName omitted
|
set lamp2 off # gadName omitted
|
||||||
set lamp2 steuern on
|
set lamp2 steuern on
|
||||||
set lamp2 steuern off
|
set lamp2 steuern off
|
||||||
set lamp2 steuern on-for-timer 10 # seconds
|
set lamp2 steuern on-for-timer 10 # seconds - minimum time is 1 sec !
|
||||||
set lamp2 steuern on-until 13:15:00
|
set lamp2 steuern on-until 13:15:00 # if timestamp is earlier than "now", device will be switched off tomorrow !
|
||||||
|
set lamp2 status on # will be refused - status has option "listenonly" set
|
||||||
|
|
||||||
set lamp3 g1 off-until 13:15:00
|
set lamp3 g1 off-until 13:15:00
|
||||||
set lamp3 g1 toogle # lamp3 change state
|
set lamp3 g1 toogle # lamp3 change state
|
||||||
@ -2359,7 +2410,7 @@ Examples:
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li><a id="KNX-get"></a><strong>Get</strong><br/>
|
<li><a id="KNX-get"></a><strong>Get</strong><br/>
|
||||||
<p>If you execute "get" for a KNX-Element the status will be requested from the device. The device has to be able to respond to a read -
|
<p>If you execute "get" for a KNX-Element the status will be requested from the KNX-device. The device has to be able to respond to a read -
|
||||||
this might not be supported by the target KNX-device.<br/>
|
this might not be supported by the target KNX-device.<br/>
|
||||||
If the GAD is restricted in the definition with "set" or "listenonly", the execution will be refused.<br/>
|
If the GAD is restricted in the definition with "set" or "listenonly", the execution will be refused.<br/>
|
||||||
The answer from the bus-device updates the readings <getName> and state.<br>
|
The answer from the bus-device updates the readings <getName> and state.<br>
|
||||||
@ -2438,9 +2489,9 @@ Examples:
|
|||||||
else the send is rejected. The reading "state" will NOT get updated!
|
else the send is rejected. The reading "state" will NOT get updated!
|
||||||
<pre>
|
<pre>
|
||||||
Examples:
|
Examples:
|
||||||
<code> attr <device> putCmd {return $state if($gadName eq 'status');} #returns value of reading state on request from bus for gadName "status".
|
<code> attr <device> putCmd {return $state if($gadName eq 'status'); return;} #returns value of reading state on request from bus for gadName "status".
|
||||||
attr <device> putCmd {return ReadingsVal('dummydev','state','error') if(...);} #returns value of device "dummydev" reading "state".
|
attr <device> putCmd {return ReadingsVal('dummydev','state','error') if(...); return;} #returns value of device "dummydev" reading "state".
|
||||||
attr <device> putCmd {return (split(/[\s]/xms,TimeNow()))[1] if ($gadName eq 'time');} #returns system timestamp (dpt10 format) ...
|
attr <device> putCmd {return (split(/[\s]/xms,TimeNow()))[1] if ($gadName eq 'time'); return;} #returns system timestamp (dpt10 format) ...
|
||||||
</code></pre>
|
</code></pre>
|
||||||
</li>
|
</li>
|
||||||
<li><a id="KNX-attr-format"></a><b>format</b><br/>
|
<li><a id="KNX-attr-format"></a><b>format</b><br/>
|
||||||
@ -2513,8 +2564,8 @@ Examples:
|
|||||||
<li><b>dpt1.021 </b> logical_or, logical_and</li>
|
<li><b>dpt1.021 </b> logical_or, logical_and</li>
|
||||||
<li><b>dpt1.022 </b> scene_A, scene_B</li>
|
<li><b>dpt1.022 </b> scene_A, scene_B</li>
|
||||||
<li><b>dpt1.023 </b> move_up/down, move_and_step_mode</li>
|
<li><b>dpt1.023 </b> move_up/down, move_and_step_mode</li>
|
||||||
<li><b>dpt1.024 </b> Day, Night</li>
|
<li><b>dpt1.024 </b> day, night</li>
|
||||||
<li><b>dpt1.100 </b> Heat, Cool</li>
|
<li><b>dpt1.100 </b> cooling, heating</li>
|
||||||
<li><b>dpt2 </b> off, on, forceOff, forceOn</li>
|
<li><b>dpt2 </b> off, on, forceOff, forceOn</li>
|
||||||
<li><b>dpt2.000 </b> 0,1,2,3</li>
|
<li><b>dpt2.000 </b> 0,1,2,3</li>
|
||||||
<li><b>dpt3 </b> -100..+100</li>
|
<li><b>dpt3 </b> -100..+100</li>
|
||||||
@ -2704,7 +2755,7 @@ No conversion, limit-, plausibility-check is done, the hex values are sent unmod
|
|||||||
<code> Examples of valid / invalid hex-strings:
|
<code> Examples of valid / invalid hex-strings:
|
||||||
00..3f # valid, single byte range x00-x3f
|
00..3f # valid, single byte range x00-x3f
|
||||||
40..ff # invalid, only values x00-x3f allowed as first byte
|
40..ff # invalid, only values x00-x3f allowed as first byte
|
||||||
001a # valid, multiple bytes have to be prefixed with x00
|
006b # valid, 1st byte > x3f and multiple bytes have to be prefixed with x00
|
||||||
001a2b3c4e5f.. # valid, any length as long as even number of hex-digits are used
|
001a2b3c4e5f.. # valid, any length as long as even number of hex-digits are used
|
||||||
00112233445 # invalid, odd number of digits
|
00112233445 # invalid, odd number of digits
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user