Hide keyboard shortcuts

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) 2019-2020 ETH Zurich, SIS ID and HVL D-ITET 

2# 

3""" 

4Main module containing the top level ExperimentManager class. 

5Inherit from this class to implement your own experiment functionality in another 

6project and it will help you start, stop and manage your devices. 

7""" 

8 

9import logging 

10from enum import Enum 

11from typing import Dict 

12 

13from .dev import Device, DeviceSequenceMixin 

14 

15 

16class ExperimentStatus(Enum): 

17 """ 

18 Enumeration for the experiment status 

19 """ 

20 

21 INITIALIZING = -1 

22 INITIALIZED = 0 

23 STARTING = 1 

24 RUNNING = 2 

25 FINISHING = 3 

26 FINISHED = 4 

27 ERROR = 5 

28 

29 

30class ExperimentError(Exception): 

31 """ 

32 Exception to indicate that the current status of the experiment manager is on 

33 ERROR and thus no operations can be made until reset. 

34 """ 

35 

36 

37class ExperimentManager(DeviceSequenceMixin): 

38 """ 

39 Experiment Manager can start and stop communication protocols and devices. 

40 It provides methods to queue commands to 

41 devices and collect results. 

42 """ 

43 

44 def __new__(cls, *args, **kwargs) -> "ExperimentManager": 

45 """ 

46 Construct a new `ExperimentManager` instance. 

47 

48 :param args: `__init__` arguments 

49 :param kwargs: `__init__` keyword arguments 

50 :return: `ExperimentManager` with status set to `ExperimentStatus.INITIALIZING`. 

51 """ 

52 obj = super().__new__(cls) 

53 # experiment manager status 

54 obj._status = ExperimentStatus.INITIALIZING 

55 return obj 

56 

57 def __init__(self, devices: Dict[str, Device]) -> None: 

58 """ 

59 Initialize `ExperimentManager`. Takes a dictionary of instantiated devices 

60 and initializes the experiment status to `ExperimentStatus.INITIALIZED`. 

61 

62 :param devices: Devices sequenced in the experiment. 

63 """ 

64 super().__init__(devices) 

65 self._change_status(ExperimentStatus.INITIALIZED) 

66 

67 def _change_status(self, new_status: ExperimentStatus) -> None: 

68 """ 

69 Change the status of this experiment manager to a new value. Includes logging. 

70 

71 :param new_status: is the Enum of the new experiment status. 

72 """ 

73 

74 if new_status is not self._status: 

75 log_msg = f"Experiment Status: {new_status} {new_status.name}" 

76 # new status is different, log 

77 if new_status is ExperimentStatus.ERROR: 

78 logging.error(log_msg) 

79 else: 

80 logging.info(log_msg) 

81 self._status: ExperimentStatus = new_status 

82 

83 def run(self) -> None: 

84 """ 

85 Start experimental setup, start all devices. 

86 """ 

87 

88 self._change_status(ExperimentStatus.STARTING) 

89 

90 try: 

91 super().start() 

92 except Exception as e: 

93 logging.error(e) 

94 self._change_status(ExperimentStatus.ERROR) 

95 raise ExperimentError from e 

96 else: 

97 self._change_status(ExperimentStatus.RUNNING) 

98 

99 def finish(self) -> None: 

100 """ 

101 Stop experimental setup, stop all devices. 

102 """ 

103 

104 self._change_status(ExperimentStatus.FINISHING) 

105 

106 try: 

107 super().stop() 

108 except Exception as e: 

109 logging.error(e) 

110 self._change_status(ExperimentStatus.ERROR) 

111 raise ExperimentError from e 

112 else: 

113 self._change_status(ExperimentStatus.FINISHED) 

114 

115 def start(self) -> None: 

116 """ 

117 Alias for ExperimentManager.run() 

118 """ 

119 self.run() 

120 

121 def stop(self) -> None: 

122 """ 

123 Alias for ExperimentManager.finish() 

124 """ 

125 self.finish() 

126 

127 @property 

128 def status(self) -> ExperimentStatus: 

129 """ 

130 Get experiment status. 

131 

132 :return: experiment status enum code. 

133 """ 

134 

135 return self._status 

136 

137 def is_finished(self) -> bool: 

138 """ 

139 Returns true, if the status of the experiment manager is `finished`. 

140 

141 :return: True if finished, false otherwise 

142 """ 

143 return self.status == ExperimentStatus.FINISHED 

144 

145 def is_running(self) -> bool: 

146 """ 

147 Returns true, if the status of the experiment manager is `running`. 

148 

149 :return: True if running, false otherwise 

150 """ 

151 return self.status == ExperimentStatus.RUNNING 

152 

153 def is_error(self) -> bool: 

154 """ 

155 Returns true, if the status of the experiment manager is `error`. 

156 

157 :return: True if on error, false otherwise 

158 """ 

159 return self.status == ExperimentStatus.ERROR 

160 

161 def add_device(self, name: str, device: Device) -> None: 

162 """ 

163 Add a new device to the manager. If the experiment is running, automatically 

164 start the device. If a device with this name already exists, raise an exception. 

165 

166 :param name: is the name of the device. 

167 :param device: is the instantiated Device object. 

168 :raise DeviceExistingException: 

169 """ 

170 

171 if self.status == ExperimentStatus.ERROR: 

172 logging.error("Experiment is on ERROR, cannot add device") 

173 raise ExperimentError 

174 

175 super().add_device(name, device) 

176 

177 # check experiment status and start device if started 

178 if self.status == ExperimentStatus.RUNNING: 

179 logging.info(f"Experiment is already RUNNING, start Device {device}") 

180 try: 

181 device.start() 

182 except Exception as e: 

183 logging.error(e) 

184 self._change_status(ExperimentStatus.ERROR) 

185 raise ExperimentError from e