Coverage for C:\Users\hjanssen\HOME\pyCharmProjects\ethz_hvl\hvl_ccb\hvl_ccb\dev\utils.py : 94%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (c) 2020 ETH Zurich, SIS ID and HVL D-ITET
2#
3import logging
4import time
5from concurrent.futures._base import Future
6from concurrent.futures.thread import ThreadPoolExecutor
7from threading import Event
8from typing import Callable, Optional
10from ..utils.typing import Number
12logger = logging.getLogger(__name__)
15class Poller:
16 """
17 Poller class wrapping `concurrent.futures.ThreadPoolExecutor` which enables passing
18 of results and errors out of the polling thread.
19 """
21 def __init__(
22 self,
23 spoll_handler: Callable,
24 polling_delay_sec: Number = 0,
25 polling_interval_sec: Number = 1,
26 polling_timeout_sec: Optional[Number] = None,
27 ):
28 """
29 Initialize the polling helper.
31 :param spoll_handler: Polling function.
32 :param polling_delay_sec: Delay before starting the polling, in seconds.
33 :param polling_interval_sec: Polling interval, in seconds.
34 """
35 self.spoll_handler: Callable = spoll_handler
36 self.polling_delay_sec: Number = polling_delay_sec
37 self.polling_interval_sec: Number = polling_interval_sec
38 self.polling_timeout_sec: Optional[Number] = polling_timeout_sec
39 self._polling_future: Optional[Future] = None
40 self._polling_stop_event: Optional[Event] = None
42 def is_polling(self) -> bool:
43 """
44 Check if device status is being polled.
46 :return: `True` when polling thread is set and alive
47 """
48 return self._polling_future is not None and self._polling_future.running()
50 def _if_poll_again(
51 self, stop_event: Event, delay_sec: Number, stop_time: Optional[Number]
52 ) -> bool:
53 """
54 Check if to poll again.
56 :param stop_event: Polling stop event.
57 :param delay_sec: Delay time (in seconds).
58 :param stop_time: Absolute stop time.
59 :return: `True` if another polling handler call is due, `False` otherwise.
60 """
61 not_stopped = not stop_event.wait(delay_sec)
62 not_timeout = stop_time is None or time.time() < stop_time
63 return not_stopped and not_timeout
65 def _poll_until_stop_or_timeout(self, stop_event: Event) -> Optional[object]:
66 """
67 Thread for polling until stopped or timed-out.
69 :param stop_event: Event used to stop the polling
70 :return: Last result of the polling function call
71 """
72 start_time = time.time()
73 stop_time = (
74 (start_time + self.polling_timeout_sec)
75 if self.polling_timeout_sec
76 else None
77 )
78 last_result = None
80 if self._if_poll_again(stop_event, self.polling_delay_sec, stop_time):
81 last_result = self.spoll_handler()
83 while self._if_poll_again(stop_event, self.polling_interval_sec, stop_time):
84 last_result = self.spoll_handler()
86 return last_result
88 def start_polling(self) -> bool:
89 """
90 Start polling.
92 :return: `True` if was not polling before, `False` otherwise
93 """
94 was_not_polling = not self.is_polling()
96 if was_not_polling:
97 logger.info("Start polling")
98 self._polling_stop_event = Event()
99 pool = ThreadPoolExecutor(max_workers=1)
100 self._polling_future = pool.submit(
101 self._poll_until_stop_or_timeout, self._polling_stop_event
102 )
104 return was_not_polling
106 def stop_polling(self) -> bool:
107 """
108 Stop polling.
110 Wait for until polling function returns a result as well as any exception that
111 might have been raised within a thread.
113 :return: `True` if was polling before, `False` otherwise, and last result of
114 the polling function call.
115 :raises: polling function exceptions
116 """
117 was_polling = self.is_polling()
119 if was_polling:
120 logger.info("Stop polling")
121 if self._polling_stop_event is None:
122 raise RuntimeError("Was polling but stop event is missing.")
123 self._polling_stop_event.set()
124 if self._polling_future is None:
125 raise RuntimeError("Was polling but polling future is missing.")
127 return was_polling
129 def wait_for_polling_result(self):
130 """
131 Wait for until polling function returns a result as well as any exception that
132 might have been raised within a thread.
134 :return: polling function result
135 :raises: polling function errors
136 """
138 return self._polling_future.result()