0

I have the following where it keeps checking for current time in a while loop, and when it eventually matches the time_defined then runs the code in the if statement.

Also, any suggestions of improvement are welcome.

Thank you in advance!

Jo Ko
  • 7,225
  • 15
  • 62
  • 120
  • 3
    Set your computer to not go to sleep, or to wake up when you need to execute things. Programs don't run while the computer is sleeping. – user2357112 Apr 07 '17 at 18:38
  • I think you can handle it with signals but as @user2357112 said "Programs don't run while the computer is sleeping. " – RaminNietzsche Apr 07 '17 at 18:45
  • @user2357112 Would it be possible to keep it always running in the background if it's in a server? If what's the proper way of doing so? – Jo Ko Apr 08 '17 at 00:30
  • @RaminNietzsche Any leads as to how to go about with signals? Thank you in advance! – Jo Ko Apr 08 '17 at 00:30
  • @JoKo It's not possible for the script to run while your computer is asleep. But either of these are possible: (1) you start the script manually, the script pauses while your computer sleeps, then it performs the scheduled action immediately after the computer wakes up; or (2) the script runs automatically (at boot) on a server which never goes to sleep. Do you have a preference for one or the other of these? – Matthias Fripp Apr 15 '17 at 07:06
  • @mfripp Appreciate the clarification. Starting the script manually for now. Is it possible to pass in or give parameters as well when starting the automation manually? Thank you in advance – Jo Ko Apr 17 '17 at 17:37
  • @JoKo Passing in parameters is really a separate topic. But you can run your script with something like `python myscript.py --start-time 08:00` then your script can look in `sys.argv` for the extra arguments. If you want to formalize this, I'd recommend using the tools available in the standard `argparse` module. – Matthias Fripp Apr 17 '17 at 18:37
  • @mfripp Do you mind clarifying with an example as an answer? So that I can accept and upvote as well. Would like to pass in a few numbers as an argument to define `time_defined`. – Jo Ko Apr 17 '17 at 18:57
  • @JoKo I added some command line parsing -- maybe that will get you going. – Matthias Fripp Apr 18 '17 at 03:52

2 Answers2

6

Any long-running Python script (including yours) will run while the computer is awake, pause execution while the computer sleeps, then automatically resume execution when the computer wakes up again. However, there are a couple of problems with your script that may be preventing it from running correctly, especially if the computer sleeps through the time specified in time_defined.

First, datetime.datetime.now() and datetime.datetime() have 1-microsecond precision. Depending how fast your loop runs, it may not evaluate the time_now = datetime.datetime.now() line during the exact microsecond specified in time_defined (implicitly 20:00:00.000000). This is especially true if your computer happens to be asleep during that particular microsecond. In either of these cases, the test will never come out true, and your script will never emerge from its loop. You could fix both of these problems by testing for time_now >= time_defined instead of time_now == time_defined. There is no way to run the scheduled code while the computer is sleeping. However, with this change, if the computer sleeps through the scheduled time, the scheduled code will run as soon as it wakes up.

Second, your code is using a busy loop for scheduling, which is very inefficient. This won't prevent it from running correctly, but it will peg one core of your processor at 100% usage while it is running. If you use time.sleep(), you can reduce the processor use nearly to zero. In principle, you could use something like time.sleep((time_defined-time_now).totalseconds()) to sleep exactly the right number of seconds. However, (at least on a Mac) that timer only counts seconds during which the computer is awake, so suspending the computer would throw off the schedule. However, you could use a sequence of short sleeps (e.g., 1 second) to improve efficiency dramatically and still have fairly precise timekeeping.

Here's some code that fixes both of these problems:

import datetime, time

def background_program():
    time_defined = datetime.datetime(2017, 4, 7, 20, 0, 0)
    ...

    while(True):
        time_now = datetime.datetime.now()

        if time_now >= time_defined:
            try:
                # Run some code
                pass
            except:
                print('Failed to run')
            finally:
                break
        else:
            # use a shorter delay if you want a more exact runtime
            time.sleep(1)

Here's some more streamlined code that does the same thing. I've also added a command line argument, so you can call this like python myscript.py --start-time 2017-04-17 20:00:00. (You can also run python myscript.py --help to get a little help.)

import datetime, time, argparse

def background_program(time_defined):

    # wait until time_defined, sleeping in short steps
    # note: you could use a shorter delay for more precision
    print "waiting until {}".format(time_defined.strftime('%Y-%m-%d %H:%M:%S'))
    while(datetime.datetime.now() < time_defined):
        time.sleep(1)

    # perform scheduled action
    try:
        print('Running the scheduled task.')
    except:
        print('Failed to run')


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Run a background task.')
    parser.add_argument(
        '--start-time', nargs=2, default=None,
        help='Specify the time to run the task, in YYYY-MM-DD HH-MM-SS format.'
    )
    args = parser.parse_args()
    if args.start_time is not None:
        start_time = datetime.datetime.strptime(' '.join(args.start_time), '%Y-%m-%d %H:%M:%S')
        background_program(start_time)
