http://www.perlmonks.org?node_id=1203229

stevieb has asked for the wisdom of the Perl Monks concerning the following question:

I've been experiencing some trouble with some clock timing code and its associated code that sets the times in my indoor grow room automation software, and am looking for some assistance in completely re-writing it.

I have to admit that what I thought was going to be a simple task ended up (at least to me) not being simple at all. The below attempt at the refactor uses DateTime objects to perform the math. It seems like it has to do a lot of expensive work, and I'm hoping the Monks can point out and/or help me devise something that's more efficient in terms of the work being done. Note that the light timer routine is run every 15 seconds in an async event, so it most definitely is not just a one-off process.

An example of what I'm trying to achieve:

One problem I experienced with my existing code was that I was searching for the on-time with a regex using localtime(): if (localtime(time) =~ /$on_time:/){...}. If the power went out and came back on say at 1805 hrs, the light would not trigger, nor the future date would be set. This is a significant and potentially fatal problem for the plants (it can cause the females enough stress to go hermaphrodite) if this isn't noticed within a day or two. This new code does not check the time in the same way, and it eliminates this (single) problem.

Am I way overthinking this here? Perhaps I've been staring at it for too long and am blinded to something simply obvious. Or, maybe not, and this is a good way to achieve what I want and there's just a more efficient and less costly way. Or perhaps it's perfect, and I shouldn't change anything. I have laid out the code so it doesn't wrap here on PM, and added comments in hopes to make it easier to follow as it's not just a simple code block.

Thoughts?

use warnings; use strict; use feature 'say'; use DateTime; # this is a prototype of a light timer my $on_at = '18:00'; # lights on my $on_time = 12; # hours on for my $off_time; my $on = 0; # light is on bool my $t = time; my $count = 0; # iter count my $done = 20000; # num iterations # dt objects my ($t1, $on_at_dt, $off_at_dt) = init_time($on_at, $on_time); while (1){ # on if ($t1 > $on_at_dt && ! $on){ # light is not on, and it's time $on = 1; say "\non: $t1"; } # off if ($t1 > $off_at_dt && $on){ # light's out time $on = 0; say "off: $t1"; $on_at_dt = set_on_timer( $t1, $on_at_dt, $on_at ); $off_at_dt = set_off_timer( $on_at_dt, $off_at_dt, $on_time ); } # expedite the current time for testing $t1->add(seconds => 30); $count++; last if $count == $done; } sub init_time { my ($on_at, $on_time) = @_; my $t1 = DateTime->now; $t1->set_time_zone('local'); my $on_at_dt = $t1->clone; $on_at_dt->set_hour( (split(/:/, $on_at))[0] ); $on_at_dt->set_minute( (split(/:/, $on_at))[1] ); my $off_at_dt = $on_at_dt->clone; $off_at_dt->add(hours => $on_time); return ($t1, $on_at_dt, $off_at_dt); } sub set_off_timer { my ($on_dt, $off_dt, $on_time) = @_; $off_dt = $on_dt->clone; $off_dt->add(hours => $on_time); return $off_dt; } sub set_on_timer { my ($t1, $dt, $on_at) = @_; $dt = $t1->clone; $dt->set_second(0); $dt->set_hour((split(/:/, $on_at))[0]); $dt->set_minute((split(/:/, $on_at))[1]); return $dt; }

Here are a couple of benchmarks I put together to try to get it to be as efficient as I could make it.

Thanks,

-stevieb

