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

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

## 51 statements

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#

3 import logging

4 import time

5 from concurrent.futures._base import Future

6 from concurrent.futures.thread import ThreadPoolExecutor

7 from threading import Event

8 from typing import Callable, Optional

9

10 from ..utils.typing import Number

11

12 logger = logging.getLogger(__name__)

13

14

15 class Poller:

16 """

17 Poller class wrapping `concurrent.futures.ThreadPoolExecutor` which enables passing

18 of results and errors out of the polling thread.

19 """

20

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.

30

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

41

42 def is_polling(self) -> bool:

43 """

44 Check if device status is being polled.

45

46 :return: `True` when polling thread is set and alive

47 """

48 return self._polling_future is not None and self._polling_future.running()

49

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.

55

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

64

65 def _poll_until_stop_or_timeout(self, stop_event: Event) -> Optional[object]:

66 """

67 Thread for polling until stopped or timed-out.

68

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

79

80 if self._if_poll_again(stop_event, self.polling_delay_sec, stop_time):

81 last_result = self.spoll_handler()

82

83 while self._if_poll_again(stop_event, self.polling_interval_sec, stop_time):

84 last_result = self.spoll_handler()

85

86 return last_result

87

88 def start_polling(self) -> bool:

89 """

90 Start polling.

91

92 :return: `True` if was not polling before, `False` otherwise

93 """

94 was_not_polling = not self.is_polling()

95

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 )

103

104 return was_not_polling

105

106 def stop_polling(self) -> bool:

107 """

108 Stop polling.

109

110 Wait for until polling function returns a result as well as any exception that

111 might have been raised within a thread.

112

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()

118

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.")

126

127 return was_polling

128

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.

133

134 :return: polling function result

135 :raises: polling function errors

136 """

137

138 return self._polling_future.result()