Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer

comment on

( #3333=superdoc: print w/replies, xml ) Need Help??
Because the Raspberry Pi does not have an onboard time clock (thanks Marshall for pointing this out),

That can quite easily be fixed, for a few bucks. See for just one of many similar solutions. All you need is a cheap RTC module and four wires (GND, VCC, SCL, SDA). Some RTC modules can even be plugged right onto the Raspi, no wires needed.

the first thing my script does is to check that it has a valid time that has been obtained from the network. It does this by comparing the current year as given by localtime to 2020.

Now, if you can go back one step, think about the trivial cron-based solution from Re: Continuous or timed?.

One of its big advantages is that you don't have to care about running a daemon, and keep it running, without conflicts, without multiple instances. The system's cron daemon has already solved all of that problems. A further advantage is that you need to run very little code. Linux inherits from Unix, and Unix is a huge collection of little tools that are all designed to cooperate. You don't have to re-invent wheels, a chassis, doors, windows, and an engine. It's all there, you just need to connect the parts. No need to dig for ore to smith a primitive wheel and an axe, and to hack down some trees to build a chassis.

So, let's go back to the cron way:

You need three parts. One that calculates sunrise and sunset, and submits those times to cron for the next two curtain movements. This needs to be run daily, either at night or at day, as you like, but before the curtains have to move. The two other parts are very similar and can in fact be the same tool, invoked with different parameters. One needs to open the curtains, the other has to close them.

The two latter parts should be trivial, setup the GPIOs, simulate either a button press on "OPEN" or on "CLOSE", and probably keep those buttons pressed - i.e. keep one of the relais switched on - for a few seconds. Then release the button / switch off the relais and exit. Those two parts could run on a fixed schedule, i.e. run move-curtains open at 07:00 and move-curtains close at 21:00. For most crons, you would just need those two lines in a file submitted to crontab:

0 7 * * * /usr/local/bin/move-curtains open 0 21 * * * /usr/local/bin/move-curtains close

(First line translated to human language: Run at minute 0, hour 7, every day, every month, every weekday, the command "move-curtains open".)

Part one is the little cherry on top that replaces those fixed times with sunrise and sunset. Calculate both times way before sunrise, and submit a similar file with hours and minutes replaced with the times of sunrise and sunset. Think about it: It's also a cron job!

So what the part one actually submits is something like this:

0 0 * * * /usr/local/bin/sunrise-sunset 51 6 * * * /usr/local/bin/move-curtains open 13 21 * * * /usr/local/bin/move-curtains close

How does the sunrise-sunset script look like? About like this. Note that I omitted the sunrise and sunset calculations.

#!/usr/bin/perl use strict; use warnings; use feature 'say'; sub calc_sunrise_sunset { # some astro math here } my ($sunrise_hour, $sunrise_minute, $sunset_hour, $sunset_minute) = ca +lc_sunrise_sunset(); open my $pipe,'|-','crontab','-' or die "Can't run crontab: $!"; say $pipe "0 0 * * * $0"; # keep this script running every day at midn +ight say $pipe "$sunrise_hour $sunrise_minute * * * /usr/local/bin/move_cur +tains open"; # open at sunrise say $pipe "$sunset_hour $sunset_minute * * * /usr/local/bin/move_curta +ins close"; # close at runset close $pipe;

And that's about all it takes. The move_curtains script is hardly more than the little demo code from Re^12: Controlling USB on Raspberry Pi, just select the GPIO pin based on $ARGV[0].

Alternatively to have the sunrise_sunset script re-submit itself to cron, you could also start it from a distribution-specific directory that contains scripts to be run daily. On the Debian-based Raspberrian, this is /etc/cron.daily/. From there, your script will be run every night, at some time between midnight and sunrise. In that case, omit the following line:

say $pipe "0 0 * * * $0"; # keep this script running every day at midn +ight

When started as root, crontab may need a user name. Change the open line like this:

open my $pipe,'|-','crontab','-','-u','root' or die "Can't run crontab +: $!";

Now, the problem of the lost time at boot. First, you keep the raspi running 24/7/365. So you will rarely lose the current time. For those days when you lost time, you do a three-line trick in both scripts, right after the "use" lines:

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime() +; $year += 1900; die "Clock lost" if $year < 2020;

(You could shorten that to a single line, without variables, but I think this is more readable.)

This will prevent any action until the clock is set to the correct time. In the worst case, if the Raspi had a power outage around sunrise, the curtains will stay closed the entire day. And similarly, if the power outage was around sunset, the curtains will stay open the entire night. If the power outage was around midnight, the curtains will move at yesterday's sunrise and sunset times, and you will hardly notice it. If the power outage was at any other time, nothing bad will happen. cron will happily start the two scripts as scheduled.

Adding an RTC module also removes the delay until network is up, as it will be read very early during startup, way before cron is started, way before network is started, way before ntpd is started. The RTC may be off by a few minutes, like PC clocks (because it is more or less a cheap PC clock), but ntpd and adjtime will fix that very soon.


Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

In reply to Re: Preventing multiple instances by afoken
in thread Preventing multiple instances by Bod

Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":

  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or or How to display code and escape characters are good places to start.
Log In?

What's my password?
Create A New User
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (3)
As of 2021-06-13 15:22 GMT
Find Nodes?
    Voting Booth?
    What does the "s" stand for in "perls"? (Whence perls)

    Results (57 votes). Check out past polls.