Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion src/blueapi/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class BlueskyContext:
devices: dict[str, Device] = field(default_factory=dict)

_reference_cache: dict[type, type] = field(default_factory=dict)
_warned_name_clash: bool = field(default=False, init=False, repr=False)

def __post_init__(self, configuration: ApplicationConfig | None):
if not configuration:
Expand Down Expand Up @@ -342,6 +343,11 @@ def register_device(self, device: Device, name: str | None = None) -> None:
`name` parameter here. If the device conforms to a different protocol then
the parameter must be used to name it.

Children of ophyd-async devices, including signals, are also registered
under their own names, e.g. registering "stage" also registers "stage-x"
and "stage-x-velocity". If a name is already in use, the existing device
is kept and the new one and its children are not registered.

Args:
device (Device): The device to register
name (Optional[str], optional): A name for the device. Defaults to None.
Expand All @@ -359,7 +365,39 @@ def register_device(self, device: Device, name: str | None = None) -> None:
else:
raise KeyError(f"Must supply a name for this device: {device}")

self.devices[name] = device
if self._add_device(name, device):
self._register_children(device)

def _register_children(self, device: Device) -> None:
"""Register the children of an ophyd-async device under their own names."""

if not isinstance(device, AsyncDevice):
return

for _, child in device.children():
if child.name and self._add_device(child.name, child):
self._register_children(child)

def _add_device(self, name: str, device: Device) -> bool:
"""Add a device unless a different one is already registered as name."""

existing = self.devices.setdefault(name, device)
if existing is not device:
# Expected for e.g. a motor's user_readback, which has the motor's name.
# The caller skips its children too, as their names start with this one
if not self._warned_name_clash:
LOGGER.warning(
"Some devices were not registered as their names are in use, "
"e.g. motor readbacks. Enable debug logging to see which"
)
self._warned_name_clash = True
LOGGER.debug(
"Not registering %s or its children, name '%s' is in use",
device,
name,
)
return False
return True

def unregister_all_devices(self):
"""Unregister all devices from the context."""
Expand Down
Loading
Loading