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:
parent
8d4d30f3d6
commit
1c7d671143
@ -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));
|
||||||
|
|
||||||
@ -419,7 +422,7 @@ sub KNXIO_ReadH {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
KNXIO_Log ($name, 4, q{TunnelRequest received - send Ack and decode. } .
|
KNXIO_Log ($name, 4, q{TunnelRequest received - send Ack and decode. } .
|
||||||
qq{seqcntrRx= $hash->{KNXIOhelper}->{SEQUENCECNTR}} ) if (! defined($discardFrame));
|
qq{seqcntrRx= $hash->{KNXIOhelper}->{SEQUENCECNTR}} ) if (! defined($discardFrame));
|
||||||
my $tacksend = pack('nnnCCCC',0x0610,0x0421,10,4,$ccid,$hash->{KNXIOhelper}->{SEQUENCECNTR},0); # send ack
|
my $tacksend = pack('nnnCCCC',0x0610,0x0421,10,4,$ccid,$hash->{KNXIOhelper}->{SEQUENCECNTR},0); # send ack
|
||||||
$hash->{KNXIOhelper}->{SEQUENCECNTR}++;
|
$hash->{KNXIOhelper}->{SEQUENCECNTR}++;
|
||||||
$hash->{KNXIOhelper}->{SEQUENCECNTR} = 0 if ($hash->{KNXIOhelper}->{SEQUENCECNTR} > 255);
|
$hash->{KNXIOhelper}->{SEQUENCECNTR} = 0 if ($hash->{KNXIOhelper}->{SEQUENCECNTR} > 255);
|
||||||
@ -456,7 +459,7 @@ sub KNXIO_ReadH {
|
|||||||
RemoveInternalTimer($hash,\&KNXIO_keepAlive);
|
RemoveInternalTimer($hash,\&KNXIO_keepAlive);
|
||||||
if ($errcode > 0) {
|
if ($errcode > 0) {
|
||||||
KNXIO_Log ($name, 3, q{ConnectionResponse received } .
|
KNXIO_Log ($name, 3, q{ConnectionResponse received } .
|
||||||
qq{CCID= $hash->{KNXIOhelper}->{CCID} Status=} . KNXIO_errCodes($errcode));
|
qq{CCID= $hash->{KNXIOhelper}->{CCID} Status=} . KNXIO_errCodes($errcode));
|
||||||
KNXIO_disconnect($hash,2);
|
KNXIO_disconnect($hash,2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user