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# 

3import logging 

4import re 

5from datetime import datetime, timedelta 

6from time import sleep 

7from typing import Type, Union, Optional 

8 

9from bitstring import BitArray 

10 

11from .base import SingleCommDevice 

12from .utils import Poller 

13from ..comm import VisaCommunication, VisaCommunicationConfig 

14from ..configuration import configdataclass 

15from ..utils.typing import Number 

16 

17 

18@configdataclass 

19class _VisaDeviceConfigBase: 

20 """ 

21 Required VisaDeviceConfig keys, separated from the default ones to enable config 

22 extension by inheritance with required keys. 

23 """ 

24 

25 # NOTE: this class is unnecessary as there are no keys here; it's coded here only 

26 # to illustrate a solution; for detailed explanations of the issue see: 

27 # https://stackoverflow.com/questions/51575931/class-inheritance-in-python-3-7-dataclasses/ 

28 pass 

29 

30 

31@configdataclass 

32class _VisaDeviceConfigDefaultsBase: 

33 

34 spoll_interval: Number = 0.5 

35 """ 

36 Seconds to wait between status polling. 

37 """ 

38 

39 spoll_start_delay: Number = 2 

40 """ 

41 Seconds to delay the start of status polling. 

42 """ 

43 

44 def clean_values(self): 

45 if self.spoll_interval <= 0: 

46 raise ValueError("Polling interval needs to be positive.") 

47 

48 if self.spoll_start_delay < 0: 

49 raise ValueError("Polling start delay needs to be non-negative.") 

50 

51 

52@configdataclass 

53class VisaDeviceConfig(_VisaDeviceConfigDefaultsBase, _VisaDeviceConfigBase): 

54 """ 

55 Configdataclass for a VISA device. 

56 """ 

57 

58 pass 

59 

60 

61class VisaDevice(SingleCommDevice): 

62 """ 

63 Device communicating over the VISA protocol using VisaCommunication. 

64 """ 

65 

66 def __init__( 

67 self, 

68 com: Union[VisaCommunication, VisaCommunicationConfig, dict], 

69 dev_config: Union[VisaDeviceConfig, dict, None] = None, 

70 ) -> None: 

71 

72 super().__init__(com, dev_config) 

73 

74 self._spoll_thread: Union[Poller, None] = None 

75 

76 self._notify_operation_complete: bool = False 

77 

78 @staticmethod 

79 def default_com_cls() -> Type[VisaCommunication]: 

80 """ 

81 Return the default communication protocol for this device type, which is 

82 VisaCommunication. 

83 

84 :return: the VisaCommunication class 

85 """ 

86 

87 return VisaCommunication 

88 

89 @staticmethod 

90 def config_cls(): 

91 return VisaDeviceConfig 

92 

93 def get_identification(self) -> str: 

94 """ 

95 Queries `"*IDN?"` and returns the identification string of the connected device. 

96 

97 :return: the identification string of the connected device 

98 """ 

99 

100 return self.com.query("*IDN?") 

101 

102 def start(self) -> None: 

103 """ 

104 Start the VisaDevice. Sets up the status poller and starts it. 

105 

106 :return: 

107 """ 

108 super().start() 

109 self._spoll_thread = Poller( 

110 polling_interval_sec=self.config.spoll_interval, 

111 polling_delay_sec=self.config.spoll_start_delay, 

112 spoll_handler=self.spoll_handler, 

113 ) 

114 self._spoll_thread.start_polling() 

115 

116 def stop(self) -> None: 

117 """ 

118 Stop the VisaDevice. Stops the polling thread and closes the communication 

119 protocol. 

120 

121 :return: 

122 """ 

123 if self._spoll_thread: 

124 self._spoll_thread.stop_polling() 

125 super().stop() 

126 

127 def spoll_handler(self): 

128 """ 

129 Reads the status byte and decodes it. The status byte STB is defined in 

130 IEEE 488.2. It provides a rough overview of the instrument status. 

131 

132 :return: 

133 """ 

134 stb = self.com.spoll() 

135 

136 if stb: 

137 bits = BitArray(length=8, int=stb) 

138 bits.reverse() 

139 

140 if bits[0]: 

141 # has no meaning, always zero 

142 pass 

143 

144 if bits[1]: 

145 # has no meaning, always zero 

146 pass 

147 

148 if bits[2]: 

149 # error queue contains new error 

150 logging.debug(f"Error bit set in STB: {stb}") 

151 self.get_error_queue() 

152 

153 if bits[3]: 

154 # Questionable Status QUES summary bit 

155 logging.debug(f"Questionable status bit set in STB: {stb}") 

156 

157 if bits[4]: 

158 # Output buffer holds data (RTO 1024), MAV bit (Message available) 

159 pass 

160 

161 if bits[5]: 

162 # Event status byte ESB, summary of ESR register (RTO 1024) 

163 logging.debug(f"Operation status bit set in STB: {stb}") 

164 

165 # read event status register 

166 esr = int(self.com.query("*ESR?")) 

167 esr_bits = BitArray(length=8, int=esr) 

168 esr_bits.reverse() 

169 

170 if esr_bits[0]: 

171 # Operation complete bit set. This bit is set on receipt of the 

172 # command *OPC exactly when all previous commands have been 

173 # executed. 

174 logging.debug(f"Operation complete bit set in ESR: {esr}") 

175 self._notify_operation_complete = True 

176 

177 if bits[6]: 

178 # RQS/MSS bit (RTO 1024) 

179 pass 

180 

181 if bits[7]: 

182 # Operation Status OPER summary bit 

183 pass 

184 

185 def wait_operation_complete(self, timeout: Optional[float] = None) -> bool: 

186 """ 

187 Waits for a operation complete event. Returns after timeout [s] has expired 

188 or the operation complete event has been caught. 

189 

190 :param timeout: Time in seconds to wait for the event; `None` for no timeout. 

191 :return: True, if OPC event is caught, False if timeout expired 

192 """ 

193 

194 # reset event bit 

195 self._notify_operation_complete = False 

196 

197 # compute timeout 

198 timeout_time = datetime.now() + timedelta(seconds=(timeout or 0)) 

199 

200 # wait until event is caught 

201 while not self._notify_operation_complete: 

202 sleep(0.01) 

203 if timeout is not None and datetime.now() > timeout_time: 

204 break 

205 

206 # if event was caught, return true 

207 if self._notify_operation_complete: 

208 self._notify_operation_complete = False 

209 return True 

210 

211 # if timeout expired, return false 

212 return False 

213 

214 def get_error_queue(self) -> str: 

215 """ 

216 Read out error queue and logs the error. 

217 

218 :return: Error string 

219 """ 

220 

221 err_string = self.com.query("SYSTem:ERRor:ALL?") 

222 for error in re.findall("[^,]+,[^,]+", err_string): 

223 logging.error(f"VISA Error from Device: {error}") 

224 return err_string 

225 

226 def reset(self) -> None: 

227 """ 

228 Send `"*RST"` and `"*CLS"` to the device. Typically sets a defined state. 

229 """ 

230 

231 self.com.write("*RST", "*CLS")