Skip to content

How to run a session without hardware

With no hardware at hand, you can still work on a session by running it as a mocked session. Its devices connect to simulated backends that ophyd-async provides, and the session launches no service. Components explains how a session connects its devices.

Prerequisites

You need a session that builds with its hardware present, such as MyApp below, which launches a service and points a device at it.

A mocked device still needs the package of its protocol, which for the signals below is ophyd-async[ca]:

uv add "ophyd-async[ca]"

The session:

from typing import Annotated

from ophyd_async.core import StandardReadable
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw

from redsun import AsDevice, AsService, Declare, Launch
from redsun.qt import QtSession


class MyMotor(StandardReadable):
    def __init__(self, name: str = "", *, prefix: str = "") -> None:
        with self.add_children_as_readables():
            self.readback = epics_signal_r(float, prefix + "Readback")
        self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
        super().__init__(name=name)


class MyApp(QtSession):
    stage_ioc: Annotated[
        AsService,
        Launch("mylab.iocs.stage", ready="Server startup complete", prefix="STAGE:"),
    ]
    stage: Annotated[AsDevice[MyMotor], Declare(service="stage_ioc")]

Set mock

Pass mock in the configuration the session is made with:

MyApp({"mock": True}).run()

The mapping goes on top of the configuration the class already has, so every other key keeps its value. A session loaded from a file takes mock the same way, as the last of its sources:

from redsun import Session

app = Session.from_config(["session.yaml", {"mock": True}]).build()

To mock every run of a session, write the key in its session file:

mock: true

Check the log

The build logs one line where it would start the services, then the usual summary:

[29-09-26|08:43:21][INFO]: Services not started: the session is mocked
[29-09-26|08:43:21][INFO]: Session built: 1/1 devices, 0/0 presenters, 0/0 views

Every device the build connects uses a simulated backend, including one declared with service=. That device still gets the service's prefix, but nothing answers on it. A signal of a mocked device starts at the default of its type, 0.0 for a float, and keeps the last value written to it.

A session keeping a catalog starts it as usual, because the catalog reaches no hardware, and records the runs of the mocked devices.

The build doesn't connect a device declared with autoconnect=False; see How to connect a device on demand.

Give a mocked device its values

To give a mocked device its values, set its signals from a component that only a mocked session declares. set_mock_value works on signals the device only reads, such as a readback, and callback_on_mock_put makes the readback follow each value written to the setpoint:

from typing import Any, ClassVar

from ophyd_async.core import callback_on_mock_put, set_mock_value

from redsun import AsPresenter, DeviceMapping


class SimulatedStage:
    def __init__(self, name: str, *, devices: DeviceMapping) -> None:
        self.name = name
        stage = devices["stage"]
        assert isinstance(stage, MyMotor)
        set_mock_value(stage.readback, 2.5)
        callback_on_mock_put(
            stage.setpoint, lambda value: set_mock_value(stage.readback, value)
        )


class MySimulation(MyApp):
    config: ClassVar[dict[str, Any]] = {"mock": True}
    simulation: AsPresenter[SimulatedStage]

Run MySimulation().run() to open the window, where a view of MyApp that shows the stage reads those values. The session builds presenters after the devices connect, so the values are in place before any view shows them.

Both functions raise on a device that isn't mocked

set_mock_value and callback_on_mock_put raise on a device not connected with mock=True. Keep SimulatedStage out of MyApp, which can run against hardware.

Go back to the hardware

Remove mock from the configuration, or set it to false, and the next build starts the services and connects the devices to them.