A service is a server your devices talk to, such as an
EPICSIOC, a camera server or a motion
controller's gateway. Your ophyd-async devices
talk to it over Channel Access or
PVAccess without redsun taking part. redsun does
the rest: it starts a launched service, notices when it exits, stops it
cleanly, and hands its prefix and transport to the
devices.
Devices without a service
You don't strictly need a service, since an ophyd-async device can
reach its hardware on its own. For now, though, redsun supports such
devices only in part, so a service is the preferred way to reach
hardware. We will provide tutorials on wrapping third-party packages
through ophyd-async in the future.
A session splits your setup into devices and services, which know as little as
possible about each other. Point at a shape to read what it does:
Devices meet services at the prefix
the setup, as devicesthe hardware, as servicesmotor controllercamerastagex, yA device is a set of signals that presenters and plans read and set. It says what can be controlled, not how to reach the hardware.cameraexposure, roiA device is a set of signals that presenters and plans read and set. It says what can be controlled, not how to reach the hardware.stage serviceA service opens the serial port or the camera, speaks the vendor's protocol or runs the vendor's library, and offers the result as process variables. It knows nothing about the setup around it.camera serviceA service opens the serial port or the camera, speaks the vendor's protocol or runs the vendor's library, and offers the result as process variables. It knows nothing about the setup around it.prefix ST:prefix CAM:A device is a set of signals that presenters and plans read and set. It says what can be controlled, not how to reach the hardware.A device is a set of signals that presenters and plans read and set. It says what can be controlled, not how to reach the hardware.A service opens the serial port or the camera, speaks the vendor's protocol or runs the vendor's library, and offers the result as process variables. It knows nothing about the setup around it.A service opens the serial port or the camera, speaks the vendor's protocol or runs the vendor's library, and offers the result as process variables. It knows nothing about the setup around it.
The devices model your setup the way its users think of it, as an
ophyd-async tree: a microscope has a stage, the stage an X and a Y axis, each
axis a position. A service describes no setup; it only makes hardware
reachable, so the same model can sit on real hardware or on a simulation.
A session can declare the whole setup as one device at the root of such a
tree. MyMicroscope holds its stage and its camera, so the session declares
only the microscope:
ophyd-async keeps the names of the bluesky methods, such as stage,
read and set, so a child given one of them raises a NameError when
the device is made. Name the stage after what it moves, as xy here.
The session then holds every device of the tree, under the name ophyd-async
gives it, such as microscope-xy, so the positioner finds the stage's axes
and a plan can be offered the camera.
Asking about devices says how components
see them. A device can also declare its children with annotations, in the
ophyd-async style. An EpicsDevice creates them when it is made, or while it
connects when it asks the IOC for its structure (with_pvi=True). The session
reads the tree after connecting, so it finds them either way.
The pieces of a setup often sit behind different services, as the stage and
the camera do in the picture above. So services maps each of the root's
prefix arguments to the service that gives it: here stage_prefix gets the
prefix of stage_ioc, and camera_prefix that of camera_ioc. A session
file says the same under the device's entry:
An argument whose default is None makes its piece optional. When
camera_ioc does not start, the session leaves camera_prefix out, builds
the microscope with its stage alone, and says so in the build summary:
Built without: microscope (camera_prefix: camera_ioc not started)
Services not started: camera_ioc (exited with code 3 before it was ready)
An argument with any other default, or none, is required, so the session
skips the whole device instead. A
strict session stops in either case, because
a service did not start. You can still declare each piece as a device of its
own, and one session can mix both styles.
A device declared with service="stage_ioc" gets that service's prefix as
its prefix argument, the same as services={"prefix": "stage_ioc"}, and the
prefix is all it knows of the service. So you
can change one side and keep the other:
The same components run against the lab's services or simulated ones; only
the services change:
# common.yaml: what the setup isdevices:stage:plugin_name:mylabplugin_id:stageservice:stage_ioc
# lab.yaml: how it is reached in the labservices:stage_ioc:plugin_name:mylabplugin_id:stage-iocprefix:"ST:"
# simulation.yaml: how it is reached without hardwareservices:stage_ioc:plugin_name:mylabplugin_id:stage-simprefix:"ST:"
The session lists common.yaml and one of the other two in its config.
A crashing vendor library takes down its service, not the session; the
service's devices time out until it is back.
An attached service can run on another
machine, since the devices need only its prefix.
A device that needs no hardware, such as the soft stage in the
tutorial, needs no service.
A device reaches its hardware in two steps, and each step connects on its own:
The two connection levels
A device reaches its hardware in two levels, and each one connects on its own.
session processviewpresenterdevice2. open / close1. connect()
session processviewpresenterdevice2. open / close1. connect()
Level 1: the build calls connect() on every device declared with autoconnect, all at once. A device that hasn't connected after 10 seconds is left out.
session processservice processor containerviewpresenterdeviceThe build calls connect() on every device declared with autoconnect, all at once. A device that hasn't connected after 10 seconds is left out, like one that fails to build.service2. open / close1. connect()The build calls connect() on every device declared with autoconnect, all at once. A device that hasn't connected after 10 seconds is left out, like one that fails to build.
session processservice processor containerviewpresenterdeviceThe build calls connect() on every device declared with autoconnect, all at once. A device that hasn't connected after 10 seconds is left out, like one that fails to build.service2. open / close1. connect()The build calls connect() on every device declared with autoconnect, all at once. A device that hasn't connected after 10 seconds is left out, like one that fails to build.
Level 2: the service opens the hardware its process variables name, such as a serial port or a camera, on the machine it runs on.
session processservice processor containerviewpresenterdeviceThe build calls connect() on every device declared with autoconnect, all at once. A device that hasn't connected after 10 seconds is left out, like one that fails to build.serviceThe service opens a serial port or a camera. Which one it opens is set through its process variables.hardware2. open / close1. connect()The build calls connect() on every device declared with autoconnect, all at once. A device that hasn't connected after 10 seconds is left out, like one that fails to build.The service opens a serial port or a camera. Which one it opens is set through its process variables.
session processservice processor containerviewpresenterdeviceThe build calls connect() on every device declared with autoconnect, all at once. A device that hasn't connected after 10 seconds is left out, like one that fails to build.serviceThe service opens a serial port or a camera. Which one it opens is set through its process variables.hardware2. open / close1. connect()The build calls connect() on every device declared with autoconnect, all at once. A device that hasn't connected after 10 seconds is left out, like one that fails to build.The service opens a serial port or a camera. Which one it opens is set through its process variables.
A mocked session connects each device to a
simulated backend and launches no service, so it reaches neither level. The
two levels are kept apart on purpose: a session can stay connected to a
service that holds no hardware yet, and a service can release its hardware
while every connection stays up.
A service is either launched by the session or
attached to it. You declare a launched one with Launch, naming the module
the session runs, and an attached one with Attach, naming only its prefix:
An attached service already runs, in a container or on another host; the
session only passes its prefix on, and address says where to look when the
network search wouldn't find it. Either kind can also be declared in a session
file's services section, which fills any keyword the class leaves out; see
Write a service.
A launched service starts in the first build step, before any device:
Starting a launched service
sessionservice processdevicespython -m mylab.iocs.cameraprints the ready line,within startup_timeoutbuild, each with itsservice's prefixconnect()
sessionservice processdevicespython -m mylab.iocs.cameraprints the ready line,within startup_timeoutbuild, each with itsservice's prefixconnect()
All launched services start at once, each waited for up to startup_timeout
seconds, STARTUP_TIMEOUT (15) unless the
declaration gives another; one declared without ready text counts as ready
when its process starts. A service that doesn't
start is logged, and every device naming it is skipped. Services stop at
shutdown, after every component and before the log files close, so the files
record how each ended; a build that raises stops the ones it started.
The session asks a service to stop more and more firmly. After each step it
waits up to stop_timeout seconds, 10 unless the declaration gives another,
for the service to exit:
How the session stops a service
The session asks a service to stop more and more firmly, waiting stop_timeout seconds, 10 unless the declaration says otherwise, after each request.
close itsstandard inputsend SIGINTkill itstill running,on POSIXstill runningstill running,on Windows
close itsstandard inputsend SIGINTkill itstill running,on POSIXstill runningstill running,on Windows
First it closes the service's standard input. A service that watches its input cleans up and exits, on every platform.
close itsstandard inputA service watching its standard input cleans up and exits.send SIGINTkill itstill running,on POSIXstill runningstill running,on WindowsA service watching its standard input cleans up and exits.
close itsstandard inputA service watching its standard input cleans up and exits.send SIGINTkill itstill running,on POSIXstill runningstill running,on WindowsA service watching its standard input cleans up and exits.
On POSIX, a service still running then gets SIGINT.
close itsstandard inputsend SIGINTOnly on POSIX. On Windows, the session kills a service still running after the first step.kill itstill running,on POSIXstill runningstill running,on WindowsOnly on POSIX. On Windows, the session kills a service still running after the first step.
close itsstandard inputsend SIGINTOnly on POSIX. On Windows, the session kills a service still running after the first step.kill itstill running,on POSIXstill runningstill running,on WindowsOnly on POSIX. On Windows, the session kills a service still running after the first step.
A service still running after that, or on Windows after the first request, is killed, and may leave work such as a large file unfinished.
close itsstandard inputsend SIGINTkill itA killed service may leave work unfinished, typically a large file it was writing.still running,on POSIXstill runningstill running,on WindowsA killed service may leave work unfinished, typically a large file it was writing.
close itsstandard inputsend SIGINTkill itA killed service may leave work unfinished, typically a large file it was writing.still running,on POSIXstill runningstill running,on WindowsA killed service may leave work unfinished, typically a large file it was writing.
Closing standard input comes first because it is the only request that runs a
service's cleanup on every platform: Popen.terminate() skips cleanup on
Windows and Linux, and a console control event never reaches a process
started without a console window, as a Qt application starts its services. It
also stops a service when the session crashes, since the operating system
closes the pipe. A service that needs longer to close takes a longer
stop_timeout.
A session speaks channel-access unless it says otherwise, and a file layered
over another can't change it. Pick a scenario to compare how each transport
sets up the launched services:
What the transport sets up
The transport, named once in the services section, decides how the session sets up each launched service.
Channel Access: each launched service gets a server port of its own, and the session adds each port to its own process's address list.
session processThe session gives each launched service a server port of its own, and adds each port to the address list of its own process.camera_iocown port 5101stage_iocown port 5102looks at127.0.0.1:5101looks at127.0.0.1:5102The session gives each launched service a server port of its own, and adds each port to the address list of its own process.
session processThe session gives each launched service a server port of its own, and adds each port to the address list of its own process.camera_iocown port 5101stage_iocown port 5102looks at127.0.0.1:5101looks at127.0.0.1:5102The session gives each launched service a server port of its own, and adds each port to the address list of its own process.
PVAccess: each launched service binds 127.0.0.1 on any free port, and the session tells its own process to search 127.0.0.1.
session processThe session binds each launched service to 127.0.0.1 and lets it take any free port, and tells its own process to look at 127.0.0.1.camera_ioc127.0.0.1, free portstage_ioc127.0.0.1, free portlooks at127.0.0.1looks at127.0.0.1The session binds each launched service to 127.0.0.1 and lets it take any free port, and tells its own process to look at 127.0.0.1.
session processThe session binds each launched service to 127.0.0.1 and lets it take any free port, and tells its own process to look at 127.0.0.1.camera_ioc127.0.0.1, free portstage_ioc127.0.0.1, free portlooks at127.0.0.1looks at127.0.0.1The session binds each launched service to 127.0.0.1 and lets it take any free port, and tells its own process to look at 127.0.0.1.
The port numbers are examples: the session takes free ones.
Environment variables lists what it sets.
There is one transport because both protocols read variables that hold one
setting for the whole process. Channel Access services get a port each
because of Windows, where a second IOC on the default port is never found:
libca reads its list of server addresses once per process, so each service
keeps its port for as long as the session process runs. PVAccess servers
share the search port, and the client is told to look at 127.0.0.1 because
it doesn't search the loopback address otherwise.
Under sharedbox the session sets up nothing, because a device finds its
service's boxes by name, under the service's prefix, with
no port or address involved. The service and the session then have to run on
the same computer. How to serve a FastCS controller over
sharedbox shows both sides.
Another program can take the port first
A port is free when the session chooses it, but nothing holds it until
the service binds it, so another program on the host can take it in
between. If that happens, the service fails to start or its devices don't
connect. The service keeps its port for the life of the session process,
so to get a different port, start the session again in a new process.
redsun itself depends on ophyd-async and on nothing that either network
protocol needs. Each component brings what its own service speaks, such as
caproto, p4p or fastcs, plus ophyd-async[ca] or ophyd-async[pva] for
the device side; see Write a service. The
sharedbox transport comes with the sharedbox extra of redsun.
A launched service is told its name and prefix, its ready text and the
session's log level, on top of the transport's variables. A Python service
reads them with identity,
ready and
configure_logging, so one module can
serve several sessions and name its channels from its identity.
Environment variables lists how they are
passed.
Nothing restarts a launched service that stops on its own. Step through what
happens instead:
A launched service exits on its own
camera_ioc runs, and the camera's devices read and write through it.
camera devicesanswercamera_iocrunningreads, writes
camera devicesanswercamera_iocrunningreads, writes
camera_ioc exits on its own, after it was ready and without the session asking it to stop.
camera devicesanswercamera_iocexitedreads, writes
camera devicesanswercamera_iocexitedreads, writes
The session logs the exit code with the last 20 lines the service printed, and emits sig_exited with the service's name and code.
camera devicesanswercamera_iocexitedERROR in the logThe session logs the exit code and the last 20 lines the service printed.sig_exited(name, code)reads, writesThe session logs the exit code and the last 20 lines the service printed.
camera devicesanswercamera_iocexitedERROR in the logThe session logs the exit code and the last 20 lines the service printed.sig_exited(name, code)reads, writesThe session logs the exit code and the last 20 lines the service printed.
Connected slots run: a view's on the main thread in a Qt session, a presenter's on the thread that read the service's output unless it names another.
camera devicesanswercamera_iocexitedERROR in the logThe session logs the exit code and the last 20 lines the service printed.sig_exited(name, code)view slotmain threadIn a Qt session, the slots of a view that is a widget run on the main thread.presenter slotthread service-camera_iocA presenter slot runs on the thread that read the service's output, unless you choose another thread for it.reads, writesThe session logs the exit code and the last 20 lines the service printed.In a Qt session, the slots of a view that is a widget run on the main thread.A presenter slot runs on the thread that read the service's output, unless you choose another thread for it.
camera devicesanswercamera_iocexitedERROR in the logThe session logs the exit code and the last 20 lines the service printed.sig_exited(name, code)view slotmain threadIn a Qt session, the slots of a view that is a widget run on the main thread.presenter slotthread service-camera_iocA presenter slot runs on the thread that read the service's output, unless you choose another thread for it.reads, writesThe session logs the exit code and the last 20 lines the service printed.In a Qt session, the slots of a view that is a widget run on the main thread.A presenter slot runs on the thread that read the service's output, unless you choose another thread for it.
Nothing restarts the service, so every read or write of its devices times out after 10 seconds.
camera devicesTimeoutError after 10 scamera_iocexitedERROR in the logThe session logs the exit code and the last 20 lines the service printed.sig_exited(name, code)view slotmain threadIn a Qt session, the slots of a view that is a widget run on the main thread.presenter slotthread service-camera_iocA presenter slot runs on the thread that read the service's output, unless you choose another thread for it.reads, writesThe session logs the exit code and the last 20 lines the service printed.In a Qt session, the slots of a view that is a widget run on the main thread.A presenter slot runs on the thread that read the service's output, unless you choose another thread for it.
camera devicesTimeoutError after 10 scamera_iocexitedERROR in the logThe session logs the exit code and the last 20 lines the service printed.sig_exited(name, code)view slotmain threadIn a Qt session, the slots of a view that is a widget run on the main thread.presenter slotthread service-camera_iocA presenter slot runs on the thread that read the service's output, unless you choose another thread for it.reads, writesThe session logs the exit code and the last 20 lines the service printed.In a Qt session, the slots of a view that is a widget run on the main thread.A presenter slot runs on the thread that read the service's output, unless you choose another thread for it.
Once you start the service again, its devices answer again: both protocols reconnect on their own.
camera devicesanswer againBoth protocols reconnect on their own, so the devices need no reconnect.camera_iocstarted againERROR in the logThe session logs the exit code and the last 20 lines the service printed.sig_exited(name, code)view slotmain threadIn a Qt session, the slots of a view that is a widget run on the main thread.presenter slotthread service-camera_iocA presenter slot runs on the thread that read the service's output, unless you choose another thread for it.reads, writesBoth protocols reconnect on their own, so the devices need no reconnect.The session logs the exit code and the last 20 lines the service printed.In a Qt session, the slots of a view that is a widget run on the main thread.A presenter slot runs on the thread that read the service's output, unless you choose another thread for it.
camera devicesanswer againBoth protocols reconnect on their own, so the devices need no reconnect.camera_iocstarted againERROR in the logThe session logs the exit code and the last 20 lines the service printed.sig_exited(name, code)view slotmain threadIn a Qt session, the slots of a view that is a widget run on the main thread.presenter slotthread service-camera_iocA presenter slot runs on the thread that read the service's output, unless you choose another thread for it.reads, writesBoth protocols reconnect on their own, so the devices need no reconnect.The session logs the exit code and the last 20 lines the service printed.In a Qt session, the slots of a view that is a widget run on the main thread.A presenter slot runs on the thread that read the service's output, unless you choose another thread for it.
sig_exited is emitted only when the
service exits after it was ready and without being asked to stop, from the
thread reading its output, service-<name>. A
presenterslot that touches what
only the main thread may use, such as a Qt widget, needs
@slot(thread="main"). An attached service has no process to watch, so an
outage shows up only as timeouts on its devices.
Everything a launched service prints ends up in the logs, under the logger
redsun.service.<name> and in the service's own log file, next to the
application's:
Where a line of service output goes
a line theservice printsJSON log recordkeeps its level and timeA record written by loguru with serialize=True, or an object with the fields of a logging.LogRecord. It keeps its traceback, and its logger name under the service's logger.pvxs linekeeps its level and timeThe text pvxs, the library under a PVAccess server, writes: a time, a level, a logger and a message.any other linelogged at DEBUGSuch as a print, logged under the service's logger.the service'slog fileA record written by loguru with serialize=True, or an object with the fields of a logging.LogRecord. It keeps its traceback, and its logger name under the service's logger.The text pvxs, the library under a PVAccess server, writes: a time, a level, a logger and a message.Such as a print, logged under the service's logger.
a line theservice printsJSON log recordkeeps its level and timeA record written by loguru with serialize=True, or an object with the fields of a logging.LogRecord. It keeps its traceback, and its logger name under the service's logger.pvxs linekeeps its level and timeThe text pvxs, the library under a PVAccess server, writes: a time, a level, a logger and a message.any other linelogged at DEBUGSuch as a print, logged under the service's logger.the service'slog fileA record written by loguru with serialize=True, or an object with the fields of a logging.LogRecord. It keeps its traceback, and its logger name under the service's logger.The text pvxs, the library under a PVAccess server, writes: a time, a level, a logger and a message.Such as a print, logged under the service's logger.
Log from a service shows
how a service writes records the session can read.
Standby, a service releasing its hardware while it stays connected. For now
the presenter that owns the devices does it, see
Letting go of hardware; the session
will take it over once ophyd-async can disconnect a device.
Launching a service in a container. An attached service covers one you start
beside the session with docker compose.