Timer for variable time delay - python

I would like a timer (Using Python 3.8 currently) that first checks the system time or Naval Observatory clock, so it can be started at any time and synch with the 00 seconds.
I'm only interested in the number of seconds on the system clock or Naval Observatory. At the top of every minute, i.e when the seconds = 00 I need to write data to a DataFrame or database, then sleep again for another 60 seconds.
I first checked the system time, determined how long it is from the 00 seconds, and placed the first delay for that amount. After that it should delay or sleep for 60 seconds, then run again. Data is constantly changing but at this point I only need to write the data every 60 seconds, would like to also have it have the capability of using other time frames like 5 minutes, 15 minutes etc, but once the first is done the other time frames will be easy.
Here is my lame attempt, it runs a few iterations then quits, and I'm sure it's not very efficient
def time_delay():
sec = int(time.strftime('%S'))
if sec != 0:
wait_time = 60 - sec
time.sleep(wait_time)
sec = int(time.strftime('%S'))
wait_time = 60 - sec
elif time.sleep(60):
time_delay()

This isn't quite as general as you wanted, and as I said in a comment, it's not possible to synchronize exactly with the time source due to the inherent overhead involved (but you can get pretty close).
What the following does is define a Thread subclass that can be used to call a user specified function at fixed intervals starting at a whole multiple of seconds on the system clock.
It does this by first delaying until the next multiple of the specified number of seconds will occur, and then from that point on calling the function every interval number of seconds while compensating for however long the call to the specified function took to execute.
from threading import Thread, Event
import time
class TimedCalls(Thread):
def __init__(self, func, interval, multiple):
super().__init__()
self.func = func
self.interval = interval
if multiple == 0:
self.multiple = 60 # Avoid ZeroDivisionError.
else:
self.multiple = multiple
self.stopped = Event()
def cancel(self):
self.stopped.set()
def start(self):
# Initially sync to a whole multiple of the interval.
frac = self.multiple - (time.time() % self.multiple)
time.sleep(frac)
super().start()
def run(self):
next_call = time.time()
while not self.stopped.is_set():
self.func(next_call) # Target activity.
next_call = next_call + self.interval
# Block until beginning of next interval (unless canceled).
self.stopped.wait(next_call - time.time())
def my_function(tm):
print(f"{tm} → {time.strftime('%H:%M:%S', time.localtime(tm))}")
# Call function every 10 sec starting at multiple of 5 secs
timed_calls = TimedCalls(my_function, 10, 5)
timed_calls.start()
try:
time.sleep(125) # Let the timed calls occur.
finally:
timed_calls.cancel()
print('done')
Sample output:
1617662925.0018559 → 15:48:45
1617662935.0018559 → 15:48:55
1617662945.0018559 → 15:49:05
1617662955.0018559 → 15:49:15
1617662965.0018559 → 15:49:25
1617662975.0018559 → 15:49:35
1617662985.0018559 → 15:49:45
1617662995.0018559 → 15:49:55
1617663005.0018559 → 15:50:05
1617663015.0018559 → 15:50:15
1617663025.0018559 → 15:50:25
1617663035.0018559 → 15:50:35
1617663045.0018559 → 15:50:45
done

This will call a function when the seconds are 0:
def time_loop(job):
while True:
if int(time.time()) % 60 == 0:
job()
time.sleep( 1 )

Related

Schedule an iterative function every x seconds without drifting

