Architecture#

Overview#

polar-bits supports three hutches and a Raman setup at the 4ID-POLAR beamline, all sharing a common codebase in id4_common with beamline-specific overrides in separate packages.

src/
├── id4_common/            # Shared: devices, plans, callbacks, utils
├── id4_b/                 # 4IDB-specific startup and config extras
├── id4_g/                 # 4IDG-specific startup and config extras
├── id4_h/                 # 4IDH-specific startup and config extras
├── id4_raman/             # Raman-specific startup and config extras
├── id4_common_qserver/    # Shared QueueServer components
├── id4_b_qserver/         # 4IDB QueueServer config and launch script
├── id4_g_qserver/         # 4IDG QueueServer config and launch script
├── id4_h_qserver/         # 4IDH QueueServer config and launch script
└── id4_raman_qserver/     # Raman QueueServer config and launch script

What goes where#

Layer

Location

Examples

Core shared devices

id4_common/devices/

undulator, monochromator, KB mirrors

Shared scan plans

id4_common/plans/

lup, ascan, grid_scan

Data callbacks

id4_common/callbacks/

SPEC writer, NeXus writer, dichro stream

Session utilities

id4_common/utils/

device loader, counters, HKL utilities

Safety suspenders

id4_common/suspenders/

shutter-based suspenders

Instrument overrides

id4_{b,g,h,raman}/

startup.py, iconfig_extras.yml


Startup Flow#

startup.py orchestrates session initialization in this order:

  1. Load iconfig.yml

  2. Set up APS DM integration (aps_dm_setup)

  3. Initialize RE (RunEngine), cat (databroker catalog), bec, and supplementary data streams

  4. Conditionally load SPEC / NeXus callbacks based on iconfig.yml

  5. Load dichro stream callback

  6. Prompt user to load devices — calls make_devices() which reads devices.yml

  7. Connect devices tagged with that beamline’s station labels

  8. Install shutter suspenders on the RunEngine


Station Labels#

Device loading is controlled by labels defined in devices.yml. Each beamline connects only devices whose labels intersect with its stations list:

Beamline

Stations loaded

4IDB

["core", "4idb"]

4IDG

["core", "4idg"]

4IDH

["core", "4idh"]

Raman

["raman",]

Common labels:

Label

Meaning

"core"

Loaded by all hutches (shared upstream/optics devices)

"4idb", "4idg", "4idh", "raman"

Instrument-specific devices

"baseline"

Added to the supplemental baseline data stream

"detector", "motor", "slit"

Functional category — used for filtering

To make a device available to an additional beamline, add that beamline’s label to its entry in devices.yml. That is the only change required.


Device Registry (devices.yml)#

src/id4_common/configs/devices.yml is the single source of truth for all device definitions. Each entry maps a Python class path to EPICS PV details:

id4_common.devices.aps_xbpm.MyXBPM:
- name: aps_xbpm
  prefix: S04
  labels: ["core", "source", "baseline"]

Multiple instances of the same class are listed as a YAML sequence under one class key. See Configuration for the full YAML schema.


Deferred EPICS Connection Pattern#

make_devices() instantiates all device objects without making EPICS connections. Only connect_device() (via wait_for_connection()) triggers actual EPICS interaction.

Rule: Never subscribe to or read from EPICS/PVA signals inside __init__. Instead, implement _post_connect_setup() on the device class:

class MyDevice(Device):
    signal = Component(EpicsSignalRO, "PV:NAME")

    def _post_connect_setup(self):
        """Called by connect_device() after EPICS connection is live."""
        self.signal.subscribe(self._my_callback, run=False)

connect_device() calls this hook automatically after wait_for_connection() succeeds.


Key Components in id4_common/#

devices/#

~66 device modules covering the full instrument:

  • Area detectors: ad_eiger1M, ad_lambda, ad_lightfield, ad_vimba

  • X-ray diagnostics: aps_xbpm, aps_undulator, aps_status

  • Optics: kb_generic (KB mirror factory), transfocator_device (transfocator factory)

  • Monochromator / energy: monochromator, energy_device

  • Diffractometer: polar_diffractometer

  • Detectors: scaler, vortex_xmap, vortex_xspress3_*

  • Utilities: shutters, electromagnet, chopper_device

plans/#

  • local_scanslup, ascan, grid_scan, rel_grid_scan, count, qxscan

  • dm_plans — APS Data Management workflow submission

  • center_maximum, flyscan_demo, workflow_plan

callbacks/#

  • spec_data_file_writer — SPEC .dat output

  • nexus_data_file_writer — NeXus/HDF5 output

  • dichro_stream — circular dichroism processing

utils/#

~33 utility modules including device loader, counters class, HKL/crystallography helpers, DM integration, attenuator control, and more.