2015-10-07 10:54:09 +00:00
#
#
# 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
2015-11-08 09:12:39 +00:00
# 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
2015-11-19 09:47:05 +00:00
# 19.10.15 GA add overall heating switch
2015-11-08 09:12:39 +00:00
2015-10-07 10:54:09 +00:00
##############################################
# $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" ;
2015-11-08 09:12:39 +00:00
$ hash - > { AttrList } = "event-on-change-reading" ;
2015-10-07 10:54:09 +00:00
}
###################################
sub
PWM_Calculate ( $ )
{
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my % RoomsToSwitchOn = ( ) ;
my % RoomsToSwitchOff = ( ) ;
my % RoomsToStayOn = ( ) ;
my % RoomsToStayOff = ( ) ;
my % RoomsPulses = ( ) ;
my $ roomsActive = 0 ;
my $ newpulseSum = 0 ;
2015-11-08 09:12:39 +00:00
my $ newpulseMax = 0 ;
2015-10-07 10:54:09 +00:00
my $ wkey = "" ;
if ( $ hash - > { INTERVAL } > 0 ) {
InternalTimer ( gettimeofday ( ) + $ hash - > { INTERVAL } , "PWM_Calculate" , $ hash , 0 ) ;
}
Log3 ( $ hash , 3 , "PWM_Calculate $name" ) ;
2015-11-08 09:12:39 +00:00
readingsBeginUpdate ( $ hash ) ;
2015-10-07 10:54:09 +00:00
#$hash->{STATE} = "lastrun: ".TimeNow();
#$hash->{STATE} = "calculating";
2015-11-08 09:12:39 +00:00
readingsBulkUpdate ( $ hash , "lastrun" , "calculating" ) ;
2015-10-07 10:54:09 +00:00
$ hash - > { STATE } = "lastrun: " . $ hash - > { READINGS } { lastrun } { TIME } ;
# 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 } ) { # referencing to this fb
Log3 ( $ hash , 4 , "PWM_Calculate calc $name, room $d" ) ;
########################
# calculate room
# $newstate is "" if state is unchanged
# $newstate is "on" or "off" if state changes
my ( $ newstate , $ newpulse , $ cycletime , $ oldstate ) = PWM_CalcRoom ( $ hash , $ defs { $ d } ) ;
$ defs { $ d } - > { READINGS } { oldpulse } { TIME } = TimeNow ( ) ;
$ defs { $ d } - > { READINGS } { oldpulse } { VAL } = $ newpulse ;
my $ onoff = $ newpulse * $ cycletime ;
if ( $ newstate eq "off" ) {
$ onoff = ( 1 - $ newpulse ) * $ cycletime
}
$ wkey = $ name . "_" . $ d ;
if ( defined ( $ roomsWaitOffset { $ wkey } ) ) {
$ newpulse += $ roomsWaitOffset { $ wkey } ;
} else {
$ roomsWaitOffset { $ wkey } = 0 ;
}
$ roomsActive + + ;
$ RoomsPulses { $ d } = $ newpulse ;
$ newpulseSum += $ newpulse ;
2015-11-08 09:12:39 +00:00
$ newpulseMax = max ( $ newpulseMax , $ newpulse ) ;
2015-10-07 10:54:09 +00:00
# $newstate ne "" -> state changed "on" -> "off" or "off" -> "on"
if ( ( int ( $ hash - > { MINONOFFTIME } ) > 0 ) &&
( $ newstate ne "" ) &&
( $ 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 ;
}
}
}
}
}
}
}
# 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 ) ;
# treat less than 8 active rooms as 8 (more can get active)
# 16.01.2015
#my $maxRoomsOn = $roomsActive * 0.7;
# 23.09.2015
#my $maxRoomsOn = $roomsActive * 0.6; # 11 rooms -> max 6 active
#$maxRoomsOn = (8 * 0.7) if ($roomsActive < 8);
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 } ;
2015-11-08 09:12:39 +00:00
my $ minRoomsOnList = "" ;
2015-10-07 10:54:09 +00:00
if ( $ minRoomsOn > 0 ) {
my $ roomsCounted = 0 ;
my $ pulseSum = 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}" ) ;
2015-11-08 09:12:39 +00:00
$ minRoomsOnList . = "$room," ;
2015-10-07 10:54:09 +00:00
$ pulseSum += $ RoomsPulses { $ room } ;
$ roomsCounted + + ;
}
2015-11-08 09:12:39 +00:00
$ minRoomsOnList =~ s/,$// ;
2015-10-07 10:54:09 +00:00
2015-11-19 09:47:05 +00:00
#if ($roomsActive == 0 or $hash->{NoRoomsToStayOnThreshold} == 0 or $newpulseSum/$roomsActive < $hash->{NoRoomsToStayOnThreshold})
2015-10-07 10:54:09 +00:00
if ( $ roomsActive == 0 or $ hash - > { NoRoomsToStayOnThreshold } == 0 or $ pulseSum / $ roomsCounted < $ hash - > { NoRoomsToStayOnThreshold } ) {
$ minRoomsOn = 0 ;
}
#Log3 ($hash, 3, "PWM_Calculate: newpulseSum $newpulseSum avg ".$newpulseSum/$roomsActive." minRoomsOn(".$minRoomsOn.")") if ($roomsActive > 0);
Log3 ( $ hash , 3 , "PWM_Calculate: pulseSum $pulseSum avg " . $ pulseSum / $ roomsCounted . " 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
#
2015-11-08 09:12:39 +00:00
my $ cntRoomsOn = 0 ;
my $ cntRoomsOff = 0 ;
my $ pulseRoomsOn = 0 ;
my $ pulseRoomsOff = 0 ;
2015-10-07 10:54:09 +00:00
foreach my $ roomStay ( sort keys % RoomsToStayOff ) {
PWMR_SetRoom ( $ defs { $ roomStay } , "" ) ;
2015-11-08 09:12:39 +00:00
$ cntRoomsOff + + ;
$ pulseRoomsOff += $ RoomsPulses { $ roomStay } ;
2015-10-07 10:54:09 +00:00
}
foreach my $ roomStay ( sort keys % RoomsToStayOn ) {
PWMR_SetRoom ( $ defs { $ roomStay } , "" ) ;
2015-11-08 09:12:39 +00:00
$ cntRoomsOn + + ;
$ pulseRoomsOn += $ RoomsPulses { $ roomStay } ;
2015-10-07 10:54:09 +00:00
}
foreach my $ roomOff ( sort keys % RoomsToSwitchOff ) {
PWMR_SetRoom ( $ defs { $ roomOff } , "off" ) ;
2015-11-08 09:12:39 +00:00
$ cntRoomsOff + + ;
$ pulseRoomsOff += $ RoomsPulses { $ roomOff } ;
2015-10-07 10:54:09 +00:00
}
foreach my $ roomOn ( sort keys % RoomsToSwitchOn ) {
my $ wkey = $ name . "-" . $ roomOn ;
$ roomsWaitOffset { $ wkey } = 0 ;
PWMR_SetRoom ( $ defs { $ roomOn } , "on" ) ;
2015-11-08 09:12:39 +00:00
$ cntRoomsOn + + ;
$ pulseRoomsOn += $ RoomsPulses { $ roomOn } ;
2015-10-07 10:54:09 +00:00
}
2015-11-08 09:12:39 +00:00
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 ) ;
if ( $ hash - > { NoRoomsToStayOn } > 0 ) {
readingsBulkUpdate ( $ hash , "roomsToStayOn" , $ minRoomsOn ) ;
readingsBulkUpdate ( $ hash , "roomsToStayOnList" , $ minRoomsOnList ) ;
}
2015-11-19 09:47:05 +00:00
if ( defined ( $ hash - > { OverallHeatingSwitch } ) ) {
if ( $ hash - > { OverallHeatingSwitch } ne "" ) {
my $ newstate = ( $ cntRoomsOn > 0 ) ? "on" : "off" ;
my $ actor = $ hash - > { OverallHeatingSwitch } ;
my $ actstate = ( $ defs { $ actor } { STATE } =~ $ hash - > { OverallHeatingSwitch_regexp_on } ) ? "on" : "off" ;
if ( $ newstate ne $ actstate ) {
my $ ret = fhem sprintf ( "set %s %s" , $ hash - > { OverallHeatingSwitch } , $ newstate ) ;
if ( ! defined ( $ ret ) ) { # sucessfull
Log3 ( $ name , 4 , "PWMR_SetRoom: $name: set $actor $newstate" ) ;
readingsBulkUpdate ( $ hash , "OverallHeatingSwitch" , $ newstate , 1 ) ;
# push @{$room->{CHANGED}}, "actor $newstate";
# DoTrigger($name, undef);
} else {
Log3 ( $ name , 4 , "PWMR_SetRoom $name: set $actor $newstate failed ($ret)" ) ;
}
}
}
}
2015-11-08 09:12:39 +00:00
readingsEndUpdate ( $ hash , 1 ) ;
2015-10-07 10:54:09 +00:00
# if(!$hash->{LOCAL}) {
# DoTrigger($name, undef) if($init_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 ) =
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"
{
# 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"
{
# 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 ) ;
}
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 ) = @ _ ;
return "argument is missing" if ( int ( @ a ) != 2 ) ;
my $ msg ;
if ( $ a [ 1 ] ne "status" ) {
return "Unknown argument $a[1], choose one of status" ;
}
#return $hash->{READINGS}{STATE}{VAL};
return $ hash - > { STATE } ;
}
#############################
sub
PWM_Set ( $@ )
{
my ( $ hash , @ a ) = @ _ ;
my $ u = "Unknown argument $a[1], choose one of recalc interval cycletime" ;
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};
} else {
return $ u ;
}
return undef ;
}
#############################
sub
PWM_Define ( $$ )
{
my ( $ hash , $ def ) = @ _ ;
my @ a = split ( "[ \t][ \t]*" , $ def ) ;
my $ name = $ hash - > { NAME } ;
2015-11-19 09:47:05 +00:00
return "syntax: define <name> PWM [<interval>] [<cycletime>] [<minonofftime>] [<maxPulse>] [<maxSwitchOnPerCycle>,<maxSwitchOffPerCycle>] [<roomStayOn>,<roomStayOff>,<stayOnThreshold>]" .
" [<overallHeatingSwitch>[;<h_regexp_on>]]"
if ( int ( @ a ) < 2 || int ( @ a ) > 9 ) ;
2015-10-07 10:54:09 +00:00
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 ) ? min ( $ a [ 5 ] , 1.00 ) : 0.85 ) ;
$ hash - > { INTERVAL } = $ interval ;
$ hash - > { CYCLETIME } = $ cycletime ;
$ hash - > { MINONOFFTIME } = $ minonofftime ;
$ hash - > { MaxPulse } = $ maxPulse ;
$ hash - > { STATE } = "defined" ;
##########
# [<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
} 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"
}
2015-11-19 09:47:05 +00:00
##########
# [<overallHeatingSwitch>]
if ( int ( @ a ) > 8 ) {
my ( $ hactor , $ h_regexp_on ) = split ( ":" , $ a [ 8 ] , 2 ) ;
$ h_regexp_on = "on" unless defined ( $ h_regexp_on ) ;
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_regexp_on } = $ h_regexp_on ;
} else {
$ hash - > { OverallHeatingSwitch } = "" ;
$ hash - > { OverallHeatingSwitch_regexp_on } = "" ;
}
2015-10-07 10:54:09 +00:00
AssignIoPort ( $ hash ) ;
if ( $ hash - > { INTERVAL } > 0 ) {
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 ;
}
1 ;
= pod
= 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>
2015-11-19 09:47:05 +00:00
<code> define & lt ; name & gt ; PWM [ & lt ; interval & gt ; ] [ & lt ; cycletime & gt ; ] [ & lt ; minonofftime & gt ; ] [ & lt ; maxPulse & gt ; ] [ & lt ; maxSwitchOnPerCycle & gt ; , & lt ; maxSwitchOffPerCycle & gt ; ] [ & lt ; roomStayOn & gt ; , & lt ; roomStayOff & gt ; , & lt ; stayOnThreshold & gt ; ] [ & lt ; overallHeatingSwitch & gt ; ] <br> </code>
<br>
eg . define fb PWM 60 900 120 1 99 , 99 0 , 0 , 0 pumpactor <br>
2015-10-07 10:54:09 +00:00
<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>
2015-11-19 09:47:05 +00:00
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 .
2015-10-07 10:54:09 +00:00
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>
2015-11-19 09:47:05 +00:00
<li> overallHeatingSwitch [ : & lt ; overallHeatingSwitch_regexp_on & gt ; ] <br>
Universal switch to controll eg . pumps or the heater itself . It will be set to "off" if no heating is required ( all rooms off ) and otherwise "on" . <br>
<i> overallHeatingSwitch_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>
<br>
</li>
2015-10-07 10:54:09 +00:00
</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>
</ul>
<b> Get </b>
<ul>
<li> status <br>
Retrieve content of variable <i> STATE </i> .
</li> <br>
</ul>
<br>
<b> Attributes </b>
<ul>
</ul>
<br>
</ul>
= end html
= cut