2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-10 09:16:53 +00:00

00_KNXIO.pm: add FIFO & ratelimit for set/get cmds (Forum #127792)

git-svn-id: https://svn.fhem.de/fhem/trunk@28022 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
erwin 2023-10-04 06:43:37 +00:00
parent 8d4d30f3d6
commit 1c7d671143

View File

@ -60,6 +60,9 @@
# 13/07/2023 cleanup # 13/07/2023 cleanup
# moved KNX_scan function to KNX-Module, KNX_scan cmdline cmd into new Module 98_KNX_scan.pm # moved KNX_scan function to KNX-Module, KNX_scan cmdline cmd into new Module 98_KNX_scan.pm
# 25/08/2023 reorg opendev for mode X # 25/08/2023 reorg opendev for mode X
# 02/10/2023 Rate limit for write (set/get-cmd) from KNX-Modul
# remove unused imports...
# add $readingFnAttributes to AttrList
package KNXIO; ## no critic 'package' package KNXIO; ## no critic 'package'
@ -72,7 +75,7 @@ use Time::HiRes qw(gettimeofday);
use DevIo qw(DevIo_OpenDev DevIo_SimpleWrite DevIo_SimpleRead DevIo_CloseDev DevIo_Disconnected DevIo_IsOpen); use DevIo qw(DevIo_OpenDev DevIo_SimpleWrite DevIo_SimpleRead DevIo_CloseDev DevIo_Disconnected DevIo_IsOpen);
use TcpServerUtils qw(TcpServer_Open TcpServer_SetLoopbackMode TcpServer_MCastAdd TcpServer_MCastRemove use TcpServerUtils qw(TcpServer_Open TcpServer_SetLoopbackMode TcpServer_MCastAdd TcpServer_MCastRemove
TcpServer_MCastSend TcpServer_MCastRecv TcpServer_Close); TcpServer_MCastSend TcpServer_MCastRecv TcpServer_Close);
use HttpUtils qw(HttpUtils_gethostbyname HttpUtils_gethostbyname ip2str); use HttpUtils qw(HttpUtils_gethostbyname ip2str);
use feature qw(switch); use feature qw(switch);
no if $] >= 5.017011, warnings => 'experimental'; no if $] >= 5.017011, warnings => 'experimental';
use GPUtils qw(GP_Import GP_Export); # Package Helper Fn use GPUtils qw(GP_Import GP_Export); # Package Helper Fn
@ -91,23 +94,25 @@ use GPUtils qw(GP_Import GP_Export); # Package Helper Fn
BEGIN { BEGIN {
# Import from main context # Import from main context
GP_Import( GP_Import(
qw(readingsSingleUpdate readingsBulkUpdate readingsBulkUpdateIfChanged readingsBeginUpdate readingsEndUpdate qw(readingsSingleUpdate readingsBeginUpdate readingsEndUpdate
readingsBulkUpdate readingsBulkUpdateIfChanged
Log3 Log3
AttrVal AttrNum ReadingsVal ReadingsNum AttrVal AttrNum ReadingsVal ReadingsNum
readingFnAttributes
AssignIoPort IOWrite AssignIoPort IOWrite
CommandDefine CommandDelete CommandModify CommandDefMod
DoTrigger DoTrigger
Dispatch Dispatch
defs modules attr cmds defs attr
readingFnAttributes
selectlist readyfnlist selectlist readyfnlist
InternalTimer RemoveInternalTimer InternalTimer RemoveInternalTimer
init_done init_done
IsDisabled IsDummy IsDevice IsDisabled IsDummy IsDevice
devspec2array devspec2array
AnalyzePerlCommand EvalSpecials
TimeNow) TimeNow)
); );
# CommandDefine CommandDelete CommandModify CommandDefMod
# AnalyzePerlCommand EvalSpecials
# modules cmds
} }
# export to main context # export to main context
@ -134,7 +139,7 @@ sub Initialize {
$hash->{UndefFn} = \&KNXIO_Undef; $hash->{UndefFn} = \&KNXIO_Undef;
$hash->{ShutdownFn} = \&KNXIO_Shutdown; $hash->{ShutdownFn} = \&KNXIO_Shutdown;
$hash->{AttrList} = 'disable:1 verbose:1,2,3,4,5 enableKNXscan:0,1,2'; $hash->{AttrList} = 'disable:1 verbose:1,2,3,4,5 enableKNXscan:0,1,2 ' . $readingFnAttributes;
$hash->{Clients} = 'KNX'; $hash->{Clients} = 'KNX';
$hash->{MatchList} = { '1:KNX' => '^C.*' }; $hash->{MatchList} = { '1:KNX' => '^C.*' };
@ -160,7 +165,7 @@ sub KNXIO_Define {
$hash->{model} = $mode; # use it also for fheminfo statistics $hash->{model} = $mode; # use it also for fheminfo statistics
# handle mode X for FHEM2FHEM configs # handle mode X for FHEM2FHEM configs
return InternalTimer(gettimeofday() + 0.2,\&KNXIO_openDevX,$hash) if ($mode eq q{X}); return InternalTimer(gettimeofday() + 0.2,\&KNXIO_openDev,$hash) if ($mode eq q{X});
return q{KNXIO-define syntax: "define <name> KNXIO <H|M|T> <ip-address|hostname>:<port> <phy-adress>" } . "\n" . return q{KNXIO-define syntax: "define <name> KNXIO <H|M|T> <ip-address|hostname>:<port> <phy-adress>" } . "\n" .
q{ or "define <name> KNXIO S <pathToUnixSocket> <phy-address>" } if (scalar(@arg) < 5); q{ or "define <name> KNXIO S <pathToUnixSocket> <phy-address>" } if (scalar(@arg) < 5);
@ -256,8 +261,6 @@ sub KNXIO_Read {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $mode = $hash->{model}; my $mode = $hash->{model};
# return if IsDisabled($name); # move after read function 8/2023
my $buf = undef; my $buf = undef;
if ($mode eq 'M') { if ($mode eq 'M') {
my ($rhost,$rport) = ::TcpServer_MCastRecv($hash, $buf, 1024); my ($rhost,$rport) = ::TcpServer_MCastRecv($hash, $buf, 1024);
@ -270,7 +273,7 @@ sub KNXIO_Read {
return; return;
} }
return if IsDisabled($name); return if IsDisabled($name); # moved after read function 8/2023
KNXIO_Log ($name, 5, 'buf=' . unpack('H*',$buf)); KNXIO_Log ($name, 5, 'buf=' . unpack('H*',$buf));
@ -464,7 +467,6 @@ sub KNXIO_ReadH {
$hash->{PhyAddr} = KNXIO_addr2hex($phyaddr,2); # correct Phyaddr. $hash->{PhyAddr} = KNXIO_addr2hex($phyaddr,2); # correct Phyaddr.
KNXIO_handleConn($hash); KNXIO_handleConn($hash);
KNXIO_Log ($name, 3, q{connected});
$hash->{KNXIOhelper}->{SEQUENCECNTR} = 0; $hash->{KNXIOhelper}->{SEQUENCECNTR} = 0;
return InternalTimer(gettimeofday() + 60, \&KNXIO_keepAlive, $hash); # start keepalive return InternalTimer(gettimeofday() + 60, \&KNXIO_keepAlive, $hash); # start keepalive
} }
@ -561,27 +563,54 @@ sub KNXIO_Write {
} }
elsif ($mode eq 'M') { elsif ($mode eq 'M') {
$completemsg = pack('nnnnnnnCCC*',0x0610,0x0530,$datasize + 16,0x2900,0xBCE0,$src,$dst,$datasize,0,@data); # use src addr $completemsg = pack('nnnnnnnCCC*',0x0610,0x0530,$datasize + 16,0x2900,0xBCE0,$src,$dst,$datasize,0,@data); # use src addr
$ret = ::TcpServer_MCastSend($hash,$completemsg);
} }
else { # $mode eq 'H' else { # $mode eq 'H'
# total length= $size+20 - include 2900BCEO,src,dst,size,0 # total length= $size+20 - include 2900BCEO,src,dst,size,0
$completemsg = pack('nnnCC',0x0610,0x0420,$datasize + 20,4,$hash->{KNXIOhelper}->{CCID}) . $completemsg = pack('nnnCC',0x0610,0x0420,$datasize + 20,4,$hash->{KNXIOhelper}->{CCID}) .
pack('CCnn',$hash->{KNXIOhelper}->{SEQUENCECNTR_W},0,0x1100,0xBCE0) . pack('CCnn',$hash->{KNXIOhelper}->{SEQUENCECNTR_W},0,0x1100,0xBCE0) .
pack('nnCCC*',$src,$dst,$datasize,0,@data); # send TunnelInd pack('nnCCC*',$src,$dst,$datasize,0,@data); # send TunnelInd
# Timeout function - expect TunnelAck within 1 sec! - but if fhem has a delay....
$hash->{KNXIOhelper}->{LASTSENTMSG} = unpack('H*',$completemsg); # save msg for resend in case of TO
InternalTimer(gettimeofday() + 1.5, \&KNXIO_TunnelRequestTO, $hash);
} }
$ret = ::DevIo_SimpleWrite($hash,$completemsg,0) if ($mode ne 'M'); ## rate limit
KNXIO_Log ($name, 4, qq{Mode= $mode buf=} . unpack('H*',$completemsg) . qq{ rc= $ret}); push(@{$hash->{KNXIOhelper}->{FIFOW}},$mode,$completemsg);
return; return KNXIO_Write2($hash);
} }
KNXIO_Log ($name, 2, qq{Could not send message $msg}); KNXIO_Log ($name, 2, qq{Could not send message $msg});
return; return;
} }
### handle send que and send control msg-rate
sub KNXIO_Write2 {
my $hash = shift;
my $timenow = gettimeofday();
my $lastwrite = $hash->{KNXIOhelper}->{lastWrite} // $timenow;
my $adddelay = (scalar(@{$hash->{KNXIOhelper}->{FIFOW}}) - 2) * 0.1;
if ($lastwrite > $timenow) {
KNXIO_Log ($hash->{NAME}, 3, q{frequent IO-write cmd - delayed});
InternalTimer($lastwrite + $adddelay, \&KNXIO_Write2,$hash);
return;
}
$hash->{KNXIOhelper}->{lastWrite} = $timenow + 0.2; # add delay
my $mode = shift(@{$hash->{KNXIOhelper}->{FIFOW}});
my $completemsg = shift(@{$hash->{KNXIOhelper}->{FIFOW}});
my $ret = q{};
if ($mode eq 'M') {
$ret = ::TcpServer_MCastSend($hash,$completemsg);
}
else {
$ret = ::DevIo_SimpleWrite($hash,$completemsg,0);
if ($mode eq 'H') {
# Timeout function - expect TunnelAck within 1 sec! - but if fhem has a delay....
$hash->{KNXIOhelper}->{LASTSENTMSG} = unpack('H*',$completemsg); # save msg for resend in case of TO
InternalTimer($timenow + 1.5, \&KNXIO_TunnelRequestTO, $hash);
}
}
KNXIO_Log ($hash->{NAME}, 4, qq{Mode= $mode buf=} . unpack('H*',$completemsg) . qq{ rc= $ret});
return;
}
##################################### #####################################
## a FHEM-rename changes the internal IODev of KNX-dev's, ## a FHEM-rename changes the internal IODev of KNX-dev's,
## but NOT the reading IODev & attr IODev ## but NOT the reading IODev & attr IODev
@ -678,22 +707,7 @@ sub KNXIO_openDev {
return if (IsDisabled($name) == 1); return if (IsDisabled($name) == 1);
=pod return KNXIO_openDevX($hash) if ($mode eq q{X});
# handle mode X first
if ($mode eq 'X') {
my @f2flist = devspec2array('TYPE=FHEM2FHEM'); # get F2F devices
foreach my $f2fdev (@f2flist) {
next if (IsDevice($f2fdev) == 0); # no F2Fdevice found
my $rawdev = $defs{$f2fdev}->{rawDevice};
next if (IsDevice($rawdev,'KNXIO') == 0);
next if ($rawdev ne $name);
KNXIO_init($hash);
return;
}
readingsSingleUpdate($hash, 'state', 'disconnected', 1);
return qq{KNXIO_openDev ($name): open failed};
}
=cut
if (exists $hash->{DNSWAIT}) { if (exists $hash->{DNSWAIT}) {
$hash->{DNSWAIT} += 1; $hash->{DNSWAIT} += 1;
@ -789,8 +803,6 @@ sub KNXIO_openDevX {
my $hash = shift; my $hash = shift;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
return if (IsDisabled($name) == 1);
my @f2flist = devspec2array('TYPE=FHEM2FHEM'); # get F2F devices my @f2flist = devspec2array('TYPE=FHEM2FHEM'); # get F2F devices
foreach my $f2fdev (@f2flist) { foreach my $f2fdev (@f2flist) {
next if (IsDevice($f2fdev) == 0); # no F2Fdevice found next if (IsDevice($f2fdev) == 0); # no F2Fdevice found
@ -823,7 +835,6 @@ sub KNXIO_init {
# state 'connected' is set in decode_EMI (model ST) or in readH (model H) # state 'connected' is set in decode_EMI (model ST) or in readH (model H)
else { else {
KNXIO_handleConn($hash); KNXIO_handleConn($hash);
KNXIO_Log ($name, 3, q{connected});
} }
return; return;
@ -837,6 +848,7 @@ sub KNXIO_handleConn {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if (exists($hash->{KNXIOhelper}->{startdone})) { if (exists($hash->{KNXIOhelper}->{startdone})) {
KNXIO_Log ($name, 3, q{connected});
readingsSingleUpdate($hash, 'state', 'connected', 1); readingsSingleUpdate($hash, 'state', 'connected', 1);
main::KNX_scan('TYPE=KNX:FILTER=IODev=' . $name) if (AttrNum($name,'enableKNXscan',0) >= 2); # on every connect main::KNX_scan('TYPE=KNX:FILTER=IODev=' . $name) if (AttrNum($name,'enableKNXscan',0) >= 2); # on every connect
} }
@ -1041,7 +1053,6 @@ sub KNXIO_decodeEMI {
if ($id == 0x0026) { if ($id == 0x0026) {
KNXIO_Log ($name, 4, 'OpenGrpCon response received'); KNXIO_Log ($name, 4, 'OpenGrpCon response received');
KNXIO_handleConn($hash); KNXIO_handleConn($hash);
KNXIO_Log ($name, 3, q{connected});
} }
else { else {
KNXIO_Log ($name, 3, 'invalid message code ' . sprintf('%04x',$id)); KNXIO_Log ($name, 3, 'invalid message code ' . sprintf('%04x',$id));
@ -1215,7 +1226,7 @@ sub KNXIO_TunnelRequestTO {
# send disco request # send disco request
my $hpai = pack('nCCCCn',(0x0801,0,0,0,0,0)); my $hpai = pack('nCCCCn',(0x0801,0,0,0,0,0));
my $msg = pack('nnnCC',(0x0610,0x0209,16,$hash->{KNXIOhelper}->{CCID},0)) . $hpai; my $msg = pack('nnnCC',(0x0610,0x0209,16,$hash->{KNXIOhelper}->{CCID},0)) . $hpai;
::DevIo_SimpleWrite($hash,$msg,0); # send disconn requ ::DevIo_SimpleWrite($hash,$msg,0);
return; return;
} }