Skip to content

Commit 4e274f4

Browse files
authored
Restructure create-device docs (#1482)
* restructure create-device section,. * correct literalinclude to md * clean up * small correction * review changes * correct devicevector * check order in reusing an existing class
1 parent 117d948 commit 4e274f4

File tree

1 file changed

+46
-12
lines changed

1 file changed

+46
-12
lines changed

docs/how-to/create-device.md

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,66 @@
11
Creating a new device
22
---------------------
33

4-
Devices are written using the ophyd-async framework, the hardware abstraction library at Diamond. Before creating your device, you should [consider where the new code should live](../reference/device-standards.rst#where_to_put_devices).
4+
Devices in dodal use the ophyd-async framework for hardware abstraction at Diamond Light Source. Before starting, [review where your code should live](../reference/device-standards.rst#where_to_put_devices) to avoid duplication and ensure maintainability.
55

66
Reusing an existing class
77
=========================
88

9-
When creating a new device, first check if there is a device class that claims to support the device: e.g. all EPICS Motor records should use the [Motor](https://github.com/bluesky/ophyd-async/blob/main/src/ophyd_async/epics/motor.py) type; for reading or monitoring signals from the storage ring the [Synchrotron](https://github.com/DiamondLightSource/dodal/blob/main/src/dodal/devices/synchrotron.py) device should be used; AreaDetectors should use the [StandardDetector](https://github.com/bluesky/ophyd-async/blob/main/src/ophyd_async/core/_detector.py) type- of which there are examples in ophyd_async and in dodal.
9+
Before creating a new device, always check if a suitable class already exists in dodal or ophyd-async. This helps avoid duplication, ensures maintainability, and leverages tested code.
1010

11-
The module `dodal.devices.motors` defines a series of `Stage`s or groups of motors- a `Stage` represents a physical relationship between motors. For example, an `XYStage` is two perpendicular motors and should not be used for two motors that run parallel to each other (e.g. a coarse motor and a fine adjustment motor in the same axis) or that are unrelated (e.g. attached to different stages with no common frame of reference). Already defined are some relationships with linear and rotational stages. Any device that is a groups of motors only should be additionally defined there, if possible making re-use of the existing classes. Classes that define physical stages but also define additional signals may then extend the `Stage` class outside of the `motors` module for additional behaviour.
11+
- **Motors:** Use [Motor](https://github.com/bluesky/ophyd-async/blob/main/src/ophyd_async/epics/motor.py) for EPICS motor records.
12+
- **Storage ring signals:** Use [Synchrotron](https://github.com/DiamondLightSource/dodal/blob/main/src/dodal/devices/synchrotron.py).
13+
- **AreaDetectors:** Use [StandardDetector](https://github.com/bluesky/ophyd-async/tree/main/src/ophyd_async/epics/adcore) or [adcore](https://github.com/bluesky/ophyd-async/tree/main/src/ophyd_async/epics/adcore).
1214

13-
Device classes that take a list of devices of a type may make use of the ophyd-async `DeviceVector`, and those that use other collections (e.g. a dict of Motors) to set attributes on themselves at runtime should be avoided- these device classes may appear tempting for their genericness and extensibility, but they will be a hindrance when trying to write plans. When writing plans, the device class is all that is available to the IDE, not the instance that you "just know" has specific fields. Making use of type checking and hints should make writing plans and debugging them a much less frustrating experience, while also enabling discoverability of the devices that are in use.
15+
If a compatible device class exists:
16+
- Use it and add it to the [beamline](./create-beamline.rst) to avoid re-implementation and share improvements.
17+
- You can use ophyd-async's [DeviceVector](https://blueskyproject.io/ophyd-async/main/explanations/decisions/0006-procedural-device-definitions.html) to handle a collection of identical devices.
18+
- If a device class only differs by PV address, request an alias in the EPICS IOC support module with the relevant controls support team.
19+
- If that's not possible (e.g. proprietary support), add configurability to the dodal device class, ensuring defaults match existing patterns and that `dodal connect` still works for current devices.
20+
- Avoid dynamic attribute assignment (e.g. dicts of motors) as it hinders type checking and plan writing.
21+
- Use static attributes and type hints for better IDE support and maintainability.
1422

15-
If there is a compatible device class it should be used- adding it to the [the beamline](./create-beamline.rst)- this prevents reimplementing the device, and allows improvements to be shared. Improving the device to meet your use case is better than starting again.
23+
Many device classes in `dodal.devices.motors` represent physical relationships between motors, such as `Stage` and `XYStage`.
1624

17-
If a device class is incompatible due to differences in PV address only, first request that an alias is added to the EPICS support module for the IOC - or request support in making that change. Only if it is not possible to add an alias, for example the support module is proprietary, add configurability to the dodal device class, taking care not to break existing devices- e.g. make new fields have a default that matches the existing pattern, and ensure that `dodal connect` is still able to connect to the device for the existing instances.
18-
19-
If the device class is sufficiently different (or you cannot find a similar device), create a device that connects to the required signals and can be tested for your desired behaviour. During the review process, attempts to bring it closer in line with existing devices may allow to deduplicate some parts, through inheritance or composition of the device from existing components.
25+
For example, only use `XYStage` for two perpendicular motors (e.g. X and Y axes on a sample table):
26+
- Do not use `XYStage` for unrelated motors or for motors that move in the same axis (e.g. coarse and fine adjustment).
27+
- Only a device that represents a group of motors with a physical relationship, should be defined in `motor`.
28+
- If your class define an `XYStage` but you need extra signals or behaviour, extend the `XYStage` class outside the `motor` module.
2029

30+
**Only if no suitable class exists**, create a new device that connects to the required signals. During review, refactor to align with existing devices if needed, using inheritance or composition to deduplicate code.
2131

2232
Writing a device class
2333
======================
2434

25-
The aim should be to get a new device ready for testing it on the beamline as soon as possible, to ensure fast iteration: write a device against your assumptions of how it should work, write tests against those assumptions then test your assumptions on the beamline. Write issues from beamline testing, to resolve offline to reserve as much time for testing that requires the beamline as possible.
35+
To develop a new device, get an initial, working version of your code into the main branch as early as possible. Test it at the beamline, then continuously make and merge small changes into main. This approach prevents pull requests from becoming long-standing issues.
36+
37+
- **Follow the [ophyd-async device implementation guide](https://blueskyproject.io/ophyd-async/main/tutorials/implementing-devices.html)** to structure your device code.
38+
- **Choose the right base class** by consulting the [base class guide](https://blueskyproject.io/ophyd-async/main/how-to/choose-right-baseclass.html), If your device needs to move, you'll need to extend the Movable protocol. For detailed guidance on when and how to do this, refer to the [movable device guide](https://blueskyproject.io/ophyd-async/main/explanations/when-to-extend-movable.html).
39+
- **Write thorough unit tests** for all expected use cases. Reference the [ophyd-async device test guide](https://blueskyproject.io/ophyd-async/main/tutorials/implementing-devices.html) for best practices.
40+
- **Validate your device on the beamline** and keep notes of any issues for later fixes.
41+
- **Make use of type annotations** so that pyright will validate that you are passing around values that ophyd-async will accept.
42+
- Use `dodal connect <beamline>` to check device connectivity and `cainfo <PV address>` to confirm PVs and datatypes.
43+
44+
**Device best practices for Bluesky plans:**
45+
46+
- Interact with devices in plans only through the Bluesky [messages protocol](https://blueskyproject.io/bluesky/main/msg.html), such as `yield from bps.abs_set(...)` or `yield Msg("set", ...)`.
47+
- Avoid calling device methods directly inside plans (e.g. `device.do_thing()`), as this breaks the abstraction and can lead to unpredictable behaviour.
48+
49+
Example of what **not** to do:
50+
```python
51+
class MyDevice(Device):
52+
def do_thing(...):
53+
...
2654

27-
Dodal's CLI `dodal connect <beamline>` is a useful way to verify that PV addresses are correct, together with `cainfo <PV address>` to find the datatype of signals.
55+
def my_plan():
56+
yield from bps.set(...)
57+
device.do_thing() # This is bad: do not call device methods directly in plans
58+
```
2859

29-
If you're not sure how to represent a PV as a Signal: ask! Seek feedback early (e.g. by opening a draft PR) and merge with other devices where it makes sense to. The test suite should provide confidence to do so without breaking existing code.
60+
**Tip:**
3061

62+
- If you are unsure how to represent a PV as a Signal, seek feedback early (for example, by opening a draft PR).
63+
- Whenever possible, merge with existing devices—comprehensive tests help ensure changes do not break current functionality.
64+
- Document any device-specific quirks or limitations for future maintainers.
3165

32-
.. _ophyd-async: https://blueskyproject.io/ophyd-async/main/how-to/choose-interfaces-for-devices.html
66+
For further guidance, see the [ophyd-async documentation](https://blueskyproject.io/ophyd-async/main/how-to/choose-interfaces-for-devices.html).

0 commit comments

Comments
 (0)