Work in progress: this documentation is still being written and reviewed. Check details against the source code.

Bluesky and EPICS

The Bluesky integration wraps EROBS’s main action as a device with a set() method. A Bluesky plan can ask that device to execute task JSON and monitor the returned status. EPICS utilities in the same directory expose individual process variables to ROS through PyEpics, independently of that action path.

Files and responsibilities

File under src/bluesky_ros/

Responsibility

mtc_ophyd_device.py

MTCExecutionDevice: sends a goal and spins until its status completes

mtc_ophyd_device_async.py

MTCExecutionDeviceAsync: handles action callbacks in a background thread

simple_mtc_bluesky.py

Small RunEngine integration example

pv_to_topic.py

Monitor one PV and publish numeric values to ROS

pv_service.py

Answer a ROS service request with a PV read

This directory is a Python source package, not a colcon package: it has no package.xml or setup metadata. Building beambot_interfaces supplies the generated action interface but does not install bluesky_ros itself.

From an EROBS checkout, its import parent is src/:

export PYTHONPATH="$PWD/src${PYTHONPATH:+:$PYTHONPATH}"

This command only selects Python source. The host also needs the appropriate ROS environment, generated beambot_interfaces, Bluesky and Ophyd dependencies.

Goal and waiting behavior

Both wrappers target beambot_execution and populate MTCExecution.Goal.full_json. A string ending in .json is treated as a filename and read; other input is treated as raw JSON text. They do not translate individual pick/place actions or rewrite old task schemas. A task file must already satisfy the current task reference.

Compare MTCExecutionDevice.set() and MTCExecutionDeviceAsync.set() when choosing when control returns to the plan:

Wrapper

Waiting behavior

Synchronous

Spins ROS callbacks until the status completes. RunEngine’s wait=False does not remove this blocking wait.

Async

Waits up to ten seconds for the action server, submits the goal, then starts a background spin thread. The returned status can be monitored while the plan continues.

For illustration, a plan using an already configured async device has this shape:

import bluesky.plan_stubs as bps


def sample_plan(robot, motor, task_file, measurement_position):
    yield from bps.abs_set(robot, task_file, wait=True)
    yield from bps.mv(motor, measurement_position)

Running this plan executes the supplied robot task, waits for completion, then moves the motor. Supply configured devices and deployment-specific targets.

Current support boundary

End-to-end operation is unverified. The wrappers submit the main JSON action and have the error-handling defects described below.

Both wrappers reverse the canceled and aborted status labels:

ROS status

Correct meaning

Wrapper label

5

Canceled

Aborted

6

Aborted

Canceled

Both branches mark the Bluesky status as failed. The incorrect label affects error diagnosis.

The synchronous wrapper also ignores the boolean result of its server-availability wait. Rejection, unavailable server, cancellation, aborted action, cleanup and repeated submissions all need integration checks. Check the returned action result as well as the Bluesky status.

EPICS read utilities

pv_to_topic.py PV_NAME calls epics.camonitor() and publishes changes as std_msgs/msg/Float64 on /pv_value. Its callback converts the value with float(), so the intended input is a numeric PV. It is not a generic mapping for strings, arrays or disconnected values.

pv_service.py PV_NAME offers /get_pv using std_srvs/srv/Trigger. Each request calls epics.caget(), marks success when the result is not None, and places the textual value in response.message.

Both names are fixed in the source’s default namespace. Neither utility writes PVs. The local scripts are distinct from docker/epics, whose image recipe clones a separate epics_bridge branch. Review that branch separately when using the image.