mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-25 22:15:09 +00:00
1546 lines
55 KiB
Perl
1546 lines
55 KiB
Perl
#
|
|
#
|
|
# 94_PWM.pm
|
|
# written by Andreas Goebel 2012-07-25
|
|
# e-mail: ag at goebel-it dot de
|
|
#
|
|
# 21.09.15 GA update, use Log3
|
|
# 07.10.15 GA initial version published
|
|
# 13.10.15 GA add event-on-change-reading
|
|
# 13.10.15 GA add several readings
|
|
# 15.10.15 GA add reading for avg pulses
|
|
# 19.10.15 GA add overall heating switch
|
|
# 22.10.15 GA add new definition for overall heating switch. Decision now based on threshold for pulseMax
|
|
# 30.11.15 GA add new definition for overall heating switch. based on pulseMax or roomsOn
|
|
# 30.11.15 GA add new followUpTime can now delay switching of OverallHeatingSwitch from "on" to "off"
|
|
# 26.01.16 GA fix don't call AssignIoPort
|
|
# 26.01.16 GA fix IODev from PWMR object is now a reference to PWM object
|
|
# 29.06.16 GA add attribute valveProtectIdlePeriod
|
|
# 16.08.16 GA add event-min-interval
|
|
# 23.09.16 GA fix set default for maxPulse to 1 (from 0.85)
|
|
# 28.09.16 GA add "get timers" to collect a maximum of all timers from the rooms attached
|
|
# 11.10.16 GA add new delayTimeOn can now suspend switching of OverallHeadtingSwitch from "off" to "on"
|
|
# 16.11.16 GA add new attribute overallHeatingSwitchRef for threshold based configuration
|
|
# 17.11.16 GA add internals for configuration parameters: p_interval, p_cycletime, p_minOnOffTime,
|
|
# p_maxPulse, p_roomsMinOnOffThreshold and p_overallHeatingSwitch
|
|
# 01.08.17 GA add attribute disable to stop calculations of PWM
|
|
# 01.08.17 GA fix OverallHeatingSwitch (without threshold) now independent from ValveProtection
|
|
# 17.08.17 GA add attribute overallHeatingSwitchThresholdTemp define a threshold temperature to prevent switch to "on"
|
|
# 30.11.17 GA add helper for last pulses of rooms
|
|
# 30.11.17 GA fix clear roomsToStayOn and roomsToStayOnList if not used
|
|
# 05.12.17 GA add extend helper for last pulses by $roomsWaitOffset{$wkey}
|
|
# 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
|
|
# 05.03.19 GA fix reading maxOffTimeCalculation was set but not used
|
|
# 30.12.19 GA fix access to ReadingsVal via $name (reported by stromer-12)
|
|
# 26.05.20 GA fix division by zero if minRoomsOn is >0 and roomsCounted is zero
|
|
# 22.12.20 GA fix maxOffTime for P calculation never activated
|
|
# 28.12.20 GA fix maxOffTime; maxOffTimeApply is now only set if no heating is required
|
|
# 31.01.21 GA add attribute maxOffTimeMode (max, 1, 2, 3)
|
|
# 01.02.21 GA fix move reading maxOffTimeCalculation into an attribute and internal values
|
|
# 11.03.21 GA fix prevent parallel InternalTimer calls
|
|
# 14.03.21 GA fix change order of PWMR processing for maxOffTime handling
|
|
# 18.03.21 GA fix maxOffTime handling bug in restriction of max rooms on
|
|
# 10.11.21 GA fix default verbosity of logs adjusted
|
|
|
|
##############################################
|
|
# $Id$
|
|
|
|
|
|
# module for PWM (Pulse Width Modulation) calculation
|
|
# this module uses PWMR (R like room) to
|
|
# - get information (ReadRoom)
|
|
# - set actors (SetRoom)
|
|
#
|
|
# standard heating devices support 0 to 100% heating they can be driven by the PID module
|
|
# heating devices only supporing "on" of "off" can be driven by PWM
|
|
# in PWM 50% is realised by defining a timeframe (cycletime)
|
|
# and switch the defive "on" for 50% of this time
|
|
# basis for calculation of this pulse is a factor multiplied with the difference
|
|
# between desired-temp and act-temp
|
|
#
|
|
# default for cycletime is 15 minutes (900 sec)
|
|
# since the devices act very slow
|
|
# there is a parameter minonofftime to prevent "senseless" switches
|
|
# PWM recalculates the needed pulse every 60 seconds and
|
|
# then decides if the devices will be switched
|
|
# "on->off", "off->on" or stays in the current state
|
|
#
|
|
|
|
|
|
|
|
package main;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
sub PWM_Get($@);
|
|
sub PWM_Set($@);
|
|
sub PWM_Define($$);
|
|
sub PWM_Calculate($);
|
|
sub PWM_Undef($$);
|
|
sub PWM_CalcRoom(@);
|
|
|
|
my %roomsWaitOffset = ();
|
|
|
|
###################################
|
|
sub
|
|
PWM_Initialize($)
|
|
{
|
|
my ($hash) = @_;
|
|
|
|
$hash->{GetFn} = "PWM_Get";
|
|
$hash->{SetFn} = "PWM_Set";
|
|
$hash->{DefFn} = "PWM_Define";
|
|
$hash->{UndefFn} = "PWM_Undef";
|
|
$hash->{AttrFn} = "PWM_Attr";
|
|
|
|
$hash->{AttrList} = "disable:1,0 valveProtectIdlePeriod overallHeatingSwitchRef:pulseMax,pulseSum,pulseAvg,pulseAvg2,pulseAvg3,avgPulseRoomsOn".
|
|
" overallHeatingSwitchThresholdTemp maxOffTimeCalculation:on,off maxOffTimeMode:max,1,2,3 ".$readingFnAttributes;
|
|
|
|
#$hash->{GetList} = "status timers";
|
|
|
|
}
|
|
|
|
###################################
|
|
sub
|
|
PWM_Calculate($)
|
|
{
|
|
my ($hash) = @_;
|
|
|
|
my $name = $hash->{NAME};
|
|
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 $RoomsMaxOffTimeProtect_off = 0;
|
|
my $RoomsMaxOffTimeProtect_stay_on = 0;
|
|
|
|
my $wkey = "";
|
|
|
|
if($hash->{INTERVAL} > 0) {
|
|
RemoveInternalTimer($hash, "PWM_Calculate");
|
|
InternalTimer(gettimeofday() + $hash->{INTERVAL}, "PWM_Calculate", $hash, 0);
|
|
}
|
|
|
|
if (defined($attr{$name}{disable}) and $attr{$name}{disable} == 1) {
|
|
Log3 ($hash, 3, "PWM_Calculate $name");
|
|
#$hash->{STATE} = "disabled";
|
|
readingsSingleUpdate ($hash, "state", "disabled", 1);
|
|
readingsSingleUpdate ($hash, "lastrun", "disabled", 0);
|
|
return;
|
|
}
|
|
|
|
Log3 ($hash, 3, "PWM_Calculate $name");
|
|
|
|
$hash->{helper}{pulses} = ();
|
|
|
|
readingsBeginUpdate ($hash);
|
|
|
|
readingsBulkUpdate ($hash, "lastrun", "calculating");
|
|
readingsBulkUpdate ($hash, "state", "lastrun: ".$hash->{READINGS}{lastrun}{TIME});
|
|
|
|
# migrate reading maxOffTimeCalculate to attribute, added 01.02.2021, will be deleted later
|
|
if (defined($hash->{READINGS}{maxOffTimeCalculation})) {
|
|
$hash->{c_maxOffTimeCalculation} = $hash->{READINGS}{maxOffTimeCalculation}{VAL};
|
|
$attr{$name}{maxOffTimeCalculation} = $hash->{READINGS}{maxOffTimeCalculation}{VAL};
|
|
|
|
$hash->{c_maxOffTimeMode} = 999 unless defined ($hash->{c_maxOffTimeMode});
|
|
|
|
delete($hash->{READINGS}{maxOffTimeCalculation});
|
|
}
|
|
|
|
# loop over all devices
|
|
# fetch all PWMR devices
|
|
# which are not disabled
|
|
# and are linked to me (via IODev)
|
|
|
|
foreach my $d (sort keys %defs) {
|
|
if ( (defined ($defs{$d}{TYPE})) && $defs{$d}{TYPE} eq "PWMR" ) { # all PWMR objects
|
|
if (!defined ($attr{$d}{disable}) or $attr{$d}{disable} == 0) { # not disabled
|
|
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;
|
|
if ($newstate =~ "off.*") {
|
|
$onoff = (1 - $newpulse) * $cycletime
|
|
}
|
|
|
|
##############################
|
|
##### Valve Protect
|
|
|
|
if ($newstate eq "on_vp") {
|
|
$RoomsValveProtect{$d} = "on";
|
|
} elsif ($newstate eq "off_vp") {
|
|
$RoomsValveProtect{$d} = "off";
|
|
} elsif ($newstate =~ /mop/) {
|
|
|
|
##############################
|
|
##### maxOffTimeProtect
|
|
|
|
if ($newstate eq "on_mop") {
|
|
$RoomsMaxOffTimeProtect_on++;
|
|
$RoomsMaxOffTimeProtect{$d} = $newstate;
|
|
$newstate = "on";
|
|
} elsif ($newstate eq "on_mop_stay") {
|
|
$RoomsMaxOffTimeProtect_stay_on++;
|
|
$RoomsMaxOffTimeProtect{$d} = $newstate;
|
|
$newstate = "";
|
|
} elsif ($newstate eq "on_mop_maybe") {
|
|
$RoomsMaxOffTimeProtect{$d} = $newstate;
|
|
$newstate = "";
|
|
} elsif ($newstate eq "off_mop") {
|
|
$RoomsMaxOffTimeProtect_off++;
|
|
$RoomsMaxOffTimeProtect{$d} = $newstate;
|
|
$newstate = "off";
|
|
}
|
|
|
|
} else {
|
|
|
|
##############################
|
|
##### 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
############################
|
|
# maxOffTimeProtect handling
|
|
|
|
if (defined($hash->{c_maxOffTimeCalculation}) and ($hash->{c_maxOffTimeCalculation} eq "on")) {
|
|
|
|
my $maxOffTimeCnt = $RoomsMaxOffTimeProtect_stay_on;
|
|
my $maxOffTimeMode = $hash->{c_maxOffTimeMode};
|
|
|
|
Log3 ($hash, 3, "PWM_Calculate $name: checkpoint maxOffTime (param $maxOffTimeMode) (cur $maxOffTimeCnt)");
|
|
|
|
# handle: off_mop, on_mop_stay
|
|
foreach my $d (sort keys %RoomsMaxOffTimeProtect) {
|
|
|
|
if ($RoomsMaxOffTimeProtect{$d} eq "off_mop") {
|
|
$RoomsToSwitchOff{$d} = 1;
|
|
$RoomsPulses{$d} = 0;
|
|
|
|
} elsif ($RoomsMaxOffTimeProtect{$d} eq "on_mop_stay") {
|
|
$RoomsToStayOn{$d} = 1;
|
|
$RoomsPulses{$d} = $hash->{MaxPulse};
|
|
}
|
|
|
|
}
|
|
|
|
# handle: on_mop
|
|
# sort ascending reading lastswitch (longest time off serves first)
|
|
foreach my $d (
|
|
sort { $defs{$a}->{READINGS}{lastswitch}{VAL} <=> $defs{$b}->{READINGS}{lastswitch}{VAL} }
|
|
keys %RoomsMaxOffTimeProtect) {
|
|
|
|
if ($RoomsMaxOffTimeProtect{$d} eq "on_mop") {
|
|
|
|
if ($maxOffTimeCnt < int($maxOffTimeMode)) {
|
|
$RoomsToSwitchOn{$d} = 1;
|
|
$RoomsPulses{$d} = $hash->{MaxPulse};
|
|
$maxOffTimeCnt++;
|
|
} else {
|
|
Log3 ($hash, 3, "PWM_Calculate $defs{$d}->{NAME}: F19 maxOffTime protection stay off (Max $maxOffTimeMode)");
|
|
$RoomsToStayOff{$d} = 1;
|
|
$RoomsPulses{$d} = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
# handle: on_mop_maybe
|
|
# sort ascending reading lastswitch (longest time off serves first)
|
|
foreach my $d (
|
|
sort { $defs{$a}->{READINGS}{lastswitch}{VAL} <=> $defs{$b}->{READINGS}{lastswitch}{VAL} }
|
|
keys %RoomsMaxOffTimeProtect) {
|
|
|
|
#Log3 ($hash, 3, "PWM_Calculate $name: checkpoint2 maxOffTime $defs{$d}->{NAME} ".
|
|
# "$defs{$d}->{READINGS}{lastswitch}{TIME} ".
|
|
# "$defs{$d}->{READINGS}{actorState}{VAL} "
|
|
#);
|
|
|
|
if ($RoomsMaxOffTimeProtect{$d} eq "on_mop_maybe") {
|
|
|
|
# on_mop_maybe may only be set if c_maxOffTimeMode > 1
|
|
if (($RoomsMaxOffTimeProtect_on + $RoomsMaxOffTimeProtect_stay_on > 0)
|
|
and ($maxOffTimeCnt < int($maxOffTimeMode))) {
|
|
Log3 ($hash, 3, "PWM_Calculate $defs{$d}->{NAME}: F20 maxOffTime protection pulled on with another room");
|
|
$RoomsToSwitchOn{$d} = 1;
|
|
$RoomsPulses{$d} = $hash->{MaxPulse};
|
|
$maxOffTimeCnt++;
|
|
|
|
} else {
|
|
$RoomsToStayOff{$d} = 1;
|
|
$RoomsPulses{$d} = 0;
|
|
}
|
|
}
|
|
|
|
# for all types of *mop*
|
|
$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
|
|
#
|
|
# algorithm:
|
|
# -> if more than 2 rooms are switched off at the same time,
|
|
# -> simply keep some on (but this will last only for one calculation cycle)
|
|
#
|
|
# assumption: 100% "on" time is not allowed (max newpulse = 85%)
|
|
# -> in the morning all rooms will be switched on at the same time
|
|
# -> and then off at the same time
|
|
|
|
|
|
# normally we switch off only one room at the same time
|
|
# normally we switch on only one room at the same time
|
|
my $switchOn = $hash->{MaxSwitchOnPerCycle}; # default 1
|
|
my $switchOff = $hash->{MaxSwitchOffPerCycle}; # default 1
|
|
|
|
# rooms may stay on due to logic below ...
|
|
#
|
|
# switch off only (one) the room with lowest need for heating
|
|
|
|
# sort rooms with ascending "newpulse"
|
|
foreach my $room (sort { $RoomsToSwitchOff{$a} <=> $RoomsToSwitchOff{$b} } keys %RoomsToSwitchOff) {
|
|
|
|
# only the first room in the list will be switched off
|
|
# all others will stay on
|
|
# first room has the lowest need for heating ... it will be switched off
|
|
|
|
$switchOff--;
|
|
|
|
if ($switchOff >= 0) {
|
|
Log3 ($hash, 3, "PWM_Calculate $room: F99 switch off ".
|
|
"(pulse=$RoomsToSwitchOff{$room})");
|
|
next;
|
|
}
|
|
|
|
Log3 ($hash, 3, "PWM_Calculate $room: F99 keep room on ".
|
|
"(pulse=$RoomsToSwitchOff{$room})");
|
|
|
|
$RoomsToStayOn{$room} = 1;
|
|
if (defined($RoomsToSwitchOff{$room})) {
|
|
delete ($RoomsToSwitchOff{$room});
|
|
}
|
|
}
|
|
|
|
# try to minimize the situation where all rooms are "on" at the same time
|
|
# switch "on" only one room at the same time
|
|
|
|
# sort rooms with decending "newpulse"
|
|
foreach my $room (sort { $RoomsToSwitchOn{$b} <=> $RoomsToSwitchOn{$a} } keys %RoomsToSwitchOn) {
|
|
|
|
# only the first room in the list will be switched on
|
|
# all others will stay off
|
|
# first room has the highest need for heating ... it will be switched on
|
|
|
|
$switchOn--;
|
|
|
|
if ($switchOn >= 0) {
|
|
Log3 ($hash, 3, "PWM_Calculate $room: F98 switch on ".
|
|
"(pulse=$RoomsToSwitchOn{$room})");
|
|
next;
|
|
}
|
|
|
|
Log3 ($hash, 3, "PWM_Calculate $room: F98 keep room off ".
|
|
"(pulse=$RoomsToSwitchOn{$room})");
|
|
|
|
my $wkey = $name."_".$room;
|
|
$roomsWaitOffset{$wkey} += 0.0001;
|
|
|
|
$RoomsToStayOff{$room} = 1;
|
|
if (defined($RoomsToSwitchOn{$room})) {
|
|
delete ($RoomsToSwitchOn{$room});
|
|
}
|
|
|
|
}
|
|
|
|
|
|
# in addition to the above max. of 85% of the active rooms may be on at the same time
|
|
# 11 * 0.8 = 8.8 ... 8 is ok ... 9, 10, 11 is not (laraEG!)
|
|
|
|
my $roomsOn = (scalar keys %RoomsToStayOn) - (scalar keys %RoomsToSwitchOff);
|
|
|
|
my $maxRoomsOn = $roomsActive - $hash->{NoRoomsToStayOff};
|
|
|
|
#
|
|
# looks complicated but this will work if more than one room would be switched on
|
|
#
|
|
# prevent rooms to be switched on if maxRoomsOn is reached
|
|
#
|
|
while (
|
|
(($roomsOn + (scalar keys %RoomsToSwitchOn)) > $maxRoomsOn) &&
|
|
((scalar keys %RoomsToSwitchOn) > 0)
|
|
) {
|
|
|
|
# sort rooms with ascending "newpulse"
|
|
foreach my $room (sort { $RoomsToSwitchOn{$a} <=> $RoomsToSwitchOn{$b} } keys %RoomsToSwitchOn) {
|
|
|
|
Log3 ($hash, 3, "PWM_Calculate $room: F97 keep room off ".
|
|
"(pulse=$RoomsToSwitchOn{$room}) (max=$maxRoomsOn)");
|
|
|
|
|
|
my $wkey = $name."_".$room;
|
|
$roomsWaitOffset{$wkey} += 0.001;
|
|
|
|
$RoomsToStayOff{$room} = 1;
|
|
if (defined($RoomsToSwitchOn{$room})) {
|
|
delete ($RoomsToSwitchOn{$room});
|
|
}
|
|
|
|
last; # continue in while loop
|
|
}
|
|
}
|
|
|
|
# in addition to the above try to prevent that too many rooms are off
|
|
# use $roomsActive and $newpulseSum to differentiate if heating is required
|
|
# 11 * 0.27 = 2.97 ... 3 rooms is ok ... 0,1 or 2 is not
|
|
|
|
# 23.09.2015
|
|
#my $minRoomsOn = $roomsActive * 0.29;
|
|
|
|
# if overall required heating is below 0.42 ... possibly drive Vaillant into "Sperrzeit"
|
|
# 15.01.2015: adjust this from 0.42 to 0.25 (=25% Pulse needed)
|
|
# 23.09.2015
|
|
#if ($roomsActive == 0 or $newpulseSum/$roomsActive < 0.42) {
|
|
# $minRoomsOn = 0;
|
|
#}
|
|
|
|
my $minRoomsOn = $hash->{NoRoomsToStayOn};
|
|
my $minRoomsOnList = "";
|
|
|
|
if ($minRoomsOn > 0) {
|
|
|
|
my $roomsCounted = 0;
|
|
my $pulseSum = 0;
|
|
my $avgPulse = 0;
|
|
|
|
foreach my $room (sort { $RoomsPulses{$b} <=> $RoomsPulses{$a} } keys %RoomsPulses) {
|
|
|
|
last if ($roomsCounted == $minRoomsOn);
|
|
Log3 ($hash, 3, "PWM_Calculate: loop $roomsCounted $room $RoomsPulses{$room}");
|
|
|
|
$minRoomsOnList .= "$room,";
|
|
$pulseSum += $RoomsPulses{$room};
|
|
$roomsCounted++;
|
|
}
|
|
$minRoomsOnList =~ s/,$//;
|
|
|
|
$avgPulse = $pulseSum/$roomsCounted if ($roomsCounted > 0);
|
|
|
|
if ($roomsActive == 0 or $hash->{NoRoomsToStayOnThreshold} == 0 or $avgPulse < $hash->{NoRoomsToStayOnThreshold}) {
|
|
$minRoomsOn = 0;
|
|
$minRoomsOnList = "";
|
|
}
|
|
|
|
#Log3 ($hash, 3, "PWM_Calculate: newpulseSum $newpulseSum avg ".$newpulseSum/$roomsActive." minRoomsOn(".$minRoomsOn.")") if ($roomsActive > 0);
|
|
Log3 ($hash, 3, "PWM_Calculate: pulseSum $pulseSum avg ".$avgPulse." minRoomsOn(".$minRoomsOn.")") if ($roomsActive > 0);
|
|
|
|
}
|
|
|
|
|
|
#
|
|
# looks complicated but this will work if more than one room would stay on
|
|
#
|
|
while (
|
|
(((scalar keys %RoomsToStayOn) + (scalar keys %RoomsToSwitchOn)) < $minRoomsOn) &&
|
|
((scalar keys %RoomsToSwitchOff) > 0)
|
|
) {
|
|
|
|
# sort rooms with decending "newpulse"
|
|
foreach my $room (sort { $RoomsToSwitchOff{$b} <=> $RoomsToSwitchOff{$a} } keys %RoomsToSwitchOff) {
|
|
|
|
my $ron = 1 + (scalar keys %RoomsToStayOn) + (scalar keys %RoomsToSwitchOn);
|
|
|
|
Log3 ($hash, 3, "PWM_Calculate $room: F96 keep room on ".
|
|
"(pulse=$RoomsToSwitchOff{$room}) (min=$minRoomsOn) (roomsOn=$ron)");
|
|
|
|
my $wkey = $name."_".$room;
|
|
$roomsWaitOffset{$wkey} -= 0.001;
|
|
|
|
$RoomsToStayOn{$room} = 1;
|
|
if (defined($RoomsToSwitchOff{$room})) {
|
|
delete ($RoomsToSwitchOff{$room});
|
|
}
|
|
|
|
last; # continue in while loop
|
|
}
|
|
}
|
|
|
|
#
|
|
# now process the calculated actions
|
|
#
|
|
|
|
my $cntRoomsOn = 0;
|
|
my $cntRoomsOnVP = 0;
|
|
my $cntRoomsOff = 0;
|
|
my $pulseRoomsOn = 0;
|
|
my $pulseRoomsOff = 0;
|
|
|
|
foreach my $roomStay (sort keys %RoomsToStayOff) {
|
|
|
|
PWMR_SetRoom ($defs{$roomStay}, "");
|
|
|
|
$cntRoomsOff++;
|
|
$pulseRoomsOff += $RoomsPulses{$roomStay};
|
|
|
|
}
|
|
|
|
foreach my $roomStay (sort keys %RoomsToStayOn) {
|
|
|
|
PWMR_SetRoom ($defs{$roomStay}, "");
|
|
|
|
$cntRoomsOn++;
|
|
$pulseRoomsOn += $RoomsPulses{$roomStay};
|
|
|
|
}
|
|
|
|
foreach my $roomOff (sort keys %RoomsToSwitchOff) {
|
|
|
|
PWMR_SetRoom ($defs{$roomOff}, "off");
|
|
|
|
if (defined($defs{$roomOff}->{helper}{maxOffTimeLastSwitch})) {
|
|
delete ($defs{$roomOff}->{helper}{maxOffTimeLastSwitch});
|
|
}
|
|
|
|
$cntRoomsOff++;
|
|
$pulseRoomsOff += $RoomsPulses{$roomOff};
|
|
}
|
|
|
|
foreach my $roomOn (sort keys %RoomsToSwitchOn) {
|
|
|
|
my $wkey = $name."-".$roomOn;
|
|
$roomsWaitOffset{$wkey} = 0;
|
|
PWMR_SetRoom ($defs{$roomOn}, "on");
|
|
|
|
if (defined($RoomsMaxOffTimeProtect{$roomOn})) {
|
|
$defs{$roomOn}->{helper}{maxOffTimeLastSwitch} = time();
|
|
}
|
|
|
|
$cntRoomsOn++;
|
|
$pulseRoomsOn += $RoomsPulses{$roomOn};
|
|
|
|
}
|
|
|
|
|
|
foreach my $roomVP (sort keys %RoomsValveProtect) {
|
|
|
|
my $wkey = $name."-".$roomVP;
|
|
$roomsWaitOffset{$wkey} = 0;
|
|
|
|
if ( $RoomsValveProtect{$roomVP} eq "on") {
|
|
|
|
PWMR_SetRoom ($defs{$roomVP}, "on");
|
|
$cntRoomsOn++;
|
|
$cntRoomsOnVP++;
|
|
$pulseRoomsOn += $RoomsPulses{$roomVP};
|
|
|
|
} else {
|
|
|
|
PWMR_SetRoom ($defs{$roomVP}, "off");
|
|
$cntRoomsOff++;
|
|
$pulseRoomsOff += $RoomsPulses{$roomVP};
|
|
}
|
|
|
|
}
|
|
|
|
my $cntAvg = 0;
|
|
|
|
# sort rooms with decending "newpulse"
|
|
foreach my $room (sort { $RoomsPulses{$b} <=> $RoomsPulses{$a} } keys %RoomsPulses) {
|
|
|
|
$newpulseAvg += $RoomsPulses{$room};
|
|
$newpulseAvg2 += $RoomsPulses{$room} if ($cntAvg < 2);
|
|
$newpulseAvg3 += $RoomsPulses{$room} if ($cntAvg < 3);
|
|
|
|
$cntAvg++;
|
|
}
|
|
|
|
$newpulseAvg = sprintf ("%.02f", $newpulseAvg / $cntAvg) if ($cntAvg > 0);
|
|
$newpulseAvg2 = sprintf ("%.02f", $newpulseAvg2 / minNum (2, $cntAvg)) if ($cntAvg > 0);
|
|
$newpulseAvg3 = sprintf ("%.02f", $newpulseAvg3 / minNum (3, $cntAvg)) if ($cntAvg > 0);
|
|
|
|
|
|
readingsBulkUpdate ($hash, "roomsActive", $roomsActive);
|
|
readingsBulkUpdate ($hash, "roomsOn", $cntRoomsOn);
|
|
readingsBulkUpdate ($hash, "roomsOff", $cntRoomsOff);
|
|
readingsBulkUpdate ($hash, "avgPulseRoomsOn", ($cntRoomsOn > 0 ? sprintf ("%.2f", $pulseRoomsOn / $cntRoomsOn) : 0));
|
|
readingsBulkUpdate ($hash, "avgPulseRoomsOff", ($cntRoomsOff > 0 ? sprintf ("%.2f", $pulseRoomsOff /$cntRoomsOff) : 0));
|
|
readingsBulkUpdate ($hash, "pulseMax", $newpulseMax);
|
|
readingsBulkUpdate ($hash, "pulseSum", $newpulseSum);
|
|
readingsBulkUpdate ($hash, "pulseAvg", $newpulseAvg);
|
|
readingsBulkUpdate ($hash, "pulseAvg2", $newpulseAvg2);
|
|
readingsBulkUpdate ($hash, "pulseAvg3", $newpulseAvg3);
|
|
|
|
if ( $hash->{NoRoomsToStayOn} > 0) {
|
|
readingsBulkUpdate ($hash, "roomsToStayOn", $minRoomsOn);
|
|
readingsBulkUpdate ($hash, "roomsToStayOnList", $minRoomsOnList);
|
|
} else {
|
|
readingsBulkUpdate ($hash, "roomsToStayOn", 0);
|
|
readingsBulkUpdate ($hash, "roomsToStayOnList", "");
|
|
}
|
|
|
|
|
|
if ( defined ($hash->{OverallHeatingSwitch}) ) {
|
|
if ( $hash->{OverallHeatingSwitch} ne "") {
|
|
|
|
my $newstateOHS = "on";
|
|
if ( $hash->{OverallHeatingSwitch_threshold} > 0) {
|
|
|
|
# threshold based
|
|
my $refValue = $newpulseMax;
|
|
|
|
if (defined($attr{$name}{overallHeatingSwitchRef})) {
|
|
|
|
my $ref = $attr{$name}{overallHeatingSwitchRef};
|
|
|
|
$refValue = $newpulseMax if ($ref eq "pulseMax");
|
|
$refValue = $newpulseSum if ($ref eq "pulseSum");
|
|
$refValue = $newpulseAvg if ($ref eq "pulseAvg");
|
|
$refValue = $newpulseAvg2 if ($ref eq "pulseAvg2");
|
|
$refValue = $newpulseAvg3 if ($ref eq "pulseAvg3");
|
|
}
|
|
|
|
$newstateOHS = ($refValue > $hash->{OverallHeatingSwitch_threshold}) ? "on" : "off";
|
|
|
|
} else {
|
|
|
|
# room based
|
|
$newstateOHS = (($cntRoomsOn - $cntRoomsOnVP) > 0) ? "on" : "off";
|
|
|
|
}
|
|
|
|
# OverallHeatingSwitchThresholdTemp may prevent switch ot on and sets OverallHeatingSwitch to e-off
|
|
my $newstateOHS_eoff = 0;
|
|
if ($newstateOHS eq "on" and defined ($hash->{OverallHeatingSwitchTT_tsensor})) {
|
|
|
|
my $sensor = $hash->{OverallHeatingSwitchTT_tsensor};
|
|
my $reading = $hash->{OverallHeatingSwitchTT_reading};
|
|
|
|
if (defined ($defs{$sensor}) and defined ($defs{$sensor}->{READINGS}{$reading})) {
|
|
|
|
my $t_regexp = $hash->{OverallHeatingSwitchTT_t_regexp};
|
|
my $maxTemp = $hash->{OverallHeatingSwitchTT_maxTemp};
|
|
|
|
my $temp = $defs{$sensor}->{READINGS}{$reading}{VAL};
|
|
$temp =~ /$t_regexp/;
|
|
if (defined ($1))
|
|
{
|
|
$temp = $1;
|
|
if ($temp >= $maxTemp)
|
|
{
|
|
$newstateOHS_eoff = 1;
|
|
$newstateOHS = "off";
|
|
Log3 ($name, 3, "PWM_Calculate: $name: OverallHeatingSwitch forced to off since ThresholdTemp reached maxTemp ($temp >= $maxTemp)");
|
|
readingsBulkUpdate ($hash, "OverallHeatingSwitchTT_Off", 1);
|
|
}
|
|
else
|
|
{
|
|
if ($hash->{READINGS}{OverallHeatingSwitchTT_Off}{VAL} == 1) {
|
|
readingsBulkUpdate ($hash, "OverallHeatingSwitchTT_Off", 0);
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
Log3 ($name, 3, "PWM_Calculate: $name: OverallHeatingSwitchThresholdTemp t_regexp does not match temperature");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log3 ($name, 3, "PWM_Calculate: $name: OverallHeatingSwitchThresholdTemp refers to invalid device or reading");
|
|
}
|
|
}
|
|
|
|
my $actor = $hash->{OverallHeatingSwitch};
|
|
my $actstateOHS = ($defs{$actor}{STATE} =~ $hash->{OverallHeatingSwitch_regexp_on}) ? "on" : "off";
|
|
|
|
if ($hash->{OverallHeatingSwitch_followUpTime} > 0) {
|
|
|
|
if ($newstateOHS_eoff == 1)
|
|
{
|
|
readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", "");
|
|
}
|
|
else
|
|
{
|
|
if ($actstateOHS eq "on" and $newstateOHS eq "off") {
|
|
|
|
if ($hash->{READINGS}{OverallHeatingSwitchWaitUntilOff}{VAL} eq "") {
|
|
$newstateOHS = "on";
|
|
Log3 ($name, 3, "PWM_Calculate: $name: OverallHeatingSwitch wait for followUpTime before switching off (init timestamp)");
|
|
readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", FmtDateTime(time() + $hash->{OverallHeatingSwitch_followUpTime}));
|
|
|
|
} elsif ($hash->{READINGS}{OverallHeatingSwitchWaitUntilOff}{VAL} ge TimeNow()) {
|
|
$newstateOHS = "on";
|
|
Log3 ($name, 3, "PWM_Calculate: $name: OverallHeatingSwitch wait for followUpTime before switching off");
|
|
} else {
|
|
readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", "");
|
|
}
|
|
|
|
} else {
|
|
readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", "");
|
|
}
|
|
}
|
|
}
|
|
if ($hash->{OverallHeatingSwitch_delayTimeOn} > 0) {
|
|
|
|
if ($actstateOHS eq "off" and $newstateOHS eq "on") {
|
|
|
|
if ($hash->{READINGS}{OverallHeatingSwitchWaitBeforeOn}{VAL} eq "") {
|
|
$newstateOHS = "off";
|
|
Log3 ($name, 3, "PWM_Calculate: $name: OverallHeatingSwitch wait for delayTimeOn before switching on (init timestamp)");
|
|
readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitBeforeOn", FmtDateTime(time() + $hash->{OverallHeatingSwitch_delayTimeOn}));
|
|
|
|
} elsif ($hash->{READINGS}{OverallHeatingSwitchWaitBeforeOn}{VAL} ge TimeNow()) {
|
|
$newstateOHS = "off";
|
|
Log3 ($name, 3, "PWM_Calculate: $name: OverallHeatingSwitch wait for delayTimeOn before switching on");
|
|
} else {
|
|
readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitBeforeOn", "");
|
|
}
|
|
|
|
} else {
|
|
readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitBeforeOn", "");
|
|
}
|
|
}
|
|
|
|
if ($newstateOHS ne $actstateOHS or $hash->{READINGS}{OverallHeatingSwitch}{VAL} ne $actstateOHS) {
|
|
|
|
my $ret = fhem sprintf ("set %s %s", $hash->{OverallHeatingSwitch}, $newstateOHS);
|
|
if (!defined($ret)) { # sucessfull
|
|
Log3 ($name, 4, "PWMR_SetRoom: $name: set $actor $newstateOHS");
|
|
|
|
readingsBulkUpdate ($hash, "OverallHeatingSwitch", $newstateOHS);
|
|
|
|
|
|
# push @{$room->{CHANGED}}, "actor $newstateOHS";
|
|
# DoTrigger($name, undef);
|
|
|
|
} else {
|
|
Log3 ($name, 4, "PWMR_SetRoom $name: set $actor $newstateOHS failed ($ret)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
Log3 ($hash, 3, "PWM_Calculate $name done");
|
|
}
|
|
|
|
###################################
|
|
sub
|
|
PWM_CalcRoom(@)
|
|
{
|
|
my ($hash, $room) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 ($hash, 4, "PWM_CalcRoom: $name ($room->{NAME})");
|
|
|
|
my $cycletime = $hash->{CYCLETIME};
|
|
|
|
my ($temperaturV, $actorV, $factor, $oldpulse, $newpulse, $prevswitchtime, $windowV, $maxOffTimeApply, $maxOffTime, $maxOffTimePeriod, $maxOffTimeAct) =
|
|
PWMR_ReadRoom($room, $cycletime, $hash->{MaxPulse});
|
|
|
|
my $nextswitchtime;
|
|
if ($actorV eq "on") {
|
|
$nextswitchtime = int($oldpulse * $cycletime) + $prevswitchtime;
|
|
} else {
|
|
$nextswitchtime = int((1-$oldpulse) * $cycletime) + $prevswitchtime;
|
|
}
|
|
|
|
#Log3 ($hash, 4, "PWM_CalcRoom $room->{NAME}: $cycletime ($prevswitchtime/$nextswitchtime)=".($nextswitchtime-$prevswitchtime));
|
|
|
|
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");
|
|
return ("off_mop", $newpulse, $cycletime, $actorV);
|
|
}
|
|
|
|
}
|
|
|
|
# ----------------
|
|
# check if valve protection is active, keep this state for 5 minutes
|
|
|
|
if (defined ($room->{helper}{valveProtectLastSwitch})) {
|
|
if ( $room->{helper}{valveProtectLastSwitch} + 300 > time()) {
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F13 valveProtect continue");
|
|
return ("", $newpulse, $cycletime, $actorV);
|
|
} else {
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F14 valveProtect off");
|
|
delete ($room->{helper}{valveProtectLastSwitch});
|
|
return ("off_vp", $newpulse, $cycletime, $actorV);
|
|
}
|
|
|
|
}
|
|
|
|
# ----------------
|
|
# decide if to change to "off"
|
|
|
|
if ($newpulse == 1) {
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F10 stay on");
|
|
return ("", $newpulse, $cycletime, $actorV);
|
|
}
|
|
|
|
if ($newpulse < $oldpulse) { # on: was 80% now it is 30%
|
|
|
|
if ( time() >= $nextswitchtime ) # F3
|
|
{
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F3 new off");
|
|
return ("off", $newpulse, $cycletime, $actorV);
|
|
|
|
|
|
# state changed and it is worth to move the device
|
|
}
|
|
else #( time() < $nextswitchtime ) # F1
|
|
{
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F1 stay on");
|
|
return ("", $newpulse, $cycletime, $actorV);
|
|
}
|
|
|
|
} else { #($newpulse >= $oldpulse) # unchanged, or was 30% now 40%
|
|
|
|
# maybe we switch off
|
|
# - because several cycles were not calculated
|
|
# - or on time is simply over
|
|
# - newpulse 0 is also handled here
|
|
|
|
if ( time() >= $nextswitchtime) { # F4
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F4 new off");
|
|
return ("off", $newpulse, $cycletime, $actorV);
|
|
} else {
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F9 stay on");
|
|
return ("", $newpulse, $cycletime, $actorV);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
elsif ($actorV eq "off") # current state is "off"
|
|
{
|
|
|
|
# ----------------
|
|
# check if valve protection is activated (attribute valveProtectIdlePeriod is set)
|
|
|
|
if (defined ($attr{$name}{"valveProtectIdlePeriod"})) {
|
|
# 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);
|
|
}
|
|
}
|
|
|
|
# ----------------
|
|
# 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);
|
|
}
|
|
|
|
# ----------------
|
|
# check if maxOffTime protection is activated (attribute maxOffTimeIdlePeriod is set)
|
|
# $maxOffTImeApply will only be set if no heating is required
|
|
|
|
if ($maxOffTimeApply > 0 and defined($hash->{c_maxOffTimeCalculation}) and ($hash->{c_maxOffTimeCalculation} eq "on")) {
|
|
|
|
## 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 ($hash->{c_maxOffTimeMode} > 1) {
|
|
if ($maxOffTimeAct >= $maxOffTime / 2) {
|
|
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F18 maxOffTime protection (possible)");
|
|
return ("on_mop_maybe", $newpulse, $cycletime, $actorV);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($newpulse == 0) {
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F11 stay off (0)");
|
|
return ("", $newpulse, $cycletime, $actorV);
|
|
}
|
|
|
|
if ($newpulse > $oldpulse) { # was 30% now it is 80%
|
|
# F5
|
|
if ( time() < $nextswitchtime )
|
|
{
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F5 stay off");
|
|
return ("", $newpulse, $cycletime, $actorV);
|
|
|
|
}
|
|
else # time >= $nextswitchtime
|
|
{
|
|
# F6
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F6 new on");
|
|
return ("on", $newpulse, $cycletime, $actorV);
|
|
}
|
|
|
|
} else { # unchanged, was 80% now 30%
|
|
# F2
|
|
if ( time() >= $nextswitchtime ) {
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F2 new on");
|
|
return ("on", $newpulse, $cycletime, $actorV);
|
|
} else {
|
|
Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F8 stay off");
|
|
return ("", $newpulse, $cycletime, $actorV);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
else # $actorV not "on" of "off"
|
|
{
|
|
Log3 ($hash, 3, "PWM_CalcRoom -> $name -> $room->{NAME}: invalid actor state ($actorV) try to switch off");
|
|
return ("off", 0, $cycletime, $actorV);
|
|
|
|
}
|
|
|
|
return ("", $newpulse, $cycletime, $actorV);
|
|
|
|
}
|
|
|
|
###################################
|
|
sub
|
|
PWM_Get($@)
|
|
{
|
|
my ($hash, @a) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
return "argument is missing" if(int(@a) != 2);
|
|
|
|
my $msg;
|
|
|
|
if ($a[1] eq "status") {
|
|
return $hash->{STATE};
|
|
|
|
} elsif ($a[1] eq "timers") {
|
|
Log3 ($hash, 1, "in get timers");
|
|
|
|
my $cnt = 0;
|
|
my %tmpTimersFrom = ();
|
|
my %tmpTimersTo = ();
|
|
|
|
foreach my $d (sort keys %defs) {
|
|
if ( (defined ($defs{$d}{TYPE})) && $defs{$d}{TYPE} eq "PWMR" ) { # all PWMR objects
|
|
if (!defined ($attr{$d}{disable}) or $attr{$d}{disable} == 0) { # not disabled
|
|
if ($name eq $defs{$d}{IODev}->{NAME}) { # referencing to this fb
|
|
my $room = $defs{$d};
|
|
Log3 ($hash, 1, "PWM_Get $name collect $room->{NAME}");
|
|
|
|
foreach my $reading ("timer1_Mo", "timer2_Di", "timer3_Mi", "timer4_Do", "timer5_Fr", "timer6_Sa", "timer7_So") {
|
|
if (defined ($room->{READINGS}{$reading}) and $room->{READINGS}{$reading} ne "") {
|
|
|
|
$cnt++;
|
|
|
|
Log3 ($hash, 1, "PWM_Get $name collect $room->{NAME} $reading");
|
|
my (@timers) = split / /, $room->{READINGS}{$reading}{VAL};
|
|
|
|
my ($mintime, $minTempId, $minTemp ) = split /,/, $timers[0];
|
|
my ($maxtime, $maxTempId, $maxTemp ) = split /,/, $timers[$#timers];
|
|
|
|
my ($minfrom, $minto) = split /-/, $mintime;
|
|
my ($maxfrom, $maxto) = split /-/, $maxtime;
|
|
|
|
$tmpTimersFrom{$reading} = $minfrom unless defined($tmpTimersFrom{$reading});
|
|
$tmpTimersTo{$reading} = $maxto unless defined($tmpTimersTo{$reading});
|
|
|
|
$tmpTimersFrom{$reading} = $minfrom if ($tmpTimersFrom{$reading} > $minfrom);
|
|
$tmpTimersTo{$reading} = $maxto if ($tmpTimersTo{$reading} < $maxto);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($cnt == 0) {
|
|
foreach my $reading ("timer1_Mo", "timer2_Di", "timer3_Mi", "timer4_Do", "timer5_Fr", "timer6_Sa", "timer7_So") {
|
|
delete ($hash->{READINGS}{$reading});
|
|
}
|
|
} else {
|
|
readingsBeginUpdate ($hash);
|
|
foreach my $reading ("timer1_Mo", "timer2_Di", "timer3_Mi", "timer4_Do", "timer5_Fr", "timer6_Sa", "timer7_So") {
|
|
readingsBulkUpdate ($hash, "$reading", $tmpTimersFrom{$reading}."-".$tmpTimersTo{$reading});
|
|
}
|
|
readingsEndUpdate($hash, 1);
|
|
}
|
|
|
|
#return "$reading from $minfrom to $maxto";
|
|
return "";
|
|
|
|
} else {
|
|
return "Unknown argument $a[1], choose one of status timers";
|
|
}
|
|
}
|
|
|
|
#############################
|
|
sub
|
|
PWM_Set($@)
|
|
{
|
|
my ($hash, @a) = @_;
|
|
|
|
my $name = $hash->{NAME};
|
|
my $u = "Unknown argument $a[1], choose one of recalc interval cycletime maxOffTimeCalculation:on,off";
|
|
|
|
|
|
if ( $a[1] =~ /^interval$|^cycletime$/ ) {
|
|
return $u if(int(@a) != 3);
|
|
|
|
my $hw = uc($a[1]);
|
|
$hash->{$hw}= $a[2];
|
|
|
|
} elsif ( $a[1] =~ /^recalc$/ ) {
|
|
|
|
#$hash->{LOCAL} = 1;
|
|
RemoveInternalTimer($hash);
|
|
my $v = PWM_Calculate($hash);
|
|
#delete $hash->{LOCAL};
|
|
|
|
} elsif ( $a[1] =~ /^maxOffTimeCalculation$/ ) {
|
|
|
|
$hash->{c_maxOffTimeCalculation} = $a[2];
|
|
$attr{$name}{maxOffTimeCalculation} = $a[2];
|
|
|
|
$hash->{c_maxOffTimeMode} = 999 unless defined ($hash->{c_maxOffTimeMode});
|
|
|
|
} else {
|
|
|
|
return $u;
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
|
|
#############################
|
|
sub
|
|
PWM_Define($$)
|
|
{
|
|
my ($hash, $def) = @_;
|
|
my @a = split("[ \t][ \t]*", $def);
|
|
|
|
my $name = $hash->{NAME};
|
|
|
|
return "syntax: define <name> PWM [<interval>] [<cycletime>] [<minonofftime>] [<maxPulse>] [<maxSwitchOnPerCycle>,<maxSwitchOffPerCycle>] [<roomStayOn>,<roomStayOff>,<stayOnThreshold>]".
|
|
" [<overallHeatingSwitch>[,<pulseThreshold>[,<followUpTime>[,<h_regexp_on>[,<delayTimeOn>]]]]"
|
|
if(int(@a) < 2 || int(@a) > 9);
|
|
|
|
my $interval = ((int(@a) > 2) ? $a[2] : 60);
|
|
my $cycletime = ((int(@a) > 3) ? $a[3] : 900);
|
|
my $minonofftime = ((int(@a) > 4) ? $a[4] : 120);
|
|
my $maxPulse = ((int(@a) > 5) ? minNum ($a[5], 1.00) : 1.00);
|
|
|
|
$hash->{INTERVAL} = $interval;
|
|
$hash->{CYCLETIME} = $cycletime;
|
|
$hash->{MINONOFFTIME} = $minonofftime;
|
|
$hash->{MaxPulse} = $maxPulse;
|
|
|
|
$hash->{STATE} = "defined";
|
|
$hash->{p_interval} = $interval;
|
|
$hash->{p_cycletime} = $cycletime;
|
|
$hash->{p_minOnOfftime} = $minonofftime;
|
|
$hash->{p_maxPulse} = $maxPulse;
|
|
|
|
|
|
##########
|
|
# [<maxSwitchOnPerCycle>,<maxSwitchOffPerCycle>]
|
|
|
|
if (int(@a) > 6) {
|
|
my ($maxOn, $maxOff) = split (",", $a[6]);
|
|
$maxOff = $maxOn unless (defined($maxOff));
|
|
|
|
$hash->{MaxSwitchOnPerCycle} = $maxOn;
|
|
$hash->{MaxSwitchOffPerCycle} = $maxOff;
|
|
|
|
} else {
|
|
|
|
if ($maxPulse == 1) {
|
|
$hash->{MaxSwitchOnPerCycle} = 99;
|
|
$hash->{MaxSwitchOffPerCycle} = 99;
|
|
} else {
|
|
$hash->{MaxSwitchOnPerCycle} = 1;
|
|
$hash->{MaxSwitchOffPerCycle} = 1;
|
|
}
|
|
|
|
}
|
|
|
|
##########
|
|
# [<roomStayOn>,<roomStayOff>,<stayOnThreshold>]
|
|
|
|
if (int(@a) > 7) {
|
|
my ($stayOn, $stayOff, $onThreshold) = split (",", $a[7]);
|
|
|
|
$stayOff = 1 unless (defined($stayOff)); # one room stays off
|
|
$onThreshold = 0.3 unless (defined($onThreshold)); # $stayOn is used only if average pluse is >= 0.3
|
|
|
|
$hash->{NoRoomsToStayOn} = $stayOn; # eg. 4 rooms stay switched on (unless average pulse is less then threshold)
|
|
$hash->{NoRoomsToStayOff} = $stayOff; # 1 room stays off to limit energy used (maxPulse should be < 1 if this is used)
|
|
$hash->{NoRoomsToStayOnThreshold} = $onThreshold; # $stayOn is used only if average pluse is >= threshold
|
|
$hash->{p_roomsMinOnOffThreshold} = $a[7];
|
|
|
|
} else {
|
|
|
|
$hash->{NoRoomsToStayOn} = 0; # switch off all rooms is allowd
|
|
$hash->{NoRoomsToStayOff} = 0; # switch on all rooms if allowed
|
|
$hash->{NoRoomsToStayOnThreshold} = 0; # pulse threshold to use "NoRoomsToStayOn"
|
|
$hash->{p_minOnOffThreshold} = "";
|
|
|
|
}
|
|
|
|
##########
|
|
# [<overallHeatingSwitch>]
|
|
|
|
if (int(@a) > 8) {
|
|
my ($hactor, $h_threshold, $h_followUpTime, $h_regexp_on, $h_delayTimeOn) = split (",", $a[8], 5);
|
|
$h_followUpTime = 0 unless ($h_followUpTime);
|
|
$h_threshold = 0 unless ($h_threshold);
|
|
$h_regexp_on = "on" unless ($h_regexp_on);
|
|
$h_delayTimeOn = 0 unless ($h_delayTimeOn);
|
|
|
|
if (!$defs{$hactor} && $hactor ne "dummy")
|
|
{
|
|
my $msg = "$name: Unknown actor device $hactor specified";
|
|
Log3 ($hash, 3, "PWM_Define $msg");
|
|
return $msg;
|
|
}
|
|
|
|
$hash->{OverallHeatingSwitch} = $hactor;
|
|
$hash->{OverallHeatingSwitch_threshold} = $h_threshold;
|
|
$hash->{OverallHeatingSwitch_regexp_on} = $h_regexp_on;
|
|
$hash->{OverallHeatingSwitch_roomBased} = ($h_threshold > 0) ? "off" : "on";
|
|
$hash->{OverallHeatingSwitch_followUpTime} = $h_followUpTime;
|
|
$hash->{OverallHeatingSwitch_delayTimeOn} = $h_delayTimeOn;
|
|
$hash->{p_overallHeatingSwitch} = $a[8];
|
|
readingsSingleUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", "", 0);
|
|
readingsSingleUpdate ($hash, "OverallHeatingSwitchWaitBeforeOn", "", 0);
|
|
readingsSingleUpdate ($hash, "OverallHeatingSwitch", "", 0);
|
|
|
|
delete ($hash->{READINGS}{OverallHeatingSwitchWaitUntil}) if defined ($hash->{READINGS}{OverallHeatingSwitchWaitUntil});
|
|
delete ($hash->{READINGS}{OverallHeatingSwitchWaitBefore}) if defined ($hash->{READINGS}{OverallHeatingSwitchWaitBefore});
|
|
} else {
|
|
$hash->{OverallHeatingSwitch} = "";
|
|
$hash->{OverallHeatingSwitch_threshold} = "";
|
|
$hash->{OverallHeatingSwitch_regexp_on} = "";
|
|
$hash->{OverallHeatingSwitch_roomBased} = "";
|
|
$hash->{OverallHeatingSwitch_followUpTime} = "";
|
|
$hash->{OverallHeatingSwitch_delayTimeOn} = "";
|
|
$hash->{p_overallHeatingSwitch} = "";
|
|
readingsSingleUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", "", 0);
|
|
readingsSingleUpdate ($hash, "OverallHeatingSwitchWaitBeforeOn", "", 0);
|
|
readingsSingleUpdate ($hash, "OverallHeatingSwitch", "", 0);
|
|
|
|
delete ($hash->{READINGS}{OverallHeatingSwitchWaitUntil}) if defined ($hash->{READINGS}{OverallHeatingSwitchWaitUntil});
|
|
delete ($hash->{READINGS}{OverallHeatingSwitchWaitBefore}) if defined ($hash->{READINGS}{OverallHeatingSwitchWaitBefore});
|
|
}
|
|
|
|
#AssignIoPort($hash);
|
|
|
|
if($hash->{INTERVAL} > 0) {
|
|
RemoveInternalTimer($hash, "PWM_Calculate");
|
|
InternalTimer(gettimeofday() + 10, "PWM_Calculate", $hash, 0);
|
|
}
|
|
|
|
Log3 ($hash, 3, "PWM Define $name");
|
|
|
|
return undef;
|
|
}
|
|
|
|
###################################
|
|
sub PWM_Undef($$)
|
|
{
|
|
my ($hash, $args) = @_;
|
|
|
|
my $name = $hash->{NAME};
|
|
Log3 ($hash, 3, "PWM Undef $name");
|
|
|
|
if ( $hash->{INTERVAL} )
|
|
{
|
|
RemoveInternalTimer($hash);
|
|
}
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
sub
|
|
PWM_Attr(@)
|
|
{
|
|
my @a = @_;
|
|
my ($action, $name, $attrname, $attrval) = @a;
|
|
|
|
my $hash = $defs{$name};
|
|
|
|
$attrval = "" unless defined ($attrval);
|
|
|
|
if ($action eq "del")
|
|
{
|
|
if ($attrname eq "overallHeatingSwitchThresholdTemp")
|
|
{
|
|
delete ($hash->{OverallHeatingSwitchTT_tsensor} ) if defined ($hash->{OverallHeatingSwitchTT_tsensor});
|
|
delete ($hash->{OverallHeatingSwitchTT_reading} ) if defined ($hash->{OverallHeatingSwitchTT_reading});
|
|
delete ($hash->{OverallHeatingSwitchTT_t_regexp} ) if defined ($hash->{OverallHeatingSwitchTT_t_regexp});
|
|
delete ($hash->{OverallHeatingSwitchTT_maxTemp} ) if defined ($hash->{OverallHeatingSwitchTT_maxTemp});
|
|
delete ($hash->{READINGS}{OverallHeatingSwitchTT_Off} ) if defined ($hash->{READINGS}{OverallHeatingSwitchTT_Off});
|
|
|
|
} elsif ($attrname eq "maxOffTimeCalculation") {
|
|
delete ($hash->{c_maxOffTimeCalculation}) if defined ($hash->{c_maxOffTimeCalculation});
|
|
delete ($hash->{c_maxOffTimeMode}) if defined ($hash->{c_maxOffTimeMode});
|
|
|
|
} elsif ($attrname eq "maxOffTimeMode") {
|
|
$hash->{c_maxOffTimeMode} = 999;
|
|
}
|
|
|
|
if (defined $attr{$name}{$attrname}) {
|
|
delete ($attr{$name}{$attrname});
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
elsif ($action eq "set")
|
|
{
|
|
if (defined $attr{$name}{$attrname})
|
|
{
|
|
}
|
|
if ($attrname eq "overallHeatingSwitchThresholdTemp")
|
|
{
|
|
my ($obj, $temp) = split (",", $attrval, 2);
|
|
$temp = 50 unless (defined($temp));
|
|
|
|
unless ($temp =~ /^(\d[\d\.]+)$/)
|
|
{
|
|
return "$name: invalid temperature for attribute $attrname ($attrval)";
|
|
}
|
|
|
|
if (defined ($obj))
|
|
{
|
|
my ($sensor, $reading, $t_regexp) = split (":", $obj, 3);
|
|
$reading = "temperature" unless defined ($reading);
|
|
$t_regexp = '(\d[\d\.]+)', unless defined ($t_regexp);
|
|
|
|
if (defined($sensor)) # may be not defined yet
|
|
{
|
|
$hash->{OverallHeatingSwitchTT_tsensor} = $sensor;
|
|
$hash->{OverallHeatingSwitchTT_reading} = $reading;
|
|
$hash->{OverallHeatingSwitchTT_t_regexp} = $t_regexp;
|
|
$hash->{OverallHeatingSwitchTT_maxTemp} = $temp;
|
|
|
|
|
|
} else {
|
|
Log3 ($hash, 2, "invalid temperature reading in attribute overallHeatingSwitchThresholdTemp");
|
|
return "$name: invalid value for attribute $attrname ($attrval)";
|
|
}
|
|
} else {
|
|
Log3 ($hash, 2, "invalid value for attribute overallHeatingSwitchThresholdTemp");
|
|
return "$name: invalid value for attribute $attrname ($attrval)";
|
|
}
|
|
|
|
} elsif ($attrname eq "maxOffTimeCalculation") {
|
|
$hash->{c_maxOffTimeCalculation} = $attrval;
|
|
$hash->{c_maxOffTimeMode} = 999 unless defined ($hash->{c_maxOffTimeMode});
|
|
|
|
} elsif ($attrname eq "maxOffTimeMode") {
|
|
if ($attrval eq "max") {
|
|
$hash->{c_maxOffTimeMode} = 999;
|
|
} else {
|
|
$hash->{c_maxOffTimeMode} = $attrval;
|
|
}
|
|
$hash->{c_maxOffTimeCalculation} = "off" unless defined ($hash->{c_maxOffTimeCalculation});
|
|
|
|
}
|
|
}
|
|
|
|
Log3 (undef, 2, "called PWM_Attr($a[0],$a[1],$a[2],<$a[3]>)");
|
|
|
|
return undef;
|
|
}
|
|
|
|
###################################
|
|
1;
|
|
|
|
=pod
|
|
=item device
|
|
=item summary Device for room temperature control using PWM. See also 93_PWM.pm
|
|
=begin html
|
|
|
|
<a name="PWM"></a>
|
|
<h3>PWM</h3>
|
|
<ul>
|
|
|
|
<table>
|
|
<tr><td>
|
|
The PMW module implements temperature regulation for heating systems only capeable of switching on/off.<br><br>
|
|
PWM is based on Pulse Width Modulation which means valve position 70% is implemented in switching the device on for 70% and off for 30% in a given timeframe.<br>
|
|
PWM defines a calculation unit and depents on objects based on PWMR which define the rooms to be heated.<br>
|
|
<br>
|
|
</td></tr>
|
|
</table>
|
|
|
|
<b>Define</b>
|
|
<ul>
|
|
<code>define <name> PWM [<interval>] [<cycletime>] [<minonofftime>] [<maxPulse>] [<maxSwitchOnPerCycle>,<maxSwitchOffPerCycle>] [<roomStayOn>,<roomStayOff>,<stayOnThreshold>] [<overallHeatingSwitch>[,<pulseThreshold>[,<followUpTime>[,<h_regexp_on>[,<delayTimeOn>]]]]]<br></code>
|
|
<br>
|
|
eg. define fb PWM 60 900 120 1 99,99 0,0,0 pumpactor<br>
|
|
<br>
|
|
Define a calculation object with the following parameters:<br>
|
|
<ul>
|
|
<li>interval<br>
|
|
Calculate the pulses every <i>interval</i> seconds. Default is 60 seconds.<br>
|
|
</li>
|
|
|
|
<li>cycletime<br>
|
|
Timeframe to which the pulses refere to. Default is 900 seconds (=15 Minutes). "valve position" of 100% calculates to "on" for this period.<br>
|
|
</li>
|
|
|
|
<li>minonofftime<br>
|
|
Default is 120 seconds.
|
|
Floor heating systems are driven by thermomechanic elements which react very slow. on/off status changes for lower periods are ignored.<br>
|
|
</li>
|
|
|
|
<li>maxPulse<br>
|
|
Default is 1, which means that a device can be switched on for the full <i>cylcetime</i> period.<br>
|
|
For energy saving reasons it may be wanted to prevent situations were all rooms are switched on (high energy usage) and afterwards off.<br>
|
|
In this case <i>maxPulse</i> is set to 0.85 (=12:45 minutes) which forces a room with a pulse of 1 (=100%) to be switched off after 12:45 minutes to give another
|
|
room the chance to be switched on.
|
|
<br>
|
|
</li>
|
|
|
|
<li>maxSwitchOnPerCycle,maxSwitchoffPerCycle<br>
|
|
Defaults are 99 for both values. This means that 99 PWMR object can be switched on or off at the same time.<br>
|
|
To prevent energy usage peaks followend by "no energy consumption" situations set both values to "1".<br>
|
|
This means after the room the the least energy required is switched off the next will be switched off.<br>
|
|
Rooms are switched on or off one after the other (in <interval> cycles) and not all at one time.<br>
|
|
Waiting times are honored by a addon to the pulse.<br>
|
|
<br>
|
|
</li>
|
|
|
|
<li>roomStayOn,roomStayOff,stayOnThreshold<br>
|
|
Defauts: <br>
|
|
<i>roomStayOn</i> = 0 ... all rooms can be switched off at the same time.<br>
|
|
<i>roomStayOff</i> = 0 ... all rooms can be switched on at the same time.<br>
|
|
<i>stayOnThreshold</i> = 0 ... no impact.<br>
|
|
For energy saving reasons the following may be set: "4,1,0.25". This means:<br>
|
|
The room with the least pulse will be kept off (<i>roomsStayOff</i>=1)<br>
|
|
If the average pulse for the (<i>roomsStayOn</i>=4) rooms with the most heating required is greater than (<i>stayOnThreshold</i>=0.25) then <i>maxRoomStayOn</i> will be kept in state "on", even if the time for the current pulse is reached.
|
|
If the threshold is not reached (not so much heating required) then all rooms can be switched off at the same time.<br>
|
|
<br>
|
|
</li>
|
|
|
|
<li><overallHeatingSwitch>[,<pulseThreshold>[,<followUpTime>[,<regexp_on>[,<delayTimeOn>]]]]<br>
|
|
Universal switch to controll eg. pumps or the heater itself. It will be set to "off" if no heating is required and otherwise "on".<br>
|
|
<i>pulseThreshold</i> defines a threshold which is applied to reading <i>pulseMax</i>, <i>pulseSum</i>, <i>pulseAvg</i>, <i>pulseAvg2</i> or <i>pulseAvg3</i> of the PWM object to decide if heating is required. If (calculated pulse > threshold) then actor is set to "on", otherwise "off".<br>
|
|
If <i>pulseThreshold</i> is set to 0 (or is not defined) then the decision is based on <i>roomsOn</i>. If (roomsOn > 0) then actor is set to "on", otherwise "off".<br>
|
|
<i>followUpTime</i> defines a number of seconds which is used to delay the status change from "on" to "off". This can be used to prevent a toggling switch.<br>
|
|
<i>regexp_on</i> defines a regular expression to be applied to the state of the actor. Default is "on". If state matches the regular expression it is handled as "on", otherwise "off".<br>
|
|
<i>delayTimeOn</i> defines a number of seconds which is used to delay the status change from "off" to "on". This can be used to give the valves time to open before switching..<br>
|
|
The pulse used for comparision is defined by attribute <i>overallHeatingSwitchRef</i>. Default is <i>maxPulse</i>.<br>
|
|
<br>
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
<br>
|
|
Example:<br>
|
|
<br>
|
|
<code>define fh PWM</code>
|
|
<br>which is equal to<br>
|
|
<code>define fh PWM 60 900 120 1 99,99 0,0,0</code>
|
|
<br>Energy saving definition might be<br>
|
|
<code>define fh PWM 60 900 120 0.85 1,1 4,1,0.25</code>
|
|
<br><br>
|
|
|
|
|
|
</ul>
|
|
<br>
|
|
|
|
<b>Set </b>
|
|
<ul>
|
|
<li>cycletime<br>
|
|
Temporary change of parameter <i>cycletime</i>.
|
|
</li><br>
|
|
|
|
<li>interval<br>
|
|
Temporary change of parameter <i>interval</i>.
|
|
</li><br>
|
|
|
|
<li>recalc<br>
|
|
Cause recalculation that normally appeary every <i>interval</i> seconds.
|
|
</li><br>
|
|
|
|
<li>maxOffTimeCalculation<br>
|
|
Defines whether parameter maxOffTime for rooms (PWMR objects) is evaluated or not. Possible Values are "on" or "off". Sets attribute maxOffTimeCalculation and internal values c_maxOffTimeCalculation and c_maxOffTimeMode.<br>
|
|
</li><br>
|
|
|
|
</ul>
|
|
|
|
<b>Get</b>
|
|
<ul>
|
|
<li>status<br>
|
|
Retrieve content of variable <i>STATE</i>.
|
|
</li><br>
|
|
<li>timers<br>
|
|
Retrieve values from the readings "timer?_??" from the attached rooms..<br>
|
|
The readings define start and end times for different room temperatures.<br>
|
|
This funktion will retrieve the first start and the last end time. <i>STATE</i>.
|
|
</li><br>
|
|
|
|
</ul>
|
|
<br>
|
|
|
|
<b>Attributes</b>
|
|
<ul>
|
|
<li>disable<br>
|
|
Set to 1 will disable all calculations and STATE will be set to "disabled".<br>
|
|
</li><br>
|
|
<li>valveProtectIdlePeriod<br>
|
|
Protect Valve by switching on actor for 300 seconds.<br>
|
|
After <i>valveProtectIdlePeriod</i> number of days without switching the valve the actor is set to "on" for 300 seconds.
|
|
overallHeatingSwitch is not affected.
|
|
</li><br>
|
|
<li>overallHeatingSwitchRef<br>
|
|
Defines which reading is used for threshold comparision for <i>OverallHeatingSwitch</i> calculation. Possible values are:<br>
|
|
<i>pulseMax</i>,
|
|
<i>pulseSum</i>,
|
|
<i>pulseAvg</i>,
|
|
<i>pulseAvg2</i>,
|
|
<i>pulseAvg3</i>,
|
|
<i>avgPulseRoomsOn</i><br>
|
|
pulseAvg is an average pulse of all rooms which should be switched to "on".<br>
|
|
pulseAvg2 and pulseAvg3 refer to the 2 or 3 romms with highest pulses.
|
|
</li><br>
|
|
<li>overallHeatingSwitchThresholdTemp<br>
|
|
Defines a reading for a temperature and a maximum value that prevents the overallHeatingSwitch from switching to "on".<br>
|
|
Value has the following format: tsensor[:reading[:t_regexp]],maxValue.<br>
|
|
<i>tsensor</i> defines the temperature sensor for the actual temperature.<br>
|
|
<i>reading</i> defines the reading of the temperature sensor. Default is "temperature"<br>
|
|
<i>t_regexp</i> defines a regular expression to be applied to the reading. Default is '(\d[\d\.]+)'.<br>
|
|
if <i>maxValue</i> is reached as a temperature from tsensor then overallHeatingSwitch will not be switch to "on".<br>
|
|
Example: tsensor,44 or tsensor:temperature,44 or tsensor:temperature:(\d+).*,44<br>
|
|
The reading OverallHeatingSwitchTT_Off will be set to 1 if temperature from tsensor prevents <i>overallHeatingSwitch</i> from switching to "on".<br>
|
|
Please be aware that temperatures raising to high will seriously harm your heating system and this parameter should not be used as the only protection feature.<br>
|
|
Using this parameter is on your own risk. Please test your settings very carefully.<br>
|
|
</li><br>
|
|
|
|
<li>maxOffTimeCalculation<br>
|
|
Defines whether parameter maxOffTime for rooms (PWMR objects) is evaluated or not. Possible Values are "on" or "off". Sets internal values c_maxOffTimeCalculation and c_maxOffTimeMode.<br>
|
|
</li><br>
|
|
|
|
<li>maxOffTimeMode<br>
|
|
Defines the strategy for maxOffTime handling if maxOffTimeCalculation is set. Sets internal value c_maxOffTimeMode.<br>
|
|
<i>max</i>: try to activate as many rooms as possible. If one room switches to on and a second waited half of his maxOffTime, the second will be switched on as well.<br>
|
|
<i>1</i>, <i>2</i>, <i>3</i>: defines how many rooms can be switched on at the same time.<br>
|
|
</li><br>
|
|
|
|
</ul>
|
|
<br>
|
|
</ul>
|
|
|
|
=end html
|
|
=cut
|