Sleep until a certain amount of time has passed - python

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!

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. '''
...

How to add a component of randomness to a schedule

I am trying to schedule a job to run every 3 minutes on average, with a component of randomness between +/- 30 seconds. So the next job runs anywhere between 2mins 30secs - 3mins 30secs later.
This code works nicely for exactly 3 minutes, but I can't think of a way to introduce the 30 secs of randomness:
import schedule
def job:
print('hi')
schedule.every(3).minutes.do(job)
You need to look up the random module that comes with python.
import schedule
import random
def job():
print('hi')
two_mins_30 = 2 * 60 + 30
schedule.every(two_mins_30 + random.randint(0, 60)).seconds.do(job)
This calculation is: two minutes, 30 plus up to another minute at random.
Update:
It turns out you can directly do this with schedule because the Job class has a to() method:
schedule.every(two_mins_30).to(two_mins_30 + 60).seconds.do(job)

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!

Timer for variable time delay

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 )

Can I call the current time two times in one function?

This is my code:
import datetime, time
def function():
start = datetime.now()
...
stop = datetime.now()
result = (stop - start).total_seconds()
return result
But when I execute it, it returns 0... why tho
If you don't need the date, you could just run the following:
import time
def function():
start_time = time.time()
time.sleep(5)
end_time = time.time()
print(f'Completed in: {end_time - start_time} seconds')
function()
As is, the code does not run at all and not return anything. Instead, I get an Attribute error:
AttributeError: module 'datetime' has no attribute 'now'
When I fix your code (from datetime import datetime) and run it, the result is
0:00:00
which is likely hours, minutes and seconds. That is with ... as the only command between start and stop.
If the ... part takes muss less than a millisecond, it may not be detected. I doubt that it takes 50 ms as stated by you in a comment (see also below).
Can I call the current time two times in one function?
Yes:
import time
from datetime import datetime
def function():
start = datetime.now()
time.sleep(.001)
stop = datetime.now()
result = stop - start
return result
print(function())
0:00:00.001994
As you see, at 1 ms sleep time, the measured time is off by ~100% and times are not accurate any more.
it does the right thing for me under Linux (5.3), i.e:
from datetime import datetime
(datetime.now() - datetime.now()).total_seconds()
evaluates to -8e-06 which is good. I'd therefore assume the timer used under your Windows system isn't very high precision. I'd suggest using time.perf_counter() for these sorts of short durations, datetime is more for when you care about obscure human things like months.
for example
from time import perf_counter
perf_counter() - perf_counter()
gives me approx -2.8e-7, so we've also reduced the duration of the call to a few hundred nanoseconds.

Categories

Resources