Replies are listed 'Best First'.
Re: Efficiency of indoor grow light timer
by holli (Abbot) on Nov 12, 2017 at 19:48 UTC
    You don't need any datetime arithmatic at all. Just loop endlessly and check once a minute wether the lamp state matches the daytime.
    use feature 'signatures'; my $on = 0; sub switch_lamps ($switch_to) { state $lamp_state = 0; return if $lamp_state == $switch_to; $lamp_state = $switch_to; # ... } sub is_nighttime { #... } while (1) { switch_lamps( $on && is_nighttime() ); sleep(60); }


    holli

    You can lead your users to water, but alas, you cannot drown them.

      Thanks holli,

      I can't see how I can avoid arithmetic, but as I said, this is the second full-blown attempt at refactoring this in the last six months, so I could be blinded.

      Somehow I need to know if the on/off time is between two times. So if I am in vegetative "season", my grow lights would be on for 18 hours a day, so I'd have to figure out what time of day it is, and check if we're within that range or not.

      Am I missing something?

        No, you don't, you Stoner :-) But finding out wether a time in between a given interval or not is easy. Doing it this way saves you from having to deal with fixed switch times, which are, as you wrote, unreliable in case of outages or just the computer being shut down at the wrong moment.
        use 5.24.1; use feature qw:signatures:; no warnings qw:experimental:; my @off_from = (22, 0, 0); #22:00:00 my @off_until = (08, 0, 1); #08:00:00 sub cmpt ( $a, $b ) { for ( 0 .. 2 ){ return $a->[$_] <=> $b->[$_] if $a->[$_] != $b->[$_] ; } return 0; } sub is_nighttime( $now ) { my @now = @{$now} || (localtime(time))[2,1,0]; if ( cmpt( \@off_from, \@off_until ) == 1 ) { # over midnight return cmpt(\@now, \@off_from) == 1 || cmpt( \@now, \@off_unti +l ) == -1; } else { # all same day return cmpt(\@now, \@off_from) == 1 && cmpt( \@now, \@off_unti +l ) == -1; } return 0; }
        Edit: Removed C&P remnant
        Edit: added code
        Edit: added <=> (it's been a while)


        holli

        You can lead your users to water, but alas, you cannot drown them.
Re: Efficiency of indoor grow light timer
by 1nickt (Canon) on Nov 12, 2017 at 19:24 UTC

      Cheers Nick,

      To be honest, I've had my head so far in this, I didn't even consider looking for a module for this... d'oh!

      I'll have a look at all three after I'm done cutting and stacking wood for the day, but particularly the latter one looks very promising.

      Thanks!

Re: Efficiency of indoor grow light timer
by SwissGeorge (Initiate) on Nov 13, 2017 at 15:41 UTC
    please allow do some thoughts outside - not really offtopic.
    - your problem is a timing problem .. first of all there are simple to use features provided by operating system.
    - you are talking about having the light on for 12 hours. why not set an alarm(43200) and catch this event?
    your process then gets inactiv for a long time.
    possible strategy eventdriven logic
    {
      $state=off
      set_signal_handler()
      toggle_switch('time') #never get out of this function; see wait;
    
      set_signal_handler() {
        sig_alrm=toggle_switch()
        sig_int=switch_off_and_exit()
        sig_term=ignore
        sig_hup=switch_off_and_exit()
      }
    
      toggle_switch() {
         if ($stat==off) { $waitsec=43200 }
         else { $waitsec=calc_sec_to_switch('time') }
         alarm($waitsec)
         wait(-1) #go to sleep til alarm, or signals (INT,HUP)
         switch_on_off()
      }
    }
    
    have fun
Re: Efficiency of indoor grow light timer
by Marshall (Canon) on Nov 14, 2017 at 09:05 UTC
    Hi stevieb!
    I do figure that you are way over-thinking this.

    To handle power-failure restarts, put a call to turn_off_lights(); in the Pi boot routine.

    Schedule a chron job to run this code every 10,20 minutes, or even every hour.
    Even if the Pi doesn't acquire the correct time, this should prevent the problem of the lights being on too long for multiple days.

    #!/usr/bin/perl use strict; use warnings; # time off: 1800 - 0600 6PM to 6AM my $hour = (localtime(time))[2]; if ($hour >= 06 and $hour <= 18) { turn_on_lights(); #day time } else #18:01 to 05:59 avoids the hour 00 discontinuity { turn_off_lights(); #night time }
    Update:
    I think I had the wrong sense between day and night time before. Oops.
    Here is one link to ChronTab.

    I have a Raspberry PI 3. What version do you have?

Re: Efficiency of indoor grow light timer
by Anonymous Monk on Nov 14, 2017 at 16:03 UTC
    At the risk of sounding stupid, why not just use a crontab ??
Re: Efficiency of indoor grow light timer
by stevieb (Canon) on Dec 12, 2017 at 00:26 UTC

    I want to thank those who mentioned putting this into a crontab; I thought of that, along with the benefits and drawbacks extensively.

    The reason I don't want to do that, is because I wanted this to be self-contained Perl, without relying on system resources (so to speak in this sense).

    The event that is run to enable/disable the light relies heavily on the API behind this Dancer2 application, and I didn't want to have to mess around trying to load code from an external process. Although the code that does run it is indeed external (ie. it runs in a separate proc started with Async::Event::Interval), it's all within the Dancer2 application framework itself, and I was hell-bent on keeping it that way.

    If I could not get it to work the way I needed it, cron was definitely an option, but alas, a very, very last resort.

    Cheers,

    -stevieb

Re: Efficiency of indoor grow light timer
by Anonymous Monk on Nov 14, 2017 at 00:42 UTC
    You obviously do not need to check every fifteen seconds for an eventuality which you know will not occur for many hours hence.

      Just because an answer is "obvious" doesn't mean it is correct.

      The frequency with which you check for the event depends only on how quickly you have to react to it when it does happen. If the SLA says that the system must respond with 15 seconds of the event happening, then you have to check for it at least that often. It doesn't matter whether it happens every 30 seconds or every 30 days.

      I reckon we are the only monastery ever to have a dungeon stuffed with 16,000 zombies.

      The status is checked at such periods is because a DB entry needs to be updated, so the front-end UI, driven by JS, represents the accurate status of the grow lamp being on or off. If something goes wrong, I can't just check every X hours; I need to know live-time whether things are ok or not. To further, the checks happen to ensure the sanity of the day, the time of day, and that the configuration is correct for the "season" for which we're growing in.