added "kind" to channel list
This commit is contained in:
@@ -4,100 +4,138 @@ from ophyd import Device, Component as Cpt, EpicsSignal
|
||||
# ---------------------------
|
||||
# Registry: sections/channels
|
||||
# ---------------------------
|
||||
|
||||
CHANNELS = {
|
||||
"Valves Frontend": [
|
||||
{"attr": "FEVVPG0000", "label": "FE-VVPG-0000", "pv": "X12SA-FE-VVPG-0000:PLC_OPEN"},
|
||||
{"attr": "FEVVPG1010", "label": "FE-VVPG-1010", "pv": "X12SA-FE-VVPG-1010:PLC_OPEN"},
|
||||
{"attr": "FEVVFV2010", "label": "FE-VVFV-2010", "pv": "X12SA-FE-VVFV-2010:PLC_OPEN"},
|
||||
{"attr": "FEVVPG2010", "label": "FE-VVPG-2010", "pv": "X12SA-FE-VVPG-2010:PLC_OPEN"},
|
||||
{"attr": "FEVVPG0000", "label": "FE-VVPG-0000",
|
||||
"pv": "X12SA-FE-VVPG-0000:PLC_OPEN", "kind": "valve"},
|
||||
{"attr": "FEVVPG1010", "label": "FE-VVPG-1010",
|
||||
"pv": "X12SA-FE-VVPG-1010:PLC_OPEN", "kind": "valve"},
|
||||
{"attr": "FEVVFV2010", "label": "FE-VVFV-2010",
|
||||
"pv": "X12SA-FE-VVFV-2010:PLC_OPEN", "kind": "valve"},
|
||||
{"attr": "FEVVPG2010", "label": "FE-VVPG-2010",
|
||||
"pv": "X12SA-FE-VVPG-2010:PLC_OPEN", "kind": "valve"},
|
||||
],
|
||||
|
||||
"Valves Optics Hutch": [
|
||||
{"attr": "OPVVPG1010", "label": "OP-VVPG-1010", "pv": "X12SA-OP-VVPG-1010:PLC_OPEN"},
|
||||
{"attr": "OPVVPG2010", "label": "OP-VVPG-2010", "pv": "X12SA-OP-VVPG-2010:PLC_OPEN"},
|
||||
{"attr": "OPVVPG3010", "label": "OP-VVPG-3010", "pv": "X12SA-OP-VVPG-3010:PLC_OPEN"},
|
||||
{"attr": "OPVVPG3020", "label": "OP-VVPG-3020", "pv": "X12SA-OP-VVPG-3020:PLC_OPEN"},
|
||||
{"attr": "OPVVPG4010", "label": "OP-VVPG-4010", "pv": "X12SA-OP-VVPG-4010:PLC_OPEN"},
|
||||
{"attr": "OPVVPG5010", "label": "OP-VVPG-5010", "pv": "X12SA-OP-VVPG-5010:PLC_OPEN"},
|
||||
{"attr": "OPVVPG6010", "label": "OP-VVPG-6010", "pv": "X12SA-OP-VVPG-6010:PLC_OPEN"},
|
||||
{"attr": "OPVVPG7010", "label": "OP-VVPG-7010", "pv": "X12SA-OP-VVPG-7010:PLC_OPEN"},
|
||||
{"attr": "OPVVPG1010", "label": "OP-VVPG-1010",
|
||||
"pv": "X12SA-OP-VVPG-1010:PLC_OPEN", "kind": "valve"},
|
||||
{"attr": "OPVVPG2010", "label": "OP-VVPG-2010",
|
||||
"pv": "X12SA-OP-VVPG-2010:PLC_OPEN", "kind": "valve"},
|
||||
{"attr": "OPVVPG3010", "label": "OP-VVPG-3010",
|
||||
"pv": "X12SA-OP-VVPG-3010:PLC_OPEN", "kind": "valve"},
|
||||
{"attr": "OPVVPG3020", "label": "OP-VVPG-3020",
|
||||
"pv": "X12SA-OP-VVPG-3020:PLC_OPEN", "kind": "valve"},
|
||||
{"attr": "OPVVPG4010", "label": "OP-VVPG-4010",
|
||||
"pv": "X12SA-OP-VVPG-4010:PLC_OPEN", "kind": "valve"},
|
||||
{"attr": "OPVVPG5010", "label": "OP-VVPG-5010",
|
||||
"pv": "X12SA-OP-VVPG-5010:PLC_OPEN", "kind": "valve"},
|
||||
{"attr": "OPVVPG6010", "label": "OP-VVPG-6010",
|
||||
"pv": "X12SA-OP-VVPG-6010:PLC_OPEN", "kind": "valve"},
|
||||
{"attr": "OPVVPG7010", "label": "OP-VVPG-7010",
|
||||
"pv": "X12SA-OP-VVPG-7010:PLC_OPEN", "kind": "valve"},
|
||||
],
|
||||
|
||||
"Valves ES Hutch": [
|
||||
{"attr": "ESVVPG1010", "label": "ES-VVPG-1010", "pv": "X12SA-ES-VVPG-1010:PLC_OPEN"},
|
||||
{"attr": "ESVVPG1010", "label": "ES-VVPG-1010",
|
||||
"pv": "X12SA-ES-VVPG-1010:PLC_OPEN", "kind": "valve"},
|
||||
],
|
||||
|
||||
"Shutters Frontend": [
|
||||
{"attr": "FEPSH1", "label": "FE-PSH1-EMLS-0010", "pv": "X12SA-FE-PSH1-EMLS-0010:OPEN"},
|
||||
{"attr": "FESTO1", "label": "FE-STO1-EMLS-0010", "pv": "X12SA-FE-STO1-EMLS-0010:OPEN"},
|
||||
{"attr": "FEPSH1", "label": "FE-PSH1-EMLS-0010",
|
||||
"pv": "X12SA-FE-PSH1-EMLS-0010:OPEN", "kind": "shutter"},
|
||||
{"attr": "FESTO1", "label": "FE-STO1-EMLS-0010",
|
||||
"pv": "X12SA-FE-STO1-EMLS-0010:OPEN", "kind": "shutter"},
|
||||
],
|
||||
|
||||
"Shutters Endstation": [
|
||||
{"attr": "ESPSH17010", "label": "OP-PSH1-EMLS-7010", "pv": "X12SA-OP-PSH1-EMLS-7010:OPEN"},
|
||||
{"attr": "ESPSH17010", "label": "OP-PSH1-EMLS-7010",
|
||||
"pv": "X12SA-OP-PSH1-EMLS-7010:OPEN", "kind": "shutter"},
|
||||
],
|
||||
|
||||
# -------------------------------
|
||||
# 🔵 DMM MONOCHROMATOR
|
||||
# -------------------------------
|
||||
# -------------------------------------------------
|
||||
# DMM MONOCHROMATOR — with temperature, switches,
|
||||
# heater faults, energy, string readbacks
|
||||
# -------------------------------------------------
|
||||
"DMM Monochromator": [
|
||||
# Temperature sensors (:TEMP)
|
||||
{"attr": "DMM_ETTC_3010", "label": "DMM Temp Surface 1", "pv": "X12SA-OP-DMM-ETTC-3010:TEMP"},
|
||||
{"attr": "DMM_ETTC_3020", "label": "DMM Temp Surface 2", "pv": "X12SA-OP-DMM-ETTC-3020:TEMP"},
|
||||
{"attr": "DMM_ETTC_3030", "label": "DMM Temp Shield 1 (disaster)", "pv": "X12SA-OP-DMM-ETTC-3030:TEMP"},
|
||||
{"attr": "DMM_ETTC_3040", "label": "DMM Temp Shield 2 (disaster)", "pv": "X12SA-OP-DMM-ETTC-3040:TEMP"},
|
||||
|
||||
# Translation and Bragg switches (:THRU / :IN)
|
||||
{"attr": "DMM_EMLS_3010", "label": "DMM Translation ThruPos", "pv": "X12SA-OP-DMM-EMLS-3010:THRU"},
|
||||
{"attr": "DMM_EMLS_3020", "label": "DMM Translation InPos", "pv": "X12SA-OP-DMM-EMLS-3020:IN"},
|
||||
{"attr": "DMM_EMLS_3030", "label": "DMM Bragg ThruPos", "pv": "X12SA-OP-DMM-EMLS-3030:THRU"},
|
||||
{"attr": "DMM_EMLS_3040", "label": "DMM Bragg InPos", "pv": "X12SA-OP-DMM-EMLS-3040:IN"},
|
||||
# Temperature sensors (1 decimal)
|
||||
{"attr": "DMM_ETTC_3010", "label": "DMM Temp Surface 1",
|
||||
"pv": "X12SA-OP-DMM-ETTC-3010:TEMP", "kind": "temp"},
|
||||
{"attr": "DMM_ETTC_3020", "label": "DMM Temp Surface 2",
|
||||
"pv": "X12SA-OP-DMM-ETTC-3020:TEMP", "kind": "temp"},
|
||||
{"attr": "DMM_ETTC_3030", "label": "DMM Temp Shield 1 (disaster)",
|
||||
"pv": "X12SA-OP-DMM-ETTC-3030:TEMP", "kind": "temp"},
|
||||
{"attr": "DMM_ETTC_3040", "label": "DMM Temp Shield 2 (disaster)",
|
||||
"pv": "X12SA-OP-DMM-ETTC-3040:TEMP", "kind": "temp"},
|
||||
|
||||
# Heater faults (ALL use :SWITCH)
|
||||
{"attr": "DMM_EMSW_3050_SWITCH", "label": "DMM Heater Fault XTAL 1 (switch)", "pv": "X12SA-OP-DMM-EMSW-3050:SWITCH"},
|
||||
{"attr": "DMM_EMSW_3060_SWITCH", "label": "DMM Heater Fault XTAL 2 (switch)", "pv": "X12SA-OP-DMM-EMSW-3060:SWITCH"},
|
||||
{"attr": "DMM_EMSW_3070_SWITCH", "label": "DMM Heater Fault Support 1 (switch)", "pv": "X12SA-OP-DMM-EMSW-3070:SWITCH"},
|
||||
# Switches (ACTIVE/INACTIVE)
|
||||
{"attr": "DMM_EMLS_3010", "label": "DMM Translation ThruPos",
|
||||
"pv": "X12SA-OP-DMM-EMLS-3010:THRU", "kind": "switch"},
|
||||
{"attr": "DMM_EMLS_3020", "label": "DMM Translation InPos",
|
||||
"pv": "X12SA-OP-DMM-EMLS-3020:IN", "kind": "switch"},
|
||||
{"attr": "DMM_EMLS_3030", "label": "DMM Bragg ThruPos",
|
||||
"pv": "X12SA-OP-DMM-EMLS-3030:THRU", "kind": "switch"},
|
||||
{"attr": "DMM_EMLS_3040", "label": "DMM Bragg InPos",
|
||||
"pv": "X12SA-OP-DMM-EMLS-3040:IN", "kind": "switch"},
|
||||
|
||||
# Heater faults (OK / FAULT)
|
||||
{"attr": "DMM_EMSW_3050_SWITCH", "label": "DMM Heater Fault XTAL 1",
|
||||
"pv": "X12SA-OP-DMM-EMSW-3050:SWITCH", "kind": "fault"},
|
||||
{"attr": "DMM_EMSW_3060_SWITCH", "label": "DMM Heater Fault XTAL 2",
|
||||
"pv": "X12SA-OP-DMM-EMSW-3060:SWITCH", "kind": "fault"},
|
||||
{"attr": "DMM_EMSW_3070_SWITCH", "label": "DMM Heater Fault Support 1",
|
||||
"pv": "X12SA-OP-DMM-EMSW-3070:SWITCH", "kind": "fault"},
|
||||
|
||||
# Readbacks
|
||||
{"attr": "DMM1_ENERGY_GET", "label": "DMM Energy", "pv": "X12SA-OP-DMM1:ENERGY-GET"},
|
||||
{"attr": "DMM1_POSITION", "label": "DMM Position", "pv": "X12SA-OP-DMM1:POSITION"},
|
||||
{"attr": "DMM1_STRIPE", "label": "DMM Stripe", "pv": "X12SA-OP-DMM1:STRIPE"},
|
||||
{"attr": "DMM1_ENERGY_GET", "label": "DMM Energy",
|
||||
"pv": "X12SA-OP-DMM1:ENERGY-GET", "kind": "energy"},
|
||||
{"attr": "DMM1_POSITION", "label": "DMM Position",
|
||||
"pv": "X12SA-OP-DMM1:POSITION", "kind": "string"},
|
||||
{"attr": "DMM1_STRIPE", "label": "DMM Stripe",
|
||||
"pv": "X12SA-OP-DMM1:STRIPE", "kind": "string"},
|
||||
],
|
||||
|
||||
# -------------------------------
|
||||
# 🔵 CCM MONOCHROMATOR
|
||||
# -------------------------------
|
||||
# -------------------------------------------------
|
||||
# CCM MONOCHROMATOR
|
||||
# -------------------------------------------------
|
||||
"CCM Monochromator": [
|
||||
# Temperature sensors (:TEMP)
|
||||
{"attr": "CCM_ETTC_4010", "label": "CCM Temp Crystal", "pv": "X12SA-OP-CCM-ETTC-4010:TEMP"},
|
||||
{"attr": "CCM_ETTC_4020", "label": "CCM Temp Shield (disaster)", "pv": "X12SA-OP-CCM-ETTC-4020:TEMP"},
|
||||
|
||||
# Heater faults (ALL use :SWITCH)
|
||||
{"attr": "CCM_EMSW_4010_SWITCH", "label": "CCM Heater Fault 1 (switch)", "pv": "X12SA-OP-CCM-EMSW-4010:SWITCH"},
|
||||
{"attr": "CCM_EMSW_4020_SWITCH", "label": "CCM Heater Fault 2 (switch)", "pv": "X12SA-OP-CCM-EMSW-4020:SWITCH"},
|
||||
{"attr": "CCM_EMSW_4030_SWITCH", "label": "CCM Heater Fault 3 (switch)", "pv": "X12SA-OP-CCM-EMSW-4030:SWITCH"},
|
||||
# Temperatures
|
||||
{"attr": "CCM_ETTC_4010", "label": "CCM Temp Crystal",
|
||||
"pv": "X12SA-OP-CCM-ETTC-4010:TEMP", "kind": "temp"},
|
||||
{"attr": "CCM_ETTC_4020", "label": "CCM Temp Shield (disaster)",
|
||||
"pv": "X12SA-OP-CCM-ETTC-4020:TEMP", "kind": "temp"},
|
||||
|
||||
# Heater faults
|
||||
{"attr": "CCM_EMSW_4010_SWITCH", "label": "CCM Heater Fault 1",
|
||||
"pv": "X12SA-OP-CCM-EMSW-4010:SWITCH", "kind": "fault"},
|
||||
{"attr": "CCM_EMSW_4020_SWITCH", "label": "CCM Heater Fault 2",
|
||||
"pv": "X12SA-OP-CCM-EMSW-4020:SWITCH", "kind": "fault"},
|
||||
{"attr": "CCM_EMSW_4030_SWITCH", "label": "CCM Heater Fault 3",
|
||||
"pv": "X12SA-OP-CCM-EMSW-4030:SWITCH", "kind": "fault"},
|
||||
|
||||
# Readbacks
|
||||
{"attr": "CCM1_ENERGY_GET", "label": "CCM Energy", "pv": "X12SA-OP-CCM1:ENERGY-GET"},
|
||||
{"attr": "CCM1_POSITION", "label": "CCM Position", "pv": "X12SA-OP-CCM1:POSITION"},
|
||||
{"attr": "CCM1_ENERGY_GET", "label": "CCM Energy",
|
||||
"pv": "X12SA-OP-CCM1:ENERGY-GET", "kind": "energy"},
|
||||
{"attr": "CCM1_POSITION", "label": "CCM Position",
|
||||
"pv": "X12SA-OP-CCM1:POSITION", "kind": "string"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# DYNAMIC CLASS CREATION (Ophyd-safe) — updated show_all formatting
|
||||
# DYNAMIC CLASS CREATION — Ophyd-safe
|
||||
# ==============================================================================
|
||||
def create_dynamic_eps_class():
|
||||
"""
|
||||
Create an Ophyd Device subclass with all Components generated
|
||||
from CHANNELS at class-creation time (safe for Ophyd/BEC).
|
||||
"""
|
||||
|
||||
class_attrs = dict(
|
||||
USER_ACCESS=["show_all"],
|
||||
SUB_VALUE="value",
|
||||
_default_sub="value",
|
||||
)
|
||||
|
||||
# Define all Components before the class is created
|
||||
# Dynamically define Components FIRST
|
||||
for section, items in CHANNELS.items():
|
||||
for it in items:
|
||||
class_attrs[it["attr"]] = Cpt(
|
||||
@@ -106,15 +144,20 @@ def create_dynamic_eps_class():
|
||||
read_pv=it["pv"]
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Methods
|
||||
# ----------------------------------------------------------
|
||||
class DynamicMethods:
|
||||
|
||||
def show_all(self):
|
||||
red = "\x1b[91m"
|
||||
red = "\x1b[91m"
|
||||
green = "\x1b[92m"
|
||||
white = "\x1b[0m"
|
||||
bold = "\x1b[1m"
|
||||
bold = "\x1b[1m"
|
||||
cyan = "\x1b[96m"
|
||||
|
||||
# Render helpers
|
||||
def safe_get(attr):
|
||||
"""Get value or None."""
|
||||
obj = getattr(self, attr, None)
|
||||
if obj is None:
|
||||
return None
|
||||
@@ -124,92 +167,76 @@ def create_dynamic_eps_class():
|
||||
return None
|
||||
|
||||
def is_bool_like(v):
|
||||
# handles 0/1/True/False specifically
|
||||
return isinstance(v, (bool, int)) and v in (0, 1, True, False)
|
||||
|
||||
|
||||
def format_value(value, pv):
|
||||
"""
|
||||
- Temperature PVs (…:TEMP): one decimal (e.g., 23.7)
|
||||
- Energy PVs (…:ENERGY-GET): four decimals (e.g., 12.3456)
|
||||
- Bool-like: handled separately
|
||||
- Others (strings / floats / ints): printed as-is
|
||||
"""
|
||||
def fmt_value(value, pv, kind):
|
||||
"""Format value based on semantic kind."""
|
||||
if value is None:
|
||||
return f"{red}MISSING{white}"
|
||||
|
||||
# Temperature formatting → 1 decimal
|
||||
if pv.endswith(":TEMP") and isinstance(value, (int, float)):
|
||||
# ------------------- TEMPERATURE -------------------
|
||||
if kind == "temp" and isinstance(value, (int, float)):
|
||||
return f"{value:.1f}"
|
||||
|
||||
# Energy formatting → 4 decimals
|
||||
if pv.endswith(":ENERGY-GET") and isinstance(value, (int, float)):
|
||||
# ------------------- ENERGY ------------------------
|
||||
if kind == "energy" and isinstance(value, (int, float)):
|
||||
return f"{value:.4f}"
|
||||
|
||||
# Non-bool values (e.g., strings like POSITION/STRIPE)
|
||||
if not is_bool_like(value):
|
||||
# ------------------- STRINGS -----------------------
|
||||
if kind in ("string", "position"):
|
||||
return f"{value}"
|
||||
|
||||
# Return raw value for bool handling elsewhere
|
||||
return value
|
||||
# ------------------- SWITCH (ACTIVE/INACTIVE) ------
|
||||
if kind == "switch" and is_bool_like(value):
|
||||
return f"{green+'ACTIVE'+white if value else red+'INACTIVE'+white}"
|
||||
|
||||
# ------------------- FAULT (OK/FAULT) --------------
|
||||
if kind == "fault" and is_bool_like(value):
|
||||
return f"{green+'OK'+white if not value else red+'FAULT'+white}"
|
||||
|
||||
def render_line(label, value, pv, label_width):
|
||||
# Booleans as OPEN/CLOSED with color
|
||||
if is_bool_like(value):
|
||||
is_open = bool(value)
|
||||
color = green if is_open else red
|
||||
status = "OPEN" if is_open else "CLOSED"
|
||||
return f" – {label:<{label_width}} {color}{status}{white}"
|
||||
# ------------------- VALVE/SHUTTER -----------------
|
||||
if kind in ("valve", "shutter") and is_bool_like(value):
|
||||
return f"{green+'OPEN'+white if value else red+'CLOSED'+white}"
|
||||
|
||||
# Everything else formatted via format_value
|
||||
fv = format_value(value, pv)
|
||||
# If format_value returned raw bool-like, it means not temp but bool-like; still handle color
|
||||
if fv in (0, 1, True, False):
|
||||
is_open = bool(fv)
|
||||
color = green if is_open else red
|
||||
status = "OPEN" if is_open else "CLOSED"
|
||||
return f" – {label:<{label_width}} {color}{status}{white}"
|
||||
# ------------------- FALLBACK -----------------------
|
||||
# Non-boolean numeric:
|
||||
return f"{value}"
|
||||
|
||||
return f" – {label:<{label_width}} {fv}"
|
||||
|
||||
print("X12SA valve/shutter/mono status")
|
||||
# ------------------- PRINT START ---------------------
|
||||
print(f"{bold}X12SA valve/shutter/mono status{white}")
|
||||
|
||||
for section, items in CHANNELS.items():
|
||||
print(f"\n{bold}{section}{white}")
|
||||
|
||||
# Fetch values once
|
||||
# Gather row values
|
||||
rows = []
|
||||
for it in items:
|
||||
val = safe_get(it["attr"])
|
||||
rows.append((it["label"], val, it["pv"]))
|
||||
rows.append((it["label"], val, it["pv"], it["kind"]))
|
||||
|
||||
# Compute a nice label width per section (min 32)
|
||||
label_width = max(32, *(len(label) for (label, _, _) in rows) or [32])
|
||||
# Compute label width
|
||||
label_width = max(32, *(len(label) for (label, _, _, _) in rows))
|
||||
|
||||
# Section summary when all present values are pure booleans
|
||||
present_vals = [v for (_, v, _) in rows if v is not None]
|
||||
bool_like_vals = [v for v in present_vals if is_bool_like(v)]
|
||||
if present_vals and len(bool_like_vals) == len(present_vals):
|
||||
total = len(bool_like_vals)
|
||||
open_cnt = sum(1 for v in bool_like_vals if bool(v))
|
||||
if open_cnt == total:
|
||||
print(f"{green}All channels in this section are open.{white}")
|
||||
# Detect if summary applies
|
||||
present = [v for (_, v, _, k) in rows if v is not None and k in ("valve", "shutter", "switch", "fault")]
|
||||
bools = [v for v in present if is_bool_like(v)]
|
||||
if present and len(bools) == len(present):
|
||||
total = len(bools)
|
||||
active = sum(1 for v in bools if v)
|
||||
if active == total:
|
||||
print(f"{green}All channels in this section are active.{white}")
|
||||
else:
|
||||
print(f"{red}Warning: {open_cnt}/{total} open.{white}")
|
||||
elif not present_vals:
|
||||
print(" (no channels found on this device)")
|
||||
print(f"{red}Warning: {active}/{total} active.{white}")
|
||||
|
||||
# Lines
|
||||
for label, value, pv in rows:
|
||||
print(render_line(label, value, pv, label_width))
|
||||
# Print lines
|
||||
for label, value, pv, kind in rows:
|
||||
fv = fmt_value(value, pv, kind)
|
||||
print(f" – {label:<{label_width}} {fv}")
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Duplicates / missing PV validation
|
||||
# ----------------------------------------------------------
|
||||
def consistency_report(self, *, verbose=True):
|
||||
"""
|
||||
Checks for:
|
||||
- attributes listed in CHANNELS but missing on the device
|
||||
- duplicate PVs in CHANNELS (should be none in a real system)
|
||||
"""
|
||||
missing = []
|
||||
dupes = []
|
||||
seen = {}
|
||||
@@ -228,29 +255,28 @@ def create_dynamic_eps_class():
|
||||
if verbose:
|
||||
print("=== Consistency Report ===")
|
||||
if missing:
|
||||
print("\nMissing attributes on device:")
|
||||
for section, attr, label, pv in missing:
|
||||
print(f" - [{section}] {attr} ({label}) pv={pv}")
|
||||
print("\nMissing attributes:")
|
||||
for sec, a, lbl, pv in missing:
|
||||
print(f" - [{sec}] {a} {lbl} pv={pv}")
|
||||
else:
|
||||
print("\nNo missing attributes.")
|
||||
|
||||
if dupes:
|
||||
print("\nDuplicate PVs detected:")
|
||||
for pv, first, second in dupes:
|
||||
print(f" {pv}")
|
||||
print(f" First: {first}")
|
||||
print(f" Second: {second}")
|
||||
print("\nDuplicate PVs:")
|
||||
for pv, f1, f2 in dupes:
|
||||
print(f" {pv} → {f1} AND {f2}")
|
||||
else:
|
||||
print("\nNo duplicate PVs.")
|
||||
|
||||
return {"missing_attrs": missing, "duplicate_pvs": dupes}
|
||||
|
||||
# Bind methods
|
||||
# Bind methods into class
|
||||
class_attrs["show_all"] = DynamicMethods.show_all
|
||||
class_attrs["consistency_report"] = DynamicMethods.consistency_report
|
||||
|
||||
# Create the class
|
||||
# Create the Device subclass
|
||||
return type("cSAXSEps", (Device,), class_attrs)
|
||||
|
||||
# Create the final class type for use in BEC
|
||||
|
||||
# Create final class for BEC to import
|
||||
cSAXSEps = create_dynamic_eps_class()
|
||||
|
||||
Reference in New Issue
Block a user