Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update agilent drivers to conform to docs standard #4371

Merged
merged 11 commits into from
Aug 1, 2022
1 change: 1 addition & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ clean:
# generate api docs for instruments automatically
genapi:
sphinx-apidoc -o _auto -d 10 ../qcodes \
../qcodes/instrument_drivers/agilent \
../qcodes/instrument_drivers/AimTTi \
../qcodes/instrument_drivers/keysight \
../qcodes/instrument_drivers/QuantumDesign/DynaCoolPPMS/private/*
Expand Down
7 changes: 7 additions & 0 deletions docs/drivers_api/agilent.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.. _agilent_api :

Agilent Drivers
===============

.. automodule:: qcodes.instrument_drivers.agilent
:autosummary:
1 change: 1 addition & 0 deletions docs/make.bat
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ REM is due to duplication of the folder in git that happened
REM a long time ago (i.e. "Keysight", the upper-case, is used
REM for storing drivers, not the lower-case one).
sphinx-apidoc -o _auto -d 10 ..\qcodes ^
..\qcodes\instrument_drivers\agilent\* ^
..\qcodes\instrument_drivers\AimTTi ^
..\qcodes\instrument_drivers\QuantumDesign\DynaCoolPPMS\private\*
mkdir api\generated\
Expand Down
157 changes: 7 additions & 150 deletions qcodes/instrument_drivers/agilent/Agilent_34400A.py
Original file line number Diff line number Diff line change
@@ -1,153 +1,10 @@
from typing import Any
from qcodes.utils import deprecate

from qcodes.instrument import VisaInstrument
from qcodes.parameters import Parameter
from qcodes.validators import Enum, Strings
from ._Agilent_344xxA import _Agilent344xxA


class Agilent_34400A(VisaInstrument):
"""
This is the QCoDeS driver for the Agilent_34400A DMM Series,
tested with Agilent_34401A, Agilent_34410A, and Agilent_34411A.
"""
def __init__(self, name: str, address: str, **kwargs: Any) -> None:
super().__init__(name, address, terminator='\n', **kwargs)

idn = self.IDN.get()
self.model = idn['model']

NPLC_list = {'34401A': [0.02, 0.2, 1, 10, 100],
'34410A': [0.006, 0.02, 0.06, 0.2, 1, 2, 10, 100],
'34411A': [0.001, 0.002, 0.006, 0.02, 0.06, 0.2,
1, 2, 10, 100]
}[self.model]

self._resolution_factor = {'34401A': [1e-4, 1e-5, 3e-6, 1e-6, 3e-7],
'34410A': [6e-06, 3e-06, 1.5e-06, 7e-07,
3e-07, 2e-07, 1e-07],
'34411A': [3e-05, 1.5e-05, 6e-06, 3e-06,
1.5e-06, 7e-07, 3e-07, 2e-07,
1e-07, 3e-08]
}[self.model]

self.resolution = Parameter(
"resolution",
instrument=self,
get_cmd="VOLT:DC:RES?",
get_parser=float,
set_cmd=self._set_resolution,
label="Resolution",
unit="V",
)
"""Resolution """

self.add_parameter('volt',
get_cmd='READ?',
label='Voltage',
get_parser=float,
unit='V')

self.add_parameter('fetch',
get_cmd='FETCH?',
label='Voltage',
get_parser=float,
unit='V',
snapshot_get=False,
docstring=('Reads the data you asked for, i.e. '
'after an `init_measurement()` you can '
'read the data with fetch.\n'
'Do not call this when you did not ask '
'for data in the first place!'))

self.add_parameter('NPLC',
get_cmd='VOLT:NPLC?',
get_parser=float,
set_cmd=self._set_nplc,
vals=Enum(*NPLC_list),
label='Integration time',
unit='NPLC')

self.add_parameter('terminals',
get_cmd='ROUT:TERM?')

self.add_parameter('range_auto',
get_cmd='VOLT:RANG:AUTO?',
set_cmd='VOLT:RANG:AUTO {:d}',
val_mapping={'on': 1,
'off': 0})

self.add_parameter('range',
get_cmd='SENS:VOLT:DC:RANG?',
get_parser=float,
set_cmd='SENS:VOLT:DC:RANG {:f}',
vals=Enum(0.1, 1.0, 10.0, 100.0, 1000.0))

if self.model in ['34401A']:
self.add_parameter('display_text',
get_cmd='DISP:TEXT?',
set_cmd='DISP:TEXT "{}"',
vals=Strings())

elif self.model in ['34410A', '34411A']:
self.add_parameter('display_text',
get_cmd='DISP:WIND1:TEXT?',
set_cmd='DISP:WIND1:TEXT "{}"',
vals=Strings())

self.add_parameter('display_text_2',
get_cmd='DISP:WIND2:TEXT?',
set_cmd='DISP:WIND2:TEXT "{}"',
vals=Strings())

self.connect_message()

def _set_nplc(self, value: float) -> None:
self.write(f'VOLT:NPLC {value:f}')
# resolution settings change with NPLC
self.resolution.get()

def _set_resolution(self, value: float) -> None:
rang = self.range.get()
# convert both value*range and the resolution factors
# to strings with few digits, so we avoid floating point
# rounding errors.
res_fac_strs = [f"{v * rang:.1e}" for v in self._resolution_factor]
if f"{value:.1e}" not in res_fac_strs:
raise ValueError(
'Resolution setting {:.1e} ({} at range {}) '
'does not exist. '
'Possible values are {}'.format(value, value, rang,
res_fac_strs))
self.write(f'VOLT:DC:RES {value:.1e}')
# NPLC settings change with resolution
self.NPLC.get()

def _set_range(self, value: float) -> None:
self.write(f'SENS:VOLT:DC:RANG {value:f}')
# resolution settings change with range
self.resolution.get()

def clear_errors(self) -> None:
while True:
err = self.ask('SYST:ERR?')
if 'No error' in err:
return
print(err)

def init_measurement(self) -> None:
self.write('INIT')

def display_clear(self) -> None:
if self.model in ['34401A']:
lines = ['WIND']
elif self.model in ['34410A', '34411A']:
lines = ['WIND1', 'WIND2']
else:
raise ValueError('unrecognized model: ' + str(self.model))

for line in lines:
self.write('DISP:' + line + ':TEXT:CLE')
self.write('DISP:' + line + ':STAT 1')

def reset(self) -> None:
self.write('*RST')
@deprecate(
alternative="Keysight 344xxA drivers or Agilent34401A, Agilent34410A, Agilent34411A"
)
class Agilent34400A(_Agilent344xxA):
pass
9 changes: 9 additions & 0 deletions qcodes/instrument_drivers/agilent/Agilent_34401A.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from ._Agilent_344xxA import _Agilent344xxA


class Agilent34401A(_Agilent344xxA):
"""
This is the QCoDeS driver for the Agilent 34401A DMM.
"""

pass
9 changes: 9 additions & 0 deletions qcodes/instrument_drivers/agilent/Agilent_34410A.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from ._Agilent_344xxA import _Agilent344xxA


class Agilent34410A(_Agilent344xxA):
"""
This is the QCoDeS driver for the Agilent 34410A DMM.
"""

pass
9 changes: 9 additions & 0 deletions qcodes/instrument_drivers/agilent/Agilent_34411A.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from ._Agilent_344xxA import _Agilent344xxA


class Agilent34411A(_Agilent344xxA):
"""
This is the QCoDeS driver for the Agilent 34411A DMM.
"""

pass
151 changes: 151 additions & 0 deletions qcodes/instrument_drivers/agilent/Agilent_E8257D.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import warnings
from typing import Any, Optional, Union

import numpy as np

import qcodes.validators as vals
from qcodes.instrument import VisaInstrument
from qcodes.parameters import create_on_off_val_mapping


class AgilentE8257D(VisaInstrument):
"""
This is the QCoDeS driver for the Agilent E8257D signal generator.
This driver will most likely work for multiple Agilent sources.
This driver does not contain all commands available for the E8257D but
only the ones most commonly used.
"""

def __init__(
self,
name: str,
address: str,
step_attenuator: Optional[bool] = None,
terminator: str = "\n",
**kwargs: Any
) -> None:
super().__init__(name, address, terminator=terminator, **kwargs)

if step_attenuator is not None:
warnings.warn(
"step_attenuator argument to E8257D is deprecated "
"and has no effect. It will be removed in the "
"future."
)

# Query installed options
self._options = self.ask_raw("DIAG:CPU:INFO:OPT:DET?")

# Determine installed frequency option
frequency_option = None
for f_option in ["513", "520", "521", "532", "540", "550", "567"]:
if f_option in self._options:
frequency_option = f_option
if frequency_option is None:
raise RuntimeError("Could not determine the frequency option")

# convert installed frequency option to frequency ranges, based on:
# https://www.keysight.com/us/en/assets/7018-01233/configuration-guides
# /5989-1325.pdf
# the frequency range here is the max range and not the specified
# (calibrated) one
f_options_dict = {
"513": (100e3, 13e9),
"520": (100e3, 20e9),
"521": (10e6, 20e9),
"532": (100e3, 31.8e9),
"540": (100e3, 40e9),
"550": (100e3, 50e9),
"567": (100e3, 70e9),
}

# assign min and max frequencies
self._min_freq: float
self._max_freq: float
self._min_freq, self._max_freq = f_options_dict[frequency_option]

# Based on installed frequency option and presence/absence of step
# attenuator (option '1E1') determine power range based on:
# https://www.keysight.com/us/en/assets/7018-01211/data-sheets
# /5989-0698.pdf

# assign min and max powers
self._min_power: float
self._max_power: float

if "1E1" in self._options:
if frequency_option in ["513", "520", "521", "532", "540"]:
self._min_power = -135
self._max_power = 10
else:
self._min_power = -110
self._max_power = 5
else:
# default minimal power is -20 dBm
if frequency_option in ["513", "520", "521", "532", "540"]:
self._min_power = -20
self._max_power = 10
else:
self._min_power = -20
self._max_power = 5

self.add_parameter(
name="frequency",
label="Frequency",
unit="Hz",
get_cmd="FREQ:CW?",
set_cmd="FREQ:CW" + " {:.4f}",
get_parser=float,
set_parser=float,
vals=vals.Numbers(self._min_freq, self._max_freq),
)

self.add_parameter(
name="phase",
label="Phase",
unit="deg",
get_cmd="PHASE?",
set_cmd="PHASE" + " {:.8f}",
get_parser=self.rad_to_deg,
set_parser=self.deg_to_rad,
vals=vals.Numbers(-180, 180),
)

self.add_parameter(
name="power",
label="Power",
unit="dBm",
get_cmd="POW:AMPL?",
set_cmd="POW:AMPL" + " {:.4f}",
get_parser=float,
set_parser=float,
vals=vals.Numbers(self._min_power, self._max_power),
)

self.add_parameter(
"output_enabled",
get_cmd=":OUTP?",
set_cmd="OUTP {}",
val_mapping=create_on_off_val_mapping(on_val="1", off_val="0"),
)

self.connect_message()

def on(self) -> None:
self.set("status", "on")

def off(self) -> None:
self.set("status", "off")

# functions to convert between rad and deg
@staticmethod
def deg_to_rad(
angle_deg: Union[float, str, np.floating, np.integer]
) -> "np.floating[Any]":
return np.deg2rad(float(angle_deg))

@staticmethod
def rad_to_deg(
angle_rad: Union[float, str, np.floating, np.integer]
) -> "np.floating[Any]":
return np.rad2deg(float(angle_rad))