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]:
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:
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:
To mock every run of a session, write the key in its session file:
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.