Python how to add another time interval to nested while? - python

for measurement from my custom weather station I'm currently using two nested while loops to gather measuremet in 5seconds during 5min period as follows:
interval = 300
short_interval = 5
while True:
start_time = time.time()
while time.time() - start_time <= interval:
measurement_start = time.time()
reset_counter()
while time.time() - measurement_start <= short_interval:
store_directions.append(wind_direction.get_value())
final_speed = calculate_speed(wind_interval)
store_speeds.append(final_speed)
measure_rain()
do other stuff, mqtt etc.
which works fine - storing every 5seconds and sending everything out every 5mins
What I'm bit struggling is to accomodate another timeinterval of 60mins to gather rainfall.
Now it's measured every 5mins with measure_rain()
What would be best way how to keep 5min measurements for wind and another one for rainfall which will take 60min?
Another while? I was trying that, but ended up messing everything up ;)
Thank you for any help!

That's a task for concurrent programming. Luckily, Python has native support for it using Coroutines and Tasks. You can either roll your own based on the event loop and the example given there, or, look at what's out there already. Two quick picks:
asyncio-periodic looks minimal; less than 200 LoC class; seems to do one thing (execute periodic tasks every N seconds). Not actively developed (but why would it need to?)
schedule: much larger, but expressive syntax (schedule.every(10).minutes.do(job)) and looking well maintained. Bigger codebase, but advertised as having no further dependencies.

you can define each sensor as class and assign short and long intervals for each sensor. However this is CPU intensive work because of while loop without sleep.
import time
class wind:
big_interval=10
short_interval=2
st_bigInterval=time.time()
st_shortInterval=time.time()
class rain:
big_interval=10
short_interval=2
st_bigInterval=time.time()
st_shortInterval=time.time()
while True:
if(time.time()-wind.st_shortInterval>wind.short_interval):
print("collect wind data")
wind.st_shortInterval = time.time()
if(time.time()-rain.st_shortInterval>rain.short_interval):
print("collect rain data")
rain.st_shortInterval = time.time()
if(time.time()-wind.st_bigInterval>wind.big_interval):
print("send wind data")
wind.st_bigInterval = time.time()
wind.st_shortInterval = time.time()
if(time.time()-rain.st_bigInterval>rain.big_interval):
print("send rain data")
rain.st_bigInterval = time.time()
rain.st_shortInterval = time.time()
More better approach is to use timeloop as below.
import time
from timeloop import Timeloop
from datetime import timedelta
tl = Timeloop()
#tl.job(interval=timedelta(seconds=2))
def collectWindData():
print( "2s job current time : {}".format(time.ctime()), "collect wind data")
#tl.job(interval=timedelta(seconds=5))
def sendWindData():
print ("5s job current time : {}".format(time.ctime()), "send wind data")
#tl.job(interval=timedelta(seconds=2))
def collectRainData():
print( "2s job current time : {}".format(time.ctime()), "collect rain data")
#tl.job(interval=timedelta(seconds=5))
def sendRainData():
print ("5s job current time : {}".format(time.ctime()), "send rain data")
tl.start()
while True:
time.sleep(10)

a roll your own using the built in asyncio module. It's a strange new way of thinking about programming and can be quite challenge to get your head around. Below is a niave implementation if you don't need to be precise on when you measured. You can make a precise version using a priority queue to put the next measurement first. A common approach to this problem is used in collision mechanics for games programming.
import asyncio
import random
async def measure_rain():
while True:
print(f'rainfall {random.randint(0,100)}') #measure rainfall
await asyncio.sleep(1)
return True
async def measure_wind():
while True:
print(f'windspeed: {float(random.randint(0, 100)/10)} m/s') #measure windspeed
await asyncio.sleep(3)
return True
async def main():
tasks = []
tasks.append(asyncio.create_task(measure_rain()))
tasks.append(asyncio.create_task(measure_wind()))
await *tasks
asyncio.run(main())

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 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 !

Have python.sleep variate with computation time

For an animation project, we want to model a variable number of objects. This means that the computation and rendering part will take variable computation time. But we want the frame rate of the animation to remain constant. So we would like to compute how much time a section of code has taken, and if it is less then the expected frame rate, we wait the remainded before calculation the next frame.
Is there an easy way to do this?
One way to do it is by using the time library that has a method time that allows you to count how much time a section of code has taken to execute, an example down below.
import time
start_time = time.time()
#Your code here
end_time = time.time() - start_time
print(end_time)
You stopwatch the time, and sleep it for 1-x seconds.
sleep_time = 1/1 #Second number is frames per unit of time, first number is unit of time. In seconds.
from time import time #Important function: time(): Secs since unix epoch
while True: #Init animation
init_time = time() #Init stopwatch
#Code here Do your code
timer = time()-init_time #End stopwatch and remember time in a variable.
while time() < init_time+sleep_time-timer: pass #Wait ___ seconds
#Restart and go to next frame.
If the code that you coded in lasts longer than 'sleep time', than it will not wait.
Hope this helps, and good luck with animating!

Set a timer for running a process, pause the timer under certain conditions

