From 204a9daf6eb082c744e3248da996cda219346bf3 Mon Sep 17 00:00:00 2001 From: john99sr Date: Sat, 29 Nov 2014 10:58:10 +0000 Subject: [PATCH] 98_PID20 : Feature: all readings are updated cyclically git-svn-id: https://svn.fhem.de/fhem/trunk@7089 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/98_PID20.pm | 1163 ++++++++++++++++++++--------------------- 1 file changed, 577 insertions(+), 586 deletions(-) diff --git a/fhem/FHEM/98_PID20.pm b/fhem/FHEM/98_PID20.pm index f14dbc769..6b8db0ea3 100644 --- a/fhem/FHEM/98_PID20.pm +++ b/fhem/FHEM/98_PID20.pm @@ -45,8 +45,11 @@ # V 1.0.0.2 30.10.14 - fix in PID20_Calc when setting $retStr (thanks to Thorsten Pferdekaemper) # V 1.0.0.3 # 20.11.2014 Bug: processing of D-Portion and handling of disable +# V 1.0.0.4 +# 27.11.2014 Change: all readings are updated cyclically #################################################################################################### package main; + use strict; use warnings; use feature qw/say switch/; @@ -54,316 +57,311 @@ use vars qw(%defs); use vars qw($readingFnAttributes); use vars qw(%attr); use vars qw(%modules); -my $PID20_Version = "1.0.0.3"; + +my $PID20_Version = "1.0.0.4"; sub PID20_Calc($); ######################################## sub PID20_Log($$$) { - my ( $hash, $loglevel, $text ) = @_; - my $xline = ( caller(0) )[2]; - my $xsubroutine = ( caller(1) )[3]; - my $sub = ( split( ':', $xsubroutine ) )[2]; - $sub = substr( $sub, 6 ); # without PID20 - my $instName = ( ref($hash) eq "HASH" ) ? $hash->{NAME} : "PID20"; - Log3 $hash, $loglevel, "PID20 $instName: $sub.$xline " . $text; + my ( $hash, $loglevel, $text ) = @_; + my $xline = ( caller(0) )[2]; + my $xsubroutine = ( caller(1) )[3]; + my $sub = ( split( ':', $xsubroutine ) )[2]; + $sub = substr( $sub, 6 ); # without PID20 + my $instName = ( ref($hash) eq "HASH" ) ? $hash->{NAME} : "PID20"; + Log3 $hash, $loglevel, "PID20 $instName: $sub.$xline " . $text; } ######################################## sub PID20_Initialize($) { - my ($hash) = @_; - $hash->{DefFn} = "PID20_Define"; - $hash->{UndefFn} = "PID20_Undef"; - $hash->{SetFn} = "PID20_Set"; - $hash->{GetFn} = "PID20_Get"; - $hash->{NotifyFn} = "PID20_Notify"; - $hash->{AttrList} = - "pidActorValueDecPlaces:0,1,2,3,4,5 " - . "pidActorInterval " - . "pidActorTreshold " - . "pidActorErrorAction:freeze,errorPos " - . "pidActorErrorPos " - . "pidActorKeepAlive " - . "pidActorLimitLower " - . "pidActorLimitUpper " - . "pidCalcInterval " - . "pidDeltaTreshold " - . "pidDesiredName " - . "pidFactor_P " - . "pidFactor_I " - . "pidFactor_D " - . "pidMeasuredName " - . "pidSensorTimeout " - . "pidReverseAction " - . "pidUpdateInterval " + my ($hash) = @_; + $hash->{DefFn} = "PID20_Define"; + $hash->{UndefFn} = "PID20_Undef"; + $hash->{SetFn} = "PID20_Set"; + $hash->{GetFn} = "PID20_Get"; + $hash->{NotifyFn} = "PID20_Notify"; + $hash->{AttrList} = + "pidActorValueDecPlaces:0,1,2,3,4,5 " + . "pidActorInterval " + . "pidActorTreshold " + . "pidActorErrorAction:freeze,errorPos " + . "pidActorErrorPos " + . "pidActorKeepAlive " + . "pidActorLimitLower " + . "pidActorLimitUpper " + . "pidCalcInterval " + . "pidDeltaTreshold " + . "pidDesiredName " + . "pidFactor_P " + . "pidFactor_I " + . "pidFactor_D " + . "pidMeasuredName " + . "pidSensorTimeout " + . "pidReverseAction " + . "pidUpdateInterval " - # . "pidDebugEnable:0,1 "; - . "pidDebugSensor:0,1 " - . "pidDebugActuation:0,1 " - . "pidDebugCalc:0,1 " - . "pidDebugDelta:0,1 " - . "pidDebugUpdate:0,1 " - . "pidDebugNotify:0,1 " - . "disable:0,1 " - . $readingFnAttributes; + # . "pidDebugEnable:0,1 "; + . "pidDebugSensor:0,1 " + . "pidDebugActuation:0,1 " + . "pidDebugCalc:0,1 " + . "pidDebugDelta:0,1 " + . "pidDebugUpdate:0,1 " + . "pidDebugNotify:0,1 " + . "disable:0,1 " + . $readingFnAttributes; } ######################################## sub PID20_TimeDiff($) { - my ($strTS) = @_; + my ($strTS) = @_; - #my ( $package, $filename, $line ) = caller(0); - #print "PID $strTS line $line \n"; - my $serTS = ( defined($strTS) && $strTS ne "" ) ? time_str2num($strTS) : gettimeofday(); - my $timeDiff = gettimeofday() - $serTS; - $timeDiff = 0 if ( $timeDiff < 0 ); - return $timeDiff; + #my ( $package, $filename, $line ) = caller(0); + #print "PID $strTS line $line \n"; + my $serTS = ( defined($strTS) && $strTS ne "" ) ? time_str2num($strTS) : gettimeofday(); + my $timeDiff = gettimeofday() - $serTS; + $timeDiff = 0 if ( $timeDiff < 0 ); + return $timeDiff; } ######################################## sub PID20_Define($$$) { - my ( $hash, $def ) = @_; - my @a = split( "[ \t][ \t]*", $def ); - my $name = $a[0]; - my $reFloat = '^([\\+,\\-]?\\d+\\.?\d*$)'; # gleitpunkt - if ( @a != 4 ) - { - return "wrong syntax: define PID20 " . ":reading:[regexp] [:cmd] "; - } - ################### - # Sensor - my ( $sensor, $reading, $regexp ) = split( ":", $a[2], 3 ); + my ( $hash, $def ) = @_; + my @a = split( "[ \t][ \t]*", $def ); + my $name = $a[0]; + my $reFloat = '^([\\+,\\-]?\\d+\\.?\d*$)'; # gleitpunkt + if ( @a != 4 ) + { + return "wrong syntax: define PID20 " . ":reading:[regexp] [:cmd] "; + } + ################### + # Sensor + my ( $sensor, $reading, $regexp ) = split( ":", $a[2], 3 ); - # if sensor unkonwn - if ( !$defs{$sensor} ) - { - my $msg = "$name: Unknown sensor device $sensor specified"; - PID20_Log $hash, 1, $msg; - return $msg; - } + # if sensor unkonwn + if ( !$defs{$sensor} ) + { + my $msg = "$name: Unknown sensor device $sensor specified"; + PID20_Log $hash, 1, $msg; + return $msg; + } - # if reading of sender is unkown - if ( ReadingsVal( $sensor, $reading, 'unknown' ) eq 'unkown' ) - { - my $msg = "$name: Unknown reading $reading for sensor device $sensor specified"; - PID20_Log $hash, 1, $msg; - return $msg; - } - $hash->{helper}{sensor} = $sensor; + # if reading of sender is unkown + if ( ReadingsVal( $sensor, $reading, 'unknown' ) eq 'unkown' ) + { + my $msg = "$name: Unknown reading $reading for sensor device $sensor specified"; + PID20_Log $hash, 1, $msg; + return $msg; + } + $hash->{helper}{sensor} = $sensor; - # defaults for regexp - if ( !$regexp ) - { - $regexp = $reFloat; - } - $hash->{helper}{reading} = $reading; - $hash->{helper}{regexp} = $regexp; + # defaults for regexp + if ( !$regexp ) + { + $regexp = $reFloat; + } + $hash->{helper}{reading} = $reading; + $hash->{helper}{regexp} = $regexp; - # Actor - my ( $actor, $cmd ) = split( ":", $a[3], 2 ); - if ( !$defs{$actor} ) - { - my $msg = "$name: Unknown actor device $actor specified"; - PID20_Log $hash, 1, $msg; - return $msg; - } - $hash->{helper}{actor} = $actor; - $hash->{helper}{actorCommand} = ( defined($cmd) ) ? $cmd : ""; - $hash->{helper}{stopped} = 0; - $hash->{helper}{adjust} = ""; - $modules{PID20}{defptr}{$name} = $hash; - readingsSingleUpdate( $hash, 'state', 'initializing', 1 ); - RemoveInternalTimer($name); - InternalTimer( gettimeofday() + 10, "PID20_Calc", $name, 0 ); - return undef; + # Actor + my ( $actor, $cmd ) = split( ":", $a[3], 2 ); + if ( !$defs{$actor} ) + { + my $msg = "$name: Unknown actor device $actor specified"; + PID20_Log $hash, 1, $msg; + return $msg; + } + $hash->{helper}{actor} = $actor; + $hash->{helper}{actorCommand} = ( defined($cmd) ) ? $cmd : ""; + $hash->{helper}{stopped} = 0; + $hash->{helper}{adjust} = ""; + $modules{PID20}{defptr}{$name} = $hash; + readingsSingleUpdate( $hash, 'state', 'initializing', 1 ); + RemoveInternalTimer($name); + InternalTimer( gettimeofday() + 10, "PID20_Calc", $name, 0 ); + return undef; } ######################################## sub PID20_Undef($$) { - my ( $hash, $arg ) = @_; - RemoveInternalTimer( $hash->{NAME} ); - return undef; + my ( $hash, $arg ) = @_; + RemoveInternalTimer( $hash->{NAME} ); + return undef; } -sub ######################################## - # we need a gradient for delta as base for d-portion calculation - # - PID20_Notify($$) +# we need a gradient for delta as base for d-portion calculation +# +sub PID20_Notify($$) { - my ( $hash, $dev ) = @_; - my $name = $hash->{NAME}; - my $sensorName = $hash->{helper}{sensor}; - my $DEBUG = AttrVal( $name, 'pidDebugNotify', '0' ) eq '1'; - my $disable = AttrVal( $name, 'disable', '0' ); + my ( $hash, $dev ) = @_; + my $name = $hash->{NAME}; + my $sensorName = $hash->{helper}{sensor}; + my $DEBUG = AttrVal( $name, 'pidDebugNotify', '0' ) eq '1'; + my $disable = AttrVal( $name, 'disable', '0' ); - # no action if disabled - if ( $disable eq '1' ) - { - return ""; - } - return if ( $dev->{NAME} ne $sensorName ); - my $sensorReadingName = $hash->{helper}{reading}; - my $regexp = $hash->{helper}{regexp}; - my $desiredName = AttrVal( $name, 'pidDesiredName', 'desired' ); - my $desired = ReadingsVal( $name, $desiredName, undef ); - my $max = int( @{ $dev->{CHANGED} } ); - PID20_Log $hash, 4, "check $max readings for " . $sensorReadingName; + # no action if disabled + if ( $disable eq '1' ) + { + return ""; + } + return if ( $dev->{NAME} ne $sensorName ); + my $sensorReadingName = $hash->{helper}{reading}; + my $regexp = $hash->{helper}{regexp}; + my $desiredName = AttrVal( $name, 'pidDesiredName', 'desired' ); + my $desired = ReadingsVal( $name, $desiredName, undef ); + my $max = int( @{ $dev->{CHANGED} } ); + PID20_Log $hash, 4, "check $max readings for " . $sensorReadingName; - for ( my $i = 0 ; $i < $max ; $i++ ) - { - my $s = $dev->{CHANGED}[$i]; + for ( my $i = 0 ; $i < $max ; $i++ ) + { + my $s = $dev->{CHANGED}[$i]; - # continue, if no match with reading-name - $s = "" if ( !defined($s) ); - PID20_Log $hash, 5, "check event:<$s>"; - next if ( $s !~ m/$sensorReadingName/ ); + # continue, if no match with reading-name + $s = "" if ( !defined($s) ); + PID20_Log $hash, 5, "check event:<$s>"; + next if ( $s !~ m/$sensorReadingName/ ); - # ---- build difference current - old value - # get sensor value - my $sensorStr = ReadingsVal( $sensorName, $sensorReadingName, undef ); - $sensorStr =~ m/$regexp/; - my $sensorValue = $1; + # ---- build difference current - old value + # get sensor value + my $sensorStr = ReadingsVal( $sensorName, $sensorReadingName, undef ); + $sensorStr =~ m/$regexp/; + my $sensorValue = $1; - # calc difference of delta/deltaOld - my $delta = $desired - $sensorValue if ( defined($desired) ); - my $deltaOld = ( $hash->{helper}{deltaOld} + 0 ) if ( defined( $hash->{helper}{deltaOld} ) ); - my $deltaDiff = ( $delta - $deltaOld ) if ( defined($delta) && defined($deltaOld) ); - PID20_Log $hash, 5, - "Diff: delta[" - . sprintf( "%.2f", $delta ) . "]" - . " - deltaOld[" - . sprintf( "%.2f", $deltaOld ) . "]" - . "= Diff[" - . sprintf( "%.2f", $deltaDiff ) . "]" - if ($DEBUG); + # calc difference of delta/deltaOld + my $delta = $desired - $sensorValue if ( defined($desired) ); + my $deltaOld = ( $hash->{helper}{deltaOld} + 0 ) if ( defined( $hash->{helper}{deltaOld} ) ); + my $deltaDiff = ( $delta - $deltaOld ) if ( defined($delta) && defined($deltaOld) ); + PID20_Log $hash, 5, + "Diff: delta[" + . sprintf( "%.2f", $delta ) . "]" + . " - deltaOld[" + . sprintf( "%.2f", $deltaOld ) . "]" + . "= Diff[" + . sprintf( "%.2f", $deltaDiff ) . "]" + if ($DEBUG); - # ----- build difference of timestamps (ok) - my $deltaOldTsStr = $hash->{helper}{deltaOldTS}; - my $deltaOldTsNum = time_str2num($deltaOldTsStr) if ( defined($deltaOldTsStr) ); - my $nowTsNum = gettimeofday(); - my $tsDiff = ( $nowTsNum - $deltaOldTsNum ) - if ( defined($deltaOldTsNum) && ( ( $nowTsNum - $deltaOldTsNum ) > 0 ) ); - PID20_Log $hash, 5, "tsDiff: tsDiff = $tsDiff " if ($DEBUG); + # ----- build difference of timestamps (ok) + my $deltaOldTsStr = $hash->{helper}{deltaOldTS}; + my $deltaOldTsNum = time_str2num($deltaOldTsStr) if ( defined($deltaOldTsStr) ); + my $nowTsNum = gettimeofday(); + my $tsDiff = ( $nowTsNum - $deltaOldTsNum ) + if ( defined($deltaOldTsNum) && ( ( $nowTsNum - $deltaOldTsNum ) > 0 ) ); + PID20_Log $hash, 5, "tsDiff: tsDiff = $tsDiff " if ($DEBUG); - # ----- calculate gradient of delta - my $deltaGradient = $deltaDiff / $tsDiff - if ( defined($deltaDiff) && defined($tsDiff) && ( $tsDiff > 0 ) ); - $deltaGradient = 0 if ( !defined($deltaGradient) ); - my $sdeltaDiff = ($deltaDiff) ? sprintf( "%.2f", $deltaDiff ) : ""; - my $sTSDiff = ($tsDiff) ? sprintf( "%.2f", $tsDiff ) : ""; - my $sDeltaGradient = ($deltaGradient) ? sprintf( "%.6f", $deltaGradient ) : ""; - PID20_Log $hash, 5, - "deltaGradient: (Diff[$sdeltaDiff]" - . "/tsDiff[$sTSDiff]" - . "=deltaGradient per sec [$sDeltaGradient]" - if ($DEBUG); + # ----- calculate gradient of delta + my $deltaGradient = $deltaDiff / $tsDiff + if ( defined($deltaDiff) && defined($tsDiff) && ( $tsDiff > 0 ) ); + $deltaGradient = 0 if ( !defined($deltaGradient) ); + my $sdeltaDiff = ($deltaDiff) ? sprintf( "%.2f", $deltaDiff ) : ""; + my $sTSDiff = ($tsDiff) ? sprintf( "%.2f", $tsDiff ) : ""; + my $sDeltaGradient = ($deltaGradient) ? sprintf( "%.6f", $deltaGradient ) : ""; + PID20_Log $hash, 5, + "deltaGradient: (Diff[$sdeltaDiff]" . "/tsDiff[$sTSDiff]" . "=deltaGradient per sec [$sDeltaGradient]" + if ($DEBUG); - # ----- store results - $hash->{helper}{deltaGradient} = $deltaGradient; - $hash->{helper}{deltaOld} = $delta; - $hash->{helper}{deltaOldTS} = TimeNow(); - last; - } - return ""; + # ----- store results + $hash->{helper}{deltaGradient} = $deltaGradient; + $hash->{helper}{deltaOld} = $delta; + $hash->{helper}{deltaOldTS} = TimeNow(); + last; + } + return ""; } ######################################## sub PID20_Get($@) { - my ( $hash, @a ) = @_; - my $name = $hash->{NAME}; - my $usage = "Unknown argument $a[1], choose one of params:noArg"; - return $usage if ( @a < 2 ); - my $cmd = lc( $a[1] ); - given ($cmd) - { - when ('params') - { - my $ret = "Defined parameters for PID20 $name:\n\n"; - $ret .= 'Actor name : ' . $hash->{helper}{actor} . "\n"; - $ret .= 'Actor cmd : ' . $hash->{helper}{actorCommand} . "\n\n"; - $ret .= 'Sensor name : ' . $hash->{helper}{sensor} . "\n"; - $ret .= 'Sensor reading : ' . $hash->{helper}{reading} . "\n\n"; - $ret .= 'Sensor regexp : ' . $hash->{helper}{regexp} . "\n\n"; - $ret .= 'Factor P : ' . $hash->{helper}{factor_P} . "\n"; - $ret .= 'Factor I : ' . $hash->{helper}{factor_I} . "\n"; - $ret .= 'Factor D : ' . $hash->{helper}{factor_D} . "\n\n"; - $ret .= 'Actor lower limit: ' . $hash->{helper}{actorLimitLower} . "\n"; - $ret .= 'Actor upper limit: ' . $hash->{helper}{actorLimitUpper} . "\n"; - return $ret; - } - default { return $usage; } - } + my ( $hash, @a ) = @_; + my $name = $hash->{NAME}; + my $usage = "Unknown argument $a[1], choose one of params:noArg"; + return $usage if ( @a < 2 ); + my $cmd = lc( $a[1] ); + given ($cmd) + { + when ('params') + { + my $ret = "Defined parameters for PID20 $name:\n\n"; + $ret .= 'Actor name : ' . $hash->{helper}{actor} . "\n"; + $ret .= 'Actor cmd : ' . $hash->{helper}{actorCommand} . "\n\n"; + $ret .= 'Sensor name : ' . $hash->{helper}{sensor} . "\n"; + $ret .= 'Sensor reading : ' . $hash->{helper}{reading} . "\n\n"; + $ret .= 'Sensor regexp : ' . $hash->{helper}{regexp} . "\n\n"; + $ret .= 'Factor P : ' . $hash->{helper}{factor_P} . "\n"; + $ret .= 'Factor I : ' . $hash->{helper}{factor_I} . "\n"; + $ret .= 'Factor D : ' . $hash->{helper}{factor_D} . "\n\n"; + $ret .= 'Actor lower limit: ' . $hash->{helper}{actorLimitLower} . "\n"; + $ret .= 'Actor upper limit: ' . $hash->{helper}{actorLimitUpper} . "\n"; + return $ret; + } + default { return $usage; } + } } ######################################## sub PID20_Set($@) { - my ( $hash, @a ) = @_; - my $name = $hash->{NAME}; - my $reFloat = '^([\\+,\\-]?\\d+\\.?\d*$)'; - my $usage = - "Unknown argument $a[1], choose one of stop:noArg start:noArg restart " - . AttrVal( $name, 'pidDesiredName', 'desired' ); - return $usage if ( @a < 2 ); - my $cmd = lc( $a[1] ); - my $desiredName = lc( AttrVal( $name, 'pidDesiredName', 'desired' ) ); + my ( $hash, @a ) = @_; + my $name = $hash->{NAME}; + my $reFloat = '^([\\+,\\-]?\\d+\\.?\d*$)'; + my $usage = + "Unknown argument $a[1], choose one of stop:noArg start:noArg restart " + . AttrVal( $name, 'pidDesiredName', 'desired' ); + return $usage if ( @a < 2 ); + my $cmd = lc( $a[1] ); + my $desiredName = lc( AttrVal( $name, 'pidDesiredName', 'desired' ) ); - #PID20_Log $hash, 3, "name:$name cmd:$cmd $desired:$desired"; - given ($cmd) - { - when ("?") - { - return $usage; - } - when ($desiredName) - { - return - "Set " - . AttrVal( $name, 'pidDesiredName', 'desired' ) - . " needs a parameter" - if ( @a != 3 ); - my $value = $a[2]; - $value = ( $value =~ m/$reFloat/ ) ? $1 : undef; - return "value " . $a[2] . " is not a number" - if ( !defined($value) ); - readingsSingleUpdate( $hash, $cmd, $value, 1 ); - PID20_Log $hash, 3, "set $name $cmd $a[2]"; - } - when ("start") - { - return "Set start needs a parameter" - if ( @a != 2 ); - $hash->{helper}{stopped} = 0; - } - when ("stop") - { - return "Set stop needs a parameter" - if ( @a != 2 ); - $hash->{helper}{stopped} = 1; - PID20_Calc($name); - } - when ("restart") - { - return "Set restart needs a parameter" - if ( @a != 3 ); - my $value = $a[2]; - $value = ( $value =~ m/$reFloat/ ) ? $1 : undef; + #PID20_Log $hash, 3, "name:$name cmd:$cmd $desired:$desired"; + given ($cmd) + { + when ("?") + { + return $usage; + } + when ($desiredName) + { + return "Set " . AttrVal( $name, 'pidDesiredName', 'desired' ) . " needs a parameter" + if ( @a != 3 ); + my $value = $a[2]; + $value = ( $value =~ m/$reFloat/ ) ? $1 : undef; + return "value " . $a[2] . " is not a number" + if ( !defined($value) ); + readingsSingleUpdate( $hash, $cmd, $value, 1 ); + PID20_Log $hash, 3, "set $name $cmd $a[2]"; + } + when ("start") + { + return "Set start needs a parameter" + if ( @a != 2 ); + $hash->{helper}{stopped} = 0; + } + when ("stop") + { + return "Set stop needs a parameter" + if ( @a != 2 ); + $hash->{helper}{stopped} = 1; + PID20_Calc($name); + } + when ("restart") + { + return "Set restart needs a parameter" + if ( @a != 3 ); + my $value = $a[2]; + $value = ( $value =~ m/$reFloat/ ) ? $1 : undef; - #PID20_Log $hash, 1, "value:$value"; - return "value " . $a[2] . " is not a number" - if ( !defined($value) ); - $hash->{helper}{stopped} = 0; - $hash->{helper}{adjust} = $value; - PID20_Log $hash, 3, "set $name $cmd $value"; - } - when ("calc") # inofficial function, only for debugging purposes - { - PID20_Calc($name); - } - default - { - return $usage; - } - } - return; + #PID20_Log $hash, 1, "value:$value"; + return "value " . $a[2] . " is not a number" + if ( !defined($value) ); + $hash->{helper}{stopped} = 0; + $hash->{helper}{adjust} = $value; + PID20_Log $hash, 3, "set $name $cmd $value"; + } + when ("calc") # inofficial function, only for debugging purposes + { + PID20_Calc($name); + } + default + { + return $usage; + } + } + return; } ######################################## # disabled = 0 @@ -373,341 +371,334 @@ sub PID20_Set($@) # alarm = 4 sub PID20_Calc($) { - my $reUINT = '^([\\+]?\\d+)$'; # uint without whitespaces - my $re01 = '^([0,1])$'; # only 0,1 - my $reINT = '^([\\+,\\-]?\\d+$)'; # int - my $reFloatpos = '^([\\+]?\\d+\\.?\d*$)'; # gleitpunkt positiv - my $reFloat = '^([\\+,\\-]?\\d+\\.?\d*$)'; # gleitpunkt - my ($name) = @_; - my $hash = $defs{$name}; - my $sensor = $hash->{helper}{sensor}; - my $reading = $hash->{helper}{reading}; - my $regexp = $hash->{helper}{regexp}; - my $DEBUG_Sensor = AttrVal( $name, 'pidDebugSensor', '0' ) eq '1'; - my $DEBUG_Actuation = AttrVal( $name, 'pidDebugActuation', '0' ) eq '1'; - my $DEBUG_Delta = AttrVal( $name, 'pidDebugDelta', '0' ) eq '1'; - my $DEBUG_Calc = AttrVal( $name, 'pidDebugCalc', '0' ) eq '1'; - my $DEBUG_Update = AttrVal( $name, 'pidDebugUpdate', '0' ) eq '1'; - my $DEBUG = $DEBUG_Sensor || $DEBUG_Actuation || $DEBUG_Calc || $DEBUG_Delta || $DEBUG_Update; - my $actuation = ""; - my $actuationDone = ReadingsVal( $name, 'actuation', "" ); - my $actuationCalc = ReadingsVal( $name, 'actuationCalc', "" ); - my $actuationCalcOld = $actuationCalc; - my $actorTimestamp = - ( $hash->{helper}{actorTimestamp} ) - ? $hash->{helper}{actorTimestamp} - : FmtDateTime( gettimeofday() - 3600 * 24 ); - my $sensorStr = ReadingsVal( $sensor, $reading, "" ); - my $sensorValue = ""; - my $sensorTS = ReadingsTimestamp( $sensor, $reading, undef ); - my $sensorIsAlive = 0; - my $iPortion = ReadingsVal( $name, 'p_i', 0 ); - my $pPortion = ""; - my $dPortion = ""; - my $stateStr = ""; - my $deltaOld = ReadingsVal( $name, 'delta', 0 ); - my $delta = ""; - my $deltaGradient = ( $hash->{helper}{deltaGradient} ) ? $hash->{helper}{deltaGradient} : 0; - my $calcReq = 0; + my $reUINT = '^([\\+]?\\d+)$'; # uint without whitespaces + my $re01 = '^([0,1])$'; # only 0,1 + my $reINT = '^([\\+,\\-]?\\d+$)'; # int + my $reFloatpos = '^([\\+]?\\d+\\.?\d*$)'; # gleitpunkt positiv float + my $reFloat = '^([\\+,\\-]?\\d+\\.?\d*$)'; # float - # ---------------- check different conditions - while (1) - { - # --------------- retrive values from attributes - $hash->{helper}{actorInterval} = - ( AttrVal( $name, 'pidActorInterval', 180 ) =~ m/$reUINT/ ) ? $1 : 180; - $hash->{helper}{actorThreshold} = - ( AttrVal( $name, 'pidActorTreshold', 1 ) =~ m/$reUINT/ ) ? $1 : 1; - $hash->{helper}{actorKeepAlive} = - ( AttrVal( $name, 'pidActorKeepAlive', 1800 ) =~ m/$reUINT/ ) ? $1 : 1800; - $hash->{helper}{actorValueDecPlaces} = - ( AttrVal( $name, 'pidActorValueDecPlaces', 0 ) =~ m/$reUINT/ ) ? $1 : 0; - $hash->{helper}{actorErrorAction} = - ( AttrVal( $name, 'pidActorErrorAction', 'freeze' ) eq 'errorPos' ) ? 'errorPos' : 'freeze'; - $hash->{helper}{actorErrorPos} = - ( AttrVal( $name, 'pidActorErrorPos', 0 ) =~ m/$reINT/ ) ? $1 : 0; - $hash->{helper}{calcInterval} = - ( AttrVal( $name, 'pidCalcInterval', 60 ) =~ m/$reUINT/ ) ? $1 : 60; - $hash->{helper}{deltaTreshold} = - ( AttrVal( $name, 'pidDeltaTreshold', 0 ) =~ m/$reFloatpos/ ) ? $1 : 0; - $hash->{helper}{disable} = ( AttrVal( $name, 'disable', 0 ) =~ m/$re01/ ) ? $1 : ''; - $hash->{helper}{sensorTimeout} = - ( AttrVal( $name, 'pidSensorTimeout', 3600 ) =~ m/$reUINT/ ) ? $1 : 3600; - $hash->{helper}{reverseAction} = - ( AttrVal( $name, 'pidReverseAction', 0 ) =~ m/$re01/ ) ? $1 : 0; - $hash->{helper}{updateInterval} = - ( AttrVal( $name, 'pidUpdateInterval', 600 ) =~ m/$reUINT/ ) ? $1 : 600; - $hash->{helper}{measuredName} = AttrVal( $name, 'pidMeasuredName', 'measured' ); - $hash->{helper}{desiredName} = AttrVal( $name, 'pidDesiredName', 'desired' ); - $hash->{helper}{actorLimitLower} = - ( AttrVal( $name, 'pidActorLimitLower', 0 ) =~ m/$reFloat/ ) ? $1 : 0; - my $actorLimitLower = $hash->{helper}{actorLimitLower}; - $hash->{helper}{actorLimitUpper} = - ( AttrVal( $name, 'pidActorLimitUpper', 100 ) =~ m/$reFloat/ ) ? $1 : 100; - my $actorLimitUpper = $hash->{helper}{actorLimitUpper}; - $hash->{helper}{factor_P} = - ( AttrVal( $name, 'pidFactor_P', 25 ) =~ m/$reFloatpos/ ) ? $1 : 25; - $hash->{helper}{factor_I} = - ( AttrVal( $name, 'pidFactor_I', 0.25 ) =~ m/$reFloatpos/ ) ? $1 : 0.25; - $hash->{helper}{factor_D} = ( AttrVal( $name, 'pidFactor_D', 0 ) =~ m/$reFloatpos/ ) ? $1 : 0; + my ($name) = @_; + my $hash = $defs{$name}; - if ( $hash->{helper}{disable} ) - { - $stateStr = "disabled"; - last; - } - if ( $hash->{helper}{stopped} ) - { - $stateStr = "stopped"; - last; - } - my $desired = ReadingsVal( $name, $hash->{helper}{desiredName}, "" ); + my $sensor = $hash->{helper}{sensor}; + my $reading = $hash->{helper}{reading}; + my $regexp = $hash->{helper}{regexp}; - # sensor found - PID20_Log $hash, 2, "--------------------------" if ($DEBUG); - PID20_Log $hash, 2, "S1 sensorStr:$sensorStr sensorTS:$sensorTS" if ($DEBUG_Sensor); - $stateStr = "alarm - no $reading yet for $sensor" if ( !$sensorStr && !$stateStr ); + my $DEBUG_Sensor = AttrVal( $name, 'pidDebugSensor', '0' ) eq '1'; + my $DEBUG_Actuation = AttrVal( $name, 'pidDebugActuation', '0' ) eq '1'; + my $DEBUG_Delta = AttrVal( $name, 'pidDebugDelta', '0' ) eq '1'; + my $DEBUG_Calc = AttrVal( $name, 'pidDebugCalc', '0' ) eq '1'; + my $DEBUG_Update = AttrVal( $name, 'pidDebugUpdate', '0' ) eq '1'; + my $DEBUG = $DEBUG_Sensor || $DEBUG_Actuation || $DEBUG_Calc || $DEBUG_Delta || $DEBUG_Update; - # sensor alive - if ( $sensorStr && $sensorTS ) - { - my $timeDiff = PID20_TimeDiff($sensorTS); - $sensorIsAlive = 1 if ( $timeDiff <= $hash->{helper}{sensorTimeout} ); - $sensorStr =~ m/$regexp/; - $sensorValue = $1; - $sensorValue = "" if ( !defined($sensorValue) ); - PID20_Log $hash, 2, - "S2 timeOfDay:" - . gettimeofday() - . " timeDiff:$timeDiff sensorTimeout:" - . $hash->{helper}{sensorTimeout} - . " --> sensorIsAlive:$sensorIsAlive" - if ($DEBUG_Sensor); - } + my $actuation = ""; + my $actuationDone = ReadingsVal( $name, 'actuation', "" ); + my $actuationCalc = ReadingsVal( $name, 'actuationCalc', "" ); + my $actuationCalcOld = $actuationCalc; + my $actorTimestamp = + ( $hash->{helper}{actorTimestamp} ) + ? $hash->{helper}{actorTimestamp} + : FmtDateTime( gettimeofday() - 3600 * 24 ); - # sensor dead - $stateStr = "alarm - dead sensor" if ( !$sensorIsAlive && !$stateStr ); + my $sensorStr = ReadingsVal( $sensor, $reading, "" ); + my $sensorValue = ""; + my $sensorTS = ReadingsTimestamp( $sensor, $reading, undef ); + my $sensorIsAlive = 0; - # missing desired - $stateStr = "alarm - missing desired" if ( $desired eq "" && !$stateStr ); + my $iPortion = ReadingsVal( $name, 'p_i', 0 ); + my $pPortion = ReadingsVal( $name, 'p_p', "" ); + my $dPortion = ReadingsVal( $name, 'p_d', "" ); + my $stateStr = ""; + my $deltaOld = ReadingsVal( $name, 'delta', 0 ); + my $delta = ""; + my $deltaGradient = ( $hash->{helper}{deltaGradient} ) ? $hash->{helper}{deltaGradient} : 0; + my $calcReq = 0; + my $readingUpdateReq = ''; - # check delta threshold - $delta = ( $desired ne "" && $sensorValue ne "" ) ? $desired - $sensorValue : ""; - $calcReq = 1 - if ( !$stateStr - && $delta ne "" - && ( abs($delta) >= abs( $hash->{helper}{deltaTreshold} ) ) ); - PID20_Log $hash, 2, - "D1 desired[" . ( $desired ne "" ) ? sprintf( "%.1f", $desired ) - : "" . "] - sensorValue: [" . ( $sensorValue ne "" ) ? sprintf( "%.1f", $sensorValue ) - : "" . "] = delta[" . ( $delta ne "" ) ? sprintf( "%.2f", $delta ) - : "" . "] calcReq:$calcReq" - if ($DEBUG_Delta); + # ---------------- check conditions + while (1) + { + # --------------- retrive values from attributes + $hash->{helper}{actorInterval} = ( AttrVal( $name, 'pidActorInterval', 180 ) =~ m/$reUINT/ ) ? $1 : 180; + $hash->{helper}{actorThreshold} = ( AttrVal( $name, 'pidActorTreshold', 1 ) =~ m/$reUINT/ ) ? $1 : 1; + $hash->{helper}{actorKeepAlive} = ( AttrVal( $name, 'pidActorKeepAlive', 1800 ) =~ m/$reUINT/ ) ? $1 : 1800; + $hash->{helper}{actorValueDecPlaces} = ( AttrVal( $name, 'pidActorValueDecPlaces', 0 ) =~ m/$reUINT/ ) ? $1 : 0; + $hash->{helper}{actorErrorAction} = + ( AttrVal( $name, 'pidActorErrorAction', 'freeze' ) eq 'errorPos' ) ? 'errorPos' : 'freeze'; + $hash->{helper}{actorErrorPos} = ( AttrVal( $name, 'pidActorErrorPos', 0 ) =~ m/$reINT/ ) ? $1 : 0; + $hash->{helper}{calcInterval} = ( AttrVal( $name, 'pidCalcInterval', 60 ) =~ m/$reUINT/ ) ? $1 : 60; + $hash->{helper}{deltaTreshold} = ( AttrVal( $name, 'pidDeltaTreshold', 0 ) =~ m/$reFloatpos/ ) ? $1 : 0; + $hash->{helper}{disable} = ( AttrVal( $name, 'disable', 0 ) =~ m/$re01/ ) ? $1 : ''; + $hash->{helper}{sensorTimeout} = ( AttrVal( $name, 'pidSensorTimeout', 3600 ) =~ m/$reUINT/ ) ? $1 : 3600; + $hash->{helper}{reverseAction} = ( AttrVal( $name, 'pidReverseAction', 0 ) =~ m/$re01/ ) ? $1 : 0; + $hash->{helper}{updateInterval} = ( AttrVal( $name, 'pidUpdateInterval', 600 ) =~ m/$reUINT/ ) ? $1 : 600; + $hash->{helper}{measuredName} = AttrVal( $name, 'pidMeasuredName', 'measured' ); + $hash->{helper}{desiredName} = AttrVal( $name, 'pidDesiredName', 'desired' ); + $hash->{helper}{actorLimitLower} = ( AttrVal( $name, 'pidActorLimitLower', 0 ) =~ m/$reFloat/ ) ? $1 : 0; + my $actorLimitLower = $hash->{helper}{actorLimitLower}; + $hash->{helper}{actorLimitUpper} = ( AttrVal( $name, 'pidActorLimitUpper', 100 ) =~ m/$reFloat/ ) ? $1 : 100; + my $actorLimitUpper = $hash->{helper}{actorLimitUpper}; + $hash->{helper}{factor_P} = ( AttrVal( $name, 'pidFactor_P', 25 ) =~ m/$reFloatpos/ ) ? $1 : 25; + $hash->{helper}{factor_I} = ( AttrVal( $name, 'pidFactor_I', 0.25 ) =~ m/$reFloatpos/ ) ? $1 : 0.25; + $hash->{helper}{factor_D} = ( AttrVal( $name, 'pidFactor_D', 0 ) =~ m/$reFloatpos/ ) ? $1 : 0; - #request for calculation - # ---------------- calculation request - if ($calcReq) - { - # reverse action requested - my $workDelta = ( $hash->{helper}{reverseAction} == 1 ) ? -$delta : $delta; - my $deltaOld = -$deltaOld if ( $hash->{helper}{reverseAction} == 1 ); - - # calc p-portion - $pPortion = $workDelta * $hash->{helper}{factor_P}; - - # calc d-Portion - $dPortion = ($deltaGradient) * $hash->{helper}{calcInterval} * $hash->{helper}{factor_D}; - - # calc i-portion respecting windUp - # freeze i-portion if windUp is active - my $isWindup = $actuationCalcOld - && (( $workDelta > 0 && $actuationCalcOld > $actorLimitUpper ) - || ( $workDelta < 0 && $actuationCalcOld < $actorLimitLower ) ); - if ( $hash->{helper}{adjust} ne "" ) - { - $iPortion = $hash->{helper}{adjust} - ( $pPortion + $dPortion ); - $iPortion = $actorLimitUpper if ( $iPortion > $actorLimitUpper ); - $iPortion = $actorLimitLower if ( $iPortion < $actorLimitLower ); - PID20_Log $hash, 5, - "adjust request with:" . $hash->{helper}{adjust} . " ==> p_i:$iPortion"; - $hash->{helper}{adjust} = ""; - } elsif ( !$isWindup ) # integrate only if no windUp - { - # normalize the intervall to minute=60 seconds - $iPortion = - $iPortion + - $workDelta * $hash->{helper}{factor_I} * $hash->{helper}{calcInterval} / 60; - $hash->{helper}{isWindUP} = 0; - } - $hash->{helper}{isWindUP} = $isWindup; - - # calc actuation - $actuationCalc = $pPortion + $iPortion + $dPortion; - PID20_Log $hash, 2, "P1 delta:" . sprintf( "%.2f", $delta ) . " isWindup:$isWindup" - if ($DEBUG_Calc); - PID20_Log $hash, 2, - "P2 pPortion:" - . sprintf( "%.2f", $pPortion ) - . " iPortion:" - . sprintf( "%.2f", $iPortion ) - . " dPortion:" - . sprintf( "%.2f", $dPortion ) - . " actuationCalc:" - . sprintf( "%.2f", $actuationCalc ) - if ($DEBUG_Calc); - readingsBeginUpdate($hash); - readingsBulkUpdate( $hash, 'p_p', $pPortion ); - readingsBulkUpdate( $hash, 'p_i', $iPortion ); - readingsBulkUpdate( $hash, 'p_d', $dPortion ); - readingsBulkUpdate( $hash, 'actuationCalc', $actuationCalc ); - readingsBulkUpdate( $hash, 'delta', $delta ); - readingsEndUpdate( $hash, 0 ); - - #PID20_Log $hash, 3, "calculation done"; - } - - # ---------------- acutation request - my $noTrouble = ( $desired ne "" && $sensorIsAlive ); - - # check actor fallback in case of sensor fault - if ( !$sensorIsAlive && ( $hash->{helper}{actorErrorAction} eq "errorPos" ) ) - { - $stateStr .= "- force pid-output to errorPos"; - $actuationCalc = $hash->{helper}{actorErrorPos}; - $actuationCalc = "" if ( !defined($actuationCalc) ); - } - - # check acutation diff - $actuation = $actuationCalc; - - # limit $actuation - $actuation = $actorLimitUpper if ( $actuation ne "" && ( $actuation > $actorLimitUpper ) ); - $actuation = $actorLimitLower if ( $actuation ne "" && ( $actuation < $actorLimitLower ) ); - - # check if round request - my $fmt = "%." . $hash->{helper}{actorValueDecPlaces} . "f"; - $actuation = sprintf( $fmt, $actuation ) if ( $actuation ne "" ); - my $actuationDiff = abs( $actuation - $actuationDone ) - if ( $actuation ne "" && $actuationDone ne "" ); - PID20_Log $hash, 2, - "A1 act:$actuation actDone:$actuationDone " - . " actThreshold:" - . $hash->{helper}{actorThreshold} - . " actDiff:$actuationDiff" - if ($DEBUG_Actuation); - - # check threshold-condition for actuation - my $rsTS = $actuationDone ne "" # limit exceeded - && $actuationDiff >= $hash->{helper}{actorThreshold}; - my $rsUp = $actuationDone ne "" # upper range - && $actuation > $actorLimitUpper - $hash->{helper}{actorThreshold} - && $actuationDiff != 0 - && $actuation >= $actorLimitUpper; - my $rsDown = $actuationDone ne "" # low range - && $actuation < $actorLimitLower + $hash->{helper}{actorThreshold} - && $actuationDiff != 0 - && $actuation <= $actorLimitLower; - my $rsLimit = $actuationDone ne "" - && ( $actuationDone < $actorLimitLower || $actuationDone > $actorLimitUpper ); - my $actuationByThreshold = ( ( $rsTS || $rsUp || $rsDown ) && $noTrouble ); - PID20_Log $hash, 2, "A2 rsTS:$rsTS rsUp:$rsUp rsDown:$rsDown noTrouble:$noTrouble" - if ($DEBUG_Actuation); - - # check time condition for actuation - my $actTimeDiff = PID20_TimeDiff($actorTimestamp); # $actorTimestamp is valid in each case - my $actuationByTime = ($noTrouble) && ( $actTimeDiff > $hash->{helper}{actorInterval} ); - PID20_Log $hash, 2, - "A3 actTS:$actorTimestamp" - . " actTimeDiff:" - . sprintf( "%.2f", $actTimeDiff ) - . " actInterval:" - . $hash->{helper}{actorInterval} - . "-->actByTime:$actuationByTime " - if ($DEBUG_Actuation); - - # check keep alive condition for actuation - my $actuationKeepAliveReq = ( $actTimeDiff >= $hash->{helper}{actorKeepAlive} ) - if ( defined($actTimeDiff) && $actuation ne "" ); - - # summary actuation reques - my $actuationReq = ( - ( $actuationByThreshold && $actuationByTime ) - || $actuationKeepAliveReq - || $rsLimit - || $actuationDone eq "" # startup condition - ) && $actuation ne ""; - PID20_Log $hash, 2, - "A4 (actByTh:$actuationByThreshold && actByTime:$actuationByTime)" - . "||actKeepAlive:$actuationKeepAliveReq" - . "||rsLimit:$rsLimit=actnReq:$actuationReq" - if ($DEBUG_Actuation); - - # perform output to actor - if ($actuationReq) - { - #build command for fhem - PID20_Log $hash, 5, - "actor:" - . $hash->{helper}{actor} - . " actorCommand:" - . $hash->{helper}{actorCommand} - . " actuation:" - . $actuation; - my $cmd = sprintf( "set %s %s %g", - $hash->{helper}{actor}, - $hash->{helper}{actorCommand}, $actuation ); - - # execute command - my $ret; - $ret = fhem $cmd; - - # note timestamp - $hash->{helper}{actorTimestamp} = TimeNow(); - $actuationDone = $actuation; - my $retStr = ""; - $retStr = " with return-value:" . $ret if ( defined($ret) && ( $ret ne '' ) ); - PID20_Log $hash, 3, "<$cmd> " . $retStr; - } - my $updateAlive = ( $actuation ne "" ) - && PID20_TimeDiff( ReadingsTimestamp( $name, 'actuation', gettimeofday() ) ) >= - $hash->{helper}{updateInterval}; - my $updateReq = ( ( $actuationReq || $updateAlive ) && $actuation ne "" ); - PID20_Log $hash, 2, - "U1 actReq:$actuationReq updateAlive:$updateAlive --> updateReq:$updateReq" - if ($DEBUG_Update); - - # ---------------- update request - if ($updateReq) - { - readingsBeginUpdate($hash); - readingsBulkUpdate( $hash, $hash->{helper}{desiredName}, $desired ) if ( $desired ne "" ); - readingsBulkUpdate( $hash, $hash->{helper}{measuredName}, $sensorValue ) - if ( $sensorValue ne "" ); - readingsBulkUpdate( $hash, 'p_p', $pPortion ) if ( $pPortion ne "" ); - readingsBulkUpdate( $hash, 'p_d', $dPortion ) if ( $dPortion ne "" ); - readingsBulkUpdate( $hash, 'p_i', $iPortion ) if ( $iPortion ne "" ); - readingsBulkUpdate( $hash, 'actuation', $actuationDone ) if ( $actuationDone ne "" ); - readingsBulkUpdate( $hash, 'actuationCalc', $actuationCalc ) if ( $actuationCalc ne "" ); - readingsBulkUpdate( $hash, 'delta', $delta ) if ( $delta ne "" ); - readingsEndUpdate( $hash, 1 ); - PID20_Log $hash, 5, "readings updated"; - } + if ( $hash->{helper}{disable} ) + { + $stateStr = "disabled"; last; - } # end while + } - # update statePID. - $stateStr = "idle" if ( !$stateStr && !$calcReq ); - $stateStr = "processing" if ( !$stateStr && $calcReq ); - readingsSingleUpdate( $hash, 'state', $stateStr, 0 ); - PID20_Log $hash, 2, "C1 stateStr:$stateStr calcReq:$calcReq" if ($DEBUG_Calc); + if ( $hash->{helper}{stopped} ) + { + $stateStr = "stopped"; + last; + } - # timer setup - my $next = gettimeofday() + $hash->{helper}{calcInterval}; - RemoveInternalTimer($name); # prevent multiple timers for same hash - InternalTimer( $next, "PID20_Calc", $name, 1 ); + my $desired = ReadingsVal( $name, $hash->{helper}{desiredName}, "" ); -#PID20_Log $hash, 2, "InternalTimer next:".FmtDateTime($next)." PID20_Calc name:$name DEBUG_Calc:$DEBUG_Calc"; - return; + # sensor found + PID20_Log $hash, 2, "--------------------------" if ($DEBUG); + PID20_Log $hash, 2, "S1 sensorStr:$sensorStr sensorTS:$sensorTS" if ($DEBUG_Sensor); + $stateStr = "alarm - no $reading yet for $sensor" if ( !$sensorStr && !$stateStr ); + + # sensor alive + if ( $sensorStr && $sensorTS ) + { + my $timeDiff = PID20_TimeDiff($sensorTS); + $sensorIsAlive = 1 if ( $timeDiff <= $hash->{helper}{sensorTimeout} ); + $sensorStr =~ m/$regexp/; + $sensorValue = $1; + $sensorValue = "" if ( !defined($sensorValue) ); + PID20_Log $hash, 2, + "S2 timeOfDay:" + . gettimeofday() + . " timeDiff:$timeDiff sensorTimeout:" + . $hash->{helper}{sensorTimeout} + . " --> sensorIsAlive:$sensorIsAlive" + if ($DEBUG_Sensor); + } + + # sensor dead + $stateStr = "alarm - dead sensor" if ( !$sensorIsAlive && !$stateStr ); + + # missing desired + $stateStr = "alarm - missing desired" if ( $desired eq "" && !$stateStr ); + + # check delta threshold + $delta = ( $desired ne "" && $sensorValue ne "" ) ? $desired - $sensorValue : ""; + $calcReq = 1 if ( !$stateStr && $delta ne "" && ( abs($delta) >= abs( $hash->{helper}{deltaTreshold} ) ) ); + + PID20_Log $hash, 2, + "D1 desired[" . ( $desired ne "" ) ? sprintf( "%.1f", $desired ) + : "" . "] - sensorValue: [" . ( $sensorValue ne "" ) ? sprintf( "%.1f", $sensorValue ) + : "" . "] = delta[" . ( $delta ne "" ) ? sprintf( "%.2f", $delta ) + : "" . "] calcReq:$calcReq" + if ($DEBUG_Delta); + + #request for calculation + # ---------------- calculation request + if ($calcReq) + { + # reverse action requested + my $workDelta = ( $hash->{helper}{reverseAction} == 1 ) ? -$delta : $delta; + my $deltaOld = -$deltaOld if ( $hash->{helper}{reverseAction} == 1 ); + + # calc p-portion + $pPortion = $workDelta * $hash->{helper}{factor_P}; + + # calc d-Portion + $dPortion = ($deltaGradient) * $hash->{helper}{calcInterval} * $hash->{helper}{factor_D}; + + # calc i-portion respecting windUp + # freeze i-portion if windUp is active + my $isWindup = $actuationCalcOld + && ( ( $workDelta > 0 && $actuationCalcOld > $actorLimitUpper ) + || ( $workDelta < 0 && $actuationCalcOld < $actorLimitLower ) ); + + if ( $hash->{helper}{adjust} ne "" ) + { + $iPortion = $hash->{helper}{adjust} - ( $pPortion + $dPortion ); + $iPortion = $actorLimitUpper if ( $iPortion > $actorLimitUpper ); + $iPortion = $actorLimitLower if ( $iPortion < $actorLimitLower ); + PID20_Log $hash, 5, "adjust request with:" . $hash->{helper}{adjust} . " ==> p_i:$iPortion"; + $hash->{helper}{adjust} = ""; + } elsif ( !$isWindup ) # integrate only if no windUp + { + # normalize the intervall to minute=60 seconds + $iPortion = $iPortion + $workDelta * $hash->{helper}{factor_I} * $hash->{helper}{calcInterval} / 60; + $hash->{helper}{isWindUP} = 0; + } + + $hash->{helper}{isWindUP} = $isWindup; + + # calc actuation + $actuationCalc = $pPortion + $iPortion + $dPortion; + + PID20_Log $hash, 2, "P1 delta:" . sprintf( "%.2f", $delta ) . " isWindup:$isWindup" if ($DEBUG_Calc); + + PID20_Log $hash, 2, + "P2 pPortion:" + . sprintf( "%.2f", $pPortion ) + . " iPortion:" + . sprintf( "%.2f", $iPortion ) + . " dPortion:" + . sprintf( "%.2f", $dPortion ) + . " actuationCalc:" + . sprintf( "%.2f", $actuationCalc ) + if ($DEBUG_Calc); + } + + $readingUpdateReq = 1; # in each case update readings + + # ---------------- acutation request + my $noTrouble = ( $desired ne "" && $sensorIsAlive ); + + # check actor fallback in case of sensor fault + if ( !$sensorIsAlive && ( $hash->{helper}{actorErrorAction} eq "errorPos" ) ) + { + $stateStr .= "- force pid-output to errorPos"; + $actuationCalc = $hash->{helper}{actorErrorPos}; + $actuationCalc = "" if ( !defined($actuationCalc) ); + } + + # check acutation diff + $actuation = $actuationCalc; + + # limit $actuation + $actuation = $actorLimitUpper if ( $actuation ne "" && ( $actuation > $actorLimitUpper ) ); + $actuation = $actorLimitLower if ( $actuation ne "" && ( $actuation < $actorLimitLower ) ); + + # check if round request + my $fmt = "%." . $hash->{helper}{actorValueDecPlaces} . "f"; + $actuation = sprintf( $fmt, $actuation ) if ( $actuation ne "" ); + my $actuationDiff = abs( $actuation - $actuationDone ) + if ( $actuation ne "" && $actuationDone ne "" ); + PID20_Log $hash, 2, + "A1 act:$actuation actDone:$actuationDone " + . " actThreshold:" + . $hash->{helper}{actorThreshold} + . " actDiff:$actuationDiff" + if ($DEBUG_Actuation); + + # check threshold-condition for actuation + my $rsTS = $actuationDone ne "" && $actuationDiff >= $hash->{helper}{actorThreshold}; + + # ...... special handling if acutation is in the black zone between actorLimit and (actorLimit - actorThreshold) + # upper range + my $rsUp = + $actuationDone ne "" + && $actuation > $actorLimitUpper - $hash->{helper}{actorThreshold} + && $actuationDiff != 0 + && $actuation >= $actorLimitUpper; + + # low range + my $rsDown = + $actuationDone ne "" + && $actuation < $actorLimitLower + $hash->{helper}{actorThreshold} + && $actuationDiff != 0 + && $actuation <= $actorLimitLower; + + # upper or lower limit are exceeded + my $rsLimit = $actuationDone ne "" && ( $actuationDone < $actorLimitLower || $actuationDone > $actorLimitUpper ); + + my $actuationByThreshold = ( ( $rsTS || $rsUp || $rsDown ) && $noTrouble ); + PID20_Log $hash, 2, "A2 rsTS:$rsTS rsUp:$rsUp rsDown:$rsDown noTrouble:$noTrouble" + if ($DEBUG_Actuation); + + # check time condition for actuation + my $actTimeDiff = PID20_TimeDiff($actorTimestamp); # $actorTimestamp is valid in each case + my $actuationByTime = ($noTrouble) && ( $actTimeDiff > $hash->{helper}{actorInterval} ); + PID20_Log $hash, 2, + "A3 actTS:$actorTimestamp" + . " actTimeDiff:" + . sprintf( "%.2f", $actTimeDiff ) + . " actInterval:" + . $hash->{helper}{actorInterval} + . "-->actByTime:$actuationByTime " + if ($DEBUG_Actuation); + + # check keep alive condition for actuation + my $actuationKeepAliveReq = ( $actTimeDiff >= $hash->{helper}{actorKeepAlive} ) + if ( defined($actTimeDiff) && $actuation ne "" ); + + # build total actuation request + my $actuationReq = ( + ( $actuationByThreshold && $actuationByTime ) + || $actuationKeepAliveReq # request by keep alive + || $rsLimit # upper or lower limit are exceeded + || $actuationDone eq "" # startup condition + ) && $actuation ne ""; # acutation is initialized + + PID20_Log $hash, 2, + "A4 (actByTh:$actuationByThreshold && actByTime:$actuationByTime)" + . "||actKeepAlive:$actuationKeepAliveReq" + . "||rsLimit:$rsLimit=actnReq:$actuationReq" + if ($DEBUG_Actuation); + + # ................ perform output to actor + if ($actuationReq) + { + $readingUpdateReq = 1; # update the readings + + #build command for fhem + PID20_Log $hash, 5, + "actor:" + . $hash->{helper}{actor} + . " actorCommand:" + . $hash->{helper}{actorCommand} + . " actuation:" + . $actuation; + my $cmd = sprintf( "set %s %s %g", $hash->{helper}{actor}, $hash->{helper}{actorCommand}, $actuation ); + + # execute command + my $ret; + $ret = fhem $cmd; + + # note timestamp + $hash->{helper}{actorTimestamp} = TimeNow(); + $actuationDone = $actuation; + my $retStr = ""; + $retStr = " with return-value:" . $ret if ( defined($ret) && ( $ret ne '' ) ); + PID20_Log $hash, 3, "<$cmd> " . $retStr; + } + my $updateAlive = ( $actuation ne "" ) + && PID20_TimeDiff( ReadingsTimestamp( $name, 'actuation', gettimeofday() ) ) >= $hash->{helper}{updateInterval}; + + # my $updateReq = ( ( $actuationReq || $updateAlive ) && $actuation ne "" ); + # PID20_Log $hash, 2, "U1 actReq:$actuationReq updateAlive:$updateAlive --> updateReq:$updateReq" if ($DEBUG_Update); + + # ---------------- update request + if ($readingUpdateReq) + { + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, $hash->{helper}{desiredName}, $desired ) if ( $desired ne "" ); + readingsBulkUpdate( $hash, $hash->{helper}{measuredName}, $sensorValue ) if ( $sensorValue ne "" ); + readingsBulkUpdate( $hash, 'p_p', $pPortion ) if ( $pPortion ne "" ); + readingsBulkUpdate( $hash, 'p_d', $dPortion ) if ( $dPortion ne "" ); + readingsBulkUpdate( $hash, 'p_i', $iPortion ) if ( $iPortion ne "" ); + readingsBulkUpdate( $hash, 'actuation', $actuationDone ) if ( $actuationDone ne "" ); + readingsBulkUpdate( $hash, 'actuationCalc', $actuationCalc ) if ( $actuationCalc ne "" ); + readingsBulkUpdate( $hash, 'delta', $delta ) if ( $delta ne "" ); + readingsEndUpdate( $hash, 1 ); + PID20_Log $hash, 5, "readings updated"; + } + + last; + } # end while + + # ........ update statePID. + $stateStr = "idle" if ( !$stateStr && !$calcReq ); + $stateStr = "processing" if ( !$stateStr && $calcReq ); + readingsSingleUpdate( $hash, 'state', $stateStr, 0 ); + PID20_Log $hash, 2, "C1 stateStr:$stateStr calcReq:$calcReq" if ($DEBUG_Calc); + + #......... timer setup + my $next = gettimeofday() + $hash->{helper}{calcInterval}; + RemoveInternalTimer($name); # prevent multiple timers for same hash + InternalTimer( $next, "PID20_Calc", $name, 1 ); + + #PID20_Log $hash, 2, "InternalTimer next:".FmtDateTime($next)." PID20_Calc name:$name DEBUG_Calc:$DEBUG_Calc"; + return; } 1;