Skip to content

Commit

Permalink
Merge pull request #5 from SampleEnvironment/subscribe-to-accessibles
Browse files Browse the repository at this point in the history
Subscribe to accessibles
  • Loading branch information
Bilchreis authored Jul 23, 2024
2 parents 75af94a + 2ba9df9 commit e90a791
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 24 deletions.
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ version = "0.0.1"
description = "An Interface between bluesky and SECoP, using ophyd and frappy-client"

dependencies = [
'ophyd-async == 0.2.0',
'ophyd-async == 0.3.4',
'frappy-core@git+https://github.com/SampleEnvironment/[email protected]'

]

Expand All @@ -21,7 +22,6 @@ requires-python = ">=3.10"
dev = [
'isort',
'pytest == 7.4.2',
'frappy-core@git+https://github.com/SampleEnvironment/frappy',
'black',
'flake8',
"flake8-isort == 6.1.1",
Expand Down
28 changes: 20 additions & 8 deletions src/secop_ophyd/SECoPDevices.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
)
from ophyd_async.core.async_status import AsyncStatus
from ophyd_async.core.signal import SignalR, SignalRW, SignalX, observe_value
from ophyd_async.core.standard_readable import StandardReadable
from ophyd_async.core.standard_readable import (
ConfigSignal,
HintedSignal,
StandardReadable,
)
from ophyd_async.core.utils import T
from typing_extensions import Self

Expand Down Expand Up @@ -142,6 +146,12 @@ def _signal_from_parameter(self, path: Path, sig_name: str, readonly: bool):
else:
setattr(self, sig_name, SignalRW(paramb))

def noop(val):
pass

sig: SignalR = getattr(self, sig_name)
sig.subscribe_value(noop)

async def wait_for_idle(self):
"""asynchronously waits until module is IDLE again. this is helpful,
for running commands that are not done immediately
Expand Down Expand Up @@ -246,7 +256,8 @@ def __init__(self, path: Path, secclient: AsyncFrappyClient):

self.commandx = SignalX(exec_backend)

self.set_readable_signals(read=read, config=config)
self.add_readables(read, wrapper=HintedSignal)
self.add_readables(config, wrapper=ConfigSignal)

super().__init__(name=dev_name)

Expand All @@ -260,14 +271,14 @@ def trigger(self) -> AsyncStatus:
:rtype: AsyncStatus
"""
coro = asyncio.wait_for(fut=self._exec_cmd(), timeout=None)
return AsyncStatus(awaitable=coro, watchers=None)
return AsyncStatus(awaitable=coro)

def kickoff(self) -> AsyncStatus:
# trigger execution of secop command, wait until Device is Busy

self._start_time = ttime.time()
coro = asyncio.wait_for(fut=asyncio.sleep(1), timeout=None)
return AsyncStatus(coro, watchers=None)
return AsyncStatus(coro)

async def _exec_cmd(self):
stat = self.commandx.trigger()
Expand All @@ -276,7 +287,7 @@ async def _exec_cmd(self):

def complete(self) -> AsyncStatus:
coro = asyncio.wait_for(fut=self._exec_cmd(), timeout=None)
return AsyncStatus(awaitable=coro, watchers=None)
return AsyncStatus(awaitable=coro)

