From 9d4785c68c6d89a4bfefa9d7ddf0ff65f80093e8 Mon Sep 17 00:00:00 2001 From: H Plato Date: Fri, 14 Oct 2016 12:30:03 -0600 Subject: [PATCH] Wayne's Scheduler component --- lib/Generic_Item.pm | 179 +++++++++++++- lib/SCHEDULE.pm | 565 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 617 insertions(+), 127 deletions(-) diff --git a/lib/Generic_Item.pm b/lib/Generic_Item.pm index df8c590e6..9ba7d753f 100644 --- a/lib/Generic_Item.pm +++ b/lib/Generic_Item.pm @@ -1,6 +1,5 @@ use strict; -#hp 1133 -# data/object_logs//YYYY/MM.log + package Generic_Item_Hash; require Tie::Hash; @@ -136,6 +135,11 @@ sub new { $self->{logger_enable} = $main::config_parms{object_logger_enable} if (defined $main::config_parms{object_logger_enable}); $self->{logger_mintime} = 1; $self->{logger_updatetime} = 0; + $self->restore_data('active_state', 'schedule_count'); + for my $index (1..20) { + $self->restore_data('schedule_'.$index, 'schedule_label_'.$index, 'schedule_once_'.$index); + } + $self->_initialize_schedule; return $self; } @@ -1816,6 +1820,177 @@ sub android_xml_tag { return $xml_objects; } + +sub _initialize_schedule { + my ($self) = @_; + $self->{'initialize_timer'} = ::Timer::new(); + $self->{'initialize_timer'}->set(5, sub { + if (defined($self->{schedule_object}) && ($self->{schedule_object})) { return } + if ($self->{'schedule_count'} > 1) { + ::print_log("[SCHEDULE] Initialize schedule for ". $self->get_object_name." Schedule count is ".$self->{'schedule_count'}); + $self->{'check_date_handler'} = sub { Generic_Item::check_date($self); }; + ::MainLoop_post_add_hook( $self->{'check_date_handler'}); + } +}); +} + + + +sub _set_schedule_active_state { + my ($self, $state) = @_; + $self->{'active_state'} = $state if defined($state); +} + + + +sub set_schedule { + my ($self,$index,$entry,$label) = @_; + unless ( defined($self->{'check_date_handler'}) ) { + $self->{'check_date_handler'} = sub { Generic_Item::check_date($self); }; + #::MainLoop_post_add_hook( $self->{'check_date_handler'}, 'persistent'); + ::MainLoop_post_add_hook( $self->{'check_date_handler'}); + } + + if ($index > $self->{'schedule_count'}) { $self->{'schedule_count'} = $index; } + $self->{'schedule_'.$index} = $entry if (defined($entry)); + $self->{'schedule_label_'.$index} = $label if (defined($label)); + if (defined($self->{'schedule_'.$index})) { # the UI deletes all entries and adds them back which sets this flag to 2. + $self->{'schedule_once_'.$index} = 1 if ($self->{'schedule_once_'.$index} eq 2); # We only want real deleted entries set to 2, so set to 1. + } + unless ($entry) { + undef $self->{'schedule_label_'.$index}; + undef $self->{'schedule_'.$index}; + $self->{'schedule_once_'.$index} = 2 if ($self->{'schedule_once_'.$index}); + } + $self->{set_time} = $::Time; +} + +sub set_schedule_once { + my ($self,$index,$entry,$label) = @_; + unless ($self->{'schedule_once_'.$index} eq 1) { + if ((defined($self->{'set_timer_'.$index})) && ($self->{'set_timer_'.$index}->expired)) { + $self->{'schedule_once_'.$index} = 1; + $self->set_schedule($index,$entry,$label); + } else { + $self->{'set_timer_'.$index} = ::Timer::new(); + $self->{'set_timer_'.$index}->set(10, sub { + $self->set_schedule_once($index,$entry,$label); + }); + } + } +} + + +sub delete_schedule { + my ($self,$index) = @_; + $self->set_schedule($index); +} + + +sub reset_schedule { + my ($self) = @_; + my $count = $self->{'schedule_count'}; + for my $index (1..$count) { + $self->set_schedule($index); + } + $self->{'schedule_count'} = 0; + $self->{set_time} = $::Time; +} + + +sub get_schedule { + my ($self) = @_; + if ( ( defined($self->{'initialize_timer'}) ) && ( $self->{'initialize_timer'}->active ) ) { return } + my @schedule; + my $count = $self->{'schedule_count'}; + my @states = &get_states($self); + + + $schedule[0][0] = 0; #Index + $schedule[0][1] = '0 0 5 1 1'; #schedule + $schedule[0][2] = 0; #Label + $schedule[0][3] = \@states; + my $nullcount = 0; + for my $index (1..$count) { + unless(defined($self->{'schedule_'.$index})) { + if ($self->{'schedule_once_'.$index}) { + $self->{'schedule_once_'.$index} = 2; + $self->{'schedule_label_'.$index} = undef; + } else { + $nullcount++; + $self->{'schedule_label_'.$index} = undef; + $self->{'schedule_once_'.$index} = undef; + next; + } + } + + if (defined($self->{'schedule_'.$index})) { # the UI deletes all entries and adds them back which sets this flag to 2. + $self->{'schedule_once_'.$index} = 1 if ($self->{'schedule_once_'.$index} eq 2); # We only want real deleted entries set to 2, so set to 1. + } + + if ((defined($self->{'schedule_'.$index})) || ($self->{'schedule_once_'.$index} eq 2)) { + $self->{'schedule_'.($index-$nullcount)} = $self->{'schedule_'.$index}; + $self->{'schedule_label_'.($index-$nullcount)} = $self->{'schedule_label_'.$index}; + $self->{'schedule_once_'.($index-$nullcount)} = $self->{'schedule_once_'.$index}; + $schedule[($index-$nullcount)][0] = ($index-$nullcount); + if ($self->{'schedule_once_'.$index} eq 2) { $schedule[($index-$nullcount)][1] = undef } + else { $schedule[($index-$nullcount)][1] = $self->{'schedule_'.$index} } + + if (defined($self->{'schedule_label_'.$index}) ) { $schedule[($index-$nullcount)][2] = $self->{'schedule_label_'.$index} } + else { $schedule[($index-$nullcount)][2] = ($index-$nullcount) } + + unless (($index-$nullcount) eq $index) { + $self->{'schedule_'.$index} = undef; + $self->{'schedule_label_'.$index} = undef; + $self->{'schedule_once_'.$index} = undef; + } + } + } + $self->{'schedule_count'} = scalar @schedule; + return \@schedule; +} + +sub check_date { + my ($self) = @_; + if ($::New_Minute) { + + unless ($self->{'schedule_count'} > 1) { + if ( $self->{'schedule_delete_count'} eq 2 ) { + ::print_log("[SCHEDULE] Dropping schedule for ". $self->get_object_name ." check count ". $self->{'schedule_delete_count'}); + ::MainLoop_post_drop_hook( $self->{'check_date_handler'} ); + undef $self->{'check_date_handler'}; + undef $self->{'schedule_delete_count'}; + } + $self->{'schedule_delete_count'}++; + } + + + for my $index (1..$self->{'schedule_count'}) { + if (defined($self->{'schedule_'.$index})) { + ::print_log("[SCHEDULE] Checking time for ". $self->get_object_name. " schedule is " . $self->{'schedule_'.$index} ." time_cron return ". &main::time_cron($self->{'schedule_'.$index})); + if (&main::time_cron($self->{'schedule_'.$index})) { $self->set_action($self->{'schedule_label_'.$index}) } + } + } + } +} + + +sub set_action { + my ($self,$state) = @_; + return if &main::check_for_tied_filters( $self, $state ); + $self->_set_schedule_active_state($state); + my $sub = 'set'; + $sub = $self->{sub} if defined($self->{sub}); + $self->$sub($state,'schedule',1); +} + + +sub set_sub { + my ($self, $sub) = @_; + $self->{sub} = $sub; +} + + =back =head2 INI PARAMETERS diff --git a/lib/SCHEDULE.pm b/lib/SCHEDULE.pm index e0687e3d9..1cbaa7322 100644 --- a/lib/SCHEDULE.pm +++ b/lib/SCHEDULE.pm @@ -1,3 +1,112 @@ +=head1 B + +=head2 DESCRIPTION + +Module for scheduling state changes for objects in misterhouse via the web UI. +This module is useful for scheduling for objects that do not inherit scheduleing +from the Generic_Item or do not have the states that you need by default. + +It is also very useful for thermostat scheduleing as the child object SCHEDULE_Temp +is built exactly for that and allows the scheduled temp changes to be set in real time +from the MH web UI. + +=head2 CONFIGURATION + +At minimum, you must define the SCHEDULE and one of the following objects +L or L. + +The SCHEDULE_Generic objects are for scheduling state changes for any object in misterhouse. +You can make custom states to be listed in the MH web UI. +See L + +The SCHEDULE_Temp objects are for scheduling thermostat temp changes throughout the day. Its +linked to sets of object which hold the schedule temps and can be changed in the MH web UI +in real time. It also has several overrides such as occupancy checking and outdoor temp. +See L + +=head2 Interface Configuration + +This object has no mh.private.ini configuration. + +=head2 Defining the Interface Object + +The object must be defined in the user code. + +In user code: + +$Night = new SCHEDULE('THERMO1'); + +Wherein the format for the definition is: + + $Night = new SCHEDULE(INSTANCE); + + +=head2 NOTES + +The instance is only needed when multiple schedule object are used together +for a thermostat schedule. + + +An example user code for a SCHEDULE_Generic: + + #noloop=start + use SCHEDULE; + $SCHEDULE_LIGHT1 = new SCHEDULE(); + $SCHEDULE_SG_LIGHT1 = new SCHEDULE_Generic($SCHEDULE_LIGHT1,$light1,'on','off'); #The states (on and off in this example) are optional. + $SCHEDULE2->set_schedule_default(1,'00 1 * * 1-5','off'); #Optionally sets 1st the default schedule. + $SCHEDULE2->set_schedule_default(2,'00 5 * * 1-5','on'); #Optionally sets 2nd the default schedule. + #noloop=stop + + +An example user code for a SCHEDULE_Temp: + + #noloop=start + $Night = new SCHEDULE('THERMO1'); + $Normal = new SCHEDULE('THERMO1'); + $Conserve = new SCHEDULE('THERMO1'); + $NightWinter = new SCHEDULE('THERMO1'); + + # $NormalCool/$NormalHeat have an UP and DOWN states to change the temp settings in the web interface and they are linked to the Normal schedule object above. + # $thermostat is the thermostat object that controls my Insteon thermostat, cool_setpoint and heat_setpoint are the subs that are used to set the thermostat setpoint. + $NormalCool = new SCHEDULE_Temp($Normal,'cool',$thermostat,'cool_setpoint'); + $NormalHeat = new SCHEDULE_Temp($Normal,'heat',$thermostat,'heat_setpoint'); + + $NightCool = new SCHEDULE_Temp($Night,'cool',$thermostat,'cool_setpoint'); + $NightHeat = new SCHEDULE_Temp($Night,'heat',$thermostat,'heat_setpoint'); + + $NightWCool = new SCHEDULE_Temp($NightWinter,'cool',$thermostat,'cool_setpoint'); + $NightWHeat = new SCHEDULE_Temp($NightWinter,'heat',$thermostat,'heat_setpoint'); + + $ConserveCool = new SCHEDULE_Temp($Conserve,'cool',$thermostat,'cool_setpoint'); + $ConserveHeat = new SCHEDULE_Temp($Conserve,'heat',$thermostat,'heat_setpoint'); + + + # Occupancy Override (optional, I track occupancy by checking to see if my cell is connected to wifi) + # If the $mode_occupied state is home use the $Normal object temps (from $NormalCool/$NormalHeat), + # if the $mode_occupied state changes to work change the temp settings to the $Conserve object temps (from $ConserveCool/$ConserveHeat) + $Normal->set_occpuancy('home','work',$Conserve); + $Night->set_occpuancy('home','work',$Normal); + $Conserve->set_occpuancy('work','home',$Normal); + + #Forcasted temps equal or below this (50) cause the $NightWinter temps to be used. Pulled from $Weather{Forecast Tonight}. + $Night->set_winter($NightWinter,'50'); + + # Vacation mode. During any active schedule, override the linked temps with the $Conserve temps. + $Normal->set_vacation($Conserve,'vacation'); + $Night->set_vacation($Conserve,'vacation'); + $Conserve->set_vacation($Conserve,'vacation'); + #noloop=stop + +=head2 INHERITS + +L + +=head2 METHODS + +=over + +=cut + package SCHEDULE; @SCHEDULE::ISA = ('Generic_Item'); @@ -11,9 +120,8 @@ sub new @{$$self{states}} = ('ON','OFF'); $self->restore_data('active_object', 'active_action', 'schedule_count'); #for my $index (1..$self->{'schedule_count'}) { - for my $index (1..10) { - $self->restore_data('schedule_'.$index); - $self->restore_data('schedule_label_'.$index); + for my $index (1..20) { + $self->restore_data('schedule_'.$index, 'schedule_label_'.$index, 'schedule_once_'.$index); } return $self; } @@ -25,41 +133,54 @@ sub set { $self->SUPER::set($p_state,$p_setby,1); } -#sub set_schedule { -# my ($self, $type, $p_state) = @_; -# $$self{'schedule'}{'type'} = lc($type); -# my @cals; -# #$self{'type'} = 'calendar'; - #$self{'schedule'}{'7'}{'28'}{'20'}{'41'}{'action'} = 'start'; - #$self{'schedule'}{'7'}{'28'}{'20'}{'42'}{'action'} = 'stop'; -# if ($p_state =~ /-/) { @cals = split /-/, $p_state } -# else { @cals = ($p_state) } -# foreach my $values (@cals) { -# my @calvals = split /,/, $values; -# $$self{'schedule'}{$calvals[1]}{$calvals[2]}{$calvals[3]}{$calvals[4]}{'action'} = lc($calvals[0]) if ($type eq 'calendar'); -# $$self{'schedule'}{lc($calvals[1])}{$calvals[2]}{$calvals[3]}{'action'} = lc($calvals[0]) if ($type eq 'daily'); -# $$self{'schedule'}{lc($calvals[1])}{$calvals[2]}{$calvals[3]}{'action'} = lc($calvals[0]) if ($type eq 'wdwe'); -# $$self{'schedule'}{$calvals[1]}{$calvals[2]}{'action'} = lc($calvals[0]) if ($type eq 'time'); -# } -# } sub set_schedule { my ($self,$index,$entry,$label) = @_; - ::print_log("[SCHEDULE] - set_schedule - Index " . $index . " Schedule: ". $entry ." Label ". $label); + ::print_log("[SCHEDULE] - DEBUG - set_schedule - Index " . $index . " Schedule: ". $entry ." Label ". $label) if $main::Debug{'schedule'}; if ($index > $self->{'schedule_count'}) { $self->{'schedule_count'} = $index } $self->{'schedule_'.$index} = $entry if (defined($entry)); $self->{'schedule_label_'.$index} = $label if (defined($label)); + if (defined($self->{'schedule_'.$index})) { # the UI deletes all entries and adds them back which sets this flag to 2. + $self->{'schedule_once_'.$index} = 1 if ($self->{'schedule_once_'.$index} eq 2); # We only want real deleted entries set to 2, so set to 1. + } unless ($entry) { undef $self->{'schedule_label_'.$index}; undef $self->{'schedule_'.$index}; + $self->{'schedule_once_'.$index} = 2 if ($self->{'schedule_once_'.$index}); + } + $self->{set_time} = $::Time; +} + + +sub set_schedule_default { + my ($self,$index,$entry,$label) = @_; + unless ($self->{'schedule_once_'.$index} eq 1) { + if ((defined($self->{'set_timer_'.$index})) && ($self->{'set_timer_'.$index}->expired)) { + $self->{'schedule_once_'.$index} = 1; + $self->set_schedule($index,$entry,$label); + } else { + $self->{'set_timer_'.$index} = ::Timer::new(); + $self->{'set_timer_'.$index}->set(10, sub { + $self->set_schedule_once($index,$entry,$label); + }); + } } - $self->{set_time} = $main::Time; } sub delete_schedule { my ($self,$index) = @_; $self->set_schedule($index); +} + +sub reset_schedule { + my ($self) = @_; + my $count = $self->{'schedule_count'}; + for my $index (1..$count) { + $self->set_schedule($index); + } + $self->{'schedule_count'} = 0; + $self->{set_time} = $::Time; } @@ -73,7 +194,6 @@ sub get_schedule{ $count = $object->{state_count}; if ($count eq 0) { @states = $object->{child}->get_states; - #unshift @states, 0; } else { for my $index (1..$count) { $states[$index-1] = $object->{$index}; @@ -82,28 +202,55 @@ sub get_schedule{ } else { $states[0]=undef } - #if ((defined($object->{state_count})) && ($object->{state_count} > $self->{'schedule_count'})) { $count = $object->{state_count} } - #else { $count = $self->{'schedule_count'} } $count = $self->{'schedule_count'}; $schedule[0][0] = 0; #Index $schedule[0][1] = '0 0 5 1 1'; #schedule $schedule[0][2] = 0; #Label $schedule[0][3] = \@states; - for my $index (1..$count) { - $schedule[$index][0] = $index; - $schedule[$index][1] = $self->{'schedule_'.$index}; - if (defined($self->{'schedule_label_'.$index}) ) { $schedule[$index][2] = $self->{'schedule_label_'.$index} } - #elsif (defined($object->{$index})) { $schedule[$index][2] = $object->{$index}; } - else { $schedule[$index][2] = $index; } - #$schedule[$index][3] = \@states; - } + my $nullcount = 0; + for my $index (1..$count) { + unless(defined($self->{'schedule_'.$index})) { + if ($self->{'schedule_once_'.$index}) { + $self->{'schedule_once_'.$index} = 2; + $self->{'schedule_label_'.$index} = undef; + } else { + $nullcount++; + $self->{'schedule_label_'.$index} = undef; + $self->{'schedule_once_'.$index} = undef; + next; + } + } + + if (defined($self->{'schedule_'.$index})) { # the UI deletes all entries and adds them back which sets this flag to 2. + $self->{'schedule_once_'.$index} = 1 if ($self->{'schedule_once_'.$index} eq 2); # We only want real deleted entries set to 2, so set to 1. + } + + if ((defined($self->{'schedule_'.$index})) || ($self->{'schedule_once_'.$index} eq 2)) { + $self->{'schedule_'.($index-$nullcount)} = $self->{'schedule_'.$index}; + $self->{'schedule_label_'.($index-$nullcount)} = $self->{'schedule_label_'.$index}; + $self->{'schedule_once_'.($index-$nullcount)} = $self->{'schedule_once_'.$index}; + $schedule[($index-$nullcount)][0] = ($index-$nullcount); + if ($self->{'schedule_once_'.$index} eq 2) { $schedule[($index-$nullcount)][1] = undef } + else { $schedule[($index-$nullcount)][1] = $self->{'schedule_'.$index} } + + if (defined($self->{'schedule_label_'.$index}) ) { $schedule[($index-$nullcount-$schoncecnt)][2] = $self->{'schedule_label_'.$index} } + else { $schedule[($index-$nullcount)][2] = ($index-$nullcount) } + + unless (($index-$nullcount) eq $index) { + $self->{'schedule_'.$index} = undef; + $self->{'schedule_label_'.$index} = undef; + $self->{'schedule_once_'.$index} = undef; + } + } + } + $self->{'schedule_count'} = scalar @schedule; return \@schedule; } sub am_i_active_object{ my ($self,$instance) = @_; unless (defined($instance)) { return 1 } - ::print_log("[SCHEDULE] - am_i_active_object - active object: ".$Interfaces{$instance}->get_object_name." check object: ".$self->get_object_name) if (defined($Interfaces{$instance})); + ::print_log("[SCHEDULE] - DEBUG - am_i_active_object - current active object: ".$Interfaces{$instance}->get_object_name." checked object: ".$self->get_object_name) if ( (defined($Interfaces{$instance})) && ($main::Debug{'schedule'}) ); if (defined($Interfaces{$instance})) { if ($Interfaces{$instance}->get_object_name eq $self->get_object_name) { return 1 } else { $self->{'active_object'} = 0; return 0; } @@ -149,71 +296,45 @@ Used to associate child objects with the interface. =cut sub register { - my ($self, $object, $child, $state1, $state2) = @_; + my ($self, $object, $child, $HorC) = @_; + $self->{schedule_object} = 1; + $object->{schedule_object} = 1; + $child->{schedule_object} = 1; if ($object->isa('SCHEDULE_Generic')) { ::print_log("Registering a SCHEDULE Child Object type SCHEDULE_Generic" ); push @{$self->{generic_object}}, $object; - ::MainLoop_pre_add_hook( sub {SCHEDULE::check_date($self,$object);}, 'persistent'); + #::MainLoop_pre_add_hook( sub {SCHEDULE::check_date($self,$object);}, 'persistent'); + ::MainLoop_pre_add_hook( sub {SCHEDULE::check_date($self,$object);}); } if ($object->isa('SCHEDULE_Temp')) { - my $HorC = $child; ::print_log("Registering a SCHEDULE Child Object type SCHEDULE_Temp" ); $self->{temp_object}{$HorC} = $object; if ((defined($self->{temp_object}{'cool'})) && (defined($self->{temp_object}{'heat'}))) { - ::MainLoop_pre_add_hook( sub {SCHEDULE::check_date($self,$self->{temp_object}{'cool'});}, 'persistent' ); + #::MainLoop_pre_add_hook( sub {SCHEDULE::check_date($self,$self->{temp_object}{'cool'});}, 'persistent' ); + ::MainLoop_pre_add_hook( sub {SCHEDULE::check_date($self,$self->{temp_object}{'cool'});}); } } } -#sub check_date { -# my ($self,$object) = @_; -# my $occupied_state = ($$self{occupied}->state_now) if (defined($$self{occupied})); -# if ($occupied_state) { $self->ChangeACSetpoint if (($self->am_i_active_object($$self{instance})) && (lc(state $self) eq 'on')) } - -# if ($::New_Minute) { -# ::print_log("[SCHEDULE] Checking schedule for ". $self->get_object_name." Sate is ". (state $self) . " Child object is ". $object->get_object_name); -# if (lc(state $self) eq 'on') { -# my $Week; -# if ($::Weekday) { $Week = 'weekday' } elsif ($::Weekend) { $Week = 'weekend' } -# if (($$self{'schedule'}{'type'} eq 'calendar') && -# (defined(my $action = $$self{'schedule'}{$::Month}{$::Mday}{$::Hour}{$::Minute}{'action'}))) { -# &set_action($self,$object,$action); -# } -# elsif (($$self{'schedule'}{'type'} eq 'daily') && -# (defined(my $action = $$self{'schedule'}{lc($::Day)}{$::Hour}{$::Minute}{'action'}))) { -# &set_action($self,$object,$action); -# } -# elsif (($$self{'schedule'}{'type'} eq 'wdwe') && -# (defined(my $action = $$self{'schedule'}{$Week}{$::Hour}{$::Minute}{'action'}))) { -# &set_action($self,$object,$action); -# } -# elsif (($$self{'schedule'}{'type'} eq 'time') && -# (defined(my $action = $$self{'schedule'}{$::Hour}{$::Minute}{'action'}))) { -# &set_action($self,$object,$action); -# } -# } -# -# } -#} - - sub check_date { my ($self,$object) = @_; - if ($::Startup or $::Reload) { $self->{'reloaded'} = 1 } my $occupied_state = ($$self{occupied}->state_now) if (defined($$self{occupied})); - if ($occupied_state) { $self->ChangeACSetpoint if (($self->am_i_active_object($$self{instance})) && (lc(state $self) eq 'on')) } + + if ($occupied_state) { $self->CheckOverRide if (($self->am_i_active_object($$self{instance})) && (lc(state $self) eq 'on')) } + elsif ($$self{winter_mode_type} eq 'track') { + if ($self->CheckTempOutdoor) { $self->CheckOverRide('temp_track') if (($self->am_i_active_object($$self{instance})) && (lc(state $self) eq 'on')) } + } if ($::New_Minute) { $self->am_i_active_object($$self{instance}) if (defined($$self{instance}));; - ::print_log("[SCHEDULE] Checking schedule for ". $self->get_object_name." Sate is ". (state $self) . " Child object is ". $object->get_object_name); + ::print_log("[SCHEDULE] - DEBUG - Checking schedule for ". $self->get_object_name." State is ". (state $self) . " Child object is ". $object->get_object_name) if $main::Debug{'schedule'}; if (lc(state $self) eq 'on') { - #foreach my $values (@{$self->{'schedule'}}) { for my $index (1..$self->{'schedule_count'}) { - #my @calvals = split /,/, $self->{'schedule_'.$index}; - # if (&::time_cron($calvals[1])) { &set_action($self,$object,$calvals[0]) } - if (&::time_cron($self->{'schedule_'.$index})) { &set_action($self,$object,$index) } + if (defined($self->{'schedule_'.$index})) { + if (&::time_cron($self->{'schedule_'.$index})) { &set_action($self,$object,$index) } + } } } @@ -230,24 +351,28 @@ sub setACSetpoint { my $heat_temp_control = $object->{temp_object}{'heat'}{child}; my $heat_temp_control_sub = $object->{temp_object}{'heat'}{sub}; $cool_temp_control->$cool_temp_control_sub($cool_sp); - $heat_temp_control->$heat_temp_control_sub($heat_sp); - - ::print_log("[SCHEDULE] running ".$cool_temp_control->get_object_name."->".$cool_temp_control_sub."(".$cool_sp.")"); - ::print_log("[SCHEDULE] running ".$heat_temp_control->get_object_name."->".$heat_temp_control_sub."(".$heat_sp.")"); + $self->{'set_temp_timer'} = ::Timer::new(); + $self->{'set_temp_timer'}->set('7', sub { + $heat_temp_control->$heat_temp_control_sub($heat_sp); + ::print_log("[SCHEDULE] running ".$heat_temp_control->get_object_name."->".$heat_temp_control_sub."(".$heat_sp.")"); + }); } sub set_action { my ($self,$object,$index) = @_; if ($object->isa('SCHEDULE_Generic')) { - ::print_log("[SCHEDULE] Setting ".$object->{child}->get_object_name." state to ".$self->{'schedule_label_'.$index}); + my $sub = 'set'; + $sub = $$self{sub} if defined($$self{sub}); + ::print_log("[SCHEDULE] Running ".$object->{child}->get_object_name."->".$sub."(".$self->{'schedule_label_'.$index}.")"); $self->_set_instance_active_object($$self{instance},$index) if (defined($$self{instance})); - $object->{child}->SUPER::set($self->{'schedule_label_'.$index},$self->get_object_name,1); + $object->{child}->$sub($self->{'schedule_label_'.$index},$self->get_object_name,1); } elsif ($object->isa('SCHEDULE_Temp')) { - ::print_log("[SCHEDULE] set_action - Temp object: ".$object->get_object_name." Parent object: ".$self->get_object_name); + ::print_log("[SCHEDULE] - DEBUG - set_action - Temp object: ".$object->get_object_name." Parent object: ".$self->get_object_name) if $main::Debug{'schedule'}; $self->_set_instance_active_object($$self{instance}) if (defined($$self{instance})); - $self->ChangeACSetpoint; + $$self{winter_mode_track_flag} = 0; # reset the temp track flag because the schedule changed. + $self->CheckOverRide; } } @@ -267,9 +392,12 @@ sub set_occpuancy { sub set_winter { - my ($self, $object, $temp) = @_; + my ($self, $object, $temp, $type, $high) = @_; $$self{winter_mode_object} = $object; - $$self{winter_mode_temp} = $temp + $$self{winter_mode_temp} = $temp; + $$self{winter_mode_type} = lc($type); # night, day, now + $$self{winter_mode_type} = 'night' unless (defined $type); + $$self{winter_mode_temp_high} = $high; } @@ -277,45 +405,53 @@ sub set_vacation { my ($self, $object, $state) = @_; $$self{vacation_mode_object} = $object; $$self{vacation_mode_state} = $state; + $self->set_occpuancy(undef,undef,undef) unless (defined $$self{occupied}); # Allows the use of vacation mode with out occpuancy } +sub CheckTempOutdoor { + unless ( defined($::Weather{'TempOutdoor'}) ) { return 0 } + unless ( defined($$self{LastTempOutdoor}) ) { $$self{LastTempOutdoor} = $::Weather{'TempOutdoor'}; return 0 } + if ( $$self{LastTempOutdoor} ne $::Weather{'TempOutdoor'} ) { $$self{LastTempOutdoor} = $::Weather{'TempOutdoor'}; return 1 } +} -sub ChangeACSetpoint { -my ($self,$object) = @_; -unless ($self->am_i_active_object($$self{instance})) { return 0 } -my $action = $self->get_instance_active_action($$self{instance}); -my $occ_setback_object = $$self{occ_setback_object}; -my $occ_setback_state = $$self{occ_setback_state}; -my $occ_state = $$self{occ_state}; -my $object = $self; -my $occupied_state = ($$self{occupied}->state) if (defined($$self{occupied})); - -print_log("[THERMO] - DEBUG --- in ChangeACSetpoint") if ($config_parms{"thermo_schedule"} eq 'debug'); - - if (&OverRide($self)) { +sub CheckOverRide { + my ($self,$checktype) = @_; + unless ($self->am_i_active_object($$self{instance})) { return 0 } + my $action = $self->get_instance_active_action($$self{instance}); + my $occ_setback_object = $$self{occ_setback_object}; + my $occ_setback_state = $$self{occ_setback_state}; + my $occ_state = $$self{occ_state}; + my $object = $self; + my $occupied_state = ($$self{occupied}->state) if (defined($$self{occupied})); + + + if ( $self->OverRide ) { $occ_setback_object = $$self{override_mode_setback_object} if defined($$self{override_mode_setback_object}); $occ_setback_state = $$self{override_mode_setback_state} if defined($$self{override_mode_setback_state}); - $occ_state = $$self{override_mode_occ_state} if defined($$self{occ_state}); + $occ_state = $$self{override_mode_occ_state} if defined($$self{override_mode_occ_state}); $object = $$self{override_mode_object} if defined($$self{override_mode_object}); - } - &main::print_log("[THERMO] - INFO - ChangeACSetpoint - " . $occupied_state ." ". $object->get_object_name ." state match: $occ_state"); + } + elsif ($checktype eq 'temp_track') { return } + elsif ($$self{winter_mode_track_flag}) { $object = $$self{winter_mode_object} } + + + ::print_log("[SCHEDULE] - INFO - CheckOverRide - Current occupied state:" . $occupied_state ." Current active object:". $object->get_object_name ." state to match: $occ_state"); if ((defined($$self{occupied})) && ($$self{occupied}->state eq $occ_state)) { - &main::print_log("[THERMO] - INFO - ChangeACSetpoint - occ state match ". $object->get_object_name ." setpoints, you are now $occ_state"); if ($$self{thermo_timer}->expired) { - &main::print_log("[THERMO] - INFO - setting ". $object->get_object_name ." setpoints, you are now $occ_state"); + ::print_log("[SCHEDULE] - INFO - setting ". $object->get_object_name ." setpoints, you are now $occ_state"); $self->setACSetpoint($object); } else { $$self{thermo_timer}->set($$self{thermo_timer_delay}, sub { - $self->ChangeACSetpoint; + $self->CheckOverRide; }); } } elsif ((defined($$self{occupied})) && ($$self{occupied}->state eq $occ_setback_state)) { if ($$self{thermo_timer}->expired) { - &main::print_log("[THERMO] - INFO - setting setback ". $occ_setback_object->get_object_name ."setpoints, you are now $occ_setback_state"); + ::print_log("[SCHEDULE] - INFO - setting setback ". $occ_setback_object->get_object_name ." setpoints, you are now $occ_setback_state"); $self->setACSetpoint($occ_setback_object); } else { $$self{thermo_timer}->set($$self{thermo_timer_delay_setback}, sub { - $self->ChangeACSetpoint; + $self->CheckOverRide; }); } } else { @@ -331,40 +467,119 @@ undef $$self{override_mode_setback_object}; undef $$self{override_mode_setback_state}; undef $$self{override_mode_occ_state}; undef $$self{override_mode_object}; - print_log("[THERMO] - DEBUG --- IN OVERRIDE") if ($config_parms{"thermo_schedule"} eq 'debug'); - if ($occupied_state eq 'vacation') { - print_log("[THERMO] - DEBUG --- IN OVERRIDE --- VACATION") if ($config_parms{"thermo_schedule"} eq 'debug'); - $$self{override_mode_setback_object} = $$self{vacation_mode_object}; # override the setpoint if in vacation mode - $$self{override_mode_setback_state} = $$self{vacation_mode_state}; # override the setback state if in vacation mode + ::print_log("[SCHEDULE] - DEBUG --- IN OVERRIDE") if $main::Debug{'schedule'}; + if ( $occupied_state eq $$self{vacation_mode_state} ) { + ::print_log("[SCHEDULE] - DEBUG --- IN OVERRIDE --- VACATION") if $main::Debug{'schedule'}; + $$self{override_mode_object} = $$self{vacation_mode_object}; # override the setpoint if in vacation mode return 1; - } elsif ($self->WinterMode) { - print_log("[THERMO] - DEBUG --- IN OVERRIDE --- WINTERMODE") if ($config_parms{"thermo_schedule"} eq 'debug'); - $$self{override_mode_object} = $$self{winter_mode_object}; # override the setpoint if forcast temp is below config - return 1; - } + } elsif ( $self->WinterMode ) { return 1 } return 0; } sub WinterMode { #return 1; # temp for testing +#$::Weather{'Forecast Today'} = 'Sunny. Patchy fog in the morning. Highs in the lower 90s. East winds to 10 mph.'; my ($self) = @_; - print_log("[THERMO] - DEBUG --- IN WINTERMODE") if ($config_parms{"thermo_schedule"} eq 'debug'); - if ($::Weather{'Forecast Tonight'} =~ /lows in the ([\w ]+) (\d+)/i) { - print_log("[THERMO] - DEBUG --- IN WINTERMODE --- FORCAST --- $1 $2") if ($config_parms{"thermo_schedule"} eq 'debug'); + ::print_log("[SCHEDULE] - DEBUG --- IN WINTERMODE") if $main::Debug{'schedule'}; + if ( ($$self{winter_mode_type} eq 'night') && ($::Weather{'Forecast Tonight'} =~ /lows in the ([\w ]+) (\d+)/i) ) { + ::print_log("[SCHEDULE] - DEBUG --- IN WINTERMODE --- FORCAST --- $1 $2") if $main::Debug{'schedule'}; my $fc = $2; if (lc($1) =~ /mid/) { $fc = $fc + 3 } # Translate low, mid, and upper to a value if (lc($1) =~ /up/) { $fc = $fc + 6 } ##if the value we got from the weather script is equal #or lower than our defined value, return the defined winter mode if ($fc <= ($$self{winter_mode_temp})) { - ::print_log("[THERMO] - DEBUG --- IN WINTERMODE ---- M1 --- LOWS -- $fc -- $$self{winter_mode_temp}") if ($config_parms{"thermo_schedule"} eq 'debug'); + ::print_log("[SCHEDULE] - DEBUG --- IN WINTERMODE ---- M1 --- LOWS -- $fc -- $$self{winter_mode_temp}") if $main::Debug{'schedule'}; + $$self{override_mode_object} = $$self{winter_mode_object}; # override the setpoint if forcast temp is below config + return 1; + } + return 0; + } + if ( ($$self{winter_mode_type} eq 'day') && + (($::Weather{'Forecast Today'} =~ /Highs in the ([\w ]+) (\d+)/i) || + ($::Weather{'Forecast Today'} =~ /Highs ([\w ]+) (\d+)/i)) ) { + ::print_log("[SCHEDULE] - DEBUG --- IN WINTERMODE --- FORCAST --- $1 $2") if $main::Debug{'schedule'}; + my $fc = $2; + if (lc($1) =~ /around/) { $fc = $fc + 0 } + elsif (lc($1) =~ /low/) { $fc = $fc + 0 } + elsif (lc($1) =~ /mid/) { $fc = $fc + 3 } # Translate low, mid, and upper to a value + elsif (lc($1) =~ /up/) { $fc = $fc + 6 } + else { ::print_log("[SCHEDULE] - NOTICE --- WINTERMODE --- Unknown forecast modifier: $1 -- full text: $::Weather{'Forecast Today'}") } + ##if the value we got from the weather script is equal + #or lower than our defined value, return the defined winter mode + if ($fc <= ($$self{winter_mode_temp})) { + ::print_log("[SCHEDULE] - DEBUG --- IN WINTERMODE ---- M1 --- LOWS -- $fc -- $$self{winter_mode_temp}") if $main::Debug{'schedule'}; + $$self{override_mode_object} = $$self{winter_mode_object}; # override the setpoint if forcast temp is below config + return 1; + } + return 0; + } + if ( ($$self{winter_mode_type} eq 'now') && ($::Weather{'TempOutdoor'} =~ /(\d+)/i) ) { + ::print_log("[SCHEDULE] - DEBUG --- IN WINTERMODE --- TEMP NOW --- $1") if $main::Debug{'schedule'}; + my $fc = $1; + if ($fc <= ($$self{winter_mode_temp})) { + ::print_log("[SCHEDULE] - DEBUG --- IN WINTERMODE --- TEMP NOW -- $fc -- $$self{winter_mode_temp}") if $main::Debug{'schedule'}; + $$self{override_mode_object} = $$self{winter_mode_object}; # override the setpoint if current temp is below config + return 1; + } + return 0; + } + if ( ($$self{winter_mode_type} eq 'track') && ($::Weather{'TempOutdoor'} =~ /(\d+)/i) ) { + ::print_log("[SCHEDULE] - DEBUG --- IN WINTERMODE --- TEMP TRACK --- $1") if $main::Debug{'schedule'}; + my $fc = $1; + if ( ($fc <= ($$self{winter_mode_temp})) && (not $$self{winter_mode_track_flag}) ) { + $$self{winter_mode_track_flag} = 1; + ::print_log("[SCHEDULE] - DEBUG --- IN WINTERMODE --- TEMP TRACK -- TempOutdoor: $fc -- Config low: $$self{winter_mode_temp}") if $main::Debug{'schedule'}; + $$self{override_mode_object} = $$self{winter_mode_object}; # override the setpoint if current temp is below config + return 1; + } + elsif ( ($fc > ($$self{winter_mode_temp_high})) && ($$self{winter_mode_track_flag}) ) { + $$self{winter_mode_track_flag} = 0; + ::print_log("[SCHEDULE] - DEBUG --- IN WINTERMODE --- TEMP TRACK -- TempOutdoor: $fc -- Config high: $$self{winter_mode_temp}") if $main::Debug{'schedule'}; + $$self{override_mode_object} = $self; # set the setpoint back to normal if temp is higher than config return 1; } return 0; } } +=back + +=head1 B + +=head2 SYNOPSIS + +User code: + + $SCHEDULE_SG_LIGHT1 = new SCHEDULE_Generic($SCHEDULE_LIGHT1,$light1,'on','off'); + + Wherein the format for the definition is: + $SCHEDULE_SG_LIGHT1 = new SCHEDULE_Generic(MASTER_SCHEDULE_OBJECT,CONTROLLED_OBJECT,STATES); + + +=head2 NOTES + +The master schedule object (SCHEDULE object) holds the scheduling data which is set using the MH web UI. +The SCHEDULE_Generic object links the master schedule object to the controlled object and optionally allows the +user to set custom states to be used in the schedules in the MH web. +The controlled object can be any MH object such as a light. + +=head2 DESCRIPTION + +Links the master schedule object to the controlled object and optionally allows the user to set custom states +to be used in the schedules in the MH web. + +=head2 INHERITS + +L + +=head2 METHODS + +=over + +=cut + package SCHEDULE_Generic; @SCHEDULE_Generic::ISA = ('Generic_Item'); @@ -380,8 +595,8 @@ sub new $$self{state_count} = ((scalar @_) - 3); my @states; for my $i (3..(scalar @_)) { if (defined @_[$i]) { $self->{$i-2}=@_[$i]; push (@states, @_[$i]); } } - @{$$self{states}} = @states if (defined @states); - $$self{parent}->register($self,$child); + @{$$self{states}} = @states if (@states); + $$self{parent}->register($self,$$self{child}); return $self; } @@ -393,6 +608,64 @@ sub set { } +=item C + +Allows the user to change the sub used to set the state of the controlled object. By default 'set' is used. + +User code: + + $SCHEDULE_SG_LIGHT1->set_sub('set_cool') + + Wherein the format for the definition is: + $SCHEDULE_SG_LIGHT1->set_sub(SUB) + +=cut + + +sub set_sub { + my ($self, $sub) = @_; + $$self{sub} = $sub; +} + + +=back + +=head1 B + +=head2 SYNOPSIS + +User code: + + $NormalCool = new SCHEDULE_Temp($Normal,'cool',$thermostat,'cool_setpoint'); + + Wherein the format for the definition is: + $NormalCool = new SCHEDULE_Temp(MASTER_SCHEDULE_OBJECT,cool/heat,CONTROLLED_THERMOSTAT_OBJECT,SUB); + + +=head2 NOTES + +The master schedule object (SCHEDULE object) holds the scheduling data which is set using the MH web UI. +The SCHEDULE_Temp object holds the temp setting for the schedule and links the master schedule object to +the controlled object. +The controlled object is the thermostat object used to change your thermostat set points. +cool/heat is literally 'heat' or 'cool', you should have 1 SCHEDULE_Temp object set to 'cool' and 1 set to 'heat'. + +=head2 DESCRIPTION + +This object holds the temp setting and links the master schedule object to the controlled thermostat object, its also +where the user can easily change the temp settings for the schedule in the MH web UI. + +=head2 INHERITS + +L + +=head2 METHODS + +=over + +=cut + + package SCHEDULE_Temp; @SCHEDULE_Temp::ISA = ('Generic_Item'); @@ -408,7 +681,7 @@ sub new $$self{sub} = $sub; $$self{state_count} = 7; @{$$self{states}} = ('up','down'); - $parent->register($self,$HorC); + $parent->register($self,$child,$HorC); return $self; } @@ -436,3 +709,45 @@ sub set { $self->SUPER::set($p_state,$p_setby,1);; } } + + +=item C + +Allows the user to change the sub used to set the state of the controlled object. By default 'set' is used. +This can also be set in the SCHEDULE_Temp definition. + +User code: + + $NormalCool->set_sub('set_cool'); + + Wherein the format for the definition is: + $NormalCool->set_sub(SUB) + +=cut + + +sub set_sub { + my ($self, $sub) = @_; + $$self{sub} = $sub; +} + + +=back + +=head2 NOTES + +=head2 AUTHOR + +Wayne Gatlin + +=head2 SEE ALSO + +=head2 LICENSE + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +=cut