Skip to content
Open
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
37 changes: 37 additions & 0 deletions src/blueapi/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,10 @@ 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".
Args:
device (Device): The device to register
name (Optional[str], optional): A name for the device. Defaults to None.
Expand All @@ -358,7 +362,40 @@ def register_device(self, device: Device, name: str | None = None) -> None:
else:
raise KeyError(f"Must supply a name for this device: {device}")

existing = self.devices.get(name)
if existing is not None and existing is not device:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are duplicate devices overwritten here but not below (389..397)?

LOGGER.warning("Replacing existing device registered as '%s'", name)
self.devices[name] = device
self._register_children(device)

def _register_children(
self, device: Device, ancestor_names: frozenset[str] = frozenset()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it work to use devices instead of the ancestors set? Does it make a difference if the name clashes with a parent compared to a child of a different device?

) -> None:
"""Register the children of an ophyd-async device under their own names."""

if not isinstance(device, AsyncDevice):
return

ancestor_names = ancestor_names | {device.name}
for attr_name, child in device.children():
if child.name:
# Some children share an ancestor's name, e.g. a motor's
# user_readback, so name these after their attribute instead
if child.name in ancestor_names:
name = f"{device.name}-{attr_name}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to make up a new name for a signal? If some devices is named foo is anyone going to need to access it via a derived foo-bar name and not the foo.bar dotted access that is still supported?

else:
name = child.name
existing = self.devices.setdefault(name, child)
if existing is not child:
# Its children's names start with the clashing name, so skip
# them too
LOGGER.warning(
"Not registering %s or its children, name '%s' is in use",
child,
name,
)
continue
self._register_children(child, ancestor_names)

def unregister_all_devices(self):
"""Unregister all devices from the context."""
Expand Down
12 changes: 9 additions & 3 deletions src/blueapi/service/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,21 @@ def get_plan(name: str) -> PlanModel:

def get_devices() -> list[DeviceModel]:
"""Get all available devices in the BlueskyContext"""
return [DeviceModel.from_device(device) for device in context().devices.values()]
# Use the registered name, as some children are registered under a name
# other than their own, e.g. a motor's user_readback
return [
DeviceModel.from_device(device, name)
for name, device in context().devices.items()
]


def get_device(name: str) -> DeviceModel:
"""Retrieve device by name from the BlueskyContext"""
if not (device := context().find_device(name)):
ctx = context()
if not (device := ctx.find_device(name)):
raise KeyError(name)

return DeviceModel.from_device(device)
return DeviceModel.from_device(device, name if name in ctx.devices else None)


def submit_task(
Expand Down
5 changes: 3 additions & 2 deletions src/blueapi/service/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ class DeviceModel(BlueapiBaseModel):
)

@classmethod
def from_device(cls, device: Device) -> "DeviceModel":
name = device.name if isinstance(device, HasName) else _UNKNOWN_NAME
def from_device(cls, device: Device, name: str | None = None) -> "DeviceModel":
if name is None:
name = device.name if isinstance(device, HasName) else _UNKNOWN_NAME
return cls(name=name, protocols=list(_protocol_info(device)))


Expand Down
Loading
Loading