Python3: Time-critical code

Whilst thinking about how to tackle my Raspberry Pi temperature project, I found and issue that needed resolving.
In short, I want to record the temperature of my room every 15 minutes. I wanted to record every 15th minute of every hour for a couple of reasons:

  1. To keep the data fair: simply rounding to the nearest quarter hour could mean a difference of up to 15 minutes should the Pi turn off and start running again.
  2. Uniformity: I didn’t want the time to be every 15 minutes after the program starts, for example 10:21 and then 10:36. I want to make a clear comparison of the temperature exactly at the same time for any time I start or stop the program.

Now there are a couple of ways to start this automation, specifically in Linux:

  • cronjob: can run code on a specified time/interva
  • init.d startup: can run code once when startup commences

The cronjob method would be good, but let’s think about this in some more detail:
Every 15 minutes, the system will fire up the code from scratch. Which means all imports will take place, load functions into memory, assign variables etc. which costs time (yes I am being THAT pedantic) and would not always be accurate. Also, it takes extra setting up (so if someone else uses my project, it can be more complicated to set up)

Init.d will simply run the code once on startup. This will allow you to manage time better, although if the code crashes (for some reason), you will have to manually start it or restart the machine.

There a pros and cons to both methods, but ultimately I wanted full control over time and as you may have gathered, opted for init.d.

Now, the interesting bit! Getting the time right, every time.
Again there were a couple of ways that can accomplish this. I needed to offset the start time (start of the script) to take measurement at exactly every quarter hour.
Obviously, this can be any time between a 15 minute window.
I could have arbitrarily looped over the current UNIX time until it divided by 900 (seconds in 15 minutes) but this takes processing time and if the difference to run the loop (in MS) pushes it over a second, I could potentially miss a record of data.

Ideally, I wanted to calculate the time difference and wait it out. Then I could take a record of data and process it, before calculating the difference again for the next time. Doing things this way means I have a 14 minute 59 seconds window to run whatever code I wanted to, until the next time. Obviously, 14 minutes 59 seconds is a long time in terms of running code, but obviously depends on application.
For example, processing dynamic data could take you different times to complete.

Below is my algorithm to calculate the time needed to wait until the next quarter hour. Immediately after time.sleep(), the time critical code, then after that, you have almost a full 15 minutes to do what you need to catch the next one.

# Calculate the time required to wait until the next quart hour

from datetime import datetime
import time

# minute, second, millisecond to a string and split into list
min_sec = datetime.now().strftime("%M %S .%f").split(" ")

# calculate minutes to wait for the next whole quart hour
# explanation of m calculation: e.g. time = 12:33 : min = 33
# 33 % 15 = 3 : find out minutes since last quart
# 15 - 3 = 12 : whole minutes to wait for next quart
# 12 * 60 = 720 : whole seconds to wait with time.sleep()
m = (15-(int(min_sec[0])%15))*60  # convert string to int and calculate seconds

s = int(min_sec[1])  # convert string to int
ms = float(min_sec[2])  # convert ms string to float

# time to sleep until next whole quart
# calculation:
# minutes in seconds minus seconds over whole minute minus ms over whole second
# sleep for required time
time.sleep(float(m-s) - ms)

# ..
# whole quart of hour reached....
# time should be X:(00/15/30/45):00.000
# run time sensitive code here