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

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
9from bitstring import BitArray
11from .base import SingleCommDevice
12from .utils import Poller
13from ..comm import VisaCommunication, VisaCommunicationConfig
14from ..configuration import configdataclass
15from ..utils.typing import Number
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 """
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
31@configdataclass
32class _VisaDeviceConfigDefaultsBase:
34 spoll_interval: Number = 0.5
35 """
36 Seconds to wait between status polling.
37 """
39 spoll_start_delay: Number = 2
40 """
41 Seconds to delay the start of status polling.
42 """
44 def clean_values(self):
45 if self.spoll_interval <= 0:
46 raise ValueError("Polling interval needs to be positive.")
48 if self.spoll_start_delay < 0:
49 raise ValueError("Polling start delay needs to be non-negative.")
52@configdataclass
53class VisaDeviceConfig(_VisaDeviceConfigDefaultsBase, _VisaDeviceConfigBase):
54 """
55 Configdataclass for a VISA device.
56 """
58 pass
61class VisaDevice(SingleCommDevice):
62 """
63 Device communicating over the VISA protocol using VisaCommunication.
64 """
66 def __init__(
67 self,
68 com: Union[VisaCommunication, VisaCommunicationConfig, dict],
69 dev_config: Union[VisaDeviceConfig, dict, None] = None,
70 ) -> None:
72 super().__init__(com, dev_config)
74 self._spoll_thread: Union[Poller, None] = None
76 self._notify_operation_complete: bool = False
78 @staticmethod
79 def default_com_cls() -> Type[VisaCommunication]:
80 """
81 Return the default communication protocol for this device type, which is
82 VisaCommunication.
84 :return: the VisaCommunication class
85 """
87 return VisaCommunication
89 @staticmethod
90 def config_cls():
91 return VisaDeviceConfig
93 def get_identification(self) -> str:
94 """
95 Queries `"*IDN?"` and returns the identification string of the connected device.
97 :return: the identification string of the connected device
98 """
100 return self.com.query("*IDN?")
102 def start(self) -> None:
103 """
104 Start the VisaDevice. Sets up the status poller and starts it.
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()
116 def stop(self) -> None:
117 """
118 Stop the VisaDevice. Stops the polling thread and closes the communication
119 protocol.
121 :return:
122 """
123 if self._spoll_thread:
124 self._spoll_thread.stop_polling()
125 super().stop()
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.
132 :return:
133 """
134 stb = self.com.spoll()
136 if stb:
137 bits = BitArray(length=8, int=stb)
138 bits.reverse()
140 if bits[0]:
141 # has no meaning, always zero
142 pass
144 if bits[1]:
145 # has no meaning, always zero
146 pass
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()
153 if bits[3]:
154 # Questionable Status QUES summary bit
155 logging.debug(f"Questionable status bit set in STB: {stb}")
157 if bits[4]:
158 # Output buffer holds data (RTO 1024), MAV bit (Message available)
159 pass
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}")
165 # read event status register
166 esr = int(self.com.query("*ESR?"))
167 esr_bits = BitArray(length=8, int=esr)
168 esr_bits.reverse()
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
177 if bits[6]:
178 # RQS/MSS bit (RTO 1024)
179 pass
181 if bits[7]:
182 # Operation Status OPER summary bit
183 pass
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.
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 """
194 # reset event bit
195 self._notify_operation_complete = False
197 # compute timeout
198 timeout_time = datetime.now() + timedelta(seconds=(timeout or 0))
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
206 # if event was caught, return true
207 if self._notify_operation_complete:
208 self._notify_operation_complete = False
209 return True
211 # if timeout expired, return false
212 return False
214 def get_error_queue(self) -> str:
215 """
216 Read out error queue and logs the error.
218 :return: Error string
219 """
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
226 def reset(self) -> None:
227 """
228 Send `"*RST"` and `"*CLS"` to the device. Typically sets a defined state.
229 """
231 self.com.write("*RST", "*CLS")