|
1 |
| -# delay_ms.py |
| 1 | +# delay_ms.py Now uses ThreadSafeFlag and has extra .wait() API |
| 2 | +# Usage: |
| 3 | +# from primitives.delay_ms import Delay_ms |
2 | 4 |
|
3 |
| -# Copyright (c) 2018-2020 Peter Hinch |
| 5 | +# Copyright (c) 2018-2021 Peter Hinch |
4 | 6 | # Released under the MIT License (MIT) - see LICENSE file
|
5 |
| -# Rewritten for uasyncio V3. Allows stop time to be brought forwards. |
6 | 7 |
|
7 | 8 | import uasyncio as asyncio
|
8 | 9 | from utime import ticks_add, ticks_diff, ticks_ms
|
9 |
| -from micropython import schedule |
10 | 10 | from . import launch
|
11 |
| -# Usage: |
12 |
| -# from primitives.delay_ms import Delay_ms |
13 | 11 |
|
14 | 12 | class Delay_ms:
|
15 |
| - verbose = False # verbose and can_alloc retained to avoid breaking code. |
16 |
| - def __init__(self, func=None, args=(), can_alloc=True, duration=1000): |
| 13 | + |
| 14 | + class DummyTimer: # Stand-in for the timer class. Can be cancelled. |
| 15 | + def cancel(self): |
| 16 | + pass |
| 17 | + _fake = DummyTimer() |
| 18 | + |
| 19 | + def __init__(self, func=None, args=(), duration=1000): |
17 | 20 | self._func = func
|
18 | 21 | self._args = args
|
19 |
| - self._duration = duration # Default duration |
20 |
| - self._tstop = None # Stop time (ms). None signifies not running. |
21 |
| - self._tsave = None # Temporary storage for stop time |
22 |
| - self._ktask = None # timer task |
23 |
| - self._retrn = None # Return value of launched callable |
24 |
| - self._do_trig = self._trig # Avoid allocation in .trigger |
| 22 | + self._durn = duration # Default duration |
| 23 | + self._retn = None # Return value of launched callable |
| 24 | + self._tend = None # Stop time (absolute ms). |
| 25 | + self._busy = False |
| 26 | + self._trig = asyncio.ThreadSafeFlag() |
| 27 | + self._tout = asyncio.Event() # Timeout event |
| 28 | + self.wait = self._tout.wait # Allow: await wait_ms.wait() |
| 29 | + self._ttask = self._fake # Timer task |
| 30 | + asyncio.create_task(self._run()) |
25 | 31 |
|
26 |
| - def stop(self): |
27 |
| - if self._ktask is not None: |
28 |
| - self._ktask.cancel() |
| 32 | + async def _run(self): |
| 33 | + while True: |
| 34 | + await self._trig.wait() # Await a trigger |
| 35 | + self._ttask.cancel() # Cancel and replace |
| 36 | + await asyncio.sleep_ms(0) |
| 37 | + dt = max(ticks_diff(self._tend, ticks_ms()), 0) # Beware already elapsed. |
| 38 | + self._ttask = asyncio.create_task(self._timer(dt)) |
29 | 39 |
|
30 |
| - def trigger(self, duration=0): # Update end time |
31 |
| - now = ticks_ms() |
32 |
| - if duration <= 0: # Use default set by constructor |
33 |
| - duration = self._duration |
34 |
| - self._retrn = None |
35 |
| - is_running = self() |
36 |
| - tstop = self._tstop # Current stop time |
37 |
| - # Retriggering normally just updates ._tstop for ._timer |
38 |
| - self._tstop = ticks_add(now, duration) |
39 |
| - # Identify special case where we are bringing the end time forward |
40 |
| - can = is_running and duration < ticks_diff(tstop, now) |
41 |
| - if not is_running or can: |
42 |
| - schedule(self._do_trig, can) |
| 40 | + async def _timer(self, dt): |
| 41 | + await asyncio.sleep_ms(dt) |
| 42 | + self._tout.set() # Only gets here if not cancelled. |
| 43 | + self._tout.clear() |
| 44 | + self._busy = False |
| 45 | + if self._func is not None: |
| 46 | + self._retn = launch(self._func, self._args) |
43 | 47 |
|
44 |
| - def _trig(self, can): |
45 |
| - if can: |
46 |
| - self._ktask.cancel() |
47 |
| - self._ktask = asyncio.create_task(self._timer(can)) |
| 48 | +# API |
| 49 | + # trigger may be called from hard ISR. |
| 50 | + def trigger(self, duration=0): # Update absolute end time, 0-> ctor default |
| 51 | + self._tend = ticks_add(ticks_ms(), duration if duration > 0 else self._durn) |
| 52 | + self._retn = None # Default in case cancelled. |
| 53 | + self._busy = True |
| 54 | + self._trig.set() |
| 55 | + |
| 56 | + def stop(self): |
| 57 | + self._ttask.cancel() |
| 58 | + self._ttask = self._fake |
| 59 | + self._busy = False |
48 | 60 |
|
49 | 61 | def __call__(self): # Current running status
|
50 |
| - return self._tstop is not None |
| 62 | + return self._busy |
51 | 63 |
|
52 | 64 | running = __call__
|
53 | 65 |
|
54 | 66 | def rvalue(self):
|
55 |
| - return self._retrn |
56 |
| - |
57 |
| - async def _timer(self, restart): |
58 |
| - if restart: # Restore cached end time |
59 |
| - self._tstop = self._tsave |
60 |
| - try: |
61 |
| - twait = ticks_diff(self._tstop, ticks_ms()) |
62 |
| - while twait > 0: # Must loop here: might be retriggered |
63 |
| - await asyncio.sleep_ms(twait) |
64 |
| - twait = ticks_diff(self._tstop, ticks_ms()) |
65 |
| - if self._func is not None: # Timed out: execute callback |
66 |
| - self._retrn = launch(self._func, self._args) |
67 |
| - finally: |
68 |
| - self._tsave = self._tstop # Save in case we restart. |
69 |
| - self._tstop = None # timer is stopped |
| 67 | + return self._retn |
0 commit comments