Coverage for C:\Users\hjanssen\HOME\pyCharmProjects\ethz_hvl\hvl_ccb\hvl_ccb\dev\visa.py: 39%

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

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

2#

3 import logging

4 import re

5 from datetime import datetime, timedelta

6 from time import sleep

7 from typing import Type, Union, Optional

8

9 from bitstring import BitArray

10

11 from .base import SingleCommDevice

12 from .utils import Poller

13 from ..comm import VisaCommunication, VisaCommunicationConfig

14 from ..configuration import configdataclass

15 from ..utils.typing import Number

16

17

18@configdataclass

19 class _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

32 class _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

53 class VisaDeviceConfig(_VisaDeviceConfigDefaultsBase, _VisaDeviceConfigBase):

54 """

55 Configdataclass for a VISA device.

56 """

57

58 pass

59

60

61 class 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")