15 KiB
Repository Guidelines — ophyd_devices
ophyd_devices provides reusable ophyd hardware support and simulation devices for
BEC. Prefer focused changes, follow existing local patterns,
and verify the smallest relevant test scope.
This file is an agent-oriented operating manual. User-facing documentation lives at
https://bec.readthedocs.io and is authored in the separate bec_docs repository.
Treat pyproject.toml as the source of truth for dependencies, scripts, and tool configuration.
Core Rules
- Use
PSIDeviceBasewhen a device needs BEC lifecycle or business logic. A device that merely exposes signals can use plainophyd.Device, regardless of the communication backend. - Separate reusable device control from beamline-specific business logic in
on_*hooks. - Prefer repository statuses, signals, and helpers when a BEC-aware counterpart exists.
- Return promptly from device-server calls. Represent unfinished work with a status.
- Implement safe interruption through
on_stop()and register cancellable statuses. - Use
bec_signals.pyfor BEC live data, progress, and file events. - Check the relevant device protocols when changing interfaces.
- Add an example configuration for each new reusable device family.
- Do not edit
ophyd_devices/devices/device_list.md; CI generates it. - Follow existing local patterns before introducing a new abstraction.
- Keep diffs focused and preserve unrelated local changes.
- Add regression tests for bug fixes.
- Do not commit, push, open a PR, or post a review unless explicitly requested.
First Read
Read the affected implementation and its tests. For unfamiliar areas, start here:
ophyd_devices/interfaces/base_classes/psi_device_base.py— device lifecycle and BEC integrationophyd_devices/interfaces/base_classes/psi_positioner_base.py— motionophyd_devices/interfaces/base_classes/psi_pseudo_device_base.py— pseudo devicesophyd_devices/interfaces/base_classes/psi_pseudo_motor_base.py— pseudo motorsophyd_devices/interfaces/protocols/bec_protocols.py— expected device contractsophyd_devices/utils/psi_device_base_utils.py— statuses, tasks, and file helpersophyd_devices/utils/bec_signals.py— live data publishingtests/conftest.pyandophyd_devices/tests/utils.py— reusable test helpersREADME.md— project overview
Repo Layout
ophyd_devices/interfaces/— base classes, protocols, and device configuration templatesophyd_devices/devices/— concrete devices and the generated device listophyd_devices/sim/— simulation devices and data generatorsophyd_devices/utils/— shared signals, statuses, controllers, and other helpersophyd_devices/configs/— example device configurationstests/— unit tests
Related but separate repositories:
bec— core messaging, scans, services, and the clientbec_widgets— GUI widgetsbec_docs— published documentation- beamline plugin repositories — beamline-specific devices, scans, and widgets
Local Overlay
If AGENTS_PERSONAL.md exists beside this file, read it as an extension of these instructions.
Its machine-specific environment and workflow guidance takes precedence over the corresponding
sections here. Keep it local and untracked; do not copy its contents into committed files.
Common Task Routing
If you change:
ophyd_devices/interfaces/base_classes/*: inspect relevant protocols and affected concrete and simulation devices; test staging, subscriptions, movement, and stop behavior.ophyd_devices/utils/psi_device_base_utils.py: check timeout handling and composition across status subclasses.ophyd_devices/utils/bec_signals.py: check device-server consumers and report compatibility risks forbecandbec_widgets.ophyd_devices/sim/*: check the affected device and tests consuming its scan data.ophyd_devices/devices/*or vendor integrations: add targeted device tests; include an example configuration and validation notes for a new reusable device family.ophyd_devices/configs/*: runophyd_test --config <changed-config>and inspect its report.- Documentation or templates: verify referenced paths, commands, examples, and metadata. A broad unit test run is unnecessary when executable behavior is unchanged.
Reusable hardware support belongs here. Devices specific to one beamline belong in its plugin
repository. Route core messaging, scans, and service changes to bec, GUI behavior to
bec_widgets, and published documentation to bec_docs.
Writing A Device
Control and business logic
The device's base control class defines how to communicate with the hardware: signal
definitions, commands, protocol handling, and device state. Keep this layer reusable across
beamlines and implement it in ophyd_devices. Plain ophyd control classes may be composed into
or combined with a class that uses PSIDeviceBase when business logic is needed.
A device that only exposes a collection of signals needs no business-logic layer; plain
ophyd.Device is enough. For example, SLSOperatorMessages in
ophyd_devices/devices/sls_devices.py groups operator messages and their dates using dynamic
components without scan-specific behavior. This applies equally to EPICS and other communication
backends; the need for business logic determines the base class, not the transport.
The on_* hooks typically describe business logic: how a beamline uses that control interface
during a scan. For example, the control class exposes acquisition and trigger-mode commands;
a beamline's on_stage() chooses the trigger mode and acquisition settings for its experiment,
and on_trigger() starts acquisition through the control interface.
Implement bespoke hook behavior in a subclass in the beamline plugin repository. Keep shared lifecycle behavior generic, and make hooks call reusable control methods rather than duplicating PV definitions or communication code. This lets beamlines share the same hardware support while choosing their own acquisition and scan behavior.
Base classes and hooks
-
When lifecycle or business logic is needed, use
PSIDeviceBaseto integrate the control class with BEC's lifecycle, scan information, statuses, and subscriptions. Do not add it solely to expose a collection of signals. -
Use the lifecycle hooks provided by the base class rather than replacing its wrappers:
on_init,on_connected,on_stage,on_pre_scan,on_trigger,on_complete,on_kickoff,on_unstage,on_stop, andon_destroy. -
When implementing a device inheriting from
PSIDeviceBase, copy the entire "Beamline Specific Implementations" section fromophyd_devices/interfaces/base_classes/psi_device_base.py, including its separator, all ten hooks, signatures, and docstrings. Keep the hooks together in that order, including unused hooks, so readers can quickly identify the device's business logic. -
Put helper methods outside that section under a separate separator, for example:
######################################## # Beamline Specific Implementations # ######################################## # All ten on_* hooks belong here. ######################################## # Helper Methods # ######################################## -
When replacing a hook in a subclass, preserve any required parent behavior with
super(); copied hook stubs must not silently disable an inherited implementation. -
Constructors and
on_init()must not communicate with devices. Useon_init()only for local initialization; do not read hardware state or send commands there. -
on_connected()is the first hook allowed to communicate with devices and send instructions, such as setting hardware defaults. The device server calls it once an enabled device has connected; plain instantiation, unit tests, andophyd_testdo not, so call it explicitly in tests that rely on it. It is not a per-scan hook. Use the scan lifecycle hooks for scan-specific instructions and access scan parameters throughself.scan_info.msg. -
Check the relevant protocols when adding or changing a device interface. Read the base-class implementation for hook return semantics; the protocol signatures alone do not describe them.
Completion and interruption
- Acquisition, staging, completion, and movement must return promptly to the device server. Represent unfinished work with a status; do not wait, sleep, or poll on the calling thread.
- Import statuses from
ophyd_devices.utils.psi_device_base_utils, includingDeviceStatus,MoveStatus, andStatusBase. These provide BEC timeout diagnostics and status composition. Prefer repository helpers whenever a BEC-aware counterpart exists. - Hooks such as
on_stage()andon_complete()may return a status orNone. ReturnNoneonly when there is no outstanding work. In particular,complete()convertsNoneinto an already-finished status; return a pending status while acquisition or file writing continues. - Register statuses that must fail on interruption with
self.cancel_on_stop(status). Implement hardware interruption inon_stop()and make repeated calls safe. Cancelling a status does not by itself stop hardware or a background task. - Preserve base-class stop and destroy behavior. Release subscriptions, threads, sockets, and
other owned resources in
on_destroy(); worker code must be able to exit on interruption.
Signals and configuration
- Set signal
kinddeliberately.normalandhintedsignals appear inread();hintedalso selects default BEC scan readouts.configsignals appear inread_configuration();omittedsignals appear in neither. Treat changes to kinds and names as data-interface changes. - Use the signals in
ophyd_devices/utils/bec_signals.pyfor BEC live data, progress, and file events. Reuse their message formats instead of publishing custom Redis messages. - Add an example configuration under
ophyd_devices/configs/for a new reusable device family. - Give each device class a docstring with a useful first-line description. CI uses it to generate
ophyd_devices/devices/device_list.md; do not edit that generated file by hand.
USER_ACCESS
- Prefer methods over properties for functionality exposed through
USER_ACCESS. - Give exposed methods verb-based names that describe the operation, such as
set_velocity()orget_velocity(), rather than noun-only names such asvelocity().
Validation
Add regression tests for bug fixes. New device tests must cover instantiation, the relevant
protocol, and safe stop() behavior. For asynchronous changes, also exercise completion,
failures, and interruption while work is pending.
Use mocked EPICS and sockets for unit tests. Reuse get_mock_scan_info from
ophyd_devices/tests/utils.py and existing fixtures; use simulation devices when a working device
is needed. Keep tests independent of execution order.
Run the smallest relevant test target first, with --random-order when available:
python -m pytest --random-order tests/test_psi_device_base.py
For a full unit test or coverage run:
python -m pytest --random-order tests
coverage run --source=./ophyd_devices --omit='*/ophyd_devices/tests/*' \
-m pytest --random-order tests
coverage report
ophyd_test writes reports to ./device_test_reports by default. Use --connect only when the
user explicitly requests hardware validation and the target is reachable. Report whether device
validation used mocks, simulation, or real hardware; include the model and firmware when known.
Running BEC Locally
Full service validation needs Redis, usually at localhost:6379, and an environment with BEC
services installed. Unit tests normally use mocks and need no hardware.
When service management is part of the requested validation, start services and open the client:
bec-server start
bec
Run the client in another shell. Restart the device server after changing code it has already loaded; otherwise a running session may use stale code.
Style And Change Hygiene
- Use Python 3.11-compatible syntax, four-space indentation, and a 100-character line limit.
- Use f-strings and
pathlib. - Type-annotate new public functions and methods; document public APIs.
- Follow existing naming and docstring conventions.
- Run Black and isort on changed Python files using
pyproject.tomlconfiguration. - Avoid formatting unrelated files and introducing new Pylint warnings.
Development Environment
Use an existing suitable environment when available. To create one, use Python 3.11 or newer:
python -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'
python -c "import ophyd_devices; print(ophyd_devices.__file__)"
Verify imports resolve to the checkout under test. Separate worktrees need separate editable
installations; do not repoint another task's environment. The dev extra includes bec-server.
Platform Notes
Keep code portable across macOS and Linux. Windows is unsupported and untested.
Commit And PR Notes
Branch from main for new work. Continue an existing PR on its current branch.
The manual PR template lives at .github/PULL_REQUEST_TEMPLATE/pull_request_template.md.
When writing a description:
- Lead with the concrete problem and resulting behavior. For a bug fix, explain the trigger and before/after result. Describe the final change for a reviewer who has not followed the work.
- Keep detail proportional to the change. Replace prompts and remove unused sections; avoid a
file-by-file recap. Put lengthy examples in a
<details>block and label before/after output. - Use
Closes #123for resolved issues andRelated to #123for partial work. Link companion PRs andbec_docsupdates, including any required merge or deployment order. - Give exact test commands or manual steps and expected outcomes. Separate instructions for reviewers from checks already performed; report results and material limitations honestly.
- Explain compatibility changes, affected consumers, defaults, migrations, and remaining limits. Include hardware/simulation validation and configuration-check results where applicable.
- State whether documentation was updated or why it is unnecessary. Add design tradeoffs and follow-ups only when they help assess the change. Update the description when scope changes.
Use Conventional Commit titles: <type>(<scope>): <summary>. Allowed types are build, chore,
ci, docs, feat, fix, perf, refactor, style, and test. Mark breaking changes with
! or a BREAKING CHANGE: footer.
For reviews, use .github/pull_request_review_template.md. Separate findings introduced or
exposed by the change from inherited issues and optional suggestions. Identify the reviewed
revision, give concrete evidence, and state validation limits. Return the review in chat unless
posting to GitHub is explicitly requested.