Skip to content

Devices

A device represents an interface with a hardware component.

redsun delegates the device layer entirely to ophyd-async: device primitives are imported directly from ophyd_async.core. The redsun.device module only hosts redsun-specific device protocols (currently HasAsyncShutdown).

from ophyd_async.core import Device, StandardReadable, SignalRW, soft_signal_rw

Choosing a base class

ophyd-async provides several base classes depending on the complexity of your device:

Base class Use when
Device bare async device with no built-in read/describe logic
StandardReadable readable device that composes signals into read() / describe() automatically
StandardDetector detector composed from trigger/acquire/data logic, with a built-in prepare/kickoff/complete/collect lifecycle
StandardFlyer flyer device that runs asynchronously and emits data at completion
DeviceMap a Device holding string-keyed child devices (e.g. motor axes)

For most simple devices, StandardReadable is the right starting point.

Signals

Signals are the typed, named attributes of a device. ophyd-async provides four signal types:

Signal type bluesky protocols Description
SignalR[T] Readable[T], Subscribable[T] read-only
SignalW[T] HasName, Movable[T] write-only
SignalRW[T] Readable[T], Subscribable[T], Movable[T] read-write
SignalX HasName, Triggerable trigger / execute

Soft signals

For simulation and testing, soft signals hold their value in memory. Use soft_signal_rw to create a read-write soft signal and soft_signal_r_and_setter to create a read-only signal paired with a programmatic setter:

from ophyd_async.core import StandardReadable, soft_signal_rw


class MyStage(StandardReadable):
    def __init__(self, name: str) -> None:
        self.position = soft_signal_rw(float, initial_value=0.0, units="mm")
        self.velocity = soft_signal_rw(float, initial_value=1.0, units="mm/s")
        super().__init__(name)

Signals added before the super().__init__() call are automatically picked up by StandardReadable and included in read() / describe().

Standalone signals

Each signal is itself a bluesky-readable object and can be passed directly to a plan without going through its parent device:

import bluesky.plans as bp

stage = MyStage("stage")
RE(bp.count([stage.position]))  # read only the position signal
RE(bp.count([stage]))  # read all signals registered by StandardReadable

Detectors

StandardDetector is assembled by composition from three logic classes, each owning one concern:

Logic class Concern
DetectorTriggerLogic trigger configuration: prepare_internal / prepare_edge / prepare_level, deadtime
DetectorAcquireLogic acquisition lifecycle: ensure_ready (stage), start_acquiring (kickoff/trigger), wait_for_idle, ensure_stopped (unstage)
DetectorDataLogic data handling: prepare_unbounded / prepare_single return the data providers complete() and collect() operate on
from ophyd_async.core import StandardDetector

det = StandardDetector.__new__(StandardDetector)
det.add_detector_logics(trigger_logic, acquire_logic, data_logic)
StandardDetector.__init__(det, name="det")

Writing acquired data

Detector data logics meet redsun's storage layer through BaseStorage: the trigger logic registers a StreamSpec at prepare time, the data logic obtains a FrameSink and builds its StreamResourceDataProvider from uri_for / resource_info_for / signal_for, and the acquire logic pushes frames with await sink.put(...) from kickoff onwards. Devices never see the path provider - only the storage instance, resolved through the storage registry.

The full contract - including when to open eagerly versus lazily, and how a live view streams frames without creating a store - is documented in Session storage and ADR 0002. The reference implementation of both patterns lives in tests/sdk/storage/test_integration_plans.py.

Connecting devices

ophyd-async devices must be connected before use - this initialises their signal backends and verifies hardware communication. Use AppContainer.connect_devices() after calling build():

app = MyApp()
app.build()
app.connect_devices()  # connects all registered devices
app.run()

Pass mock=True to skip hardware communication in tests:

app.connect_devices(mock=True)

redsun-specific protocols

The only redsun-specific protocol in the device layer is HasAsyncShutdown, which marks a device as supporting an asynchronous shutdown at application teardown.