Unable to connect to QDAC (USB connection) from multiple Jupyter notebooks
Hi!
I am unable to have two instances of the QDevil QDAC (qcodes/instrument_drivers/QDevil/QDevil_QDAC.py) in two different/separate Jupyter notebooks simultaneously. Initializing the QDevil QDAC in the first Jupyter notebook works fine, but when initializing the same QDevil QDAC in the second Jupyter notebook (with a separate kernel) throws a SerialException error, probably because it is connected to the PC through USB and it doens't seem possible to connect to it from two separate notebooks.
Steps to reproduce
- Initialize the QDAC, e.g.
from qcodes.instrument_drivers.QDevil.QDevil_QDAC import QDac
qdac = QDac('qdac', 'ASRL3::INSTR')
- Initialize the QDAC in the same way in a second Jupyter notebook
- When running the cell in step 2, I get the following
SerialExceptionerror:
---------------------------------------------------------------------------
SerialException Traceback (most recent call last)
Input In [1], in <module>
1 from qcodes.instrument_drivers.QDevil.QDevil_QDAC import QDac
----> 3 qdac = QDac('qdac', 'ASRL3::INSTR')
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\qcodes\instrument_drivers\QDevil\QDevil_QDAC.py:234, in QDac.__init__(self, name, address, update_currents, **kwargs)
215 def __init__(self,
216 name: str,
217 address: str,
218 update_currents: bool = False,
219 **kwargs: Any):
220 """
221 Instantiates the instrument.
222
(...)
231 QDac object
232 """
--> 234 super().__init__(name, address, **kwargs)
235 handle = self.visa_handle
236 self._get_status_performed = False
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\qcodes\instrument\visa.py:89, in VisaInstrument.__init__(self, name, address, timeout, terminator, device_clear, visalib, **kwargs)
87 self.visa_log.info(f"Could not connect at {address}")
88 self.close()
---> 89 raise e
91 if device_clear:
92 self.device_clear()
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\qcodes\instrument\visa.py:85, in VisaInstrument.__init__(self, name, address, timeout, terminator, device_clear, visalib, **kwargs)
82 self.visalib = visalib
84 try:
---> 85 self.set_address(address)
86 except Exception as e:
87 self.visa_log.info(f"Could not connect at {address}")
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\qcodes\instrument\visa.py:124, in VisaInstrument.set_address(self, address)
121 self.visabackend = 'ni'
123 self.visa_log.info(f'Opening PyVISA resource at address: {address}')
--> 124 resource = resource_manager.open_resource(address)
125 if not isinstance(resource, visa.resources.MessageBasedResource):
126 raise TypeError("QCoDeS only support MessageBasedResource "
127 "Visa resources")
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\pyvisa\highlevel.py:3304, in ResourceManager.open_resource(self, resource_name, access_mode, open_timeout, resource_pyclass, **kwargs)
3298 if not present:
3299 raise ValueError(
3300 "%r is not a valid attribute for type %s"
3301 % (key, res.__class__.__name__)
3302 )
-> 3304 res.open(access_mode, open_timeout)
3306 for key, value in kwargs.items():
3307 setattr(res, key, value)
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\pyvisa\resources\resource.py:297, in Resource.open(self, access_mode, open_timeout)
293 logger.debug("%s - opening ...", self._resource_name, extra=self._logging_extra)
294 with self._resource_manager.ignore_warning(
295 constants.StatusCode.success_device_not_present
296 ):
--> 297 self.session, status = self._resource_manager.open_bare_resource(
298 self._resource_name, access_mode, open_timeout
299 )
301 if status == constants.StatusCode.success_device_not_present:
302 # The device was not ready when we opened the session.
303 # Now it gets five seconds more to become ready.
304 # Every 0.1 seconds we probe it with viClear.
305 start_time = time.time()
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\pyvisa\highlevel.py:3232, in ResourceManager.open_bare_resource(self, resource_name, access_mode, open_timeout)
3203 def open_bare_resource(
3204 self,
3205 resource_name: str,
3206 access_mode: constants.AccessModes = constants.AccessModes.no_lock,
3207 open_timeout: int = constants.VI_TMO_IMMEDIATE,
3208 ) -> Tuple[VISASession, StatusCode]:
3209 """Open the specified resource without wrapping into a class.
3210
3211 Parameters
(...)
3230
3231 """
-> 3232 return self.visalib.open(self.session, resource_name, access_mode, open_timeout)
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\pyvisa_py\highlevel.py:167, in PyVisaLibrary.open(self, session, resource_name, access_mode, open_timeout)
158 return (
159 VISASession(0),
160 self.handle_return_value(None, StatusCode.error_invalid_resource_name),
161 )
163 cls = sessions.Session.get_session_class(
164 parsed.interface_type_const, parsed.resource_class
165 )
--> 167 sess = cls(session, resource_name, parsed, open_timeout)
169 return self._register(sess), StatusCode.success
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\pyvisa_py\sessions.py:323, in Session.__init__(self, resource_manager_session, resource_name, parsed, open_timeout)
320 default_timeout = attributes.AttributesByID[attr].default
321 self.set_attribute(attr, default_timeout)
--> 323 self.after_parsing()
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\pyvisa_py\serial.py:89, in SerialSession.after_parsing(self)
86 def after_parsing(self) -> None:
87 cls = serial.Serial
---> 89 self.interface = cls(
90 port=("COM" if IS_WIN else "") + self.parsed.board,
91 timeout=self.timeout,
92 write_timeout=self.timeout,
93 )
95 for name in (
96 "ASRL_END_IN",
97 "ASRL_END_OUT",
(...)
101 "SUPPRESS_END_EN",
102 ):
103 attribute = getattr(constants, "VI_ATTR_" + name)
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\serial\serialwin32.py:33, in Serial.__init__(self, *args, **kwargs)
31 self._overlapped_read = None
32 self._overlapped_write = None
---> 33 super(Serial, self).__init__(*args, **kwargs)
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\serial\serialutil.py:244, in SerialBase.__init__(self, port, baudrate, bytesize, parity, stopbits, timeout, xonxoff, rtscts, write_timeout, dsrdtr, inter_byte_timeout, exclusive, **kwargs)
241 raise ValueError('unexpected keyword arguments: {!r}'.format(kwargs))
243 if port is not None:
--> 244 self.open()
File ~\anaconda3\envs\pycqed39-2\lib\site-packages\serial\serialwin32.py:64, in Serial.open(self)
62 if self._port_handle == win32.INVALID_HANDLE_VALUE:
63 self._port_handle = None # 'cause __del__ is called anyway
---> 64 raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError()))
66 try:
67 self._overlapped_read = win32.OVERLAPPED()
SerialException: could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5)
Expected/Desired behaviour
It would be great if it's possible to initialize a QDevil QDAC in multiple Jupyter notebooks/kernels, perhaps by somehow sharing the device/sharing the USB connection in a clever way.
Actual behaviour
SerialException error is thrown when the QDevil QDAC is already initialized in another notebook on the same PC.
System
OS: Windows 10 64-bit qcodes version: 0.31.0
Would you have any suggestions for a solution? Thank you very much in advance!
Hi
Most of the instruments qcodes supports are not designed to be remote controlled from more than one connection. You may want to have a look at https://github.com/toolsforexperiments/instrumentserver which is an instrument server that acts as a proxy to the instruments. We may eventually implement such a feature in qcodes but we cannot commit to any promises to when that will happen
Hi @jenshnielsen, thank you for your reply and pointing out the instrument server. In the meantime we came up with a temporary "hacky" solution for our use-case, but we might invest in looking into a cleaner solution.
It would be interesting to see an implementation in qcodes someday (though I can imagine it is not a priority for you/the developers right now)!