From 4890a724fb2f2f7a0e1eed4dd26f32a1834f63f4 Mon Sep 17 00:00:00 2001 From: jamesgo <> Date: Mon, 18 Feb 2019 13:14:35 +0000 Subject: [PATCH] 94_PWM.pm : support of maxOffTime to prevent floor from cooling out git-svn-id: https://svn.fhem.de/fhem/trunk@18631 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/94_PWM.pm | 297 ++++++++++++++++++++++++++++++++------------ 1 file changed, 221 insertions(+), 76 deletions(-) diff --git a/fhem/FHEM/94_PWM.pm b/fhem/FHEM/94_PWM.pm index 29f50f759..e23ef02cf 100644 --- a/fhem/FHEM/94_PWM.pm +++ b/fhem/FHEM/94_PWM.pm @@ -32,6 +32,7 @@ # 13.12.17 GA fix consider $roomsWaitOffset{$wkey} in oldpulse set for each room # 31.01.18 GA add support for stateFormat # 05.02.18 GA fix typo overallHeatingSwitchThresholdTemup +# 19.11.18 GA add support for attribute maxOffTime ############################################## # $Id$ @@ -99,19 +100,22 @@ PWM_Calculate($) my ($hash) = @_; my $name = $hash->{NAME}; - my %RoomsToSwitchOn = (); - my %RoomsToSwitchOff = (); - my %RoomsToStayOn = (); - my %RoomsToStayOff = (); - my %RoomsValveProtect = (); - my %RoomsPulses = (); - my $roomsActive = 0; - my $newpulseMax = 0; - my $newpulseSum = 0; - my $newpulseAvg = 0; - my $newpulseAvg2 = 0; - my $newpulseAvg3 = 0; - my $wkey = ""; + my %RoomsToSwitchOn = (); + my %RoomsToSwitchOff = (); + my %RoomsToStayOn = (); + my %RoomsToStayOff = (); + my %RoomsValveProtect = (); + my %RoomsMaxOffTimeProtect = (); + my %RoomsPulses = (); + my $roomsActive = 0; + my $newpulseMax = 0; + my $newpulseSum = 0; + my $newpulseAvg = 0; + my $newpulseAvg2 = 0; + my $newpulseAvg3 = 0; + my $RoomsMaxOffTimeProtect_on = 0; + + my $wkey = ""; if($hash->{INTERVAL} > 0) { InternalTimer(gettimeofday() + $hash->{INTERVAL}, "PWM_Calculate", $hash, 0); @@ -132,7 +136,6 @@ PWM_Calculate($) readingsBeginUpdate ($hash); readingsBulkUpdate ($hash, "lastrun", "calculating"); - #$hash->{STATE} = "lastrun: ".$hash->{READINGS}{lastrun}{TIME}; readingsBulkUpdate ($hash, "state", "lastrun: ".$hash->{READINGS}{lastrun}{TIME}); # loop over all devices @@ -146,12 +149,15 @@ PWM_Calculate($) if ($hash->{NAME} eq $defs{$d}{IODev}->{NAME}) { # referencing to this fb Log3 ($hash, 4, "PWM_Calculate calc $name, room $d"); + $roomsActive++; ######################## # calculate room # $newstate is "" if state is unchanged # $newstate is "on" or "off" if state changes # $newstate may be "on_vp" or "off_vp" if valve protection is active + # $newstate may be "off_mop" if maxOffTime protection has ended + # $newstate may be "on_mop" or "on_mop_stay" or "on_mop_maybe" if maxOffTime protection is active stays active or may become active my ($newstate, $newpulse, $cycletime, $oldstate) = PWM_CalcRoom($hash, $defs{$d}); my $onoff = $newpulse * $cycletime; @@ -159,67 +165,93 @@ PWM_Calculate($) $onoff = (1 - $newpulse) * $cycletime } + ############################## + ##### Valve Protect + if ($newstate eq "on_vp") { $RoomsValveProtect{$d} = "on"; } elsif ($newstate eq "off_vp") { $RoomsValveProtect{$d} = "off"; - } - - $wkey = $name."_".$d; - if (defined ($roomsWaitOffset{$wkey})) { - $hash->{helper}{pulses}{$d} = $newpulse." / ".$roomsWaitOffset{$wkey}; - $newpulse += $roomsWaitOffset{$wkey}; - } else { - $roomsWaitOffset{$wkey} = 0; - $hash->{helper}{pulses}{$d} = $newpulse." / ".$roomsWaitOffset{$wkey}; - } - $defs{$d}->{READINGS}{oldpulse}{TIME} = TimeNow(); - $defs{$d}->{READINGS}{oldpulse}{VAL} = $newpulse; + ############################## + ##### maxOffTimeProtect - $roomsActive++; - $RoomsPulses{$d} = $newpulse; - $newpulseSum += $newpulse; - $newpulseMax = max($newpulseMax, $newpulse); - - # $newstate ne "" -> state changed "on" -> "off" or "off" -> "on" - if ((int($hash->{MINONOFFTIME}) > 0) && - (($newstate eq "on") or ($newstate eq "off")) && - ($onoff < int($hash->{MINONOFFTIME})) - ) { - - ####################### - # actor devices take 3 minutes for an open/close cycle - # this is handled by MINONOFFTIME - - Log3 ($hash, 3, "PWM_Calculate $d: F0 stay unchanged $oldstate: ". - "($onoff < $hash->{MINONOFFTIME} sec)"); - - if ($oldstate eq "off") { - $RoomsToStayOff{$d} = $newpulse; - } else { - $RoomsToStayOn{$d} = $newpulse; - } - - } else { - - # state changed and it is worth to move the device - - if ($newstate eq "on") { - $RoomsToSwitchOn{$d} = $newpulse; - - } elsif ($newstate eq "off") { - $RoomsToSwitchOff{$d} = $newpulse; - - } elsif ($newstate eq "") { - - if ($oldstate eq "on") { - $RoomsToStayOn{$d} = $newpulse; - } else { - $RoomsToStayOff{$d} = $newpulse; - } + if ($newstate eq "on_mop") { + $RoomsMaxOffTimeProtect_on++; + $RoomsMaxOffTimeProtect{$d} = $newstate; + $newstate = "on"; + } elsif ($newstate eq "on_mop_stay") { + $RoomsMaxOffTimeProtect_on++; + $RoomsMaxOffTimeProtect{$d} = $newstate; + $newstate = ""; + } elsif ($newstate eq "on_mop_maybe") { + $RoomsMaxOffTimeProtect{$d} = $newstate; + $newstate = ""; + } elsif ($newstate eq "off_mop") { + $RoomsMaxOffTimeProtect{$d} = $newstate; + $newstate = "off"; } + + + ############################## + ##### regular calculation + + $wkey = $name."_".$d; + if (defined ($roomsWaitOffset{$wkey})) { + $hash->{helper}{pulses}{$d} = $newpulse." / ".$roomsWaitOffset{$wkey}; + $newpulse += $roomsWaitOffset{$wkey}; + + } else { + $roomsWaitOffset{$wkey} = 0; + $hash->{helper}{pulses}{$d} = $newpulse." / ".$roomsWaitOffset{$wkey}; + } + + $defs{$d}->{READINGS}{oldpulse}{TIME} = TimeNow(); + $defs{$d}->{READINGS}{oldpulse}{VAL} = $newpulse; + + $RoomsPulses{$d} = $newpulse; + $newpulseSum += $newpulse; + $newpulseMax = max($newpulseMax, $newpulse); + + # $newstate ne "" -> state changed "on" -> "off" or "off" -> "on" + if ((int($hash->{MINONOFFTIME}) > 0) && + (($newstate eq "on") or ($newstate eq "off")) && + ($onoff < int($hash->{MINONOFFTIME})) + ) { + + ####################### + # actor devices take 3 minutes for an open/close cycle + # this is handled by MINONOFFTIME + + Log3 ($hash, 3, "PWM_Calculate $d: F0 stay unchanged $oldstate: ". + "($onoff < $hash->{MINONOFFTIME} sec)"); + + if ($oldstate eq "off") { + $RoomsToStayOff{$d} = 1; + } else { + $RoomsToStayOn{$d} = 1; + } + + } else { + + # state changed and it is worth to move the device + + if ($newstate eq "on") { + $RoomsToSwitchOn{$d} = 1; + + } elsif ($newstate eq "off") { + $RoomsToSwitchOff{$d} = 1; + + } elsif ($newstate eq "") { + + if ($oldstate eq "on") { + $RoomsToStayOn{$d} = 1; + } else { + $RoomsToStayOff{$d} = 1; + } + } + } } } @@ -228,6 +260,41 @@ PWM_Calculate($) } + # maxOffTimeProtect handling + foreach my $d (keys %RoomsMaxOffTimeProtect) { + + if ($RoomsMaxOffTimeProtect{$d} eq "off_mop") { + $RoomsToSwitchOff{$d} = 1; + $RoomsPulses{$d} = 0; + + } elsif ($RoomsMaxOffTimeProtect{$d} eq "on_mop") { + $RoomsToSwitchOn{$d} = 1; + $RoomsPulses{$d} = $hash->{MaxPulse}; + + } elsif ($RoomsMaxOffTimeProtect{$d} eq "on_mop_stay") { + $RoomsToStayOn{$d} = 1; + $RoomsPulses{$d} = $hash->{MaxPulse}; + + } elsif ($RoomsMaxOffTimeProtect{$d} eq "on_mop_maybe") { + + if ($RoomsMaxOffTimeProtect_on > 0) { + $RoomsToSwitchOn{$d} = 1; + $RoomsPulses{$d} = $hash->{MaxPulse}; + + } else { + $RoomsToStayOff{$d} = 1; + $RoomsPulses{$d} = 0; + } + } + + $defs{$d}->{READINGS}{oldpulse}{TIME} = TimeNow(); + $defs{$d}->{READINGS}{oldpulse}{VAL} = $RoomsPulses{$d}; + + $newpulseSum += $RoomsPulses{$d}; + $newpulseMax = max($newpulseMax, $RoomsPulses{$d}); + } + + # synchronize the heating on the "off" edge of the pulse # try to minimize the situation where all rooms are "on" at the same time # @@ -453,6 +520,10 @@ PWM_Calculate($) PWMR_SetRoom ($defs{$roomOff}, "off"); + if (defined($defs{$roomOff}->{helper}{maxOffTimeLastSwitch})) { + delete ($defs{$roomOff}->{helper}{maxOffTimeLastSwitch}); + } + $cntRoomsOff++; $pulseRoomsOff += $RoomsPulses{$roomOff}; } @@ -463,11 +534,37 @@ PWM_Calculate($) $roomsWaitOffset{$wkey} = 0; PWMR_SetRoom ($defs{$roomOn}, "on"); + if (defined($RoomsMaxOffTimeProtect{$roomOn})) { + $defs{$roomOn}->{helper}{maxOffTimeLastSwitch} = time(); + } + $cntRoomsOn++; $pulseRoomsOn += $RoomsPulses{$roomOn}; } +if (0) { + foreach my $roomMOP (sort keys %RoomsMaxOffTimeProtect) { + + my $wkey = $name."-".$roomMOP; + $roomsWaitOffset{$wkey} = 0; + + if ( $RoomsMaxOffTimeProtect{$roomMOP} eq "on") { + + PWMR_SetRoom ($defs{$roomMOP}, "on"); + $cntRoomsOn++; + $pulseRoomsOn += $RoomsPulses{$roomMOP}; + + } else { + + PWMR_SetRoom ($defs{$roomMOP}, "off"); + $cntRoomsOff++; + $pulseRoomsOff += $RoomsPulses{$roomMOP}; + } + + } +} + foreach my $roomVP (sort keys %RoomsValveProtect) { my $wkey = $name."-".$roomVP; @@ -670,6 +767,7 @@ PWM_Calculate($) readingsEndUpdate($hash, 1); + Log3 ($hash, 3, "PWM_Calculate $name done"); # if(!$hash->{LOCAL}) { # DoTrigger($name, undef) if($init_done); @@ -688,7 +786,7 @@ PWM_CalcRoom(@) my $cycletime = $hash->{CYCLETIME}; - my ($temperaturV, $actorV, $factor, $oldpulse, $newpulse, $prevswitchtime, $windowV) = + my ($temperaturV, $actorV, $factor, $oldpulse, $newpulse, $prevswitchtime, $windowV, $maxOffTimeApply, $maxOffTime, $maxOffTimePeriod, $maxOffTimeAct) = PWMR_ReadRoom($room, $cycletime, $hash->{MaxPulse}); my $nextswitchtime; @@ -702,6 +800,21 @@ PWM_CalcRoom(@) if ($actorV eq "on") # current state is "on" { + # ---------------- + # check if maxOffTime is active and maxOffTimePeriod is over + + if (defined ($room->{helper}{maxOffTimeLastSwitch})) { + if ( $room->{helper}{maxOffTimeLastSwitch} + ($maxOffTimePeriod * 60) > time()) { + Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F15 maxOffTime continue"); + return ("on_mop_stay", $newpulse, $cycletime, $actorV); + } else { + Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F16 maxOffTime off"); + #delete ($room->{helper}{maxOffTimeLastSwitch}); + return ("off_mop", $newpulse, $cycletime, $actorV); + } + + } + # ---------------- # check if valve protection is active, keep this state for 5 minutes @@ -761,6 +874,7 @@ PWM_CalcRoom(@) } elsif ($actorV eq "off") # current state is "off" { + # ---------------- # check if valve protection is activated (attribute valveProtectIdlePeriod is set) @@ -768,9 +882,9 @@ PWM_CalcRoom(@) # period is defined in days (*86400) if ($room->{READINGS}{lastswitch}{VAL} + ($attr{$name}{"valveProtectIdlePeriod"} * 86400) < time()) { - $room->{helper}{valveProtectLastSwitch} = time(); - Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F12 valve protect"); - return ("on_vp", $newpulse, $cycletime, $actorV); + $room->{helper}{valveProtectLastSwitch} = time(); + Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F12 valve protect"); + return ("on_vp", $newpulse, $cycletime, $actorV); } } @@ -778,12 +892,34 @@ PWM_CalcRoom(@) # decide if to change to "on" if ($oldpulse == 0 && $newpulse > 0) { # was 0% now heating is required - Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F7 new on"); - return ("on", $newpulse, $cycletime, $actorV); + Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F7 new on"); + return ("on", $newpulse, $cycletime, $actorV); } + if ($newpulse == 0) { - Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F11 stay off (0)"); - return ("", $newpulse, $cycletime, $actorV); + + # ---------------- + # check if maxOffTime protection is activated (attribute maxOffTimeIdlePeriod is set) + + if ($maxOffTimeApply > 0) { + + ## wz > 2:00 + if ($maxOffTimeAct >= $maxOffTime) { + + Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F17 maxOffTime protection"); + return ("on_mop", $newpulse, $cycletime, $actorV); + } + + ## wz > 2:00 / 2 + if ($maxOffTimeAct >= $maxOffTime / 2) { + + Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F18 maxOffTime protection (possible)"); + return ("on_mop_maybe", $newpulse, $cycletime, $actorV); + } + } + + Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F11 stay off (0)"); + return ("", $newpulse, $cycletime, $actorV); } if ($newpulse > $oldpulse) { # was 30% now it is 80% @@ -813,6 +949,7 @@ PWM_CalcRoom(@) } + } else # $actorV not "on" of "off" @@ -906,7 +1043,7 @@ PWM_Set($@) { my ($hash, @a) = @_; - my $u = "Unknown argument $a[1], choose one of recalc interval cycletime"; + my $u = "Unknown argument $a[1], choose one of recalc interval cycletime maxOffTimeCalculation:on,off"; if ( $a[1] =~ /^interval$|^cycletime$/ ) { @@ -922,6 +1059,10 @@ PWM_Set($@) my $v = PWM_Calculate($hash); #delete $hash->{LOCAL}; + } elsif ( $a[1] =~ /^maxOffTimeCalculation$/ ) { + + readingsSingleUpdate ($hash, "maxOffTimeCalculation", $a[2], 1); + } else { return $u; @@ -1265,6 +1406,10 @@ PWM_Attr(@) Cause recalculation that normally appeary every interval seconds.
+
  • maxOffTimeCalculation
    + Defines if parameter maxOffTime for rooms (PWMR objects) is evaluated or not. Possible Values are "on" or "off". Sets reading maxOffTimeCalculation.
    +
  • + Get