diff --git a/src/qcodes/instrument/sims/lakeshore_model336.yaml b/src/qcodes/instrument/sims/lakeshore_model336.yaml index f04d3f22d6e..1e0277c31b1 100644 --- a/src/qcodes/instrument/sims/lakeshore_model336.yaml +++ b/src/qcodes/instrument/sims/lakeshore_model336.yaml @@ -53,6 +53,17 @@ devices: setter: q: "range A,\"{}\"" + sensor_curve_number_A: + default: 42 + getter: + q: "INCRV? A" + r: "{}" + + curve_data_query_for_curve_42: + getter: + q: "CRVHDR? 42" + r: "DT-042,01110042,2,342.0,1" + temperature_B: default: 100.0 @@ -96,7 +107,16 @@ devices: setter: q: "range A,\"{}\"" + sensor_curve_number_B: + default: 41 + getter: + q: "INCRV? B" + r: "{}" + curve_data_query_for_curve_41: + getter: + q: "CRVHDR? 41" + r: "DT-041,01110041,2,341.0,1" temperature_C: default: 100.0 @@ -140,7 +160,16 @@ devices: setter: q: "range A,\"{}\"" + sensor_curve_number_C: + default: 40 + getter: + q: "INCRV? C" + r: "{}" + curve_data_query_for_curve_40: + getter: + q: "CRVHDR? 40" + r: "DT-040,01110040,2,340.0,1" temperature_D: default: 100.0 @@ -184,6 +213,17 @@ devices: setter: q: "range A,\"{}\"" + sensor_curve_number_D: + default: 39 + getter: + q: "INCRV? D" + r: "{}" + + curve_data_query_for_curve_39: + getter: + q: "CRVHDR? 39" + r: "DT-039,01110039,2,339.0,1" + resources: GPIB::2::INSTR: diff --git a/src/qcodes/instrument_drivers/Lakeshore/Lakeshore_model_336.py b/src/qcodes/instrument_drivers/Lakeshore/Lakeshore_model_336.py index 32ec33a7a4b..e482d08ed87 100644 --- a/src/qcodes/instrument_drivers/Lakeshore/Lakeshore_model_336.py +++ b/src/qcodes/instrument_drivers/Lakeshore/Lakeshore_model_336.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING, ClassVar import qcodes.validators as vals -from qcodes.parameters import Group, GroupParameter +from qcodes.parameters import Group, GroupParameter, Parameter from .lakeshore_base import ( LakeshoreBase, @@ -203,6 +203,78 @@ def __init__( get_cmd=f"INTYPE? {self._channel}", ) + # Parameters related to temperature calibration curve (CRVHDR) + self.curve_number: Parameter = self.add_parameter( + "curve_number", + get_cmd=f"INCRV? {self._channel}", + set_cmd=False, + get_parser=int, + label="Temperature calibration curve number", + ) + """ + Temperature calibration curve number that is selected now + """ + self.curve_name: GroupParameter = self.add_parameter( + "curve_name", + label="Temperature calibration curve name", + parameter_class=GroupParameter, + ) + """ + Temperature calibration curve name + for the current curve as selected by ``curve_number`` + """ + self.curve_sn: GroupParameter = self.add_parameter( + "curve_sn", + label="Temperature calibration curve SN", + parameter_class=GroupParameter, + ) + """ + Temperature calibration curve SN + for the current curve as selected by ``curve_number`` + """ + self.curve_format: GroupParameter = self.add_parameter( + "curve_format", + label="Temperature calibration curve format", + get_parser=int, + val_mapping={"mV/K": 1, "V/K": 2, "Ohms/K": 3, "log Ohms/K": 4}, + parameter_class=GroupParameter, + ) + """ + Temperature calibration curve format + for the current curve as selected by ``curve_number`` + """ + self.curve_limit: GroupParameter = self.add_parameter( + "curve_limit", + get_parser=float, + label="Temperature calibration curve limit value", + parameter_class=GroupParameter, + ) + """ + Temperature calibration curve limit value + for the current curve as selected by ``curve_number`` + """ + self.curve_coefficient: GroupParameter = self.add_parameter( + "curve_coefficient", + get_parser=int, + label="Temperature calibration curve coefficient", + val_mapping={"negative": 1, "positive": 2}, + parameter_class=GroupParameter, + ) + """ + Temperature calibration curve coefficient + for the current curve as selected by ``curve_number`` + """ + self.curve_parameters_group = Group( + [ + self.curve_name, + self.curve_sn, + self.curve_format, + self.curve_limit, + self.curve_coefficient, + ], + get_cmd=lambda: f"CRVHDR? {self.curve_number()}", + ) + class LakeshoreModel336(LakeshoreBase): """ diff --git a/src/qcodes/parameters/group_parameter.py b/src/qcodes/parameters/group_parameter.py index be8100c3edb..89b5e58482f 100644 --- a/src/qcodes/parameters/group_parameter.py +++ b/src/qcodes/parameters/group_parameter.py @@ -149,7 +149,10 @@ def __init__(self, name, address, **kwargs): set_cmd: Format string of the command that is used for setting the values of the parameters; for example, ``CMD {a}, {b}``. get_cmd: String of the command that is used for getting the values - of the parameters; for example, ``CMD?``. + of the parameters; for example, ``CMD?``. Can also be a callable + that returns a command string, this is useful for the cases where + the command string is dynamic; for example, + ``lambda: f"CMD {get_id_that_specifies_the_command()} ?"``. separator: A separator that is used when parsing the output of the ``get_cmd`` in order to obtain the values of the parameters; it is ignored in case a custom ``get_parser`` is used. @@ -168,7 +171,7 @@ def __init__( self, parameters: Sequence[GroupParameter], set_cmd: str | None = None, - get_cmd: str | None = None, + get_cmd: str | Callable[[], str] | None = None, get_parser: Callable[[str], Mapping[str, Any]] | None = None, separator: str = ",", single_instrument: bool = True, @@ -309,7 +312,10 @@ def update(self) -> None: f"parameters - {parameter_names} since it " f"has no `get_cmd` defined." ) - ret = self.get_parser(self.instrument.ask(self._get_cmd)) + get_command = ( + self._get_cmd if isinstance(self._get_cmd, str) else self._get_cmd() + ) + ret = self.get_parser(self.instrument.ask(get_command)) for name, p in list(self.parameters.items()): p.cache._set_from_raw_value(ret[name]) diff --git a/tests/drivers/test_lakeshore.py b/tests/drivers/test_lakeshore.py index f5415291117..07d4026b17f 100644 --- a/tests/drivers/test_lakeshore.py +++ b/tests/drivers/test_lakeshore.py @@ -89,7 +89,7 @@ def ask_raw(self, cmd) -> Any: self.visa_log.debug(f"Response: {response}") return response else: - super().ask_raw(cmd) # type: ignore[misc] + return super().ask_raw(cmd) # type: ignore[misc] def query(name: str) -> Callable[[Callable[P, T]], Callable[P, T]]: diff --git a/tests/drivers/test_lakeshore_336.py b/tests/drivers/test_lakeshore_336.py index 5af219e8af6..8ad92cff29f 100644 --- a/tests/drivers/test_lakeshore_336.py +++ b/tests/drivers/test_lakeshore_336.py @@ -80,8 +80,8 @@ def __init__(self, *args, **kwargs) -> None: auto_range_enabled=0, # 'off', range=0, compensation_enabled=0, # False, - units=1, - ) # 'kelvin') + units=1, # 'kelvin' + ) for i in self.channel_name_command.keys() } @@ -259,6 +259,19 @@ def test_setpoint(lakeshore_336) -> None: assert h.setpoint() == setpoint +def test_curve_parameters(lakeshore_336) -> None: + # The curve numbers are assigned in the simulation pyvisa sim + # YAML file for each sensor/channel, and properties of the + # curves also include curve number in them to help testing + for ch, curve_number in zip(lakeshore_336.channels, (42, 41, 40, 39)): + assert ch.curve_number() == curve_number + assert ch.curve_name().endswith(str(curve_number)) + assert ch.curve_sn().endswith(str(curve_number)) + assert ch.curve_format() == "V/K" + assert str(int(ch.curve_limit())).endswith(str(curve_number)) + assert ch.curve_coefficient() == "negative" + + def test_select_range_limits(lakeshore_336) -> None: h = lakeshore_336.output_1 ranges = [1, 2, 3] diff --git a/tests/parameter/test_group_parameter.py b/tests/parameter/test_group_parameter.py index 53929efd5ab..974ec8b4478 100644 --- a/tests/parameter/test_group_parameter.py +++ b/tests/parameter/test_group_parameter.py @@ -1,12 +1,15 @@ import re from datetime import datetime -from typing import Any +from typing import TYPE_CHECKING, Any import pytest from qcodes.instrument import Instrument from qcodes.parameters import Group, GroupParameter +if TYPE_CHECKING: + from collections.abc import Callable + @pytest.fixture(autouse=True) def close_all_instruments(): @@ -23,7 +26,7 @@ def __init__( initial_a: int | None = None, initial_b: int | None = None, scale_a: float | None = None, - get_cmd: str | None = "CMD?", + get_cmd: "str | Callable[[], str] | None" = "CMD?", ) -> None: super().__init__(name) @@ -60,7 +63,9 @@ def write(self, cmd: str) -> None: self._a, self._b = (int(i) for i in result.groups()) def ask(self, cmd: str) -> str: - assert cmd == self._get_cmd + assert self._get_cmd is not None + get_cmd = self._get_cmd if isinstance(self._get_cmd, str) else self._get_cmd() + assert cmd == get_cmd return ",".join(str(i) for i in [self._a, self._b]) @@ -85,6 +90,23 @@ def test_sanity() -> None: assert dummy.b() == 10 +def test_get_cmd_being_a_callable_that_returns_a_string_works() -> None: + dummy = Dummy("dummy", get_cmd=lambda: "CMD?") + + assert dummy.a() == 0 + assert dummy.b() == 0 + + dummy.a(3) + dummy.b(6) + + assert dummy.a() == 3 + assert dummy.b() == 6 + + dummy.b(10) + assert dummy.a() == 3 + assert dummy.b() == 10 + + def test_raise_on_get_set_cmd() -> None: for arg in ["set_cmd", "get_cmd"]: kwarg = {arg: ""}