mirror of
https://github.com/bec-project/ophyd_devices.git
synced 2026-11-06 04:33:11 +01:00
283 lines
15 KiB
Markdown
283 lines
15 KiB
Markdown
# Repository Guidelines — `ophyd_devices`
|
|
|
|
`ophyd_devices` provides reusable ophyd hardware support and simulation devices for
|
|
[BEC](https://github.com/bec-project/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 `PSIDeviceBase` when a device needs BEC lifecycle or business logic. A device that merely
|
|
exposes signals can use plain `ophyd.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.py` for 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 integration
|
|
- `ophyd_devices/interfaces/base_classes/psi_positioner_base.py` — motion
|
|
- `ophyd_devices/interfaces/base_classes/psi_pseudo_device_base.py` — pseudo devices
|
|
- `ophyd_devices/interfaces/base_classes/psi_pseudo_motor_base.py` — pseudo motors
|
|
- `ophyd_devices/interfaces/protocols/bec_protocols.py` — expected device contracts
|
|
- `ophyd_devices/utils/psi_device_base_utils.py` — statuses, tasks, and file helpers
|
|
- `ophyd_devices/utils/bec_signals.py` — live data publishing
|
|
- `tests/conftest.py` and `ophyd_devices/tests/utils.py` — reusable test helpers
|
|
- `README.md` — project overview
|
|
|
|
## Repo Layout
|
|
|
|
- `ophyd_devices/interfaces/` — base classes, protocols, and device configuration templates
|
|
- `ophyd_devices/devices/` — concrete devices and the generated device list
|
|
- `ophyd_devices/sim/` — simulation devices and data generators
|
|
- `ophyd_devices/utils/` — shared signals, statuses, controllers, and other helpers
|
|
- `ophyd_devices/configs/` — example device configurations
|
|
- `tests/` — unit tests
|
|
|
|
Related but separate repositories:
|
|
|
|
- `bec` — core messaging, scans, services, and the client
|
|
- `bec_widgets` — GUI widgets
|
|
- `bec_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 for `bec` and `bec_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/*`: run `ophyd_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 `PSIDeviceBase` to 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`, and `on_destroy`.
|
|
- When implementing a device inheriting from `PSIDeviceBase`, copy the entire
|
|
"Beamline Specific Implementations" section from
|
|
`ophyd_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:
|
|
|
|
```python
|
|
########################################
|
|
# 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. Use `on_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, and `ophyd_test` do 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 through `self.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`, including `DeviceStatus`,
|
|
`MoveStatus`, and `StatusBase`. These provide BEC timeout diagnostics and status composition.
|
|
Prefer repository helpers whenever a BEC-aware counterpart exists.
|
|
- Hooks such as `on_stage()` and `on_complete()` may return a status or `None`. Return `None`
|
|
only when there is no outstanding work. In particular, `complete()` converts `None` into
|
|
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 in `on_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 `kind` deliberately. `normal` and `hinted` signals appear in `read()`; `hinted`
|
|
also selects default BEC scan readouts. `config` signals appear in `read_configuration()`;
|
|
`omitted` signals appear in neither. Treat changes to kinds and names as data-interface changes.
|
|
- Use the signals in `ophyd_devices/utils/bec_signals.py` for 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()`
|
|
or `get_velocity()`, rather than noun-only names such as `velocity()`.
|
|
|
|
## 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:
|
|
|
|
```bash
|
|
python -m pytest --random-order tests/test_psi_device_base.py
|
|
```
|
|
|
|
For a full unit test or coverage run:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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.toml` configuration.
|
|
- 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:
|
|
|
|
```bash
|
|
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 #123` for resolved issues and `Related to #123` for partial work. Link companion PRs
|
|
and `bec_docs` updates, 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.
|