Run against a real, locally-installed backend#
Most of this repo’s tutorials and tests spin a backend up and tear it down
automatically, hidden behind a fixture or a demo script. Sometimes it’s more
useful to run a genuine softIoc or Tango device server yourself in one
terminal, and connect to it with ophyd-async from a second terminal’s
ipython session – for example when reproducing an
issue, or exploring get_value()/set() against real network traffic rather
than a mocked backend.
This guide uses the fixed test-only backends that ophyd-async itself ships
for its own system tests. They’re plain scripts with a __main__ entry
point that (deliberately) don’t import anything from ophyd_async – so the
terminal hosting the backend can be a bare EPICS or PyTango install with no
ophyd-async package at all, exactly as a real IOC or device server would be
in production. The second terminal is where the ophyd-async client-side
code lives; it needs a full ophyd-async install.
No git clone is needed for either terminal – everything below works against
a plain pip install.
Install ophyd-async#
The second (client) terminal needs ophyd-async plus the extra for whichever
transport you’re using, and ipython to drive it interactively:
$ python3 -m pip install ophyd-async[ca,pva,demo]
$ python3 -m pip install ophyd-async[tango,demo]
Note
The demo extra is what pulls in ipython here – but it also happens to be
the only extra that installs pytest. That matters more than it looks:
constructing any Device from ophyd_async.epics.testing or
ophyd_async.tango.testing (not just calling an assert helper) transitively
imports ophyd_async.testing, whose __init__.py imports pytest at module
level. Installing only ophyd-async[ca] (or [pva]/[tango]) will get you
an ImportError for pytest the moment you construct one of these testing
Devices below – demo (or installing pytest yourself) avoids it.
Start the backend#
Do this in a first terminal. See Installation if this terminal needs its own from-scratch EPICS or PyTango environment.
ophyd_async.epics.testing ships a standalone IOC-launcher script
(_ioc.py) that depends on nothing but the standard library, plus whichever
executable ends up hosting the IOC database. Resolve its path with any
Python that has ophyd-async installed, and run it with a PV prefix of your
choosing, e.g. example::
$ python "$(python -c 'from ophyd_async.epics.testing import IOC; print(IOC)')" example:
By default it hosts the database with the bundled epicscorelibs.ioc
module, so the command above works with nothing installed beyond
ophyd-async[ca,pva] – no separate EPICS install required. It serves
ca:, pva: and nested: sub-topologies under the prefix you gave it, so
with example: above, PVs live under example:ca:, example:pva: and
example:nested:.
Leave this terminal running – the IOC exits when its stdin closes (Ctrl-D).
Note
If your site already has a real EPICS installation with its own softIoc
binary on PATH, use that instead of the bundled one with --softioc:
$ python "$(python -c 'from ophyd_async.epics.testing import IOC; print(IOC)')" example: --softioc softIoc
This is the more realistic scenario if EPICS is already installed system-wide rather than only pulled in as a Python dependency.
ophyd_async.tango.testing ships a standalone device-server script
(_tango_device_servers.py) that depends on nothing but tango/
tango.server, numpy and the standard library – no ophyd_async import at
all. It takes a device-name prefix and a TCP port, and serves two devices
with no Tango database needed: TestDevice at <prefix>/basic and
OneOfEverythingTangoDevice at <prefix>/everything.
$ python "$(python -c 'from ophyd_async.tango.testing import DEVICE_SERVERS; print(DEVICE_SERVERS)')" test/example 12345
It prints a TANGO_DEVICE_SERVERS_READY marker once serving, then blocks
until its stdin closes (Ctrl-D) – leave this terminal running.
Note
Because the script is standalone, you can run the exact same file under a
different PyTango installation’s interpreter – e.g. a separate venv with a
different PyTango version than the one pip install ophyd-async[tango]
gave you. Resolve the path with a Python that has ophyd-async installed
(as above), then hand that path to the other venv’s interpreter instead:
$ /path/to/other/tango-venv/bin/python "$(python -c 'from ophyd_async.tango.testing import DEVICE_SERVERS; print(DEVICE_SERVERS)')" test/example 12345
This is the realistic scenario for testing against a site’s own, already-installed PyTango stack.
Connect from a client session#
In the second terminal, start ipython and construct the matching testing
Device against the same prefix/port you gave the backend above:
$ ipython
In [1]: from ophyd_async.epics.testing import EpicsTestCaDevice, EpicsTestPvaDevice
In [2]: ca_device = EpicsTestCaDevice("ca://example:ca:")
In [3]: pva_device = EpicsTestPvaDevice("pva://example:pva:")
In [4]: await ca_device.connect()
In [5]: await pva_device.connect()
In [6]: await ca_device.a_int.get_value()
Out[6]: 0
In [7]: await pva_device.a_int.get_value()
Out[7]: 0
a_int round-trips against the live IOC’s example:ca:int/example:pva:int
records – it defaults to 0 because the underlying .db doesn’t set an
initial VAL. Try await ca_device.a_int.set(5), then get_value() it
again from either device, to see this is a genuine round trip over the
network rather than a mocked value.
$ ipython
In [1]: from ophyd_async.tango.testing import TangoTestDevice
In [2]: device = TangoTestDevice("tango://127.0.0.1:12345/test/example/everything#dbase=no")
In [3]: await device.connect()
In [4]: await device.a_str.get_value()
Out[4]: 'test_string'
a_str round-trips against the live OneOfEverythingTangoDevice server’s
a_str attribute, which defaults to "test_string". Try
await device.a_str.set("hello"), then get_value() it again, to see this
is a genuine round trip rather than a mocked value.
See also
How to interact with signals and commands while implementing bluesky verbs for more on get_value()/set() and
the other ways to interact with Signals and Commands once connected.