From 0ca5517870954a6cfcda91aa845aa75ff353ec26 Mon Sep 17 00:00:00 2001 From: Blaise Thompson Date: Mon, 14 Aug 2023 17:11:52 -0500 Subject: [PATCH] add first-class support for discrete hardware (#98) * add first-class support for discrete hardware * add * setter not getter (#94) * setter not getter * tests * rm print * add first-class support for discrete hardware * add * don't double count describe --- CHANGELOG.md | 3 ++ tests/is_discrete/config.toml | 2 + tests/is_discrete/test_is_discrete.py | 67 +++++++++++++++++++++++++++ yaqc_bluesky/_base.py | 2 +- yaqc_bluesky/_device.py | 4 +- yaqc_bluesky/_is_discrete.py | 15 ++++++ yaqc_bluesky/_property.py | 11 ++++- 7 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 tests/is_discrete/config.toml create mode 100644 tests/is_discrete/test_is_discrete.py create mode 100644 yaqc_bluesky/_is_discrete.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e7d335..eb4b033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] +### Added +- daemons supporting is-discrete now can be set according to their identifiers + ### Changed - properties now appear as sub-devices diff --git a/tests/is_discrete/config.toml b/tests/is_discrete/config.toml new file mode 100644 index 0000000..bbfdebc --- /dev/null +++ b/tests/is_discrete/config.toml @@ -0,0 +1,2 @@ +[spectrometer] +port = 38383 \ No newline at end of file diff --git a/tests/is_discrete/test_is_discrete.py b/tests/is_discrete/test_is_discrete.py new file mode 100644 index 0000000..ac96c98 --- /dev/null +++ b/tests/is_discrete/test_is_discrete.py @@ -0,0 +1,67 @@ +import pathlib +import numpy as np +import time +import subprocess +from yaqd_core import testing +import yaqc_bluesky +import bluesky +from bluesky import plan_stubs as bps + + +config = pathlib.Path(__file__).parent / "config.toml" + + +@testing.run_daemon_entry_point("fake-discrete-hardware", config=config) +def test_identifier_is_in_read(): + d = yaqc_bluesky.Device(38383) + read_keys = list(d.read().keys()) + assert f"{d.name}_position_identifier" in read_keys + + +@testing.run_daemon_entry_point("fake-discrete-hardware", config=config) +def test_identifier_is_in_describe(): + d = yaqc_bluesky.Device(38383) + describe_keys = list(d.describe().keys()) + assert f"{d.name}_position_identifier" in describe_keys + + +@testing.run_daemon_entry_point("fake-discrete-hardware", config=config) +def test_describe_read(): + d = yaqc_bluesky.Device(38383) + describe_keys = list(d.describe().keys()) + read_keys = list(d.read().keys()) + assert describe_keys == read_keys + + +@testing.run_daemon_entry_point("fake-discrete-hardware", config=config) +def test_hint_fields(): + d = yaqc_bluesky.Device(38383) + fields = d.hints["fields"] + for field in fields: + assert field in d.describe().keys() + assert field in d.read().keys() + + +@testing.run_daemon_entry_point("fake-discrete-hardware", config=config) +def test_set_read(): + d = yaqc_bluesky.Device(38383) + d.set(470) + time.sleep(1) + out = d.read() + assert out[f"{d.name}_position_identifier"]["value"] == "blue" + d.set("green") + time.sleep(1) + out = d.read() + assert np.isclose(out[d.name]["value"], 540.0) + + +@testing.run_daemon_entry_point("fake-discrete-hardware", config=config) +def test_mv(): + def plan(): + d = yaqc_bluesky.Device(38383) + for identifier, position in d.yaq_client.get_position_identifiers().items(): + yield from bps.mv(d, identifier) + assert np.isclose(d.read()[d.name]["value"], position) + + RE = bluesky.RunEngine() + RE(plan()) diff --git a/yaqc_bluesky/_base.py b/yaqc_bluesky/_base.py index cb8d591..40043f7 100644 --- a/yaqc_bluesky/_base.py +++ b/yaqc_bluesky/_base.py @@ -25,7 +25,7 @@ def __init__(self, yaq_client, *, name=None): for key, prop in self.yaq_client.properties.items(): if key in ["destination", "position"]: continue - if prop.type not in ["double"]: + if prop.type not in ["double", "string"]: continue self.children.append(PropertyDevice(self, key)) if not hasattr(self, key): # don't overwrite please diff --git a/yaqc_bluesky/_device.py b/yaqc_bluesky/_device.py index f969db6..190100e 100644 --- a/yaqc_bluesky/_device.py +++ b/yaqc_bluesky/_device.py @@ -8,9 +8,11 @@ from ._has_measure_trigger import HasMeasureTrigger from ._has_mapping import HasMapping from ._has_dependent import HasDependent +from ._is_discrete import IsDiscrete -traits = [ +traits = [ # MRO goes from top to bottom + ("is-discrete", IsDiscrete), ("has-position", HasPosition), ("has-measure-trigger", HasMeasureTrigger), ("has-mapping", HasMapping), diff --git a/yaqc_bluesky/_is_discrete.py b/yaqc_bluesky/_is_discrete.py new file mode 100644 index 0000000..6a0cc00 --- /dev/null +++ b/yaqc_bluesky/_is_discrete.py @@ -0,0 +1,15 @@ +from collections import OrderedDict +import time +import warnings + +from ._has_position import HasPosition + + +class IsDiscrete(HasPosition): + + def set(self, value): + try: + self.yaq_client.set_position(value) + except TypeError: + self.yaq_client.set_identifier(value) + return self._wait_until_still() diff --git a/yaqc_bluesky/_property.py b/yaqc_bluesky/_property.py index 3836fbe..7f2c932 100644 --- a/yaqc_bluesky/_property.py +++ b/yaqc_bluesky/_property.py @@ -15,6 +15,13 @@ def __init__(self, parent, name): self.parent = parent self.name = name self._yaq_property = self.parent.yaq_client.properties[self.name] + if self._yaq_property.type in ["int", "float", "double"]: + self._dtype = "number" + elif self._yaq_property.type == "string": + self._dtype = "string" + else: + self._dtype = "array" + self._setpoint = float("nan") def set(self, value) -> Status: @@ -30,10 +37,10 @@ def set(self, value) -> Status: def describe(self) -> dict: out = dict() - out[f"{self.parent.name}_{self.name}_readback"] = {"dtype": "number", "shape": [], "source": f"yaq:{self.parent.yaq_name}" + out[f"{self.parent.name}_{self.name}_readback"] = {"dtype": self._dtype, "shape": [], "source": f"yaq:{self.parent.yaq_name}" } if self._yaq_property._property["getter"]: - out[f"{self.parent.name}_{self.name}_setpoint"] = {"dtype": "number", "shape": [], "source": f"yaq:{self.parent.yaq_name}"} + out[f"{self.parent.name}_{self.name}_setpoint"] = {"dtype": self._dtype, "shape": [], "source": f"yaq:{self.parent.yaq_name}"} return out def read(self) -> dict: