diff --git a/slicops/device/__init__.py b/slicops/device/__init__.py index 2912f5c..2a02221 100644 --- a/slicops/device/__init__.py +++ b/slicops/device/__init__.py @@ -15,13 +15,13 @@ class AccessorPutError(RuntimeError): - """The PV for this accessor is not writable""" + """This accessor is not writable""" pass class DeviceError(RuntimeError): - """Error communicating with PV""" + """Error communicating with accessor""" pass @@ -45,9 +45,9 @@ def accessor(self, accessor_name): """Get `_Accessor` for more complex operations Args: - accessor_name (str): friendly name for PV for this device + accessor_name (str): control system independent name Returns: - _Accessor: object holding PV state + _Accessor: object holding control system state """ if self._destroyed: raise AssertionError(f"destroyed {self}") @@ -56,7 +56,7 @@ def accessor(self, accessor_name): )[accessor_name] def destroy(self): - """Disconnect from PV's and remove state about device""" + """Disconnect from accessors and remove state about device""" if self._destroyed: return self._destroyed = True @@ -66,12 +66,12 @@ def destroy(self): a.destroy() def get(self, accessor_name): - """Read from PV + """Read from accessor Args: - accessor_name (str): friendly name for PV for this device + accessor_name (str): Returns: - object: the value from the PV converted to a Python type + object: the value from the control system converted to a Python type """ return self.accessor(accessor_name).get() @@ -79,18 +79,18 @@ def has_accessor(self, accessor_name): """Check whether device has accessor Args: - accessor_name (str): friendly name for PV for this device + accessor_name (str): control system independent name Returns: bool: True if accessor is found for device """ return accessor_name in self.meta.accessor def put(self, accessor_name, value): - """Set PV to value + """Set accessor to value Args: - accessor_name (str): friendly name for PV for this device - value (object): Value to write to PV + accessor_name (str): control system independent name + value (object): Value to write to control system """ return self.accessor(accessor_name).put(value) @@ -99,11 +99,11 @@ def __repr__(self): class _Accessor: - """Container for a PV, metadata, and dynamic state + """Container for a control system value, metadata, and dynamic state Attributes: device (Device): object holding this accessor - meta (PKDict): meta data about the accessor, e.g. pv_name, pv_writable + meta (PKDict): meta data about the accessor, e.g. cs_name, writable """ def __init__(self, device, accessor_name): @@ -116,10 +116,10 @@ def __init__(self, device, accessor_name): self._initialized = threading.Event() self._initializing = False # Defer initialization - self._pv = None + self._cs = None def destroy(self): - """Stop all monitoring and disconnect from PV""" + """Stop all monitoring and disconnect from accessor""" if self._destroyed: return with self._lock: @@ -128,9 +128,9 @@ def destroy(self): self._destroyed = True self._initializing = False self._callback = None - if (p := self._pv) is None: + if (p := self._cs) is None: return - self._pv = None + self._cs = None self._initialized.set() try: # Clears all callbacks @@ -139,12 +139,12 @@ def destroy(self): pkdlog("error={} {} stack={}", e, self, pkdexc()) def get(self): - """Read from PV + """Read from control system Returns: - object: the value from the PV converted to a Python type + object: the value from the accessor converted to a Python type """ - p = self.__pv() + p = self.__cs() if (rv := p.get(timeout=_TIMEOUT)) is None: raise DeviceError(f"unable to get {self}") if not p.connected: @@ -152,13 +152,13 @@ def get(self): return self._fixup_value(rv) def monitor(self, callback): - """Monitor PV and call callback with updates and connection changes + """Monitor accessor and call callback with updates and connection changes The argument to the callback is a `PKDict` with one or more of: error : str - error occured in the values from the PV callback (unlikely) + error occured in the values from the callback (unlikely) value : object - PV reported this change + control system reported this change connected : bool connection state changed: True if connected @@ -169,25 +169,25 @@ def monitor(self, callback): self._assert_not_destroyed() if self._callback: raise AssertionError("may only call monitor once") - if self._pv or self._initializing: + if self._cs or self._initializing: raise AssertionError("monitor must be called before get/put") self._callback = callback - self.__pv() + self.__cs() def monitor_stop(self): - """Stops monitoring PV""" + """Stops monitoring accessor""" with self._lock: if self._destroyed or not self._callback: return self._callback = None def put(self, value): - """Set PV to value + """Set accessor to value Args: - value (object): Value to write to PV + value (object): Value to write to control system """ - if not self.meta.pv_writable: + if not self.meta.writable: raise AccessorPutError(f"read-only {self}") if self.meta.py_type == bool: v = bool(value) @@ -198,7 +198,7 @@ def put(self, value): else: raise AccessorPutError(f"unhandled py_type={self.meta.py_type} {self}") # ECA_NORMAL == 0 and None is normal, too, apparently - p = self.__pv() + p = self.__cs() if (e := p.put(v)) != 1: raise DeviceError(f"put error={e} value={v} {self}") if not p.connected: @@ -244,11 +244,11 @@ def _on_value(self, **kwargs): pkdlog("error={} {} stack={}", e, self, pkdexc()) raise - def __pv(self): + def __cs(self): with self._lock: self._assert_not_destroyed() - if self._pv: - return self._pv + if self._cs: + return self._cs if not (i := self._initializing): self._initializing = True if i: @@ -260,21 +260,21 @@ def __pv(self): else PKDict() ) if self.accessor_name == "image": - # TODO(robnagler) this has to be done here, because you can't get pvs + # TODO(robnagler) this has to be done here, because you can't get accessor # from within a monitor callback. # TODO(robnagler) need a better way of dealing with this self._image_shape = (self.device.get("n_row"), self.device.get("n_col")) - self._pv = epics.PV( - self.meta.pv_name, + self._cs = epics.PV( + self.meta.cs_name, connection_callback=self._on_connection, connection_timeout=_TIMEOUT, **k, ) self._initialized.set() - return self._pv + return self._cs def __repr__(self): - return f"<_Accessor {self.device.device_name}.{self.accessor_name} {self.meta.pv_name}>" + return f"<_Accessor {self.device.device_name}.{self.accessor_name} {self.meta.cs_name}>" def _run_callback(self, **kwargs): k = PKDict(accessor=self, **kwargs) diff --git a/slicops/device_db.py b/slicops/device_db.py index 30f2e7a..e9b6651 100644 --- a/slicops/device_db.py +++ b/slicops/device_db.py @@ -17,19 +17,19 @@ class DeviceDbError(Exception): _ACCESSOR_META_DEFAULT = PKDict( py_type=float, - pv_writable=False, + writable=False, ) _ACCESSOR_META = PKDict( - acquire=PKDict(py_type=bool, pv_writable=True), - enabled=PKDict(py_type=int, pv_writable=False), - image=PKDict(py_type=numpy.ndarray, pv_writable=False), - n_bits=PKDict(py_type=int, pv_writable=False), - n_col=PKDict(py_type=int, pv_writable=False), - n_row=PKDict(py_type=int, pv_writable=False), - start_scan=PKDict(py_type=int, pv_writable=True), - target_control=PKDict(py_type=int, pv_writable=True), - target_status=PKDict(py_type=int, pv_writable=False), + acquire=PKDict(py_type=bool, writable=True), + enabled=PKDict(py_type=int, writable=False), + image=PKDict(py_type=numpy.ndarray, writable=False), + n_bits=PKDict(py_type=int, writable=False), + n_col=PKDict(py_type=int, writable=False), + n_row=PKDict(py_type=int, writable=False), + start_scan=PKDict(py_type=int, writable=True), + target_control=PKDict(py_type=int, writable=True), + target_status=PKDict(py_type=int, writable=False), ) @@ -174,12 +174,12 @@ class DeviceMeta(PKDict): """Information about a device Attributes: - accessor (PKDict): name to PKDict(name, pv_name, pv_writable, py_type, ...) + accessor (PKDict): name to PKDict(name, cs_name, writable, py_type, ...) beam_area (str): area where device is located beam_path (tuple): which beam paths does it go through device_type (str): type device, e.g. "PROF" device_name (str): name of device - pv_prefix (str): prefix to all accessor PVs for device + cs_name (str): prefix to all accessors for device """ pass diff --git a/slicops/device_sql_db.py b/slicops/device_sql_db.py index e2ff4d1..711490a 100644 --- a/slicops/device_sql_db.py +++ b/slicops/device_sql_db.py @@ -32,7 +32,7 @@ def device(name): accessor=PKDict( { r.accessor_name: PKDict(r) - for r in s.select("device_pv", PKDict(device_name=name)) + for r in s.select("device_accessor", PKDict(device_name=name)) } ), ) @@ -77,8 +77,8 @@ def upstream_devices(device_type, required_accessor, beam_path, end_device): s.t.beam_path.c.beam_area == s.t.device.c.beam_area, ) .join( - s.t.device_pv, - s.t.device_pv.c.device_name == c, + s.t.device_accessor, + s.t.device_accessor.c.device_name == c, ) ) .where( @@ -87,7 +87,7 @@ def upstream_devices(device_type, required_accessor, beam_path, end_device): s.t.device_meta_float.c.device_meta_value < _device_meta(end_device, "sum_l_meters", s), s.t.device.c.device_type == device_type, - s.t.device_pv.c.accessor_name == required_accessor, + s.t.device_accessor.c.accessor_name == required_accessor, ) .order_by(s.t.device_meta_float.c.device_meta_value) ) @@ -165,10 +165,12 @@ def _devices(self, session): device_name=d.name, beam_area=d.metadata.area, device_type=d.metadata.type, - pv_prefix=d.pv_prefix, + cs_name=d.cs_name, ) - for k, v in d.pvs.items(): - session.insert("device_pv", device_name=n, accessor_name=k, pv_name=v) + for k, v in d.device_accessors.items(): + session.insert( + "device_accessor", device_name=n, accessor_name=k, cs_name=v + ) for k, v in d.metadata.items(): if k not in self._METADATA_SKIP: session.insert( @@ -199,12 +201,12 @@ def _init(): device_name=p, beam_area=n + " foreign", device_type=n, - pv_prefix=s, + cs_name=s, ), - device_pv=PKDict( + device_accessor=PKDict( device_name=p + " foreign", accessor_name=p, - pv_name=s, + cs_name=s, ), device_meta_float=PKDict( device_name=p + " foreign", diff --git a/slicops/package_data/device_db.sqlite3 b/slicops/package_data/device_db.sqlite3 index 21564c9..580d175 100644 Binary files a/slicops/package_data/device_db.sqlite3 and b/slicops/package_data/device_db.sqlite3 differ diff --git a/slicops/package_data/sliclet/screen.yaml b/slicops/package_data/sliclet/screen.yaml index 7c07650..07add71 100644 --- a/slicops/package_data/sliclet/screen.yaml +++ b/slicops/package_data/sliclet/screen.yaml @@ -29,7 +29,7 @@ fields: ui: widget: heatmap_with_lineouts writable: false - pv: + cs_name: prototype: String ui: label: PV @@ -57,7 +57,7 @@ ui_layout: rows: - beam_path - camera - - pv + - cs_name - cell_group: - start_button - stop_button diff --git a/slicops/pkcli/device_db.py b/slicops/pkcli/device_db.py index e696cfa..6d2d99a 100644 --- a/slicops/pkcli/device_db.py +++ b/slicops/pkcli/device_db.py @@ -1,91 +1,12 @@ -"""Convert `lcls_tools.common.devices.yaml` to `slicops.device_meta_raw` +"""Interact with device_db -TODO(robnagler): document, correct types, add machine and area_to_machine, beam_path_to_machine - -:copyright: Copyright (c) 2024 The Board of Trustees of the Leland Stanford Junior University, through SLAC National Accelerator Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All Rights Reserved. +:copyright: Copyright (c) 2025 The Board of Trustees of the Leland Stanford Junior University, through SLAC National Accelerator Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All Rights Reserved. :license: http://github.com/slaclab/slicops/LICENSE """ from pykern.pkcollections import PKDict from pykern.pkdebug import pkdc, pkdlog, pkdp -import copy -import importlib -import pykern.pkio -import pykern.pkyaml -import re -import slicops.device_sql_db -import slicops.const - - -# We assume the names are valid (could check, but no realy point) -# What this test is doing is ensuring we understand the structure of a pv_base -_PV_POSTFIX_RE = r"([\w.]{1,60}|\w{1,58}:[\w.]{1,58})" - -# Eventually would be canonical -# TODO(robnagler) should be dynamic, but need to add to paths so easiest to add here for now -_DEV_YAML = """ -screens: - DEV_CAMERA: - controls_information: - PVs: - acquire: 13SIM1:cam1:Acquire - image: 13SIM1:image1:ArrayData - n_col: 13SIM1:cam1:SizeX - n_row: 13SIM1:cam1:SizeY - n_bits: 13SIM1:cam1:N_OF_BITS - control_name: 13SIM1 - metadata: - area: DEV_AREA - beam_path: - - DEV_BEAM_PATH - sum_l_meters: 0.614 - type: PROF -""" - - -_KNOWN_KEYS = PKDict( - controls_information=frozenset(("PVs", "control_name", "pv_cache")), - metadata=frozenset( - ( - "area", - "beam_path", - "bpms_after_wire", - "bpms_before_wire", - "l_eff", - "lblms", - "hardware", - "rf_freq", - "sum_l_meters", - "type", - ) - ), -) - -_TOP_LEVEL_KEYS = frozenset(_KNOWN_KEYS.keys()) - -_AREAS_MISSING_BEAM_PATH = frozenset( - ( - "COL", - "GTL", - "LI27", - "LI28", - ), -) - - -def parse(): - from pykern import pkjson - - p = _Parser() - return len(p.devices) - r = pykern.pkio.mkdir_parent("raw") - for d in p.devices.values(): - pkjson.dump_pretty( - d, - filename=pykern.pkio.mkdir_parent( - r.join(d.metadata.type, d.metadata.area) - ).join(d.name + ".json"), - ) +import slicops.device_db def query(func_name, *args): @@ -97,144 +18,4 @@ def query(func_name, *args): Returns: object: result of function """ - from slicops import device_db - - return getattr(device_db, func_name)(*args) - - -def yaml_to_sql(): - """Convert device yaml file to db""" - return slicops.device_sql_db.recreate(_Parser()) - - -class _Ignore(Exception): - pass - - -class _Parser(PKDict): - def __init__(self): - self._init() - self._parse() - - def _init(self): - self._yaml_glob = ( - pykern.pkio.py_path( - importlib.import_module("lcls_tools.common.devices.yaml").__file__, - ) - .dirpath() - .join("*.yaml") - ) - self.devices = PKDict() - self.ctl_keys = set() - self.meta_keys = set() - self.accessors = PKDict() - self.pvs = set() - self.beam_paths = PKDict() - - def _parse(self): - for p in pykern.pkio.sorted_glob(self._yaml_glob): - if p.basename == "beampaths.yaml": - continue - try: - self._parse_file(pykern.pkyaml.load_file(p), p) - except Exception: - pkdlog("ERROR file={}", p) - raise - if pykern.pkconfig.in_dev_mode(): - self._parse_file( - pykern.pkyaml.load_str(_DEV_YAML), pykern.pkio.py_path(".") - ) - - def _parse_file(self, src, path): - def _assign(name, rec): - """Corrections to input data""" - if name in self.devices: - raise ValueError(f"duplicate device={name}") - self.devices[name] = rec - - def _input_fixups(name, rec): - if not rec.controls_information.PVs: - # Also many don't have beam_path - raise _Ignore() - # Save beam_paths for fixups and to return - if rec.metadata.area not in self.beam_paths: - self.beam_paths[rec.metadata.area] = tuple(rec.metadata.beam_path) - if not rec.metadata.beam_path: - if rec.metadata.area in _AREAS_MISSING_BEAM_PATH: - raise _Ignore() - rec.metadata.beam_path = self.beam_paths[rec.metadata.area] - if "VCCB" == name: - # Typo in MEME? - rec.controls_information.PVs.resolution = "CAMR:LGUN:950:RESOLUTION" - rec.controls_information.PVs.n_col = "CAMR:LGUN:950:MaxSizeX_RBV" - rec.controls_information.PVs.n_row = "CAMR:LGUN:950:MaxSizeY_RBV" - rec.metadata.type = "PROF" - elif "VCC" == name: - rec.metadata.type = "PROF" - if rec.metadata.type == "PROF": - # No cameras have Acquire for some reason - rec.controls_information.PVs.pksetdefault( - "acquire", f"{rec.controls_information.control_name}:Acquire" - ) - # TODO(robnagler) parse pv_cache - return rec - - def _meta(name, raw): - # TODO validation - c = raw.controls_information - m = raw.metadata - # TODO ignore for now - raw.metadata.pkdel("hardware") - self.meta_keys.update(m.keys()) - self.ctl_keys.update(c.keys()) - rv = PKDict( - name=name, - pv_prefix=c.control_name, - ) - for k in "area", "beam_path": - if not m.get(k): - raise AssertionError(f"missing metadata.{k}") - rv.metadata = PKDict({k: v for k, v in m.items() if v is not None}) - rv.pvs = PKDict(_pvs(c.PVs, rv)) - return rv - - def _pvs(pvs, rv): - p = re.compile(rf"^{re.escape(rv.pv_prefix)}:{_PV_POSTFIX_RE}$") - for k, v in pvs.items(): - if not (x := p.search(v)): - raise ValueError(f"pv={v} does not match regex={p}") - yield k, v - - def _validate(name, kind, raw): - if not (t := slicops.const.DEVICE_KINDS_TO_TYPES.get(kind)): - raise AssertionError(f"unknown kind={kind}") - if raw.metadata.type not in t: - raise AssertionError(f"unknown type={raw.metadata.type} expect={t}") - if x := set(raw.keys()) - _TOP_LEVEL_KEYS: - raise AssertionError(f"unknown top level keys={s}") - for x in _TOP_LEVEL_KEYS: - if y := set(raw[x].keys()) - _KNOWN_KEYS[x]: - raise AssertionError(f"unknown {x} keys={y}") - if not raw.controls_information.PVs: - raise AssertionError(f"no PVs") - return name, raw - - for k, x in src.items(): - for n, r in x.items(): - try: - - _assign( - n, - _meta( - *_validate( - n, - k, - _input_fixups(n, copy.deepcopy(r)), - ) - ), - ) - except _Ignore: - pass - except Exception: - pkdlog("ERROR device={} record={}", n, r) - raise + return getattr(slicops.device_db, func_name)(*args) diff --git a/slicops/pkcli/lcls_yaml.py b/slicops/pkcli/lcls_yaml.py new file mode 100644 index 0000000..0b3f1c3 --- /dev/null +++ b/slicops/pkcli/lcls_yaml.py @@ -0,0 +1,225 @@ +"""Convert `lcls_tools.common.devices.yaml` to `slicops.device_meta_raw` + +TODO(robnagler): document, correct types, add machine and area_to_machine, beam_path_to_machine + +:copyright: Copyright (c) 2024 The Board of Trustees of the Leland Stanford Junior University, through SLAC National Accelerator Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All Rights Reserved. +:license: http://github.com/slaclab/slicops/LICENSE +""" + +from pykern.pkcollections import PKDict +from pykern.pkdebug import pkdc, pkdlog, pkdp +import copy +import importlib +import pykern.pkio +import pykern.pkyaml +import re +import slicops.device_sql_db +import slicops.const + + +# We assume the names are valid (could check, but no realy point) +# What this test is doing is ensuring we understand the structure of a pv_base +_PV_POSTFIX_RE = r"([\w.]{1,60}|\w{1,58}:[\w.]{1,58})" + +# Eventually would be canonical +# TODO(robnagler) should be dynamic, but need to add to paths so easiest to add here for now +_DEV_YAML = """ +screens: + DEV_CAMERA: + controls_information: + PVs: + acquire: 13SIM1:cam1:Acquire + image: 13SIM1:image1:ArrayData + n_col: 13SIM1:cam1:SizeX + n_row: 13SIM1:cam1:SizeY + n_bits: 13SIM1:cam1:N_OF_BITS + control_name: 13SIM1 + metadata: + area: DEV_AREA + beam_path: + - DEV_BEAM_PATH + sum_l_meters: 0.614 + type: PROF +""" + + +_KNOWN_KEYS = PKDict( + controls_information=frozenset(("PVs", "control_name", "pv_cache")), + metadata=frozenset( + ( + "area", + "beam_path", + "bpms_after_wire", + "bpms_before_wire", + "l_eff", + "lblms", + "hardware", + "rf_freq", + "sum_l_meters", + "type", + ) + ), +) + +_TOP_LEVEL_KEYS = frozenset(_KNOWN_KEYS.keys()) + +_AREAS_MISSING_BEAM_PATH = frozenset( + ( + "COL", + "GTL", + "LI27", + "LI28", + ), +) + + +def create_sql_db(): + """Convert device yaml file to db""" + return slicops.device_sql_db.recreate(_Parser()) + + +def parse(): + from pykern import pkjson + + p = _Parser() + return len(p.devices) + r = pykern.pkio.mkdir_parent("raw") + for d in p.devices.values(): + pkjson.dump_pretty( + d, + filename=pykern.pkio.mkdir_parent( + r.join(d.metadata.type, d.metadata.area) + ).join(d.name + ".json"), + ) + + +class _Ignore(Exception): + pass + + +class _Parser(PKDict): + def __init__(self): + self._init() + self._parse() + + def _init(self): + self._yaml_glob = ( + pykern.pkio.py_path( + importlib.import_module("lcls_tools.common.devices.yaml").__file__, + ) + .dirpath() + .join("*.yaml") + ) + self.devices = PKDict() + self.ctl_keys = set() + self.meta_keys = set() + self.device_accessors = PKDict() + self.beam_paths = PKDict() + + def _parse(self): + for p in pykern.pkio.sorted_glob(self._yaml_glob): + if p.basename == "beampaths.yaml": + continue + try: + self._parse_file(pykern.pkyaml.load_file(p), p) + except Exception: + pkdlog("ERROR file={}", p) + raise + if pykern.pkconfig.in_dev_mode(): + self._parse_file( + pykern.pkyaml.load_str(_DEV_YAML), pykern.pkio.py_path(".") + ) + + def _parse_file(self, src, path): + def _assign(name, rec): + """Corrections to input data""" + if name in self.devices: + raise ValueError(f"duplicate device={name}") + self.devices[name] = rec + + def _input_fixups(name, rec): + if not rec.controls_information.PVs: + # Also many don't have beam_path + raise _Ignore() + # Save beam_paths for fixups and to return + if rec.metadata.area not in self.beam_paths: + self.beam_paths[rec.metadata.area] = tuple(rec.metadata.beam_path) + if not rec.metadata.beam_path: + if rec.metadata.area in _AREAS_MISSING_BEAM_PATH: + raise _Ignore() + rec.metadata.beam_path = self.beam_paths[rec.metadata.area] + if "VCCB" == name: + # Typo in MEME? + rec.controls_information.PVs.resolution = "CAMR:LGUN:950:RESOLUTION" + rec.controls_information.PVs.n_col = "CAMR:LGUN:950:MaxSizeX_RBV" + rec.controls_information.PVs.n_row = "CAMR:LGUN:950:MaxSizeY_RBV" + rec.metadata.type = "PROF" + elif "VCC" == name: + rec.metadata.type = "PROF" + if rec.metadata.type == "PROF": + # No cameras have Acquire for some reason + rec.controls_information.PVs.pksetdefault( + "acquire", f"{rec.controls_information.control_name}:Acquire" + ) + # TODO(robnagler) parse pv_cache + return rec + + def _meta(name, raw): + # TODO validation + c = raw.controls_information + m = raw.metadata + # TODO ignore for now + raw.metadata.pkdel("hardware") + self.meta_keys.update(m.keys()) + self.ctl_keys.update(c.keys()) + rv = PKDict( + name=name, + cs_name=c.control_name, + ) + for k in "area", "beam_path": + if not m.get(k): + raise AssertionError(f"missing metadata.{k}") + rv.metadata = PKDict({k: v for k, v in m.items() if v is not None}) + rv.device_accessors = PKDict(_pvs(c.PVs, rv)) + return rv + + def _pvs(pvs, rv): + p = re.compile(rf"^{re.escape(rv.cs_name)}:{_PV_POSTFIX_RE}$") + for k, v in pvs.items(): + if not (x := p.search(v)): + raise ValueError(f"pv={v} does not match regex={p}") + yield k, v + + def _validate(name, kind, raw): + if not (t := slicops.const.DEVICE_KINDS_TO_TYPES.get(kind)): + raise AssertionError(f"unknown kind={kind}") + if raw.metadata.type not in t: + raise AssertionError(f"unknown type={raw.metadata.type} expect={t}") + if x := set(raw.keys()) - _TOP_LEVEL_KEYS: + raise AssertionError(f"unknown top level keys={s}") + for x in _TOP_LEVEL_KEYS: + if y := set(raw[x].keys()) - _KNOWN_KEYS[x]: + raise AssertionError(f"unknown {x} keys={y}") + if not raw.controls_information.PVs: + raise AssertionError(f"no PVs") + return name, raw + + for k, x in src.items(): + for n, r in x.items(): + try: + + _assign( + n, + _meta( + *_validate( + n, + k, + _input_fixups(n, copy.deepcopy(r)), + ) + ), + ) + except _Ignore: + pass + except Exception: + pkdlog("ERROR device={} record={}", n, r) + raise diff --git a/slicops/sliclet/screen.py b/slicops/sliclet/screen.py index a0bb81c..3e4e1d2 100644 --- a/slicops/sliclet/screen.py +++ b/slicops/sliclet/screen.py @@ -33,15 +33,15 @@ ("plot.ui.visible", False), # Useful to avoid large ctx sends ("plot.value", None), - ("pv.ui.visible", False), - ("pv.value", None), + ("cs_name.ui.visible", False), + ("cs_name.value", None), ("single_button.ui.visible", False), ("start_button.ui.visible", False), ("stop_button.ui.visible", False), ) + _BUTTONS_DISABLE _DEVICE_ENABLE = ( - ("pv.ui.visible", True), + ("cs_name.ui.visible", True), ("single_button.ui.visible", True), ("start_button.ui.visible", True), ("stop_button.ui.visible", True), @@ -173,7 +173,7 @@ def _monitors(): self.__device_destroy(txn) self.__user_alert(txn, "unable to connect to camera={} error={}", camera, e) return - txn.multi_set(_DEVICE_ENABLE + (("pv.value", self.__device.meta.pv_prefix),)) + txn.multi_set(_DEVICE_ENABLE + (("cs_name.value", self.__device.meta.cs_name),)) def __handle_acquire(self, acquire): with self.lock_for_update() as txn: diff --git a/tests/device_db_test.py b/tests/device_db_test.py index 11b85b5..12f2c60 100644 --- a/tests/device_db_test.py +++ b/tests/device_db_test.py @@ -25,11 +25,11 @@ def test_basic(): device_db.device_names("xyzzy", "SC_SXR") a = device_db.meta_for_device("VCCB") - pkunit.pkeq("CAMR:LGUN:950:Image:ArrayData", a.accessor.image.pv_name) + pkunit.pkeq("CAMR:LGUN:950:Image:ArrayData", a.accessor.image.cs_name) pkunit.pkeq(numpy.ndarray, a.accessor.image.py_type) pkunit.pkeq("GUNB", a.beam_area) - # YAG01B does not have any PVs so not in db + # YAG01B does not have any accessors so not in db with pkunit.pkexcept("NoRows"): device_db.meta_for_device("YAG01B") diff --git a/tests/sliclet/screen_test.py b/tests/sliclet/screen_test.py index f4cb07f..b6135e3 100644 --- a/tests/sliclet/screen_test.py +++ b/tests/sliclet/screen_test.py @@ -66,4 +66,4 @@ async def _buttons(s, expect, msg): # TODO(robnagler) better error handling await _put(ux, "camera", "DEV_CAMERA", Exception) await s.ctx_field_set(camera="YAG01") r = await s.ctx_update() - pkunit.pkeq("YAGS:IN20:211", r.fields.pv.value) + pkunit.pkeq("YAGS:IN20:211", r.fields.cs_name.value)