from typing import TYPE_CHECKING, Any, ClassVar
import qcodes.validators as vals
from qcodes.instrument_drivers.Lakeshore.lakeshore_base import (
LakeshoreBase,
LakeshoreBaseOutput,
LakeshoreBaseSensorChannel,
)
from qcodes.parameters import Group, GroupParameter
if TYPE_CHECKING:
from typing_extensions import Unpack
from qcodes.instrument import InstrumentBaseKWArgs, VisaInstrumentKWArgs
# There are 16 sensors channels (a.k.a. measurement inputs) in Model 372
_n_channels = 16
[docs]
class LakeshoreModel372Output(LakeshoreBaseOutput):
"""An InstrumentChannel for control outputs (heaters) of Lakeshore Model 372"""
MODES: ClassVar[dict[str, int]] = {
"off": 0,
"monitor_out": 1,
"open_loop": 2,
"zone": 3,
"still": 4,
"closed_loop": 5,
"warm_up": 6,
}
POLARITIES: ClassVar[dict[str, int]] = {"unipolar": 0, "bipolar": 1}
RANGES: ClassVar[dict[str, int]] = {
"off": 0,
"31.6μA": 1,
"100μA": 2,
"316μA": 3,
"1mA": 4,
"3.16mA": 5,
"10mA": 6,
"31.6mA": 7,
"100mA": 8,
}
_input_channel_parameter_kwargs: ClassVar[dict[str, Any]] = {
"get_parser": int,
"vals": vals.Numbers(1, _n_channels),
}
def __init__(
self,
parent: "LakeshoreModel372",
output_name: str,
output_index: int,
**kwargs: "Unpack[InstrumentBaseKWArgs]",
) -> None:
super().__init__(parent, output_name, output_index, has_pid=True, **kwargs)
# Add more parameters for OUTMODE command
# and redefine the corresponding group
self.polarity: GroupParameter = self.add_parameter(
"polarity",
label="Output polarity",
docstring="Specifies output polarity (not applicable to warm-up heater)",
val_mapping=self.POLARITIES,
parameter_class=GroupParameter,
)
"""Specifies output polarity (not applicable to warm-up heater)"""
self.use_filter: GroupParameter = self.add_parameter(
"use_filter",
label="Use filter for readings",
docstring="Specifies controlling on unfiltered or filtered readings",
val_mapping={True: 1, False: 0},
parameter_class=GroupParameter,
)
"""Specifies controlling on unfiltered or filtered readings"""
self.delay: GroupParameter = self.add_parameter(
"delay",
label="Delay",
unit="s",
docstring="Delay in seconds for setpoint change during Autoscanning",
vals=vals.Ints(0, 255),
get_parser=int,
parameter_class=GroupParameter,
)
"""Delay in seconds for setpoint change during Autoscanning"""
self.output_group = Group(
[
self.mode,
self.input_channel,
self.powerup_enable,
self.polarity,
self.use_filter,
self.delay,
],
set_cmd=f"OUTMODE {output_index}, {{mode}}, "
f"{{input_channel}}, "
f"{{powerup_enable}}, {{polarity}}, "
f"{{use_filter}}, {{delay}}",
get_cmd=f"OUTMODE? {output_index}",
)
self.P.vals = vals.Numbers(0.0, 1000)
self.I.vals = vals.Numbers(0.0, 10000)
self.D.vals = vals.Numbers(0, 2500)
[docs]
class LakeshoreModel372Channel(LakeshoreBaseSensorChannel):
"""
An InstrumentChannel representing a single sensor on a Lakeshore Model 372.
"""
SENSOR_STATUSES: ClassVar[dict[int, str]] = {
0: "OK",
1: "CS OVL",
2: "VCM OVL",
4: "VMIX OVL",
8: "VDIF OVL",
16: "R. OVER",
32: "R. UNDER",
64: "T. OVER",
128: "T. UNDER",
}
def __init__(
self,
parent: "LakeshoreModel372",
name: str,
channel: str,
**kwargs: "Unpack[InstrumentBaseKWArgs]",
):
super().__init__(parent, name, channel, **kwargs)
# Parameters related to Input Channel Parameter Command (INSET)
self.enabled: GroupParameter = self.add_parameter(
"enabled",
label="Enabled",
docstring="Specifies whether the input/channel is "
"enabled or disabled. At least one "
"measurement input channel must be "
"enabled. If all are configured to "
"disabled, channel 1 will change to "
"enabled.",
val_mapping={True: 1, False: 0},
parameter_class=GroupParameter,
)
"""
Specifies whether the input/channel is enabled or disabled. At least one measurement
input channel must be enabled. If all are configured to disabled, channel 1
will change to enabled.
"""
self.dwell: GroupParameter = self.add_parameter(
"dwell",
label="Dwell",
docstring="Specifies a value for the autoscanning dwell time.",
unit="s",
get_parser=int,
vals=vals.Numbers(1, 200),
parameter_class=GroupParameter,
)
"""Specifies a value for the autoscanning dwell time."""
self.pause: GroupParameter = self.add_parameter(
"pause",
label="Change pause time",
docstring="Specifies a value for the change pause time",
unit="s",
get_parser=int,
vals=vals.Numbers(3, 200),
parameter_class=GroupParameter,
)
"""Specifies a value for the change pause time"""
self.curve_number: GroupParameter = self.add_parameter(
"curve_number",
label="Curve",
docstring="Specifies which curve the channel uses: "
"0 = no curve, 1 to 59 = standard/user "
"curves. Do not change this parameter "
"unless you know what you are doing.",
get_parser=int,
vals=vals.Numbers(0, 59),
parameter_class=GroupParameter,
)
"""
Specifies which curve the channel uses: 0 = no curve, 1 to 59 = standard/user curves.
Do not change this parameter unless you know what you are doing.
"""
self.temperature_coefficient: GroupParameter = self.add_parameter(
"temperature_coefficient",
label="Change pause time",
docstring="Sets the temperature coefficient that "
"will be used for temperature control if "
"no curve is selected (negative or "
"positive). Do not change this parameter "
"unless you know what you are doing.",
val_mapping={"negative": 1, "positive": 2},
parameter_class=GroupParameter,
)
"""
Sets the temperature coefficient that will be used for temperature control if no curve is
selected (negative or positive). Do not change this parameter unless you know what
you are doing.
"""
self.output_group = Group(
[
self.enabled,
self.dwell,
self.pause,
self.curve_number,
self.temperature_coefficient,
],
set_cmd=f"INSET {self._channel}, "
f"{{enabled}}, {{dwell}}, {{pause}}, "
f"{{curve_number}}, "
f"{{temperature_coefficient}}",
get_cmd=f"INSET? {self._channel}",
)
# Parameters related to Input Setup Command (INTYPE)
self.excitation_mode: GroupParameter = self.add_parameter(
"excitation_mode",
label="Excitation mode",
docstring="Specifies excitation mode",
val_mapping={"voltage": 0, "current": 1},
parameter_class=GroupParameter,
)
"""Specifies excitation mode"""
# The allowed values for this parameter change based on the value of
# the 'excitation_mode' parameter. Moreover, there is a table in the
# manual that assigns the numbers to particular voltage/current ranges.
# Once this parameter is heavily used, it can be implemented properly
# (i.e. using val_mapping, and that val_mapping is updated based on the
# value of 'excitation_mode'). At the moment, this parameter is added
# only because it is a part of a group.
self.excitation_range_number: GroupParameter = self.add_parameter(
"excitation_range_number",
label="Excitation range number",
docstring="Specifies excitation range number "
"(1-12 for voltage excitation, 1-22 for "
"current excitation); refer to the manual "
"for the table of ranges",
get_parser=int,
vals=vals.Numbers(1, 22),
parameter_class=GroupParameter,
)
"""
Specifies excitation range number (1-12 for voltage excitation, 1-22 for current excitation);
refer to the manual for the table of ranges
"""
self.auto_range: GroupParameter = self.add_parameter(
"auto_range",
label="Auto range",
docstring="Specifies auto range setting",
val_mapping={"off": 0, "current": 1},
parameter_class=GroupParameter,
)
"""Specifies auto range setting"""
self.range: GroupParameter = self.add_parameter(
"range",
label="Range",
val_mapping={
"2.0 mOhm": 1,
"6.32 mOhm": 2,
"20.0 mOhm": 3,
"63.2 mOhm": 4,
"200 mOhm": 5,
"632 mOhm": 6,
"2.00 Ohm": 7,
"6.32 Ohm": 8,
"20.0 Ohm": 9,
"63.2 Ohm": 10,
"200 Ohm": 11,
"632 Ohm": 12,
"2.00 kOhm": 13,
"6.32 kOhm": 14,
"20.0 kOhm": 15,
"63.2 kOhm": 16,
"200 kOhm": 17,
"632 kOhm": 18,
"2.0 MOhm": 19,
"6.32 MOhm": 20,
"20.0 MOhm": 21,
"63.2 MOhm": 22,
},
parameter_class=GroupParameter,
)
"""Parameter range"""
self.current_source_shunted: GroupParameter = self.add_parameter(
"current_source_shunted",
label="Current source shunt",
docstring="Current source either not shunted "
"(excitation on), or shunted "
"(excitation off)",
val_mapping={False: 0, True: 1},
parameter_class=GroupParameter,
)
"""Current source either not shunted (excitation on), or shunted (excitation off)"""
self.units: GroupParameter = self.add_parameter(
"units",
label="Preferred units",
docstring="Specifies the preferred units parameter "
"for sensor readings and for the control "
"setpoint (kelvin or ohms)",
val_mapping={"kelvin": 1, "ohms": 2},
parameter_class=GroupParameter,
)
"""
Specifies the preferred units parameter for sensor readings and for the control setpoint
(kelvin or ohms)
"""
self.output_group = Group(
[
self.excitation_mode,
self.excitation_range_number,
self.auto_range,
self.range,
self.current_source_shunted,
self.units,
],
set_cmd=f"INTYPE {self._channel}, "
f"{{excitation_mode}}, "
f"{{excitation_range_number}}, "
f"{{auto_range}}, {{range}}, "
f"{{current_source_shunted}}, "
f"{{units}}",
get_cmd=f"INTYPE? {self._channel}",
)
[docs]
class LakeshoreModel372(LakeshoreBase):
"""
QCoDeS driver for Lakeshore Model 372 Temperature Controller.
Note that interaction with the control input (referred to as 'A' in the
Computer Interface Operation section of the manual) is not implemented.
"""
channel_name_command: ClassVar[dict[str, str]] = {
f"ch{i:02}": str(i) for i in range(1, 1 + _n_channels)
}
input_channel_parameter_values_to_channel_name_on_instrument: ClassVar[
dict[int, str]
] = {i: f"ch{i:02}" for i in range(1, 1 + _n_channels)}
CHANNEL_CLASS = LakeshoreModel372Channel
def __init__(
self, name: str, address: str, **kwargs: "Unpack[VisaInstrumentKWArgs]"
) -> None:
super().__init__(name, address, **kwargs)
heaters = {"sample_heater": 0, "warmup_heater": 1, "analog_heater": 2}
for heater_name, heater_index in heaters.items():
self.add_submodule(
heater_name, LakeshoreModel372Output(self, heater_name, heater_index)
)