Complete newbie here so bare with me. I've got a number of devices that report status updates to a singular location, and as more sites have been added, drift with time.sleep(x) is becoming more noticeable, and with as many sites connected now it has completely doubles the sleep time between iterations.
import time
...
def client_list():
sites=pandas.read_csv('sites')
return sites['Site']
def logs(site):
time.sleep(x)
if os.path.isfile(os.path.join(f'{site}/target/', 'hit')):
stamp = time.strftime('%Y-%m-%d,%H:%M:%S')
log = open(f"{site}/log", 'a')
log.write(f",{stamp},{site},hit\n")
log.close()
os.remove(f"{site}/target/hit")
else:
stamp = time.strftime('%Y-%m-%d,%H:%M:%S')
log = open(f"{site}/log", 'a')
log.write(f",{stamp},{site},miss\n")
log.close()
...
if __name__ == '__main__':
while True:
try:
client_list()
with concurrent.futures.ThreadPoolExecutor() as executor:
executor.map(logs, client_list())
...
I did try adding calculations for drift with this:
from datetime import datetime, timedelta
def logs(site):
first_called=datetime.now()
num_calls=1
drift=timedelta()
time_period=timedelta(seconds=5)
while 1:
time.sleep(n-drift.microseconds/1000000.0)
current_time = datetime.now()
num_calls += 1
difference = current_time - first_called
drift = difference - time_period* num_calls
if os.path.isfile(os.path.join(f'{site}/target/', 'hit')):
...
It ends up with a duplicate entries in the log, and the process still drifts.
Is there a better way to schedule the function to run every x seconds and account for the drift in start times?
Create a variable equal to the desired system time at the next interval. Increment that variable by 5 seconds each time through the loop. Calculate the sleep time so that the sleep will end at the desired time. The timings will not be perfect because sleep intervals are not super precise, but errors will not accumulate. Your logs function will look something like this:
def logs(site):
next_time = time.time() + 5.0
while 1:
time.sleep(time.time() - next_time)
next_time += 5.0
if os.path.isfile(os.path.join(f'{site}/target/', 'hit')):
# do something that takes a while
So I managed to find another route that doesn't drift. The other method still drifted over time. By capturing the current time and seeing if it is divisible by x (5 in the example below) I was able to keep the time from deviating.
def timer(t1,t2)
return True if t1 % t2 == 0 else False
def logs(site):
while 1:
try:
if timer(round(time.time(), 0), 5.0):
if os.path.isfile(os.path.join(f'{site}/target/', 'hit')):
# do something that takes a while
time.sleep(1) ''' this kept it from running again immediately if the process was shorter than 1 second. '''
...

Sleep until a certain amount of time has passed

I'm writing a function in Python that waits for an external motor to finish moving, but the amount of time it takes for the motor to move can be variable. I want to standardize the time in between each move — for example, they should all take 30 seconds.
Is there a way to implement a sleep function so that it sleeps the required amount of time until 30 seconds has passed?
For example:
if the motor takes 23 seconds, the function will wait until 30 seconds have passed, so it will sleep 7 seconds.
It sounds like don't want to sleep for 30 second but rather pad the time it takes to perform an activity with a sleep so that it always takes 30 seconds.
import time
from datetime import datetime, timedelta
wait_until_time = datetime.utcnow() + timedelta(seconds=30)
move_motor()
seconds_to_sleep = (wait_until_time - datetime.utcnow()).total_seconds()
time.sleep(seconds_to_sleep)
if you are going to be doing this in multiple places you can create a decorator that you can apply to any function
import functools
import time
from datetime import datetime, timedelta
def minimum_execution_time(seconds=30)
def middle(func)
#functools.wraps(func)
def wrapper(*args, **kwargs):
wait_until_time = datetime.utcnow() + timedelta(seconds=seconds)
result = func(*args, **kwargs)
seconds_to_sleep = (wait_until_time - datetime.utcnow()).total_seconds()
time.sleep(seconds_to_sleep)
return result
return wrapper
You can then use this like so
#minimum_execution_time(seconds=30)
def move_motor(...)
# Do your stuff
It depends on how you are monitoring the runtime of your motor.
For the sake of example, let's assume you have that value stored in a variable,
slowdown_time
#slowdown_time is the variable that stores the time it took for the motor to slow down.
import time
desired_interval = 30 #seconds
sleep_time = desired_interval - slowdown_time
#sleep for remaining time
time.sleep(sleep_time)
Hope this is helpful!

How to make the threading be executed following a fequencey (say, every 3 sec) at a (relatively) accurate time considering function execution time?