I've got this program:
import multiprocessing
import time
def timer(sleepTime):
time.sleep(sleepTime)
fooProcess.terminate()
fooProcess.join() #line said to "cleanup", not sure if it is required, refer to goo.gl/Qes6KX
def foo():
i=0
while 1
print i
time.sleep(1)
i
if i==4:
#pause timerProcess for X seconds
fooProcess = multiprocessing.Process(target=foo, name="Foo", args=())
timer()
fooProcess.start()
And as you can see in the comment, under certain conditions (in this example i has to be 4) the timer has to stop for a certain X time, while foo() keeps working.
Now, how do I implement this?
N.B.: this code is just an example, the point is that I want to pause a process under certain conditions for a certain amount of time.
I am think you're going about this wrong for game design. Games always (no exceptions come to mind) use a primary event loop controlled in software.
Each time through the loop you check the time and fire off all the necessary events based on how much time has elapsed. At the end of the loop you sleep only as long as necessary before you got the next timer or event or refresh or ai check or other state change.
This gives you the best performance regarding lag, consistency, predictability, and other timing features that matter in games.
roughly:
get the current timestamp at the time start time (time.time(), I presume)
sleep with Event.wait(timeout=...)
wake up on an Event or timeout.
if on Event: get timestamp, subtract initial on, subtract result from timer; wait until foo() stops; repeat Event.wait(timeout=[result from 4.])
if on timeout: exit.
Here is an example, how I understand, what your Programm should do:
import threading, time, datetime
ACTIVE = True
def main():
while ACTIVE:
print "im working"
time.sleep(.3)
def run(thread, timeout):
global ACTIVE
thread.start()
time.sleep(timeout)
ACTIVE = False
thread.join()
proc = threading.Thread(target = main)
print datetime.datetime.now()
run(proc, 2) # run for 2 seconds
print datetime.datetime.now()
In main() it does a periodic task, here printing something. In the run() method you can say, how long main should do the task.
This code producess following output:
2014-05-25 17:10:54.390000
im working
im working
im working
im working
im working
im working
im working
2014-05-25 17:10:56.495000
please correct me, if I've understood you wrong.
I would use multiprocessing.Pipe for signaling, combined with select for timing:
#!/usr/bin/env python
import multiprocessing
import select
import time
def timer(sleeptime,pipe):
start = time.time()
while time.time() < start + sleeptime:
n = select.select([pipe],[],[],1) # sleep in 1s intervals
for conn in n[0]:
val = conn.recv()
print 'got',val
start += float(val)
def foo(pipe):
i = 0
while True:
print i
i += 1
time.sleep(1)
if i%7 == 0:
pipe.send(5)
if __name__ == '__main__':
mainpipe,foopipe = multiprocessing.Pipe()
fooProcess = multiprocessing.Process(target=foo,name="Foo",args=(foopipe,))
fooProcess.start()
timer(10,mainpipe)
fooProcess.terminate()
# since we terminated, mainpipe and foopipe are corrupt
del mainpipe, foopipe
# ...
print 'Done'
I'm assuming that you want some condition in the foo process to extend the timer. In the sample I have set up, every time foo hits a multiple of 7 it extends the timer by 5 seconds while the timer initially counts down 10 seconds. At the end of the timer we terminate the process - foo won't finish nicely at all, and the pipes will get corrupted, but you can be certain that it'll die. Otherwise you can send a signal back along mainpipe that foo can listen for and exit nicely while you join.

How do I ensure that a Python while-loop takes a particular amount of time to run?

I'm reading serial data with a while loop. However, I have no control over the sample rate.
The code itself seems to take 0.2s to run, so I know I won't be able to go any faster than that. But I would like to be able to control precisely how much slower I sample.
I feel like I could do it using 'sleep', but the problem is that there is potential that at different points the loop itself will take longer to read(depending on precisely what is being transmitted over serial data), so the code would have to make up the balance.
For example, let's say I want to sample every 1s, and the loop takes anywhere from 0.2s to 0.3s to run. My code needs to be smart enough to sleep for 0.8s (if the loop takes 0.2s) or 0.7s (if the loop takes 0.3s).
import serial
import csv
import time
#open serial stream
while True:
#read and print a line
sample_value=ser.readline()
sample_time=time.time()-zero
sample_line=str(sample_time)+','+str(sample_value)
outfile.write(sample_line)
print 'time: ',sample_time,', value: ',sample_value
Just measure the time running your code takes every iteration of the loop, and sleep accordingly:
import time
while True:
now = time.time() # get the time
do_something() # do your stuff
elapsed = time.time() - now # how long was it running?
time.sleep(1.-elapsed) # sleep accordingly so the full iteration takes 1 second
Of course not 100% perfect (maybe off one millisecond or another from time to time), but I guess it's good enough.
Another nice approach is using twisted's LoopingCall:
from twisted.internet import task
from twisted.internet import reactor
def do_something():
pass # do your work here
task.LoopingCall(do_something).start(1.0)
reactor.run()
An rather elegant method is you're working on UNIX : use the signal library
The code :
import signal
def _handle_timeout():
print "timeout hit" # Do nothing here
def second(count):
signal.signal(signal.SIGALRM, _handle_timeout)
signal.alarm(1)
try:
count += 1 # put your function here
signal.pause()
finally:
signal.alarm(0)
return count
if __name__ == '__main__':
count = 0
count = second(count)
count = second(count)
count = second(count)
count = second(count)
count = second(count)
print count
And the timing :
georgesl#cleese:~/Bureau$ time python timer.py
5
real 0m5.081s
user 0m0.068s
sys 0m0.004s
Two caveats though : it only works on *nix, and it is not multithread-safe.
At the beginning of the loop check if the appropriate amount of time has passed. If it has not, sleep.
# Set up initial conditions for sample_time outside the loop
sample_period = ???
next_min_time = 0
while True:
sample_time = time.time() - zero
if sample_time < next_min_time:
time.sleep(next_min_time - sample_time)
continue
# read and print a line
sample_value = ser.readline()
sample_line = str(sample_time)+','+str(sample_value)
outfile.write(sample_line)
print 'time: {}, value: {}'.format(sample_time, sample_value)
next_min_time = sample_time + sample_period

Categories

Resources