2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 12:49:34 +00:00

98_Modbus.pm: little bugfix and devio_getState in readyFn

git-svn-id: https://svn.fhem.de/fhem/trunk@26789 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
StefanStrobel 2022-12-05 19:47:12 +00:00
parent 12fc429059
commit 5b34fd261d

View File

@ -23,6 +23,9 @@
# #
# ToDo / Ideas # ToDo / Ideas
# Datentypen vorbelegen - gelten falls nichts anderes per attr definiert ist
# reading attr could be optional -> reading name is then object name like h234
#
# LastError Reading per logical device with error code and affected reading # LastError Reading per logical device with error code and affected reading
# verify that nextOpenDelay is integer and >= 1 # verify that nextOpenDelay is integer and >= 1
# set active results in error when tcp is already open # set active results in error when tcp is already open
@ -234,6 +237,8 @@ BEGIN { # functions / variables needed from package main
DevIo_CloseDev DevIo_CloseDev
DevIo_IsOpen DevIo_IsOpen
DevIo_Disconnected DevIo_Disconnected
DevIo_getState
DevIo_setStates
SetExtensions SetExtensions
TcpServer_Open TcpServer_Open
@ -264,7 +269,7 @@ BEGIN { # functions / variables needed from package main
}; };
my $Module_Version = '4.4.11 - 5.10.2022'; my $Module_Version = '4.4.13 - 4.12.2022';
my $PhysAttrs = join (' ', my $PhysAttrs = join (' ',
'queueDelay', 'queueDelay',
@ -552,11 +557,11 @@ sub DefineFn {
$dev .= ':502' if ($dev !~ /.*:[0-9]/); # add default port if no port specified $dev .= ':502' if ($dev !~ /.*:[0-9]/); # add default port if no port specified
delete $ioHash->{SerialConn}; delete $ioHash->{SerialConn};
} }
$ioHash->{devioNoSTATE} = 1; # let stateFormat do its thing, just set the reading 'state', not the Internal STATE
$ioHash->{DeviceName} = $dev; # needed by DevIo to get Device, Port, Speed etc. $ioHash->{DeviceName} = $dev; # needed by DevIo to get Device, Port, Speed etc.
$ioHash->{IODev} = $ioHash; # point back to self to make getIOHash easier $ioHash->{IODev} = $ioHash; # point back to self to make getIOHash easier
$ioHash->{NOTIFYDEV} = 'global'; # NotifyFn nur aufrufen wenn global events (INITIALIZED) $ioHash->{NOTIFYDEV} = 'global'; # NotifyFn nur aufrufen wenn global events (INITIALIZED)
DoClose($ioHash); DoClose($ioHash); # make sure everything is losed initally, set state to disconnected
Log3 $name, 3, "$name: defined as $dev"; Log3 $name, 3, "$name: defined as $dev";
return; # open is done later from NOTIFY return; # open is done later from NOTIFY
@ -677,7 +682,7 @@ sub DefineLDFn {
delete $hash->{TCPServer}; delete $hash->{TCPServer};
delete $hash->{TCPChild}; delete $hash->{TCPChild};
} }
SetStates($hash, 'disconnected'); # initial state after define - might modify to disabled / inactive GoToState($hash, 'disconnected'); # initial state after define - might modify to disabled / inactive
# connection will be opened later in NotifyFN (INITIALIZED, DEFINED, MODIFIED, ...) # connection will be opened later in NotifyFN (INITIALIZED, DEFINED, MODIFIED, ...)
# for serial connections we use a separate physical device. This is set in Notify # for serial connections we use a separate physical device. This is set in Notify
return; return;
@ -750,10 +755,7 @@ sub AttrFn {
# disable on a physical device # disable on a physical device
if ($cmd eq "set" && $aVal) { if ($cmd eq "set" && $aVal) {
Log3 $name, 3, "$name: attr disable set" . (IsOpen($hash) ? ", closing connection" : ""); Log3 $name, 3, "$name: attr disable set" . (IsOpen($hash) ? ", closing connection" : "");
# todo: call setstates here? would call DoClose, stop timers GoToState($hash, 'disabled'); # close, stop timers and set state
DoClose($hash); # close, set Expect, clear Buffer, set state to disconnected
UpdateTimer($hash, \&Modbus::GetUpdate, 'stop'); # even here for physical device?
# other timers are stopped in DoClose
} }
elsif ($cmd eq 'del' || ($cmd eq 'set' && !$aVal)) { elsif ($cmd eq 'del' || ($cmd eq 'set' && !$aVal)) {
Log3 $name, 3, "$name: attr disable removed"; Log3 $name, 3, "$name: attr disable removed";
@ -886,11 +888,11 @@ sub AttrLDFn {
if ($aName eq 'disable' && $init_done) { # if not init_done, nothing to be done here (see NotifyFN) if ($aName eq 'disable' && $init_done) { # if not init_done, nothing to be done here (see NotifyFN)
if ($cmd eq "set" && $aVal) { # disable set on a logical device (not physical serial here!) if ($cmd eq "set" && $aVal) { # disable set on a logical device (not physical serial here!)
SetStates($hash, 'disabled'); # set state, close / stop timers GoToState($hash, 'disabled'); # set state, close / stop timers
} }
elsif ($cmd eq 'del' || ($cmd eq 'set' && !$aVal)) { # disable removed / cleared elsif ($cmd eq 'del' || ($cmd eq 'set' && !$aVal)) { # disable removed / cleared
Log3 $name, 3, "$name: attr disable removed"; Log3 $name, 3, "$name: attr disable removed";
SetStates($hash, 'enabled'); # set state, open / start update timer GoToState($hash, 'enabled'); # set state, open / start update timer
} }
} }
return; return;
@ -1152,12 +1154,12 @@ sub ControlSet {
} }
if ($setName eq 'active' && AttrVal($name, 'enableSetInactive', 1) ) { if ($setName eq 'active' && AttrVal($name, 'enableSetInactive', 1) ) {
return 'device is disabled' if (AttrVal($name, 'disable', 0)); return 'device is disabled' if (AttrVal($name, 'disable', 0));
SetStates($hash, 'active'); # set state, open / start update timer GoToState($hash, 'active'); # set state, open / start update timer
return '0'; return '0';
} }
if ($setName eq 'inactive' && AttrVal($name, 'enableSetInactive', 1)) { if ($setName eq 'inactive' && AttrVal($name, 'enableSetInactive', 1)) {
return 'device is disabled' if (AttrVal($name, 'disable', 0)); return 'device is disabled' if (AttrVal($name, 'disable', 0));
SetStates($hash, 'inactive'); # set state, close / stop timers GoToState($hash, 'inactive'); # set state, close / stop timers
return '0'; return '0';
} }
if ($setName eq 'stop') { if ($setName eq 'stop') {
@ -1548,7 +1550,7 @@ sub DoOpen {
if ($hash->{DeviceName} eq 'none') { if ($hash->{DeviceName} eq 'none') {
Log3 $name, 5, "$name: open called from $caller, device is defined with none" if ($caller ne 'ReadyFn'); Log3 $name, 5, "$name: open called from $caller, device is defined with none" if ($caller ne 'ReadyFn');
SetStates($hash, 'opened'); OpenCB($hash); # set state and start timers
} }
elsif (!$hash->{TCPConn} && $hash->{TYPE} ne 'Modbus') { # only open physical devices or TCP elsif (!$hash->{TCPConn} && $hash->{TYPE} ne 'Modbus') { # only open physical devices or TCP
Log3 $name, 3, "$name: open called from $caller for logical device - this should not happen"; Log3 $name, 3, "$name: open called from $caller for logical device - this should not happen";
@ -1569,7 +1571,7 @@ sub DoOpen {
if ($ret) { if ($ret) {
Log3 $name, 3, "$name: TcpServerOpen returned $ret"; Log3 $name, 3, "$name: TcpServerOpen returned $ret";
} else { } else {
SetStates($hash, 'opened'); DevIo_setStates($hash, 'opened');
} }
} }
else { # normal case, physical device or TCP else { # normal case, physical device or TCP
@ -1610,12 +1612,13 @@ sub DoOpen {
$hash->{devioLoglevel} = (AttrVal($name, 'silentReconnect', 0) ? 4 : 3); $hash->{devioLoglevel} = (AttrVal($name, 'silentReconnect', 0) ? 4 : 3);
$hash->{TIMEOUT} = $timeOt; $hash->{TIMEOUT} = $timeOt;
if ($arg_ref->{FORCE}) { if ($arg_ref->{FORCE}) {
DevIo_OpenDev($hash, $ready, 0); # standard open DevIo_OpenDev($hash, $ready, 0); # standard synchronous open
OpenCB($hash); # do remaining steps (callback not specified in above call) OpenCB($hash); # do remaining steps (callback not specified in above call)
} }
else { else {
$hash->{BUSY_OPENDEV} = 1; $hash->{BUSY_OPENDEV} = 1;
DevIo_OpenDev($hash, $ready, 0, \&OpenCB); # async open DevIo_OpenDev($hash, $ready, 0, \&OpenCB); # async open
#DevIo_setStates($hash, 'opened'); # state will be set in callback
} }
} }
Profiler($hash, 'Idle'); # set category to book following time, can be Delay, Fhem, Idle, Read, Send or Wait Profiler($hash, 'Idle'); # set category to book following time, can be Delay, Fhem, Idle, Read, Send or Wait
@ -1639,13 +1642,13 @@ sub OpenCB {
Log3 $name, 5, "$name: Open callback: $msg" if ($msg); Log3 $name, 5, "$name: Open callback: $msg" if ($msg);
} }
delete $hash->{BUSY_OPENDEV}; delete $hash->{BUSY_OPENDEV};
if (IsOpen($hash)) { if (IsOpen($hash)) { # also works for 'none'
delete $hash->{TIMEOUTS} ; delete $hash->{TIMEOUTS} ;
UpdateTimer($hash, \&Modbus::GetUpdate, 'start'); # set / change timer UpdateTimer($hash, \&Modbus::GetUpdate, 'start'); # set / change timer
DevIo_setStates($hash, 'opened');
} }
# stop queue-Timer while disconnected and start it again with delay 0 here # stop queue-Timer while disconnected and start it again with delay 0 here. ProcessRequestQueue will call open again if not open yet.
StartQueueTimer($hash, \&Modbus::ProcessRequestQueue, {delay => 0, silent => 0}); StartQueueTimer($hash, \&Modbus::ProcessRequestQueue, {delay => 0, silent => 0});
return; return;
} }
@ -1658,6 +1661,7 @@ sub DoClose {
my $arg_ref = shift // {}; my $arg_ref = shift // {};
my $noDelete = $arg_ref->{NODELETE} // 0; my $noDelete = $arg_ref->{NODELETE} // 0;
my $keepQueue = $arg_ref->{KEEPQUEUE} // 0; my $keepQueue = $arg_ref->{KEEPQUEUE} // 0;
my $noState = $arg_ref->{NOSTATE} // 0;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if (!$hash->{TCPConn} && $hash->{TYPE} ne 'Modbus') { if (!$hash->{TCPConn} && $hash->{TYPE} ne 'Modbus') {
@ -1714,7 +1718,7 @@ sub DoClose {
# close even if it was not open yet but on ready list (need to remove entry from readylist) # close even if it was not open yet but on ready list (need to remove entry from readylist)
DevIo_CloseDev($hash); DevIo_CloseDev($hash);
} }
SetStates($hash, 'disconnected'); GoToState($hash, 'disconnected') if (!$noState);
ResetExpect($hash); ResetExpect($hash);
DropBuffer($hash); DropBuffer($hash);
Profiler($hash, 'Idle'); # set category to book following time, can be Delay, Fhem, Idle, Read, Send or Wait Profiler($hash, 'Idle'); # set category to book following time, can be Delay, Fhem, Idle, Read, Send or Wait
@ -1740,7 +1744,7 @@ sub ReadyFn {
my $hash = shift; my $hash = shift;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if($hash->{STATE} eq 'disconnected') { if(DevIo_getState($hash) eq 'disconnected') {
if (IsDisabled($name)) { if (IsDisabled($name)) {
Log3 $name, 3, "$name: ready called but $name is disabled - don't try to reconnect - call DoClose"; Log3 $name, 3, "$name: ready called but $name is disabled - don't try to reconnect - call DoClose";
DoClose($hash); # state sollte schon disabled sein, macht aber nichts. DoClose($hash); # state sollte schon disabled sein, macht aber nichts.
@ -4234,10 +4238,11 @@ sub CountTimeouts {
} }
################################################################# ############################################################################
# set state Reading and STATE internal # set state reading and take active / inactive into account
# call instead of setting STATE directly and when inactive / disconnected # open / close when when switching between active and inactive/disabled
sub SetStates { # call instead of setting state directly
sub GoToState {
my $hash = shift; my $hash = shift;
my $setState = shift; my $setState = shift;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -4245,10 +4250,10 @@ sub SetStates {
my $disabled = AttrVal($name, 'disable', 0); my $disabled = AttrVal($name, 'disable', 0);
my $newState = $setState; my $newState = $setState;
Log3 $name, 5, "$name: SetStates called from " . FhemCaller() . " with $setState"; Log3 $name, 5, "$name: GoToState called from " . FhemCaller() . " with $setState";
if ($setState eq 'disabled') { if ($setState eq 'disabled') {
# nothing to be done here, newstate is already copied from setState
} }
elsif ($setState eq 'enabled') { # attr disabled is not cleared yet here elsif ($setState eq 'enabled') { # attr disabled is not cleared yet here
$newState = 'active'; # enabled (disable removed) becomes active $newState = 'active'; # enabled (disable removed) becomes active
@ -4260,6 +4265,7 @@ sub SetStates {
$newState = 'disabled' if ($disabled); $newState = 'disabled' if ($disabled);
} }
elsif ($setState eq 'opened') { elsif ($setState eq 'opened') {
# nothing to be done, never called with open
} }
elsif ($setState eq 'disconnected') { elsif ($setState eq 'disconnected') {
$newState = 'inactive' if ($oldState eq 'inactive'); $newState = 'inactive' if ($oldState eq 'inactive');
@ -4272,9 +4278,8 @@ sub SetStates {
if ($newState =~ /inactive|disabled/) { if ($newState =~ /inactive|disabled/) {
if ($hash->{TCPConn}) { # Modbus over TCP connection if ($hash->{TCPConn}) { # Modbus over TCP connection
DoClose($hash); # close, set Expect, clear Buffer, set state to disconnected DoClose($hash, {NOSTATE => 1}); # close, set Expect, clear Buffer
UpdateTimer($hash, \&Modbus::GetUpdate, 'stop'); UpdateTimer($hash, \&Modbus::GetUpdate, 'stop');
# other timers are stopped in DoClose
} }
else { # connection via serial io device else { # connection via serial io device
UnregAtIODev($hash); # unregister at physical device because logical device is disabled UnregAtIODev($hash); # unregister at physical device because logical device is disabled
@ -4294,10 +4299,8 @@ sub SetStates {
} }
} }
Log3 $name, 5, "$name: SetState sets state and STATE from $oldState to $newState"; Log3 $name, 5, "$name: SetState sets state from $oldState to $newState";
$hash->{STATE} = $newState; DevIo_setStates($hash, $newState);
return if ($newState eq ReadingsVal($name, 'state', ''));
readingsSingleUpdate($hash, 'state', $newState, 1);
return; return;
} }
@ -4510,13 +4513,13 @@ sub SetIODev {
} }
if (!$ioHash) { # nothing found -> give up for now if (!$ioHash) { # nothing found -> give up for now
Log3 $name, 3, "$name: SetIODev found no usable physical modbus device"; Log3 $name, 3, "$name: SetIODev found no usable physical modbus device";
SetStates($hash, 'disconnected'); DevIo_setStates($hash, 'disconnected'); # set state
UnregAtIODev($hash); UnregAtIODev($hash);
delete $hash->{IODev}; delete $hash->{IODev};
return; return;
} }
RegisterAtIODev($hash, $ioHash); # register, set MODE and PROTOCOL RegisterAtIODev($hash, $ioHash); # register, set MODE and PROTOCOL
SetStates($hash, 'opened'); # set initial state for logical device connected through physical serial device like DevIo would do it after open DevIo_setStates($hash, 'opened'); # set initial state for logical device connected through physical serial device like DevIo would do it after open
return $ioHash; return $ioHash;
} }
@ -4929,9 +4932,9 @@ sub ObjAttr {
# Get Object Info from Attributes, # Get Object Info from Attributes,
# parseInfo Hash or default from deviceInfo Hash # parseInfo Hash or default from deviceInfo Hash
sub ObjInfo { sub ObjInfo {
my $hash = shift; my $hash = shift; # device hash
my $key = shift; my $key = shift; # objCombi like h123
my $oName = shift; my $oName = shift; # requested
my $defName = $attrDefaults{$oName}{devDefault}; my $defName = $attrDefaults{$oName}{devDefault};
@ -4947,8 +4950,8 @@ sub ObjInfo {
$reading = $parseInfo->{$key}{reading}; $reading = $parseInfo->{$key}{reading};
} }
if (!defined($reading)) { if (!defined($reading)) {
#Log3 $name, 5, "$name: ObjInfo could not find a reading name"; #return (exists($attrDefaults{$oName}{default}) ? $attrDefaults{$oName}{default} : '');
return (exists($attrDefaults{$oName}{default}) ? $attrDefaults{$oName}{default} : ''); $reading = "unnamed-$key"; # don't return default here but use key as reading name and continue
} }
#Log3 $name, 5, "$name: ObjInfo now looks at attrs for oName $oName / reading $reading / $key"; #Log3 $name, 5, "$name: ObjInfo now looks at attrs for oName $oName / reading $reading / $key";
@ -4965,7 +4968,13 @@ sub ObjInfo {
# parseInfo for object $oName if special Fhem module using parseinfoHash # parseInfo for object $oName if special Fhem module using parseinfoHash
return $parseInfo->{$key}{$oName} return $parseInfo->{$key}{$oName}
if (defined($parseInfo->{$key}) && defined($parseInfo->{$key}{$oName})); if (defined($parseInfo->{$key}) && defined($parseInfo->{$key}{$oName}));
# returning unnamed here creates problems because we don't know if a reading actually has been defined e.g. for a slave
#if ($oName eq 'reading') {
# Log3 $name, 5, "$name: ObjInfo called from " . FhemCaller() . " with $key, $oName requested, no attr reading defined, use unnamed-$key instead";
# return "unnamed-$key"; # if no attr and no parseinfo matche before
#}
# check for type entry / attr ... # check for type entry / attr ...
if ($oName ne 'type') { if ($oName ne 'type') {
#Log3 $name, 5, "$name: ObjInfo checking types"; #Log3 $name, 5, "$name: ObjInfo checking types";
@ -5001,6 +5010,7 @@ sub ObjInfo {
return $devInfo->{$type}{$defName} return $devInfo->{$type}{$defName}
if (defined($devInfo->{$type}) && defined($devInfo->{$type}{$defName})); if (defined($devInfo->{$type}) && defined($devInfo->{$type}{$defName}));
} }
# todo: the final return expression seems redundant # todo: the final return expression seems redundant
return (exists($attrDefaults{$oName}{default}) ? $attrDefaults{$oName}{default} : ''); return (exists($attrDefaults{$oName}{default}) ? $attrDefaults{$oName}{default} : '');
} }