I want to run a function every 3 second, and I do have searched similar topics. However, I didn't find any solution that can indeed meet my requirement, and the key issue is that in these solutions, they do not consider the time of executing the function itself. Consider the following code.
import datetime as dt
import time
import threading
def counting():
global num
time_now = dt.datetime.now()
if num > 0:
print(f'count: {num}, time now: {time_now}')
num -= 1
t = threading.Timer(3.0, counting)
t.start()
num = 5
counting()
This prints every 3.0 seconds. The main issue is that in a real case, in stead of print(f'count: {num}, time now: {time_now}') , I will call a function, say, func1(), which will take some time between 1 second and 2.5 seconds. Hence, the real interval time between two calls will be more than 3 seconds (about 4-5.5 seconds). How can I write it to be exactly (of course, very small error is allowed) every 3 seconds between two calls? Thanks!
The way to do it is to use a monotonic clock to find out the current time, and subtract that from the time at which you next want your scheduled function to be called; then you know exactly how long you'll need to sleep for, regardless of how long your func1() took to execute. Here's an example (I removed the threading since the logic is the same regardless of whether it's running in the main thread or some child thread):
import random
import time
def func1():
seconds_to_sleep = random.randrange(1000, 2500) / 1000.0
print("pretending to work for %f seconds" % seconds_to_sleep)
time.sleep(seconds_to_sleep)
def scheduled_function(scheduled_time, now):
if (now > scheduled_time):
print("scheduled_function was called %f seconds late" % (now-scheduled_time))
else:
print("scheduled_function was called %f seconds early" % (scheduled_time-now))
next_call_time = time.monotonic() # schedule the first call to happen right away
while True:
now = time.monotonic()
time_until_call_time = next_call_time-now
if (time_until_call_time > 0.0):
time.sleep(time_until_call_time) # wait until our next scheduled call-time
scheduled_function(next_call_time, time.monotonic())
func1() # sleep for some unpredictable amount of time to simulate a workload
next_call_time = next_call_time + 3.0 # each call should happen ~3 seconds after the previous call
The solution using the Ada programming language is very simple. Ada provides a "delay until" syntax allowing to delay until some future time.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;
with Ada.Calendar.Formatting; use Ada.Calendar.Formatting;
procedure Main is
task periodic;
task body periodic is
Now : Time;
Future : Time;
The_Delay : constant duration := 3.0;
begin
for I in 1..10 loop
Now := Clock;
Put_Line("Periodic message at time " & Image(Now));
Future := Now + The_Delay;
delay 1.0;
delay until Future;
end loop;
end periodic;
begin
null;
end Main;
A recent execution of this program results in the following output (Time is the UTC time zone):
Periodic message at time 2021-05-27 02:26:46
Periodic message at time 2021-05-27 02:26:49
Periodic message at time 2021-05-27 02:26:52
Periodic message at time 2021-05-27 02:26:55
Periodic message at time 2021-05-27 02:26:58
Periodic message at time 2021-05-27 02:27:01
Periodic message at time 2021-05-27 02:27:04
Periodic message at time 2021-05-27 02:27:07
Periodic message at time 2021-05-27 02:27:10
Periodic message at time 2021-05-27 02:27:13
The variable The_Delay is a constant value indicating 3.0 seconds. The Now time is the time at the beginning of each iteration. The Future time is Now plus 3.0 seconds. The resulting time is not offset by the task execution as long as that execution does not exceed 3.0 seconds. In order to simulate a long task execution the task delays (sleeps) 1.0 seconds during each iteration. The "delay 1.0;" statement is an absolute delay while the "delay until Future;" statement is a relative delay.
I guess I got the answer myself. We need to use schedule module. See the examples in https://schedule.readthedocs.io/en/stable/examples.html
The following is my test code.
import datetime as dt
import time
import threading
import schedule
def job():
print("I'm running on thread %s" % threading.current_thread())
print(dt.datetime.now())
time.sleep(2)
def run_threaded(job_func):
job_thread = threading.Thread(target=job_func)
job_thread.start()
schedule.every(6).seconds.do(run_threaded, job)
schedule.every(6).seconds.do(run_threaded, job)
schedule.every(6).seconds.do(run_threaded, job)
t_end = dt.datetime.now() + dt.timedelta(seconds = 20)
while dt.datetime.now() < t_end:
schedule.run_pending()
You can see that I am letting it run for every 6 seconds (by applying three multithread parallel computing), and I do let it sleep for 2 seconds in the function job() to replace the real running time. And from the output result, you will see that it runs every 6 seconds instead of 8 seconds!

How to sleep python script for xx minutes after every hour execution?

I am trying to make a python script that works in a loop mode with iteration through a text file to run for periods of one hour and make 30minute pauses between each hour loop .
After some searching I found this piece of code :
import datetime
import time
delta_hour = 0
while:
now_hour = datetime.datetime.now().hour
if delta_hour != now_hour:
# run your code
delta_hour = now_hour
time.sleep(1800) # 1800 seconds sleep
# add some way to exit the infinite loop
This code has a few issues though :
It does not consider one hour periods since the script starts running
It does not seem to work continuously for periods over one hour
Considering what I am trying to achieve (running script 1hour before each time it pauses for 30mins) what is the best approach to this ? Cron is not an option here .
For clarification :
1hour run -- 30min pause -- repeat
Thanks
Here is a so simple code, I have written for teaching purposes, which is very clear
from datetime import datetime
class control_process():
def __init__(self, woking_period, sleeping_period):
self.woking_period = woking_period # working period in minutes
self.sleeping_period = sleeping_period # sleeping period in minutes
self.reset()
def reset(self):
self.start_time = datetime.utcnow() # set starting point
def manage(self):
m = (datetime.utcnow() - self.start_time).seconds / 60 # how long since starting point
if m >= self.woking_period: # if exceeded the working period
time.sleep(self.sleeping_period * 60) # time to sleep in seconds
self.reset() # then reset time again
return # go to continue working
cp = control_process(60, 30) # release for 60 minutes and sleep for 30 minutes
while True: # you code loop
cp.manage()
'''
your code
'''
in which 'control_processobject - I calledcp- callscp.manage()` inside your executing loop.
you reset time via cp.reset() before going in the loop or whenever you want
Based on Comments
The simplicity I mean is to add this class to your general library so you can use it whenever you want by instantiation of cp then one or two controlling functions 'cp.manage()` which control the working cycles, and cp.reset() if you want to use it in another location of the code. I believe that use a function is better than a long condition statement.
Using the default library you could do something like call the script itself using subprocess. By checking whether conditions are met the process could do a task and call itself. Extending the logic with a kill pill would make it stop (I leave that up to you).
import argparse, time
from subprocess import call
DELAY = 60 * 30 # minutes
WORK_TIME = 60 * 60 # minutes
parser = argparse.ArgumentParser()
parser.add_argument("-s",
help = "interval start time",
type = float,
default = time.time())
parser.add_argument("-t",
help = "interval stop time",
type = float,
default = time.time() + WORK_TIME)
def do_task():
# implement task
print("working..")
return
if __name__ == "__main__":
args = parser.parse_args()
start = args.s
stop = args.t
# work
if start < time.time() < stop:
do_task()
# shift target
else:
start = time.time() + DELAY
stop = start + WORK_TIME
call(f"python test.py -t {stop} -s {start}".split())
The simplest solution I could come up with was the following piece of code, which I added inside my main thread :
start_time = int(time())
... #main thread code
#main thread code end
if int(time() - start_time >= 60 * 60):
print("pausing time")
sleep(30 * 60)
start_time = int(time())
From the moment the script starts this will pause every hour for 30mins and resume afterwards .
Simple yet effective !

How to restart current seconds to 59 and start counting down

I'm trying to make a break timer. I can get the current second value, but I don't know how to reset the seconds and start counting down
I've tried several formulas found here on stack overflow, but have yet to find what I'm looking for
import time
while True:
now = time.localtime(time.time())
print(now[5])
time.sleep(1)
I expect the output to count down from 59 and start over
output: count up from current second
Why don't you use something like:
import time
sec = 0
while True:
print(59 - sec)
time.sleep(1)
sec = (sec + 1) % 60
Here is version with a defined function. It will countdown at defined seconds, taking sleep every seconds.
import time
def countdown(t_sec):
while t_sec:
mins, secs = divmod(t_sec, 60)
timeformat = '{:02d}'.format(secs)
print(timeformat)
time.sleep(1)
t_sec -= 1
countdown(59)

Categories

Resources