def collect(self) -> Iterator[PartialEvent]:
yield dict(
Expand Down Expand Up @@ -372,7 +383,8 @@ def __init__(self, secclient: AsyncFrappyClient, module_name: str):

self.plans.append(plan)

self.set_readable_signals(read=self._read, config=self._config)
self.add_readables(self._read, wrapper=HintedSignal)
self.add_readables(self._config, wrapper=ConfigSignal)

self.set_name(module_name)

Expand Down Expand Up @@ -599,7 +611,7 @@ def __init__(self, secclient: AsyncFrappyClient):
setattr(self, module, secop_dev_class(self._secclient, module))
self.mod_devices[module] = getattr(self, module)

self.set_readable_signals(config=config)
self.add_readables(config, wrapper=ConfigSignal)

# register secclient callbacks (these are useful if sec node description
# changes after a reconnect)
Expand Down Expand Up @@ -797,7 +809,7 @@ def descriptiveDataChange(self, module, description): # noqa: N802
setattr(self, property, SignalR(backend=propb))
config.append(getattr(self, property))

self.set_readable_signals(config=config)
self.add_readables(config, wrapper=ConfigSignal)
else:
# Refresh changed modules
module_desc = self._secclient.modules[module]
Expand Down
42 changes: 28 additions & 14 deletions src/secop_ophyd/SECoPSignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from functools import wraps
from typing import Any, Callable, Dict, Optional

from bluesky.protocols import Descriptor, Reading
from bluesky.protocols import DataKey, Reading
from frappy.client import CacheItem
from frappy.datatypes import (
ArrayOf,
Expand Down Expand Up @@ -71,11 +71,11 @@ def __init__(

self.describe_dict: dict

self.source = self.path._module_name + ":" + self.path._accessible_name
self.source_name = self.path._module_name + ":" + self.path._accessible_name

self.describe_dict = {}

self.describe_dict["source"] = self.source
self.describe_dict["source"] = self.source("")

self.describe_dict.update(self.SECoP_type_info.describe_dict)

Expand All @@ -84,6 +84,9 @@ def __init__(
property_name = "SECoP_dtype"
self.describe_dict[property_name] = prop_val

def source(self, name: str) -> str:
return self.source_name

async def connect(self):
pass

Expand All @@ -93,7 +96,8 @@ async def put(self, value: Any | None, wait=True, timeout=None):
if self.callback is not None:
self.callback(self.reading.get_reading(), self.reading.get_value())

async def get_descriptor(self) -> Descriptor:
async def get_datakey(self, source: str) -> DataKey:
"""Metadata like source, dtype, shape, precision, units"""
return self.describe_dict

async def get_reading(self) -> Reading:
Expand Down Expand Up @@ -141,7 +145,10 @@ def __init__(
self.argument: LocalBackend | None = argument
self.result: LocalBackend | None = result

self.source = self.path._module_name + ":" + self.path._accessible_name
self.source_name = self.path._module_name + ":" + self.path._accessible_name

def source(self, name: str) -> str:
return self.source_name

async def connect(self):
pass
Expand Down Expand Up @@ -171,11 +178,11 @@ async def put(self, value: Any | None, wait=True, timeout=None):

await self.result.put(val)

async def get_descriptor(self) -> Descriptor:

async def get_datakey(self, source: str) -> DataKey:
"""Metadata like source, dtype, shape, precision, units"""
res = {}

res["source"] = self.source
res["source"] = self.source("")

# ophyd datatype (some SECoP datatypeshaveto be converted)
# signalx has no datatype and is never read
Expand Down Expand Up @@ -237,7 +244,7 @@ def __init__(self, path: Path, secclient: AsyncFrappyClient) -> None:

self.describe_dict: dict = {}

self.source = (
self.source_name = (
secclient.uri
+ ":"
+ secclient.nodename
Expand All @@ -250,7 +257,7 @@ def __init__(self, path: Path, secclient: AsyncFrappyClient) -> None:
# SECoP metadata is static and can only change when connection is reset
self.describe_dict = {}

self.describe_dict["source"] = self.source
self.describe_dict["source"] = self.source_name

# add gathered keys from SECoPdtype:
self.describe_dict.update(self.SECoP_type_info.describe_dict)
Expand All @@ -266,6 +273,9 @@ def __init__(self, path: Path, secclient: AsyncFrappyClient) -> None:
property_name = "SECoP_dtype"
self.describe_dict[property_name] = prop_val

def source(self, name: str) -> str:
return self.source_name

async def connect(self):
pass

Expand All @@ -278,7 +288,8 @@ async def put(self, value: Any | None, wait=True, timeout=None):
timeout=timeout,
)

async def get_descriptor(self) -> Descriptor:
async def get_datakey(self, source: str) -> DataKey:
"""Metadata like source, dtype, shape, precision, units"""
return self.describe_dict

async def get_reading(self) -> Reading:
Expand Down Expand Up @@ -351,7 +362,10 @@ def __init__(
self._datatype = self._get_datatype()
self._secclient: AsyncFrappyClient = secclient
# TODO full property path
self.source = prop_key
self.source_name = prop_key

def source(self, name: str) -> str:
return str(self.source_name)

def _get_datatype(self) -> str:
prop_val = self._property_dict[self._prop_key]
Expand All @@ -378,11 +392,11 @@ async def put(self, value: Optional[T], wait=True, timeout=None):
# Properties are readonly
pass

async def get_descriptor(self) -> Descriptor:
async def get_datakey(self, source: str) -> DataKey:
"""Metadata like source, dtype, shape, precision, units"""
description = {}

description["source"] = str(self.source)
description["source"] = self.source("")
description["dtype"] = self._get_datatype()
description["shape"] = [] # type: ignore

Expand Down
42 changes: 42 additions & 0 deletions tests/test_Node.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,48 @@ async def test_dev_read(cryo_sim, cryo_node_internal_loop: SECoPNodeDevice):
await cryo_node_internal_loop.disconnect_async()


async def test_signal_read(cryo_sim, cryo_node_internal_loop: SECoPNodeDevice):
# Node device has no read value, it has to return an empty dict
cryo_dev: SECoPMoveableDevice = cryo_node_internal_loop.cryo

p = await cryo_dev.p.get_value(cached=False)
assert p == 40.0

await cryo_node_internal_loop.disconnect_async()


async def test_signal_read_cached(cryo_sim, cryo_node_internal_loop: SECoPNodeDevice):
# Node device has no read value, it has to return an empty dict
cryo_dev: SECoPMoveableDevice = cryo_node_internal_loop.cryo

p = await cryo_dev.p.get_value(cached=True)

assert p == 40.0

await cryo_node_internal_loop.disconnect_async()


async def test_signal_stage_unstage_read_cached(
cryo_sim, cryo_node_internal_loop: SECoPNodeDevice
):
# Node device has no read value, it has to return an empty dict
cryo_dev: SECoPMoveableDevice = cryo_node_internal_loop.cryo

await cryo_dev.value.stage()

await asyncio.sleep(1)

await cryo_dev.value.unstage()

await asyncio.sleep(1)

p = await cryo_dev.p.get_value(cached=True)

assert p == 40.0

await cryo_node_internal_loop.disconnect_async()


async def test_status(cryo_sim, cryo_node_internal_loop: SECoPNodeDevice):
# Node device has no read value, it has to return an empty dict
cryo_dev: SECoPMoveableDevice = cryo_node_internal_loop.cryo
Expand Down
3 changes: 3 additions & 0 deletions tests/test_async_frappy_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ async def test_async_secopclient_reconn(
while async_frappy_client.state == "reconnecting":
await asyncio.sleep(0.001)

while async_frappy_client.state == "activating":
await asyncio.sleep(0.001)

assert async_frappy_client.state == "connected"

# ensures we are connected and getting fresh data again
Expand Down

0 comments on commit e90a791

Please sign in to comment.