Matthias Fripp
  • 17,670
  • 5
  • 28
  • 45
  • The OP should probably have a look at python's documentation on python's [Event scheduler](https://docs.python.org/3.6/library/sched.html). – silel Apr 13 '17 at 17:21
  • @mfripp Appreciate your response! Before I accept the answer and upvote, actually preferred automating on a server. With the code provided, what's the proper way of doing so? Thank you in advance – Jo Ko Apr 30 '17 at 03:55
  • @JoKo, Can you say what operating system (and version) your server uses? This code should run fine on any server, but the startup method depends on your operating system. – Matthias Fripp Apr 30 '17 at 04:10
  • Also, you might do best to write a simple script that does what you want as soon as it's run, and then use the server's scheduling system to launch the script when needed. – Matthias Fripp Apr 30 '17 at 05:32
  • @mfripp Certainly, have an AWS server, but not exactly sure what's the right way to go about with it. – Jo Ko May 01 '17 at 05:09
  • @JoKo do you know what operating system (Windows, CentOS, Debian, Ubuntu, etc.) and version number it is running? Is the AWS instance always running, or do you just start it for specific tasks? If it's not running all the time, do you want an instance to be scheduled to start up specifically to run your Python task at the right time, or do you just want your task to run once the instance is started for some other purpose? – Matthias Fripp May 01 '17 at 09:32
  • @mfripp I'm open for any! Just have an AWS account, and would go with whatever fits best for an automated Python script. – Jo Ko May 01 '17 at 16:50
  • @JoKo Does this server need to do anything other than run this Python script? Do you want to run the script once at a specified time or repeatedly on a regular schedule? I'm thinking you could tell AWS to automatically start a server instance at the right time, run the script and then shutdown. That would be a lot cheaper than keeping the server running all the time. – Matthias Fripp May 01 '17 at 18:10
  • @JoKo you might want to consider the AWS Lambda system, which can automatically run Python code on their servers on a pre-set schedule. See http://docs.aws.amazon.com/lambda/latest/dg/with-scheduledevents-example.html – Matthias Fripp May 01 '17 at 18:20
  • @mfripp I looked into it, and what do you think about Lambda + CloudWatch? – Jo Ko May 01 '17 at 18:39
  • @JoKo it seems like that could be just right for you, if what you want is to run a bit of Python code on a regular schedule. But I don't know what you are trying to do really (thus all the questions) and I'm not an expert on all the AWS services. – Matthias Fripp May 01 '17 at 19:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/143117/discussion-between-jo-ko-and-mfripp). – Jo Ko May 01 '17 at 19:05
1

What you are asking is not possible (everything stops executing at sleep mode).

What you can do is mark your file to resume when the PC is turned back on and on startup. There are some great answers on this matter:

  1. How to run a shell script at startup (Linux)
  2. run a python script everytime the computer wakes up from hibernation (Linux again)
  3. How to start a python file while Windows starts? (Windows)
  4. https://www.reddit.com/r/Python/comments/30br3e/is_it_possible_to_execute_a_python_script_each/?sort=top (Windows again)

For the Linux case:

  • Add your python script on your PATH

For the wakeup-resume case:

  • Find the /usr/lib/pm-utils/sleep.d, or /etc/pm/sleep.d (whichever works in your system - personally used the first choice)

  • Add a script in there, which will look like this:

    myWakeupScript

    #!/bin/sh
    
    case "$1" in
        resume)
            python path/to/your_script.py
    esac
    
  • Make your script executable: chmod +x /usr/lib/pm-utils/sleep.d/myWakeupScript

For the startup case:

  • Place a script inside /etc/init.d/:

    myStartupScript

    #!/bin/sh
    python path/to/your_script.py  
    
  • Make it executable: chmod +x /etc/init.d/myStartupScript

Possible problem and fix:

Thanks to @meetamit, if this does not run you have to create a symlink to /etc/rc.d/

ln -s /etc/init.d/start_my_app /etc/rc.d/

For the Windows case:

The provided solutions are pretty much self-explanatory and you can choose one of those to fit your purpose

I recommend this (seems the easiest):

  • Windows, right? Open Task Scheduler and create a new task with a trigger of event log entry Microsoft-Windows-Kernel-Power, Kernel-Power and whatever the EventID is for sleep/wake.

  • Use the task scheduler and trigger on an event. Choose to trigger by an event and then choose. Setting Basic Log: System Source: Kernel-Power Event ID: 42

John Moutafis
  • 22,254
  • 11
  • 68
  • 112
  • Thanks for the response. Prefer to actually throw it on a server, specifically AWS. How should I go about doing so? Thank you in advance – Jo Ko May 01 '17 at 05:10