Python how to add another time interval to nested while?

Viewed 65

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!

3 Answers

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)

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.

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